[PATCH] Add git-graft-ripple, a tool for permanently grafting history into a tree.
From: Ryan Anderson <hidden>
Date: 2016-06-15 22:42:13
Enhancements over the original example: o Each newly created commit A' references A, and (A^1)' (The first try referenced A^1 and (A^1)' but not A) o Support for incrementally rewriting history is present. Signed-off-by: Ryan Anderson <redacted> --- Documentation/git-graft-ripple | 57 +++++++++++++++++ Makefile | 3 + git-graft-ripple.perl | 137 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 196 insertions(+), 1 deletions(-) create mode 100644 Documentation/git-graft-ripple create mode 100755 git-graft-ripple.perl applies-to: 92551f12af8cd77f821d066a53c4dd301d1b41f5 9bfbdd948e6c0c9a8f0383b355e5cbbb42e0a772
diff --git a/Documentation/git-graft-ripple b/Documentation/git-graft-ripple
new file mode 100644
index 0000000..2fa38cb
--- /dev/null
+++ b/Documentation/git-graft-ripple@@ -0,0 +1,57 @@ +git-graft-ripple(1) +=============== + +NAME +---- +git-graft-ripple - Rewrite history with an additional commitish as a parent. + + +SYNOPSIS +-------- +'git-log <git repository> <branch committish> <graft SHA1> [start commitish]' + +DESCRIPTION +----------- + +git-graft-ripple rewrites a series of commit objects by adding a new parent to +the most ancestral commit, and passing the changed history back up through the +revision history. + +Proceeding in reverse merge order a new commit is created using the following +algorithim: + + If this is the first commit reached, append the graft object name to + the parents. + + Replace each parent object name with the object name generated by this + algorithm earlier. + + Add the original object name to the parents. + +If a start commitish is given, the output of git-rev-list is restricted using +it. This allows for incremental grafting to occur. + +Note: No branches are modified by this operation. + +Files +----- + +GIT_DIR/info/ripple-status contains a mapping between the original object name +and the object name generated by this algorithm. When doing incremental +grafting, this file is necessary to continue to rename objects. If this is not +present, git-graft-ripple will leave the old object name in the list of +parents, which should still allow most merges to occur cleanly. + + +Author +------ +Written by Ryan Anderson <ryan@michonline.com> + +Documentation +-------------- +Documentation by Ryan Anderson. + +GIT +--- +Part of the gitlink:git[7] suite +
diff --git a/Makefile b/Makefile
index 1109dc6..984be32 100644
--- a/Makefile
+++ b/Makefile@@ -97,7 +97,8 @@ SCRIPT_SH = \ SCRIPT_PERL = \ git-archimport.perl git-cvsimport.perl git-relink.perl \ git-shortlog.perl git-fmt-merge-msg.perl \ - git-svnimport.perl git-mv.perl git-cvsexportcommit.perl + git-svnimport.perl git-mv.perl git-cvsexportcommit.perl \ + git-graft-ripple.perl SCRIPT_PYTHON = \ git-merge-recursive.py
diff --git a/git-graft-ripple.perl b/git-graft-ripple.perl
new file mode 100755
index 0000000..b8ec990
--- /dev/null
+++ b/git-graft-ripple.perl@@ -0,0 +1,137 @@ +#!/usr/bin/perl +# Copyright 2005 Ryan Anderson <ryan@michonline.com> +# +# GPL v2 (See COPYING) +# +# This file is licensed under the GPL v2, or a later version +# at the discretion of Linus Torvalds. + +use strict; +use warnings; + +use IPC::Open2; + +sub git_commit_tree { + my ($tree,$comments,@parents) = @_; + + my @cparents; + foreach my $p (@parents) { + push @cparents,"-p",$p; + } + + my $pid = open2(*Reader, *Writer, + "git-commit-tree",$tree,@cparents); + print Writer $comments; + close(Writer); + my $commit = <Reader>; + + waitpid $pid, 0; + close(Reader); + + chomp $commit; + + return $commit; +} + +my ($gittree,$branch,$graft,$start) = @ARGV; +chdir($gittree) + or die sprintf("Failed to chdir to '%s': %s\n", $ARGV[0], $!); + +my $range = defined $start ? sprintf("%s..%s",$start,$branch) : $branch; + +#printf("Running git-rev-list --parents --merge-order %s\n",$range); +open(GRL,"-|","git-rev-list","--parents", "--merge-order", $range) + or die "Failed to run git-rev-list: " . $!; + +my %csets; +my @revs; +while(<GRL>) { + chomp; + my ($commit,@parents) = split; + $csets{$commit}{parents} = \@parents; + push @revs, $commit; +# printf("Investigating %s\n", $commit); + + open(GCF,"-|","git-cat-file","commit",$commit) + or die "Failed to open git-cat-file: " . $!; + + my $in_comments = 0; + while(<GCF>) { + chomp; + if ($in_comments) { + $csets{$commit}{comments} .= $_ . "\n"; + + } elsif (m/^tree (.+)$/) { + $csets{$commit}{tree} = $1; + #printf("tree = %s\n",$1); + + } elsif (m/^parent (.+)$/) { + # Do nothing, we already got + # the parents from rev-list. + + } elsif (m/^(author|committer) (.*) <(.*)> (.*)$/) { + #printf("%s = %s <%s> at %s\n",$1, $2,$3,$4); + @{$csets{$commit}{$1}}{qw(name email datetime)} + = ($2,$3,$4); + + } elsif (length == 0) { + $in_comments = 1; + $csets{$commit}{comments} = ""; + next; + } + } + close(GCF); + +} +close(GRL); + +@revs = reverse @revs; +shift @revs if defined $start; +push @{$csets{$revs[0]}{parents}},$graft; + +my %newcsets; +if (-f ".git/info/ripple-status") { + open(RSTATUS,"<",".git/info/ripple-status") + or die "Failed to restore status from .git/info/ripple-status: " . $!; + + while (<RSTATUS>) { + chomp; + my ($k,$newcset) = split; + $newcsets{$k} = $newcset; + } + close(RSTATUS); +} + + +foreach my $old (@revs) { + printf("\nProcessing commit %s\n",$old); + $ENV{GIT_AUTHOR_EMAIL} = $csets{$old}{author}{email}; + $ENV{GIT_AUTHOR_NAME} = $csets{$old}{author}{name}; + $ENV{GIT_AUTHOR_DATE} = $csets{$old}{author}{datetime}; + $ENV{GIT_COMMITTER_DATE} = $csets{$old}{committer}{datetime}; + $ENV{GIT_COMMITTER_EMAIL} = $csets{$old}{committer}{email}; + $ENV{GIT_COMMITTER_NAME} = $csets{$old}{committer}{name}; + + my @parents = ($old); + foreach my $p (@{$csets{$old}{parents}}) { + if (exists $newcsets{$p}) { + push @parents, $newcsets{$p}; + printf("\t%s => %s\n", + $newcsets{$p},$p); + } else { + push @parents, $p; + } + } + my $commit = git_commit_tree($csets{$old}{tree}, + $csets{$old}{comments},@parents); + $newcsets{$old} = $commit; + printf("\tNew commit: %s\n",$newcsets{$old}); +} + +open(RSTATUS,">",".git/info/ripple-status") + or die "Failed to save status in .git/info/ripple-status: " . $!; + +foreach my $k (keys %newcsets) { + printf RSTATUS "%s\t%s\n", $k, $newcsets{$k}; +} +close(RSTATUS);
--- 0.99.9.GIT