From: Ryan Anderson Date: Sun, 06 Nov 2005 22:38:18 GMT Subject: [RFC] Applying a graft to a tree and "rippling" the changes through the history Message-ID: <436E85DA.1080904@michonline.com> I've written a tool that will take a single commit, add it as a parent of another commit, and recreate the history above that second commit in a fully compatible manner. This is mostly useful for creating a fully merged-up repository of the Linux Historical tree, and the current working tree. I run this with /graft-ripple.pl linux-history.tmp/ linus origin Where "origin" is the branch the historical repository is on, and "linus" is the branch the current repository is on. Note: This does not end up fixing up HEAD or any branches, it just pulls all the objects together and recreates the full history. GPLv2, but I'll redo with a proper patch, signed-off-by, command line options and help and docs if anyone else feels this is useful as a general tool. ========= cut here ============= #!/usr/bin/perl use strict; use warnings; use Data::Dumper; 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 = ; waitpid $pid, 0; close(Reader); chomp $commit; return $commit; } chdir($ARGV[0]); open(GRL,"-|","git-rev-list","--parents",$ARGV[1]) or die "Failed to run git-rev-list: " . $!; my %csets; my @revs; while() { chomp; my ($commit,@parents) = split /\s+/; $csets{$commit}{parents} = \@parents; push @revs, $commit; open(GCF,"-|","git-cat-file","commit",$commit) or die "Failed to open git-cat-file: " . $!; my $in_comments = 0; while() { 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; push @{$csets{$revs[0]}{parents}},$ARGV[2]; my %newcsets; foreach my $old (@revs) { printf("Processing 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 = @{$csets{$old}{parents}}; foreach my $p (@{$csets{$old}{parents}}) { if (exists $newcsets{$p}) { push @parents, $newcsets{$p} if exists $newcsets{$p}; printf("Found new csetid %s for %s\n", $newcsets{$p},$p); } } my $commit = git_commit_tree($csets{$old}{tree}, $csets{$old}{comments},@parents); $newcsets{$old} = $commit; printf("Commit for version %s is %s\n",$old,$newcsets{$old}); }