X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=dgit.git;a=blobdiff_plain;f=badcommit-fixup;h=ca2e0dfbdd28a8f02db50d38cd45244c06464d1b;hp=15b1720cf4d265028b5d9bfa73a5001b1e6fdf2c;hb=270c257f81665d1bcfab1ca4a3d831b5bcabc034;hpb=2a80749484dc1ee5ad89512454de3a55ad677584 diff --git a/badcommit-fixup b/badcommit-fixup index 15b1720c..ca2e0dfb 100755 --- a/badcommit-fixup +++ b/badcommit-fixup @@ -1,59 +1,179 @@ #!/usr/bin/perl -w +# usage: +# .../badcommit-fixup [-- --test +# .../badcommit-fixup --real + use strict; -set -e -set -o pipefail - -tmp=.git/dgit-badcommit-fixup-tmp -rm -rf $tmp -mkdir $tmp - -LC_MESSAGES=C git fsck --no-dangling >$tmp/gfo 2>&1 || test $? = 1 - -perl -ne ' - print $1, "\n" or die $! if - m/^error in commit (\w+):.*invalid format - expected '\''committer/; -' <$tmp/gfo >$tmp/bad - -case `wc -l <$tmp/bad` in - 0) - echo >&2 'nothing bad found - is git-fsck doing as we expect?' ; - exit 8 ;; - 1) - read <$tmp/bad bads - nots="^$bads^0 ^$bads^1" - ;; - *) - bads="cat $tmp/bad" - ;; -esac - -args="$nots" - -refs=`git-for-each-ref --format='%(refname)'` - -if git-symbolic-ref HEAD >/dev/null 2>&1; then - refs+=' HEAD' -fi - -for head in $refs; do - exec <$tmp/bad - needed=false - for bad in $bads; do - if git merge-base --is-ancestor $bad $head; then - needed=true - break - fi - done - if ! $needed; then continue; fi - args+=" $head" -done - -git filter-branch --original dgit-badcommit --commit-filter ' -echo >&2 "FOO $*" -cat >&2 -echo >&2 ==== -false - sed -e '\''1,/^$/ s/^commiter /committer /'\'' -' $args +use POSIX; +use IPC::Open2; +use Data::Dumper; + +my $real; + +foreach my $a (@ARGV) { + if ($a eq '--test') { + $real = 0; + } elsif ($a eq '--real') { + $real = 1; + } else { + die "$a ?"; + } +} + +die unless defined $real; + +my $gcfpid = open2 \*GCFO, \*GCFI, 'git cat-file --batch' or die $!; + +our %count; + +no warnings qw(recursion); + +sub getobj ($$) { + my ($obj, $type) = @_; + print GCFI $obj, "\n" or die $!; + my $x = ; + my ($gtype, $gsize) = $x =~ m/^\w+ (\w+) (\d+)\n/ or die "$obj ?"; + $gtype eq $type or die "$obj $gtype != $type ?"; + my $gdata; + (read GCFO, $gdata, $gsize) == $gsize or die "$obj $!"; +#print STDERR ">$obj|$x|$gdata|$gsize<\n"; + $x = ; + $x eq "\n" or die "$obj ($_) $!"; + $count{inspected}++; + return $gdata; +} + +sub hashobj ($$) { + my ($data,$type) = @_; + my $gwopid = open2 \*GWO, \*GWI, + "git hash-object -w -t $type --stdin" + or die $!; + print GWI $data or die $!; + close GWI or die $!; + $_ = ; + close GWO or die $!; + waitpid $gwopid,0 == $gwopid or die $!; + die $? if $?; + m/^(\w+)\n/ or die "$_ ?"; + $count{"rewritten $type"}++; + return $1; +} + +our %memo; + +sub rewrite_commit ($); +sub rewrite_commit ($) { + my ($obj) = @_; + my $m = \ $memo{$obj}; + return $$m if defined $$m; + my $olddata = getobj $obj, 'commit'; + $olddata =~ m/(?<=\n)(?=\n)/ or die "$obj ?"; + my $msg = $'; + $_ = $`; + s{^(parent )(\w+)$}{ $1 . rewrite_commit($2) }gme; + $count{'fix overwrite'} += s{^commiter }{committer }gm; + if (!m{^author }m && !m{^committer }m) { + m{^parent (\w+)}m or die "$obj ?"; + my $parent = getobj $1, 'commit'; + $parent =~ m/^(?:.+\n)+(author .*\ncommitter .*\n)/; + m/\n$/ or die "$obj ?"; + $_ .= $1; + $count{'fix import'}++; + } + my $newdata = $_.$msg; + my $newobj; + if ($newdata eq $olddata) { + $newobj = $obj; + $count{unchanged}++; + } else { + $newobj = hashobj $newdata, 'commit'; + } + $$m= $newobj; + return $newobj; +} + +sub rewrite_tag ($) { + my ($obj) = @_; + $_ = getobj $obj, 'tag'; + m/^type (\w+)\n/m or die "$obj ?"; + if ($1 ne 'commit') { + $count{"oddtags $1"}++; + return $obj; + } + m/^object (\w+)\n/m or die "$obj ?"; + my $oldref = $1; + my $newref = rewrite_commit $oldref; + if ($oldref eq $newref) { + return $obj; + } + s/^(object )\w+$/ $1.$newref /me or die "$obj ($_) ?"; + s/^-----BEGIN PGP SIGNATURE-----\n.*^-----END PGP SIGNATURE-----\n$//sm; + return hashobj $_, 'tag'; +} + +$!=0; $?=0; +my $refs=`git for-each-ref`; +die "$? $!" if $?; + +chomp $refs; + +our @updates; + +foreach my $rline (split /\n/, $refs) { + my ($obj, $type, $refname) = + $rline =~ m/^(\w+)\s+(\w+)\s+(\S.*)/ + or die "$_ ?"; + my $rewrite; + if ($type eq 'commit') { + $rewrite = rewrite_commit($obj); + } elsif ($type eq 'tag') { + $rewrite = rewrite_tag($obj); + } else { + warn "ref $refname refers to $type\n"; + next; + } + next if $rewrite eq $obj; + push @updates, [ $refname, $obj, $rewrite ]; +} + +open U, "|git update-ref -m 'dgit bad commit fixup' --stdin" or die $!; + +if ($real) { + $!=0; $?=0; + my $bare = `git rev-parse --is-bare-repository`; + die "$? $!" if $?; + if ($bare eq 'false') { + print "detaching your HEAD\n" or die $!; + system 'git checkout --detach' and die "$! $?"; + } +} + +for my $up (@updates) { + my ($ref, $old, $new) = @$up; + my $otherref = $ref; + $otherref =~ s{^refs/}{}; + if ($real) { + print U <