chiark / gitweb /
732974c10e28e5bad7d90fce0b12f8443b29080a
[dgit.git] / badcommit-fixup
1 #!/usr/bin/perl -w
2
3 # usage:
4 #   .../badcommit-fixup --test
5 #   .../badcommit-fixup --real
6
7 use strict;
8
9 use POSIX;
10 use IPC::Open2;
11 use Data::Dumper;
12
13 my $real;
14
15 foreach my $a (@ARGV) {
16     if ($a eq '--test') {
17         $real = 0;
18     } elsif ($a eq '--real') {
19         $real = 1;
20     } else {
21         die "$a ?";
22     }
23 }
24
25 die unless defined $real;
26
27 my $gcfpid = open2 \*GCFO, \*GCFI, 'git cat-file --batch' or die $!;
28
29 our %count;
30
31 no warnings qw(recursion);
32
33 sub runcmd {
34     system @_ and die "@_ $! $?";
35 }
36
37 $!=0; $?=0;
38 my $bare = `git rev-parse --is-bare-repository`;
39 die "$? $!" if $?;
40 chomp $bare or die;
41
42 sub getobj ($$) {
43     my ($obj, $type) = @_;
44     print GCFI $obj, "\n" or die $!;
45     my $x = <GCFO>;
46     my ($gtype, $gsize) = $x =~ m/^\w+ (\w+) (\d+)\n/ or die "$obj ?";
47     $gtype eq $type or die "$obj $gtype != $type ?";
48     my $gdata;
49     (read GCFO, $gdata, $gsize) == $gsize or die "$obj $!";
50 #print STDERR ">$obj|$x|$gdata|$gsize<\n";
51     $x = <GCFO>;
52     $x eq "\n" or die "$obj ($_) $!";
53     $count{inspected}++;
54     return $gdata;
55 }
56
57 sub hashobj ($$) {
58     my ($data,$type) = @_;
59     my $gwopid = open2 \*GWO, \*GWI,
60         "git hash-object -w -t $type --stdin"
61         or die $!;
62     print GWI $data or die $!;
63     close GWI or die $!;
64     $_ = <GWO>;
65     close GWO or die $!;
66     waitpid $gwopid,0 == $gwopid or die $!;
67     die $? if $?;
68     m/^(\w+)\n/ or die "$_ ?";
69     $count{"rewritten $type"}++;
70     return $1;
71 }
72
73 our %memo;
74
75 sub rewrite_commit ($);
76 sub rewrite_commit ($) {
77     my ($obj) = @_;
78     my $m = \ $memo{$obj};
79     return $$m if defined $$m;
80     my $olddata = getobj $obj, 'commit';
81     $olddata =~ m/(?<=\n)(?=\n)/ or die "$obj ?";
82     my $msg = $';
83     local $_ = $`;
84     s{^(parent )(\w+)$}{ $1 . rewrite_commit($2) }gme;
85     $count{'fix overwrite'} += s{^commiter }{committer }gm;
86     if (!m{^author }m && !m{^committer }m) {
87         m{^parent (\w+)}m or die "$obj ?";
88         my $parent = getobj $1, 'commit';
89         $parent =~ m/^(?:.+\n)+(author .*\ncommitter .*\n)/;
90         m/\n$/ or die "$obj ?";
91         $_ .= $1;
92         $count{'fix import'}++;
93     }
94     my $newdata = $_.$msg;
95     my $newobj;
96     if ($newdata eq $olddata) {
97         $newobj = $obj;
98         $count{unchanged}++;
99     } else {
100         $newobj = hashobj $newdata, 'commit';
101     }
102     $$m= $newobj;
103     return $newobj;
104 }
105
106 sub rewrite_commit_adddummy ($$$) {
107     my ($ref, $veryold, $old) = @_;
108
109     die "$bare ?" unless $bare eq 'true';
110
111     my $td = 'dgit-broken-fixup.tmp';
112     runcmd qw(rm -rf), $td;
113     mkdir $td, 0700 or die "$td $!";
114     chdir $td or die $!;
115     runcmd qw(git init -q);
116     runcmd qw(git config gc.auto 0);
117     runcmd qw(rm -rf .git/objects);
118     symlink "../../objects", ".git/objects" or die $!;
119     runcmd qw(git checkout -q), $old;
120
121     open C, "debian/changelog" or die $!;
122     undef $/;
123     my $clog = <C>;
124     C->error and die $!;
125     close C or die $!;
126     defined $clog or die $!;
127
128     $!=0; $?=0;
129     my $v = `dpkg-parsechangelog`;
130     die "$ref $veryold $old $? $!" if $?;
131     $v =~ m/^Source: (\S+)$/m or die "$ref $veryold $old ?";
132     my $pkg = $1;
133     $v =~ m/^Version: (\S+)$/m or die "$ref $veryold $old ?";
134     my $vsn = $1;
135     $vsn .= "+~dgitfix";
136
137     open C, ">", "debian/changelog" or die $!;
138     print C <<END;
139 $pkg ($vsn) UNRELEASED; urgency=low
140
141  * Additional commit, with slightly incremented version number,
142    to override bad commits generated by dgit due to #849041.
143  * No changes to the package.
144  * Not uploaded anywhere.
145
146  -- Ian Jackson <ijackson\@chiark.greenend.org.uk>  Thu, 05 Jan 2017 17:58:21 +0000
147
148 END
149     print C $clog or die $!;
150     close C or die $!;
151
152     runcmd qw(git commit -q), 
153         '--author=Ian Jackson <ijackson@chiark.greenend.org.uk>',
154         qw(-m), 'Dummy changelog entry to work around #849041 fallout',
155         qw(debian/changelog);
156
157     $!=0; $?=0;
158     my $new = `git rev-parse HEAD`;
159     die "$? $!" if $?;
160
161     chdir '..' or die $!;
162     runcmd qw(rm -rf), $td;
163
164     $count{dummyadded}++;
165
166     return $new;
167 }
168
169 sub rewrite_tag ($) {
170     my ($obj) = @_;
171     $_ = getobj $obj, 'tag';
172     m/^type (\w+)\n/m or die "$obj ?";
173     if ($1 ne 'commit') {
174         $count{"oddtags $1"}++;
175         return $obj;
176     }
177     m/^object (\w+)\n/m or die "$obj ?";
178     my $oldref = $1;
179     my $newref = rewrite_commit $oldref;
180     if ($oldref eq $newref) {
181         return $obj;
182     }
183     s/^(object )\w+$/ $1.$newref /me or die "$obj ($_) ?";
184     s/^-----BEGIN PGP SIGNATURE-----\n.*^-----END PGP SIGNATURE-----\n$//sm;
185     return hashobj $_, 'tag';
186 }
187
188 $!=0; $?=0;
189 my $refs=`git for-each-ref`;
190 die "$? $!" if $?;
191
192 chomp $refs;
193
194 our @updates;
195
196 foreach my $rline (split /\n/, $refs) {
197     my ($obj, $type, $refname) = 
198         $rline =~ m/^(\w+)\s+(\w+)\s+(\S.*)/
199         or die "$_ ?";
200     my $rewrite;
201     if ($type eq 'commit') {
202         $rewrite = rewrite_commit($obj);
203 print STDERR "RW? $refname $obj $rewrite\n";
204         if ($refname =~ m{^refs/dgit/[^/]+$} &&
205            $rewrite ne $obj) {
206 print STDERR "RW  $refname\n";
207             $rewrite = rewrite_commit_adddummy $refname, $obj, $rewrite;
208         }
209     } elsif ($type eq 'tag') {
210         $rewrite = rewrite_tag($obj);
211     } else {
212         warn "ref $refname refers to $type\n";
213         next;
214     }
215     next if $rewrite eq $obj;
216     push @updates, [ $refname, $obj, $rewrite ];
217 }
218
219 our $worktree;
220
221 print Dumper(\@updates);
222
223 open U, "|git update-ref -m 'dgit bad commit fixup' --stdin" or die $!;
224
225 if ($real && $bare eq 'false') {
226     print "detaching your HEAD\n" or die $!;
227     runcmd 'git checkout --detach';
228 }
229
230 for my $up (@updates) {
231     my ($ref, $old, $new) = @$up;
232     my $otherref = $ref;
233     $otherref =~ s{^refs/}{};
234     if ($real) {
235         print U <<END or die $!;
236 create refs/dgit-badcommit/$otherref $old
237 update $ref $new $old
238 END
239     } else {
240         print U <<END or die $!;
241 update refs/dgit-badfixuptest/$otherref $new
242 END
243     }
244 }
245
246 $?=0; $!=0;
247 close U or die "$? $!";
248 die $? if $?;
249
250 print Dumper(\%count);
251
252 if ($real) {
253     print "old values saved in refs/dgit-badcommit/\n" or die $!;
254 } else {
255     print "testing output saved in refs/dgit-badfixuptest/\n" or die $!;
256 }