chiark / gitweb /
11a1896217e10ef51a1288d99c647904f3a5817d
[dgit.git] / git-debrebase
1 #!/usr/bin/perl -w
2 # git-debrebase
3 # Script helping make fast-forwarding histories while still rebasing
4 # upstream deltas when working on Debian packaging
5 #
6 # Copyright (C)2017,2018 Ian Jackson
7 #
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License
19 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
20
21
22 # usages:
23 #
24 #    git-debrebase [<options>] new-upstream-v0 \
25 #             <new-version> <orig-commitish> \
26 #            [<extra-orig-name> <extra-orig-commitish> ...] \
27 #            [<git-rebase options>...]
28 #
29 #    git-debrebase [<options> --] [<git-rebase options...>]
30 #    git-debrebase [<options>] analyse
31 #    git-debrebase [<options>] launder         # prints breakwater tip etc.
32 #    git-debrebase [<options>] downstream-rebase-launder-v0  # experimental
33 #
34 #    git-debrebase [<options>] gbp2debrebase-v0 \
35 #             <upstream>
36
37 # problems / outstanding questions:
38 #
39 #  *  dgit push with a `3.0 (quilt)' package means doing quilt
40 #     fixup.  Usually this involves recommitting the whole patch
41 #     series, one at a time, with dpkg-source --commit.  This is
42 #     terribly terribly slow.  (Maybe this should be fixed in dgit.)
43 #
44 #  * dgit push usually needs to (re)make a pseudomerge.  The "first"
45 #    git-debrebase stripped out the previous pseudomerge and could
46 #    have remembeed the HEAD.  But it's not quite clear what history
47 #    ought to be preserved and what should be discarded.  For now
48 #    the user will have to tell dgit --overwrite.
49 #
50 #    To fix this, do we need a new push hook for dgit ?
51 #
52 #  * Workflow is currently clumsy.  Lots of spurious runes to type.
53 #    There's not even a guide.
54 #
55 #  * There are no tests.
56 #
57 #  * new-upstream-v0 has a terrible UI.  You end up with giant
58 #    runic command lines.
59 #
60 #    One consequence of the lack of richness it can need --force in
61 #    fairly sensible situations and there is no way to tell it what
62 #    you are really trying to do, other than just --force.  There
63 #    should be an interface with some default branch names.
64 #
65 #  * There should be a standard convention for the version number,
66 #    and unfinalised or not changelog, after new-upstream.
67 #
68 #  * Handing of multi-orig dgit new-upstream .dsc imports is known to
69 #    be broken.  They may be not recognised, improperly converted, or
70 #    their conversion may be unrecognised.
71 #
72 #  * Docs need writing and updating.  Even README.git-debrebase
73 #    describes a design but may not reflect the implementation.
74 #
75 #  * We need to develop a plausible model that works for derivatives,
76 #    who probably want to maintain their stack on top of Debian's.
77 #    downstream-rebase-launder-v0 may be a starting point?
78
79 use strict;
80
81 use Debian::Dgit qw(:DEFAULT :playground);
82 setup_sigwarn();
83
84 use Memoize;
85 use Carp;
86 use POSIX;
87 use Data::Dumper;
88 use Getopt::Long qw(:config posix_default gnu_compat bundling);
89 use Dpkg::Version;
90
91 our ($opt_force);
92
93 sub badusage ($) {
94     my ($m) = @_;
95     die "bad usage: $m\n";
96 }
97
98 sub cfg ($) {
99     my ($k) = @_;
100     $/ = "\0";
101     my @cmd = qw(git config -z);
102     push @cmd, qw(--get-all) if wantarray;
103     push @cmd, $k;
104     my $out = cmdoutput @cmd;
105     return split /\0/, $out;
106 }
107
108 memoize('cfg');
109
110 sub dd ($) {
111     my ($v) = @_;
112     my $dd = new Data::Dumper [ $v ];
113     Terse $dd 1; Indent $dd 0; Useqq $dd 1;
114     return Dump $dd;
115 }
116
117 sub get_commit ($) {
118     my ($objid) = @_;
119     my $data = (git_cat_file $objid, 'commit');
120     $data =~ m/(?<=\n)\n/ or die "$objid ($data) ?";
121     return ($`,$');
122 }
123
124 sub D_UPS ()      { 0x02; } # upstream files
125 sub D_PAT_ADD ()  { 0x04; } # debian/patches/ extra patches at end
126 sub D_PAT_OTH ()  { 0x08; } # debian/patches other changes
127 sub D_DEB_CLOG () { 0x10; } # debian/ (not patches/ or changelog)
128 sub D_DEB_OTH ()  { 0x20; } # debian/changelog
129 sub DS_DEB ()     { D_DEB_CLOG | D_DEB_OTH; } # debian/ (not patches/)
130
131 our $playprefix = 'debrebase';
132 our $rd;
133 our $workarea;
134
135 our @git = qw(git);
136
137 sub in_workarea ($) {
138     my ($sub) = @_;
139     changedir $workarea;
140     my $r = eval { $sub->(); };
141     { local $@; changedir $maindir; }
142     die $@ if $@;
143 }
144
145 sub fresh_workarea () {
146     $workarea = fresh_playground "$playprefix/work";
147     in_workarea sub { playtree_setup };
148 }
149
150 sub get_differs ($$) {
151     my ($x,$y) = @_;
152     # This resembles quiltify_trees_differ, in dgit, a bit.
153     # But we don't care about modes, or dpkg-source-unrepresentable
154     # changes, and we don't need the plethora of different modes.
155     # Conversely we need to distinguish different kinds of changes to
156     # debian/ and debian/patches/.
157
158     my $differs = 0;
159
160     my $rundiff = sub {
161         my ($opts, $limits, $fn) = @_;
162         my @cmd = (@git, qw(diff-tree -z --no-renames));
163         push @cmd, @$opts;
164         push @cmd, "$_:" foreach $x, $y;
165         push @cmd, '--', @$limits;
166         my $diffs = cmdoutput @cmd;
167         foreach (split /\0/, $diffs) { $fn->(); }
168     };
169
170     $rundiff->([qw(--name-only)], [], sub {
171         $differs |= $_ eq 'debian' ? DS_DEB : D_UPS;
172     });
173
174     if ($differs & DS_DEB) {
175         $differs &= ~DS_DEB;
176         $rundiff->([qw(--name-only -r)], [qw(debian)], sub {
177             $differs |=
178                 m{^debian/patches/}      ? D_PAT_OTH  :
179                 $_ eq 'debian/changelog' ? D_DEB_CLOG :
180                                            D_DEB_OTH;
181         });
182         die "mysterious debian changes $x..$y"
183             unless $differs & (D_PAT_OTH|DS_DEB);
184     }
185
186     if ($differs & D_PAT_OTH) {
187         my $mode;
188         $differs &= ~D_PAT_OTH;
189         my $pat_oth = sub {
190             $differs |= D_PAT_OTH;
191             no warnings qw(exiting);  last;
192         };
193         $rundiff->([qw(--name-status -r)], [qw(debian/patches/)], sub {
194             no warnings qw(exiting);
195             if (!defined $mode) {
196                 $mode = $_;  next;
197             }
198             die unless s{^debian/patches/}{};
199             my $ok;
200             if ($mode eq 'A' && !m/\.series$/s) {
201                 $ok = 1;
202             } elsif ($mode eq 'M' && $_ eq 'series') {
203                 my $x_s = (git_cat_file "$x:debian/patches/series", 'blob');
204                 my $y_s = (git_cat_file "$y:debian/patches/series", 'blob');
205                 chomp $x_s;  $x_s .= "\n";
206                 $ok = $x_s eq substr($y_s, 0, length $x_s);
207             } else {
208                 # nope
209             }
210             $mode = undef;
211             $differs |= $ok ? D_PAT_ADD : D_PAT_OTH;
212         });
213         die "mysterious debian/patches changes $x..$y"
214             unless $differs & (D_PAT_ADD|D_PAT_OTH);
215     }
216
217     printdebug sprintf "get_differs %s, %s = %#x\n", $x, $y, $differs;
218
219     return $differs;
220 }
221
222 sub commit_pr_info ($) {
223     my ($r) = @_;
224     return Data::Dumper->dump([$r], [qw(commit)]);
225 }
226
227 sub calculate_committer_authline () {
228     my $c = cmdoutput @git, qw(commit-tree --no-gpg-sign -m),
229         'DUMMY COMMIT (git-debrebase)', "HEAD:";
230     my ($h,$m) = get_commit $c;
231     $h =~ m/^committer .*$/m or confess "($h) ?";
232     return $&;
233 }
234
235 sub rm_subdir_cached ($) {
236     my ($subdir) = @_;
237     runcmd @git, qw(rm --quiet -rf --cached --ignore-unmatch), $subdir;
238 }
239
240 sub read_tree_subdir ($$) {
241     my ($subdir, $new_tree_object) = @_;
242     rm_subdir_cached $subdir;
243     runcmd @git, qw(read-tree), "--prefix=$subdir/", $new_tree_object;
244 }
245
246 sub make_commit ($$) {
247     my ($parents, $message_paras) = @_;
248     my $tree = cmdoutput @git, qw(write-tree);
249     my @cmd = (@git, qw(commit-tree), $tree);
250     push @cmd, qw(-p), $_ foreach @$parents;
251     push @cmd, qw(-m), $_ foreach @$message_paras;
252     return cmdoutput @cmd;
253 }
254
255 our $fproblems;
256 sub fproblem ($$) {
257     my ($tag,$msg) = @_;
258     $fproblems++;
259     print STDERR "git-debrebase: safety catch tripped: $msg\n";
260 }
261 sub fproblems_maybe_bail () {
262     if ($fproblems) {
263         if ($opt_force) {
264             printf STDERR
265                 "safety catch trips (%d) overriden by --force\n",
266                 $fproblems;
267         } else {
268             fail sprintf
269                 "safety catch trips (%d) (you could --force)",
270                 $fproblems;
271         }
272     }
273 }
274 sub any_fproblems () {
275     return !!$fproblems;
276 }
277
278 # classify returns an info hash like this
279 #   CommitId => $objid
280 #   Hdr => # commit headers, including 1 final newline
281 #   Msg => # commit message (so one newline is dropped)
282 #   Tree => $treeobjid
283 #   Type => (see below)
284 #   Parents = [ {
285 #       Ix => $index # ie 0, 1, 2, ...
286 #       CommitId
287 #       Differs => return value from get_differs
288 #       IsOrigin
289 #       IsDggitImport => 'orig' 'tarball' 'unpatched' 'package' (as from dgit)
290 #     } ...]
291 #   NewMsg => # commit message, but with any [dgit import ...] edited
292 #             # to say "[was: ...]"
293 #
294 # Types:
295 #   Packaging
296 #   Changelog
297 #   Upstream
298 #   AddPatches
299 #   Mixed
300 #   Unknown
301 #
302 #   Pseudomerge
303 #     has additional entres in classification result
304 #       Overwritten = [ subset of Parents ]
305 #       Contributor = $the_remaining_Parent
306 #
307 #   DgitImportUnpatched
308 #     has additional entry in classification result
309 #       OrigParents = [ subset of Parents ]
310 #
311 #   BreakwaterUpstreamMerge
312 #     has additional entry in classification result
313 #       OrigParents = [ subset of Parents ]  # singleton list
314
315 sub parsecommit ($;$) {
316     my ($objid, $p_ref) = @_;
317     # => hash with                   CommitId Hdr Msg Tree Parents
318     #    Parents entries have only   Ix CommitId
319     #    $p_ref, if provided, must be [] and is used as a base for Parents
320
321     $p_ref //= [];
322     die if @$p_ref;
323
324     my ($h,$m) = get_commit $objid;
325
326     my ($t) = $h =~ m/^tree (\w+)$/m or die $objid;
327     my (@ph) = $h =~ m/^parent (\w+)$/mg;
328
329     my $r = {
330         CommitId => $objid,
331         Hdr => $h,
332         Msg => $m,
333         Tree => $t,
334         Parents => $p_ref,
335     };
336
337     foreach my $ph (@ph) {
338         push @$p_ref, {
339             Ix => scalar @$p_ref,
340             CommitId => $ph,
341         };
342     }
343
344     return $r;
345 }    
346
347 sub classify ($) {
348     my ($objid) = @_;
349
350     my @p;
351     my $r = parsecommit($objid, \@p);
352     my $t = $r->{Tree};
353
354     foreach my $p (@p) {
355         $p->{Differs} = (get_differs $p->{CommitId}, $t),
356     }
357
358     printdebug "classify $objid \$t=$t \@p",
359         (map { sprintf " %s/%#x", $_->{CommitId}, $_->{Differs} } @p),
360         "\n";
361
362     my $classify = sub {
363         my ($type, @rest) = @_;
364         $r = { %$r, Type => $type, @rest };
365         if ($debuglevel) {
366             printdebug " = $type ".(dd $r)."\n";
367         }
368         return $r;
369     };
370     my $unknown = sub {
371         my ($why) = @_;
372         $r = { %$r, Type => qw(Unknown) };
373         printdebug " ** Unknown\n";
374         return $r;
375     };
376
377     my $claims_to_be_breakwater =
378         $r->{Msg} =~ m{^\[git-debrebase breakwater.*\]$}m;
379
380     if (@p == 1) {
381         if ($claims_to_be_breakwater) {
382             return $unknown->("single-parent git-debrebase breakwater \`merge'");
383         }
384         my $d = $r->{Parents}[0]{Differs};
385         if ($d == D_PAT_ADD) {
386             return $classify->(qw(AddPatches));
387         } elsif ($d & (D_PAT_ADD|D_PAT_OTH)) {
388             return $unknown->("edits debian/patches");
389         } elsif ($d & DS_DEB and !($d & ~DS_DEB)) {
390             my ($ty,$dummy) = git_cat_file "$p[0]{CommitId}:debian";
391             if ($ty eq 'tree') {
392                 if ($d == D_DEB_CLOG) {
393                     return $classify->(qw(Changelog));
394                 } else {
395                     return $classify->(qw(Packaging));
396                 }
397             } elsif ($ty eq 'missing') {
398                 return $classify->(qw(BreakwaterStart));
399             } else {
400                 return $unknown->("parent's debian is not a directory");
401             }
402         } elsif ($d == D_UPS) {
403             return $classify->(qw(Upstream));
404         } elsif ($d & DS_DEB and $d & D_UPS and !($d & ~(DS_DEB|D_UPS))) {
405             return $classify->(qw(Mixed));
406         } elsif ($d == 0) {
407             return $unknown->("no changes");
408         } else {
409             confess "internal error $objid ?";
410         }
411     }
412     if (!@p) {
413         return $unknown->("origin commit");
414     }
415
416     my @identical = grep { !$_->{Differs} } @p;
417     if (@p == 2 && @identical == 1 && !$claims_to_be_breakwater
418         # breakwater merges can look like pseudomerges, if they are
419         # "declare" commits (ie, there are no upstream changes)
420        ) {
421         my @overwritten = grep { $_->{Differs} } @p;
422         confess "internal error $objid ?" unless @overwritten==1;
423         return $classify->(qw(Pseudomerge),
424                            Overwritten => $overwritten[0],
425                            Contributor => $identical[0]);
426     }
427     if (@p == 2 && @identical == 2) {
428         my @bytime = nsort_by {
429             my ($ph,$pm) = get_commit $_->{CommitId};
430             $ph =~ m/^committer .* (\d+) [-+]\d+$/m or die "$_->{CommitId} ?";
431             $1;
432         } @p;
433         return $classify->(qw(Pseudomerge),
434                            SubType => qw(Ambiguous),
435                            Overwritten => $bytime[0],
436                            Contributor => $bytime[1]);
437     }
438     foreach my $p (@p) {
439         my ($p_h, $p_m) = get_commit $p->{CommitId};
440         $p->{IsOrigin} = $p_h !~ m/^parent \w+$/m;
441         ($p->{IsDgitImport},) = $p_m =~ m/^\[dgit import ([0-9a-z]+) .*\]$/m;
442     }
443     my @orig_ps = grep { ($_->{IsDgitImport}//'X') eq 'orig' } @p;
444     my $m2 = $r->{Msg};
445     if (!(grep { !$_->{IsOrigin} } @p) and
446         (@orig_ps >= @p - 1) and
447         $m2 =~ s{^\[(dgit import unpatched .*)\]$}{[was: $1]}m) {
448         $r->{NewMsg} = $m2;
449         return $classify->(qw(DgitImportUnpatched),
450                            OrigParents => \@orig_ps);
451     }
452
453     my ($stype, $series) = git_cat_file "$t:debian/patches/series";
454     my $haspatches = $stype ne 'missing' && $series =~ m/^\s*[^#\n\t ]/m;
455
456     # How to decide about l/r ordering of breakwater merges ?  git
457     # --topo-order prefers to expand 2nd parent first.  There's
458     # already an easy rune to look for debian/ history anyway (git log
459     # debian/) so debian breakwater branch should be 1st parent; that
460     # way also there's also an easy rune to look for the upstream
461     # patches (--topo-order).
462
463     # The above tells us which way *we* will generate them.  But we
464     # might encounter ad-hoc breakwater merges generated manually,
465     # which might be the other way around.  In principle, in some odd
466     # situations, a breakwater merge might have two identical parents.
467     # In that case we guess which way round it is (ie, which parent
468     # has the upstream history).  The order of the 2-iteration loop
469     # controls which guess we make.
470
471     foreach my $prevbrw (qw(0 1)) {
472         if (@p == 2 &&
473             !$haspatches &&
474             !$p[$prevbrw]{IsOrigin} && # breakwater never starts with an origin
475             !($p[!$prevbrw]{Differs} & ~DS_DEB) && # no non-debian changess
476             !($p[$prevbrw]{Differs} & ~D_UPS)) { # no non-upstream changes
477             return $classify->(qw(BreakwaterUpstreamMerge),
478                                OrigParents => [ $p[!$prevbrw] ]);
479         }
480     }
481
482     # multi-orig upstreams are represented with a breakwater merge
483     # from a single upstream commit which combines the orig tarballs
484
485     return $unknown->("complex merge");
486 }
487
488 sub walk ($;$$);
489 sub walk ($;$$) {
490     my ($input,
491         $nogenerate,$report) = @_;
492     # => ($tip, $breakwater_tip, $last_upstream_merge_in_breakwater)
493     # (or nothing, if $nogenerate)
494
495     printdebug "*** WALK $input ".($nogenerate//0)." ".($report//'-')."\n";
496
497     # go through commits backwards
498     # we generate two lists of commits to apply:
499     # breakwater branch and upstream patches
500     my (@brw_cl, @upp_cl, @processed);
501     my %found;
502     my $upp_limit;
503     my @pseudomerges;
504
505     my $cl;
506     my $xmsg = sub {
507         my ($prose, $info) = @_;
508         my $ms = $cl->{Msg};
509         chomp $ms;
510         $info //= '';
511         $ms .= "\n\n[git-debrebase$info: $prose]\n";
512         return (Msg => $ms);
513     };
514     my $rewrite_from_here = sub {
515         my $sp_cl = { SpecialMethod => 'StartRewrite' };
516         push @brw_cl, $sp_cl;
517         push @processed, $sp_cl;
518     };
519     my $cur = $input;
520
521     my $prdelim = "";
522     my $prprdelim = sub { print $report $prdelim if $report; $prdelim=""; };
523
524     my $prline = sub {
525         return unless $report;
526         print $report $prdelim, @_;
527         $prdelim = "\n";
528     };
529
530     my $bomb = sub { # usage: return $bomb->();
531         print $report " Unprocessable" if $report;
532         $prprdelim->();
533         if ($nogenerate) {
534             return (undef,undef);
535         }
536         die "commit $cur: Cannot cope with this commit (d.".
537             (join ' ', map { sprintf "%#x", $_->{Differs} }
538              @{ $cl->{Parents} }). ")";
539     };
540
541     my $build;
542     my $breakwater;
543
544     my $build_start = sub {
545         my ($msg, $parent) = @_;
546         $prline->(" $msg");
547         $build = $parent;
548         no warnings qw(exiting); last;
549     };
550
551     my $last_upstream_update;
552
553     for (;;) {
554         $cl = classify $cur;
555         my $ty = $cl->{Type};
556         my $st = $cl->{SubType};
557         $prline->("$cl->{CommitId} $cl->{Type}");
558         $found{$ty. ( defined($st) ? "-$st" : '' )}++;
559         push @processed, $cl;
560         my $p0 = @{ $cl->{Parents} }==1 ? $cl->{Parents}[0]{CommitId} : undef;
561         if ($ty eq 'AddPatches') {
562             $cur = $p0;
563             $rewrite_from_here->();
564             next;
565         } elsif ($ty eq 'Packaging' or $ty eq 'Changelog') {
566             push @brw_cl, $cl;
567             $cur = $p0;
568             next;
569         } elsif ($ty eq 'BreakwaterStart') {
570             $last_upstream_update = $cur;
571             $build_start->('FirstPackaging', $cur);
572         } elsif ($ty eq 'Upstream') {
573             push @upp_cl, $cl;
574             $cur = $p0;
575             next;
576         } elsif ($ty eq 'Mixed') {
577             my $queue = sub {
578                 my ($q, $wh) = @_;
579                 my $cls = { %$cl, $xmsg->("split mixed commit: $wh part") };
580                 push @$q, $cls;
581             };
582             $queue->(\@brw_cl, "debian");
583             $queue->(\@upp_cl, "upstream");
584             $rewrite_from_here->();
585             $cur = $p0;
586             next;
587         } elsif ($ty eq 'Pseudomerge') {
588             my $contrib = $cl->{Contributor}{CommitId};
589             print $report " Contributor=$contrib" if $report;
590             push @pseudomerges, $cl;
591             $rewrite_from_here->();
592             $cur = $contrib;
593             next;
594         } elsif ($ty eq 'BreakwaterUpstreamMerge') {
595             $last_upstream_update = $cur;
596             $build_start->("PreviousBreakwater", $cur);
597         } elsif ($ty eq 'DgitImportUnpatched') {
598             my $pm = $pseudomerges[-1];
599             if (defined $pm) {
600                 # To an extent, this is heuristic.  Imports don't have
601                 # a useful history of the debian/ branch.  We assume
602                 # that the first pseudomerge after an import has a
603                 # useful history of debian/, and ignore the histories
604                 # from later pseudomerges.  Often the first pseudomerge
605                 # will be the dgit import of the upload to the actual
606                 # suite intended by the non-dgit NMUer, and later
607                 # pseudomerges may represent in-archive copies.
608                 my $ovwrs = $pm->{Overwritten};
609                 printf $report " PM=%s \@Overwr:%d", $pm, (scalar @$ovwrs)
610                     if $report;
611                 if (@$ovwrs != 1) {
612                     printdebug "*** WALK BOMB DgitImportUnpatched\n";
613                     return $bomb->();
614                 }
615                 my $ovwr = $ovwrs->[0]{CommitId};
616                 printf $report " Overwr=%s", $ovwr if $report;
617                 # This import has a tree which is just like a
618                 # breakwater tree, but it has the wrong history.  It
619                 # ought to have the previous breakwater (which the
620                 # pseudomerge overwrote) as an ancestor.  That will
621                 # make the history of the debian/ files correct.  As
622                 # for the upstream version: either it's the same as
623                 # was ovewritten (ie, same as the previous
624                 # breakwater), in which case that history is precisely
625                 # right; or, otherwise, it was a non-gitish upload of a
626                 # new upstream version.  We can tell these apart by
627                 # looking at the tree of the supposed upstream.
628                 push @brw_cl, {
629                     %$cl,
630                     SpecialMethod => 'DgitImportDebianUpdate',
631                     $xmsg->("convert dgit import: debian changes")
632                 };
633                 my $differs = (get_differs $ovwr, $cl->{Tree});
634                 printf $report " Differs=%#x", $differs if $report;
635                 if ($differs & D_UPS) {
636                     printf $report " D_UPS" if $report;
637                     # This will also trigger if a non-dgit git-based NMU
638                     # deleted .gitignore (which is a thing that some of
639                     # the existing git tools do if the user doesn't
640                     # somehow tell them not to).  Ah well.
641                     push @brw_cl, {
642                         %$cl,
643                         SpecialMethod => 'DgitImportUpstreamUpdate',
644                         $xmsg->("convert dgit import: upstream changes",
645                                 " breakwater")
646                     };
647                 }
648                 $prline->(" Import");
649                 $rewrite_from_here->();
650                 $upp_limit //= $#upp_cl; # further, deeper, patches discarded
651                 die 'BUG $upp_limit is not used anywhere?';
652                 $cur = $ovwr;
653                 next;
654             } else {
655                 # Everything is from this import.  This kind of import
656                 # is already in valid breakwater format, with the
657                 # patches as commits.
658                 printf $report " NoPM" if $report;
659                 # last thing we processed will have been the first patch,
660                 # if there is one; which is fine, so no need to rewrite
661                 # on account of this import
662                 $build_start->("ImportOrigin", $cur);
663             }
664             die "$ty ?";
665         } else {
666             printdebug "*** WALK BOMB unrecognised\n";
667             return $bomb->();
668         }
669     }
670     $prprdelim->();
671
672     printdebug "*** WALK prep done cur=$cur".
673         " brw $#brw_cl upp $#upp_cl proc $#processed pm $#pseudomerges\n";
674
675     return if $nogenerate;
676
677     # Now we build it back up again
678
679     fresh_workarea();
680
681     my $rewriting = 0;
682
683     my $read_tree_debian = sub {
684         my ($treeish) = @_;
685         read_tree_subdir 'debian', "$treeish:debian";
686         rm_subdir_cached 'debian/patches';
687     };
688     my $read_tree_upstream = sub {
689         my ($treeish) = @_;
690         runcmd @git, qw(read-tree), $treeish;
691         $read_tree_debian->($build);
692     };
693  
694     my $committer_authline = calculate_committer_authline();
695
696     printdebug "WALK REBUILD $build ".(scalar @processed)."\n";
697
698     confess "internal error" unless $build eq (pop @processed)->{CommitId};
699
700     in_workarea sub {
701         mkdir $rd or $!==EEXIST or die $!;
702         my $current_method;
703         runcmd @git, qw(read-tree), $build;
704         foreach my $cl (qw(Debian), (reverse @brw_cl),
705                         { SpecialMethod => 'RecordBreakwaterTip' },
706                         qw(Upstream), (reverse @upp_cl)) {
707             if (!ref $cl) {
708                 $current_method = $cl;
709                 next;
710             }
711             my $method = $cl->{SpecialMethod} // $current_method;
712             my @parents = ($build);
713             my $cltree = $cl->{CommitId};
714             printdebug "WALK BUILD ".($cltree//'undef').
715                 " $method (rewriting=$rewriting)\n";
716             if ($method eq 'Debian') {
717                 $read_tree_debian->($cltree);
718             } elsif ($method eq 'Upstream') {
719                 $read_tree_upstream->($cltree);
720             } elsif ($method eq 'StartRewrite') {
721                 $rewriting = 1;
722                 next;
723             } elsif ($method eq 'RecordBreakwaterTip') {
724                 $breakwater = $build;
725                 next;
726             } elsif ($method eq 'DgitImportDebianUpdate') {
727                 $read_tree_debian->($cltree);
728                 rm_subdir_cached qw(debian/patches);
729             } elsif ($method eq 'DgitImportUpstreamUpdate') {
730                 $read_tree_upstream->($cltree);
731                 push @parents, map { $_->{CommitId} } @{ $cl->{OrigParents} };
732             } else {
733                 confess "$method ?";
734             }
735             if (!$rewriting) {
736                 my $procd = (pop @processed) // 'UNDEF';
737                 if ($cl ne $procd) {
738                     $rewriting = 1;
739                     printdebug "WALK REWRITING NOW cl=$cl procd=$procd\n";
740                 }
741             }
742             my $newtree = cmdoutput @git, qw(write-tree);
743             my $ch = $cl->{Hdr};
744             $ch =~ s{^tree .*}{tree $newtree}m or confess "$ch ?";
745             $ch =~ s{^parent .*\n}{}m;
746             $ch =~ s{(?=^author)}{
747                 join '', map { "parent $_\n" } @parents
748             }me or confess "$ch ?";
749             if ($rewriting) {
750                 $ch =~ s{^committer .*$}{$committer_authline}m
751                     or confess "$ch ?";
752             }
753             my $cf = "$rd/m$rewriting";
754             open CD, ">", $cf or die $!;
755             print CD $ch, "\n", $cl->{Msg} or die $!;
756             close CD or die $!;
757             my @cmd = (@git, qw(hash-object));
758             push @cmd, qw(-w) if $rewriting;
759             push @cmd, qw(-t commit), $cf;
760             my $newcommit = cmdoutput @cmd;
761             confess "$ch ?" unless $rewriting or $newcommit eq $cl->{CommitId};
762             $build = $newcommit;
763             if (grep { $method eq $_ } qw(DgitImportUpstreamUpdate)) {
764                 $last_upstream_update = $cur;
765             }
766         }
767     };
768
769     my $final_check = get_differs $build, $input;
770     die sprintf "internal error %#x %s %s", $final_check, $build, $input
771         if $final_check & ~D_PAT_ADD;
772
773     my @r = ($build, $breakwater, $last_upstream_update);
774     printdebug "*** WALK RETURN @r\n";
775     return @r
776 }
777
778 sub get_head () { return git_rev_parse qw(HEAD); }
779
780 sub update_head ($$$) {
781     my ($old, $new, $mrest) = @_;
782     runcmd @git, qw(update-ref -m), "debrebase: $mrest", 'HEAD', $new, $old;
783 }
784
785 sub update_head_checkout ($$$) {
786     my ($old, $new, $mrest) = @_;
787     update_head $old, $new, $mrest;
788     runcmd @git, qw(reset --hard);
789 }
790
791 sub update_head_postlaunder ($$$) {
792     my ($old, $tip, $reflogmsg) = @_;
793     return if $tip eq $old;
794     print "git-debrebase: laundered (head was $old)\n";
795     update_head $old, $tip, $reflogmsg;
796     # no tree changes except debian/patches
797     runcmd @git, qw(rm --quiet --ignore-unmatch -rf debian/patches);
798 }
799
800 sub cmd_launder () {
801     badusage "no arguments to launder allowed" if @ARGV;
802     my $old = get_head();
803     my ($tip,$breakwater,$last_upstream_merge) = walk $old;
804     update_head_postlaunder $old, $tip, 'launder';
805     printf "# breakwater tip\n%s\n", $breakwater;
806     printf "# working tip\n%s\n", $tip;
807     printf "# last upstream merge\n%s\n", $last_upstream_merge;
808 }
809
810 sub defaultcmd_rebase () {
811     my $old = get_head();
812     my ($tip,$breakwater) = walk $old;
813     update_head_postlaunder $old, $tip, 'launder for rebase';
814     @ARGV = qw(-i) unless @ARGV; # make configurable
815     runcmd @git, qw(rebase), @ARGV, $breakwater;
816 }
817
818 sub cmd_analyse () {
819     die if ($ARGV[0]//'') =~ m/^-/;
820     badusage "too many arguments to analyse" if @ARGV>1;
821     my ($old) = @ARGV;
822     if (defined $old) {
823         $old = git_rev_parse $old;
824     } else {
825         $old = get_head();
826     }
827     my ($dummy,$breakwater) = walk $old, 1,*STDOUT;
828     STDOUT->error and die $!;
829 }
830
831 sub cmd_new_upstream_v0 () {
832     # tree should be clean and this is not checked
833     # automatically and unconditionally launders before rebasing
834     # if rebase --abort is used, laundering has still been done
835
836     my %pieces;
837
838     badusage "need NEW-VERSION UPS-COMMITTISH" unless @ARGV >= 2;
839
840     # parse args - low commitment
841     my $new_version = (new Dpkg::Version scalar(shift @ARGV), check => 1);
842     my $new_upstream_version = $new_version->version();
843
844     my $new_upstream = git_rev_parse shift @ARGV;
845
846     my $piece = sub {
847         my ($n, @x) = @_; # may be ''
848         my $pc = $pieces{$n} //= {
849             Name => $n,
850             Desc => ($n ? "upstream piece \`$n'" : "upstream (main piece"),
851         };
852         while (my $k = shift @x) { $pc->{$k} = shift @x; }
853         $pc;
854     };
855
856     my @newpieces;
857     my $newpiece = sub {
858         my ($n, @x) = @_; # may be ''
859         my $pc = $piece->($n, @x, NewIx => (scalar @newpieces));
860         push @newpieces, $pc;
861     };
862
863     $newpiece->('',
864         OldIx => 0,
865         New => $new_upstream,
866     );
867     while (@ARGV && $ARGV[0] !~ m{^-}) {
868         my $n = shift @ARGV;
869
870         badusage "for each EXTRA-UPS-NAME need EXTRA-UPS-COMMITISH"
871             unless @ARGV && $ARGV[0] !~ m{^-};
872
873         my $c = git_rev_parse shift @ARGV;
874         die unless $n =~ m/^$extra_orig_namepart_re$/;
875         $newpiece->($n, New => $c);
876     }
877
878     # now we need to investigate the branch this generates the
879     # laundered version but we don't switch to it yet
880     my $old_head = get_head();
881     my ($old_laundered_tip,$old_bw,$old_upstream_update) = walk $old_head;
882
883     my $old_bw_cl = classify $old_bw;
884     my $old_upstream_update_cl = classify $old_upstream_update;
885     confess unless $old_upstream_update_cl->{OrigParents};
886     my $old_upstream = parsecommit
887         $old_upstream_update_cl->{OrigParents}[0]{CommitId};
888
889     $piece->('', Old => $old_upstream->{CommitId});
890
891     if ($old_upstream->{Msg} =~ m{^\[git-debrebase }m) {
892         if ($old_upstream->{Msg} =~
893  m{^\[git-debrebase upstream-combine \.((?: $extra_orig_namepart_re)+)\:.*\]$}m
894            ) {
895             my @oldpieces = ('', split / /, $1);
896             my $parentix = -1 + scalar @{ $old_upstream->{Parents} };
897             foreach my $i (0..$#oldpieces) {
898                 my $n = $oldpieces[$i];
899                 $piece->($n, Old => $old_upstream->{CommitId}.'^'.$parentix);
900             }
901         } else {
902             fproblem 'upstream-confusing',
903                 "previous upstream $old_upstream->{CommitId} is from".
904                " git-debrebase but not an \`upstream-combine' commit";
905         }
906     }
907
908     foreach my $pc (values %pieces) {
909         if (!$pc->{Old}) {
910             fproblem 'upstream-new-piece',
911                 "introducing upstream piece \`$pc->{Name}'";
912         } elsif (!$pc->{New}) {
913             fproblem 'upstream-rm-piece',
914                 "dropping upstream piece \`$pc->{Name}'";
915         } elsif (!is_fast_fwd $pc->{Old}, $pc->{New}) {
916             fproblem 'upstream-not-ff',
917                 "not fast forward: $pc->{Name} $pc->{Old}..$pc->{New}";
918         }
919     }
920
921     printdebug "%pieces = ", (dd \%pieces), "\n";
922     printdebug "\@newpieces = ", (dd \@newpieces), "\n";
923
924     fproblems_maybe_bail();
925
926     my $new_bw;
927
928     fresh_workarea();
929     in_workarea sub {
930         my @upstream_merge_parents;
931
932         if (!any_fproblems()) {
933             push @upstream_merge_parents, $old_upstream->{CommitId};
934         }
935
936         foreach my $pc (@newpieces) { # always has '' first
937             if ($pc->{Name}) {
938                 read_tree_subdir $pc->{Name}, $pc->{New};
939             } else {
940                 runcmd @git, qw(read-tree), $pc->{New};
941             }
942             push @upstream_merge_parents, $pc->{New};
943         }
944
945         # index now contains the new upstream
946
947         if (@newpieces > 1) {
948             # need to make the upstream subtree merge commit
949             $new_upstream = make_commit \@upstream_merge_parents,
950                 [ "Combine upstreams for $new_upstream_version",
951  ("[git-debrebase upstream-combine . ".
952  (join " ", map { $_->{Name} } @newpieces[1..$#newpieces]).
953  ": new upstream]"),
954                 ];
955         }
956
957         # $new_upstream is either the single upstream commit, or the
958         # combined commit we just made.  Either way it will be the
959         # "upstream" parent of the breakwater special merge.
960
961         read_tree_subdir 'debian', "$old_bw:debian";
962
963         # index now contains the breakwater merge contents
964         $new_bw = make_commit [ $old_bw, $new_upstream ],
965             [ "Update to upstream $new_upstream_version",
966  "[git-debrebase breakwater: new upstream $new_upstream_version, merge]",
967             ];
968
969         # Now we have to add a changelog stanza so the Debian version
970         # is right.
971         die if unlink "debian";
972         die $! unless $!==ENOENT or $!==ENOTEMPTY;
973         unlink "debian/changelog" or $!==ENOENT or die $!;
974         mkdir "debian" or die $!;
975         open CN, ">", "debian/changelog" or die $!;
976         my $oldclog = git_cat_file ":debian/changelog";
977         $oldclog =~ m/^($package_re) \(\S+\) / or
978             fail "cannot parse old changelog to get package name";
979         my $p = $1;
980         print CN <<END, $oldclog or die $!;
981 $p ($new_version) UNRELEASED; urgency=medium
982
983   * Update to new upstream version $new_upstream_version.
984
985  -- 
986
987 END
988         close CN or die $!;
989         runcmd @git, qw(update-index --add --replace), 'debian/changelog';
990
991         # Now we have the final new breakwater branch in the index
992         $new_bw = make_commit [ $new_bw ],
993             [ "Update changelog for new upstream $new_upstream_version",
994               "[git-debrebase: new upstream $new_upstream_version, changelog]",
995             ];
996     };
997
998     # we have constructed the new breakwater. we now need to commit to
999     # the laundering output, because git-rebase can't easily be made
1000     # to make a replay list which is based on some other branch
1001
1002     update_head_postlaunder $old_head, $old_laundered_tip,
1003         'launder for new upstream';
1004
1005     my @cmd = (@git, qw(rebase --onto), $new_bw, $old_bw, @ARGV);
1006     runcmd @cmd;
1007     # now it's for the user to sort out
1008 }
1009
1010 sub cmd_gbp2debrebase () {
1011     badusage "needs 1 optional argument, the upstream" unless @ARGV<=1;
1012     my ($upstream_spec) = @ARGV;
1013     $upstream_spec //= 'refs/heads/upstream';
1014     my $upstream = git_rev_parse $upstream_spec;
1015     my $old_head = get_head();
1016
1017     my $upsdiff = get_differs $upstream, $old_head;
1018     if ($upsdiff & D_UPS) {
1019         runcmd @git, qw(--no-pager diff),
1020             $upstream, $old_head,
1021             qw( -- :!/debian :/);
1022  fail "upstream ($upstream_spec) and HEAD are not identical in upstream files";
1023     }
1024
1025     if (!is_fast_fwd $upstream, $old_head) {
1026         fproblem 'upstream-not-ancestor',
1027             "upstream ($upstream) is not an ancestor of HEAD";
1028     } else {
1029         my $wrong = cmdoutput
1030             (@git, qw(rev-list --ancestry-path), "$upstream..HEAD",
1031              qw(-- :/ :!/debian));
1032         if (length $wrong) {
1033             fproblem 'unexpected-upstream-changes',
1034                 "history between upstream ($upstream) and HEAD contains direct changes to upstream files - are you sure this is a gbp (patches-unapplied) branch?";
1035             print STDERR "list expected changes with:  git log --stat --ancestry-path $upstream_spec..HEAD -- :/ ':!/debian'\n";
1036         }
1037     }
1038
1039     if ((git_cat_file "$upstream:debian")[0] ne 'missing') {
1040         fproblem 'upstream-has-debian',
1041             "upstream ($upstream) contains debian/ directory";
1042     }
1043
1044     fproblems_maybe_bail();
1045
1046     my $work;
1047
1048     fresh_workarea();
1049     in_workarea sub {
1050         runcmd @git, qw(checkout -q -b gdr-internal), $old_head;
1051         # make a branch out of the patch queue - we'll want this in a mo
1052         runcmd qw(gbp pq import);
1053         # strip the patches out
1054         runcmd @git, qw(checkout -q gdr-internal~0);
1055         rm_subdir_cached 'debian/patches';
1056         $work = make_commit ['HEAD'], [
1057  'git-debrebase import: drop patch queue',
1058  'Delete debian/patches, as part of converting to git-debrebase format.',
1059  '[git-debrebase: gbp2debrebase, drop patches]'
1060                               ];
1061         # make the breakwater pseudomerge
1062         # the tree is already exactly right
1063         $work = make_commit [$work, $upstream], [
1064  'git-debrebase import: declare upstream',
1065  'First breakwater merge.',
1066  '[git-debrebase breakwater: declare upstream]'
1067                               ];
1068
1069         # rebase the patch queue onto the new breakwater
1070         runcmd @git, qw(reset --quiet --hard patch-queue/gdr-internal);
1071         runcmd @git, qw(rebase --quiet --onto), $work, qw(gdr-internal);
1072         $work = get_head();
1073     };
1074
1075     update_head_checkout $old_head, $work, 'gbp2debrebase';
1076 }
1077
1078 sub cmd_downstream_rebase_launder_v0 () {
1079     badusage "needs 1 argument, the baseline" unless @ARGV==1;
1080     my ($base) = @ARGV;
1081     $base = git_rev_parse $base;
1082     my $old_head = get_head();
1083     my $current = $old_head;
1084     my $topmost_keep;
1085     for (;;) {
1086         if ($current eq $base) {
1087             $topmost_keep //= $current;
1088             print " $current BASE stop\n";
1089             last;
1090         }
1091         my $cl = classify $current;
1092         print " $current $cl->{Type}";
1093         my $keep = 0;
1094         my $p0 = $cl->{Parents}[0]{CommitId};
1095         my $next;
1096         if ($cl->{Type} eq 'Pseudomerge') {
1097             print " ^".($cl->{Contributor}{Ix}+1);
1098             $next = $cl->{Contributor}{CommitId};
1099         } elsif ($cl->{Type} eq 'AddPatches' or
1100                  $cl->{Type} eq 'Changelog') {
1101             print " strip";
1102             $next = $p0;
1103         } else {
1104             print " keep";
1105             $next = $p0;
1106             $keep = 1;
1107         }
1108         print "\n";
1109         if ($keep) {
1110             $topmost_keep //= $current;
1111         } else {
1112             die "to-be stripped changes not on top of the branch\n"
1113                 if $topmost_keep;
1114         }
1115         $current = $next;
1116     }
1117     if ($topmost_keep eq $old_head) {
1118         print "unchanged\n";
1119     } else {
1120         print "updating to $topmost_keep\n";
1121         update_head_checkout
1122             $old_head, $topmost_keep,
1123             'downstream-rebase-launder-v0';
1124     }
1125 }
1126
1127 GetOptions("D+" => \$debuglevel,
1128            'force!') or die badusage "bad options\n";
1129 initdebug('git-debrebase ');
1130 enabledebug if $debuglevel;
1131
1132 my $toplevel = cmdoutput @git, qw(rev-parse --show-toplevel);
1133 chdir $toplevel or die "chdir $toplevel: $!";
1134
1135 $rd = fresh_playground "$playprefix/misc";
1136
1137 if (!@ARGV || $ARGV[0] =~ m{^-}) {
1138     defaultcmd_rebase();
1139 } else {
1140     my $cmd = shift @ARGV;
1141     my $cmdfn = $cmd;
1142     $cmdfn =~ y/-/_/;
1143     $cmdfn = ${*::}{"cmd_$cmdfn"};
1144
1145     $cmdfn or badusage "unknown git-debrebase sub-operation $cmd";
1146     $cmdfn->();
1147 }