git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH] Add git-graft-ripple, a tool for permanently grafting history into a tree.

From
RARyan Anderson <ryan@michonline.com>
Date
Nov 22, 2005, 20:50 UTC
Message-ID
<11326926501602-git-send-email-ryan@michonline.com>
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 <ryan@michonline.com>
---
 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
Next: Linus Torvalds
Message 1 of 7 in “Add git-graft-ripple, a tool for permanently grafting history into a tree.”
  1. Add git-graft-ripple, a tool for permanently grafting history into a tree.Ryan Anderson, Nov 22, 2005
  2. Linus TorvaldsNov 23, 2005
  3. Andreas EricssonNov 23, 2005
  4. Ryan AndersonNov 23, 2005
  5. Randal L. SchwartzNov 23, 2005
  6. Johannes SchindelinNov 23, 2005
  7. Linus TorvaldsNov 23, 2005

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.