chiark / gitweb /
exit status: Fix dgit to use Debian::Dgit::ExitStatus
[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 use strict;
22
23 use Debian::Dgit qw(:DEFAULT :playground);
24 setup_sigwarn();
25
26 use Memoize;
27 use Carp;
28 use POSIX;
29 use Data::Dumper;
30 use Getopt::Long qw(:config posix_default gnu_compat bundling);
31 use Dpkg::Version;
32 use File::FnMatch qw(:fnmatch);
33
34 our ($opt_force, $opt_noop_ok, @opt_anchors);
35 our ($opt_defaultcmd_interactive);
36
37 our $us = qw(git-debrebase);
38
39 sub badusage ($) {
40     my ($m) = @_;
41     print STDERR "bad usage: $m\n";
42     exit 12;
43 }
44
45 sub cfg ($;$) {
46     my ($k, $optional) = @_;
47     local $/ = "\0";
48     my @cmd = qw(git config -z);
49     push @cmd, qw(--get-all) if wantarray;
50     push @cmd, $k;
51     my $out = cmdoutput_errok @cmd;
52     if (!defined $out) {
53         fail "missing required git config $k" unless $optional;
54         return ();
55     }
56     my @l = split /\0/, $out;
57     return wantarray ? @l : $l[0];
58 }
59
60 memoize('cfg');
61
62 sub dd ($) {
63     my ($v) = @_;
64     my $dd = new Data::Dumper [ $v ];
65     Terse $dd 1; Indent $dd 0; Useqq $dd 1;
66     return Dump $dd;
67 }
68
69 sub get_commit ($) {
70     my ($objid) = @_;
71     my $data = (git_cat_file $objid, 'commit');
72     $data =~ m/(?<=\n)\n/ or die "$objid ($data) ?";
73     return ($`,$');
74 }
75
76 sub D_UPS ()      { 0x02; } # upstream files
77 sub D_PAT_ADD ()  { 0x04; } # debian/patches/ extra patches at end
78 sub D_PAT_OTH ()  { 0x08; } # debian/patches other changes
79 sub D_DEB_CLOG () { 0x10; } # debian/ (not patches/ or changelog)
80 sub D_DEB_OTH ()  { 0x20; } # debian/changelog
81 sub DS_DEB ()     { D_DEB_CLOG | D_DEB_OTH; } # debian/ (not patches/)
82
83 our $playprefix = 'debrebase';
84 our $rd;
85 our $workarea;
86
87 our @git = qw(git);
88
89 sub in_workarea ($) {
90     my ($sub) = @_;
91     changedir $workarea;
92     my $r = eval { $sub->(); };
93     { local $@; changedir $maindir; }
94     die $@ if $@;
95 }
96
97 sub fresh_workarea () {
98     $workarea = fresh_playground "$playprefix/work";
99     in_workarea sub { playtree_setup };
100 }
101
102 our $snags_forced;
103 our $snags_tripped;
104 our $snags_checked;
105 our @deferred_updates;
106 our @deferred_update_messages;
107
108 sub run_deferred_updates ($) {
109     my ($mrest) = @_;
110
111     confess 'dangerous internal error' if
112         !$snags_checked || $snags_tripped || $snags_forced;
113
114     my @upd_cmd = (@git, qw(update-ref --stdin -m), "debrebase: $mrest");
115     debugcmd '>|', @upd_cmd;
116     open U, "|-", @upd_cmd or die $!;
117     foreach (@deferred_updates) {
118         printdebug ">= ", $_, "\n";
119         print U $_, "\n" or die $!;
120     }
121     printdebug ">\$\n";
122     close U or failedcmd @upd_cmd;
123
124     print $_, "\n" foreach @deferred_update_messages;
125
126     @deferred_updates = ();
127     @deferred_update_messages = ();
128 }
129
130 sub get_differs ($$) {
131     my ($x,$y) = @_;
132     # This resembles quiltify_trees_differ, in dgit, a bit.
133     # But we don't care about modes, or dpkg-source-unrepresentable
134     # changes, and we don't need the plethora of different modes.
135     # Conversely we need to distinguish different kinds of changes to
136     # debian/ and debian/patches/.
137
138     my $differs = 0;
139
140     my $rundiff = sub {
141         my ($opts, $limits, $fn) = @_;
142         my @cmd = (@git, qw(diff-tree -z --no-renames));
143         push @cmd, @$opts;
144         push @cmd, "$_:" foreach $x, $y;
145         push @cmd, '--', @$limits;
146         my $diffs = cmdoutput @cmd;
147         foreach (split /\0/, $diffs) { $fn->(); }
148     };
149
150     $rundiff->([qw(--name-only)], [], sub {
151         $differs |= $_ eq 'debian' ? DS_DEB : D_UPS;
152     });
153
154     if ($differs & DS_DEB) {
155         $differs &= ~DS_DEB;
156         $rundiff->([qw(--name-only -r)], [qw(debian)], sub {
157             $differs |=
158                 m{^debian/patches/}      ? D_PAT_OTH  :
159                 $_ eq 'debian/changelog' ? D_DEB_CLOG :
160                                            D_DEB_OTH;
161         });
162         die "mysterious debian changes $x..$y"
163             unless $differs & (D_PAT_OTH|DS_DEB);
164     }
165
166     if ($differs & D_PAT_OTH) {
167         my $mode;
168         $differs &= ~D_PAT_OTH;
169         my $pat_oth = sub {
170             $differs |= D_PAT_OTH;
171             no warnings qw(exiting);  last;
172         };
173         $rundiff->([qw(--name-status -r)], [qw(debian/patches/)], sub {
174             no warnings qw(exiting);
175             if (!defined $mode) {
176                 $mode = $_;  next;
177             }
178             die unless s{^debian/patches/}{};
179             my $ok;
180             if ($mode eq 'A' && !m/\.series$/s) {
181                 $ok = 1;
182             } elsif ($mode eq 'M' && $_ eq 'series') {
183                 my $x_s = (git_cat_file "$x:debian/patches/series", 'blob');
184                 my $y_s = (git_cat_file "$y:debian/patches/series", 'blob');
185                 chomp $x_s;  $x_s .= "\n";
186                 $ok = $x_s eq substr($y_s, 0, length $x_s);
187             } else {
188                 # nope
189             }
190             $mode = undef;
191             $differs |= $ok ? D_PAT_ADD : D_PAT_OTH;
192         });
193         die "mysterious debian/patches changes $x..$y"
194             unless $differs & (D_PAT_ADD|D_PAT_OTH);
195     }
196
197     printdebug sprintf "get_differs %s, %s = %#x\n", $x, $y, $differs;
198
199     return $differs;
200 }
201
202 sub commit_pr_info ($) {
203     my ($r) = @_;
204     return Data::Dumper->dump([$r], [qw(commit)]);
205 }
206
207 sub calculate_committer_authline () {
208     my $c = cmdoutput @git, qw(commit-tree --no-gpg-sign -m),
209         'DUMMY COMMIT (git-debrebase)', "HEAD:";
210     my ($h,$m) = get_commit $c;
211     $h =~ m/^committer .*$/m or confess "($h) ?";
212     return $&;
213 }
214
215 sub rm_subdir_cached ($) {
216     my ($subdir) = @_;
217     runcmd @git, qw(rm --quiet -rf --cached --ignore-unmatch), $subdir;
218 }
219
220 sub read_tree_subdir ($$) {
221     my ($subdir, $new_tree_object) = @_;
222     rm_subdir_cached $subdir;
223     runcmd @git, qw(read-tree), "--prefix=$subdir/", $new_tree_object;
224 }
225
226 sub make_commit ($$) {
227     my ($parents, $message_paras) = @_;
228     my $tree = cmdoutput @git, qw(write-tree);
229     my @cmd = (@git, qw(commit-tree), $tree);
230     push @cmd, qw(-p), $_ foreach @$parents;
231     push @cmd, qw(-m), $_ foreach @$message_paras;
232     return cmdoutput @cmd;
233 }
234
235 our @snag_force_opts;
236 sub snag ($$) {
237     my ($tag,$msg) = @_;
238     if (grep { $_ eq $tag } @snag_force_opts) {
239         $snags_forced++;
240         print STDERR "git-debrebase: snag ignored (-f$tag): $msg\n";
241     } else {
242         $snags_tripped++;
243         print STDERR "git-debrebase: snag detected (-f$tag): $msg\n";
244     }
245 }
246
247 sub snags_maybe_bail () {
248     $snags_checked++;
249     if ($snags_forced) {
250         printf STDERR
251             "%s: snags: %d overriden by individual -f options\n",
252             $us, $snags_forced;
253         $snags_forced=0;
254     }
255     if ($snags_tripped) {
256         if ($opt_force) {
257             printf STDERR
258                 "%s: snags: %d overriden by global --force\n",
259                 $us, $snags_tripped;
260             $snags_tripped=0;
261         } else {
262             fail sprintf
263   "%s: snags: %d blockers (you could -f<tag>, or --force)",
264                 $us, $snags_tripped;
265         }
266     }
267 }
268 sub any_snags () {
269     return $snags_forced || $snags_tripped;
270 }
271
272 # classify returns an info hash like this
273 #   CommitId => $objid
274 #   Hdr => # commit headers, including 1 final newline
275 #   Msg => # commit message (so one newline is dropped)
276 #   Tree => $treeobjid
277 #   Type => (see below)
278 #   Parents = [ {
279 #       Ix => $index # ie 0, 1, 2, ...
280 #       CommitId
281 #       Differs => return value from get_differs
282 #       IsOrigin
283 #       IsDggitImport => 'orig' 'tarball' 'unpatched' 'package' (as from dgit)
284 #     } ...]
285 #   NewMsg => # commit message, but with any [dgit import ...] edited
286 #             # to say "[was: ...]"
287 #
288 # Types:
289 #   Packaging
290 #   Changelog
291 #   Upstream
292 #   AddPatches
293 #   Mixed
294 #
295 #   Pseudomerge
296 #     has additional entres in classification result
297 #       Overwritten = [ subset of Parents ]
298 #       Contributor = $the_remaining_Parent
299 #
300 #   DgitImportUnpatched
301 #     has additional entry in classification result
302 #       OrigParents = [ subset of Parents ]
303 #
304 #   Anchor
305 #     has additional entry in classification result
306 #       OrigParents = [ subset of Parents ]  # singleton list
307 #
308 #   TreatAsAnchor
309 #
310 #   BreakwaterStart
311 #
312 #   Unknown
313 #     has additional entry in classification result
314 #       Why => "prose"
315
316 sub parsecommit ($;$) {
317     my ($objid, $p_ref) = @_;
318     # => hash with                   CommitId Hdr Msg Tree Parents
319     #    Parents entries have only   Ix CommitId
320     #    $p_ref, if provided, must be [] and is used as a base for Parents
321
322     $p_ref //= [];
323     die if @$p_ref;
324
325     my ($h,$m) = get_commit $objid;
326
327     my ($t) = $h =~ m/^tree (\w+)$/m or die $objid;
328     my (@ph) = $h =~ m/^parent (\w+)$/mg;
329
330     my $r = {
331         CommitId => $objid,
332         Hdr => $h,
333         Msg => $m,
334         Tree => $t,
335         Parents => $p_ref,
336     };
337
338     foreach my $ph (@ph) {
339         push @$p_ref, {
340             Ix => scalar @$p_ref,
341             CommitId => $ph,
342         };
343     }
344
345     return $r;
346 }    
347
348 sub classify ($) {
349     my ($objid) = @_;
350
351     my @p;
352     my $r = parsecommit($objid, \@p);
353     my $t = $r->{Tree};
354
355     foreach my $p (@p) {
356         $p->{Differs} = (get_differs $p->{CommitId}, $t),
357     }
358
359     printdebug "classify $objid \$t=$t \@p",
360         (map { sprintf " %s/%#x", $_->{CommitId}, $_->{Differs} } @p),
361         "\n";
362
363     my $classify = sub {
364         my ($type, @rest) = @_;
365         $r = { %$r, Type => $type, @rest };
366         if ($debuglevel) {
367             printdebug " = $type ".(dd $r)."\n";
368         }
369         return $r;
370     };
371     my $unknown = sub {
372         my ($why) = @_;
373         $r = { %$r, Type => qw(Unknown), Why => $why };
374         printdebug " ** Unknown\n";
375         return $r;
376     };
377
378     if (grep { $_ eq $objid } @opt_anchors) {
379         return $classify->('TreatAsAnchor');
380     }
381
382     my @identical = grep { !$_->{Differs} } @p;
383     my ($stype, $series) = git_cat_file "$t:debian/patches/series";
384     my $haspatches = $stype ne 'missing' && $series =~ m/^\s*[^#\n\t ]/m;
385
386     if ($r->{Msg} =~ m{^\[git-debrebase anchor.*\]$}m) {
387         # multi-orig upstreams are represented with an anchor merge
388         # from a single upstream commit which combines the orig tarballs
389
390         # Every anchor tagged this way must be a merge.
391         # We are relying on the
392         #     [git-debrebase anchor: ...]
393         # commit message annotation in "declare" anchor merges (which
394         # do not have any upstream changes), to distinguish those
395         # anchor merges from ordinary pseudomerges (which we might
396         # just try to strip).
397         #
398         # However, the user is going to be doing git-rebase a lot.  We
399         # really don't want them to rewrite an anchor commit.
400         # git-rebase trips up on merges, so that is a useful safety
401         # catch.
402         #
403         # BreakwaterStart commits are also anchors in the terminology
404         # of git-debrebase(5), but they are untagged (and always
405         # manually generated).
406         #
407         # We cannot not tolerate any tagged linear commit (ie,
408         # BreakwaterStart commits tagged `[anchor:') because such a
409         # thing could result from an erroneous linearising raw git
410         # rebase of a merge anchor.  That would represent a corruption
411         # of the branch. and we want to detect and reject the results
412         # of such corruption before it makes it out anywhere.  If we
413         # reject it here then we avoid making the pseudomerge which
414         # would be needed to push it.
415
416         my $badanchor = sub { $unknown->("git-debrebase \`anchor' but @_"); };
417         @p == 2 or return $badanchor->("has other than two parents");
418         $haspatches and return $badanchor->("contains debian/patches");
419
420         # How to decide about l/r ordering of anchors ?  git
421         # --topo-order prefers to expand 2nd parent first.  There's
422         # already an easy rune to look for debian/ history anyway (git log
423         # debian/) so debian breakwater branch should be 1st parent; that
424         # way also there's also an easy rune to look for the upstream
425         # patches (--topo-order).
426
427         $p[0]{IsOrigin} and $badanchor->("is an origin commit");
428         $p[1]{Differs} & ~DS_DEB and
429             $badanchor->("upstream files differ from left parent");
430         $p[0]{Differs} & ~D_UPS and
431             $badanchor->("debian/ differs from right parent");
432
433         return $classify->(qw(Anchor),
434                            OrigParents => [ $p[1] ]);
435     }
436
437     if (@p == 1) {
438         my $d = $r->{Parents}[0]{Differs};
439         if ($d == D_PAT_ADD) {
440             return $classify->(qw(AddPatches));
441         } elsif ($d & (D_PAT_ADD|D_PAT_OTH)) {
442             return $unknown->("edits debian/patches");
443         } elsif ($d & DS_DEB and !($d & ~DS_DEB)) {
444             my ($ty,$dummy) = git_cat_file "$p[0]{CommitId}:debian";
445             if ($ty eq 'tree') {
446                 if ($d == D_DEB_CLOG) {
447                     return $classify->(qw(Changelog));
448                 } else {
449                     return $classify->(qw(Packaging));
450                 }
451             } elsif ($ty eq 'missing') {
452                 return $classify->(qw(BreakwaterStart));
453             } else {
454                 return $unknown->("parent's debian is not a directory");
455             }
456         } elsif ($d == D_UPS) {
457             return $classify->(qw(Upstream));
458         } elsif ($d & DS_DEB and $d & D_UPS and !($d & ~(DS_DEB|D_UPS))) {
459             return $classify->(qw(Mixed));
460         } elsif ($d == 0) {
461             return $unknown->("no changes");
462         } else {
463             confess "internal error $objid ?";
464         }
465     }
466     if (!@p) {
467         return $unknown->("origin commit");
468     }
469
470     if (@p == 2 && @identical == 1) {
471         my @overwritten = grep { $_->{Differs} } @p;
472         confess "internal error $objid ?" unless @overwritten==1;
473         return $classify->(qw(Pseudomerge),
474                            Overwritten => [ $overwritten[0] ],
475                            Contributor => $identical[0]);
476     }
477     if (@p == 2 && @identical == 2) {
478         my $get_t = sub {
479             my ($ph,$pm) = get_commit $_[0]{CommitId};
480             $ph =~ m/^committer .* (\d+) [-+]\d+$/m or die "$_->{CommitId} ?";
481             $1;
482         };
483         my @bytime = @p;
484         my $order = $get_t->($bytime[0]) <=> $get_t->($bytime[1]);
485         if ($order > 0) { # newer first
486         } elsif ($order < 0) {
487             @bytime = reverse @bytime;
488         } else {
489             # same age, default to order made by -s ours
490             # that is, commit was made by someone who preferred L
491         }
492         return $classify->(qw(Pseudomerge),
493                            SubType => qw(Ambiguous),
494                            Contributor => $bytime[0],
495                            Overwritten => [ $bytime[1] ]);
496     }
497     foreach my $p (@p) {
498         my ($p_h, $p_m) = get_commit $p->{CommitId};
499         $p->{IsOrigin} = $p_h !~ m/^parent \w+$/m;
500         ($p->{IsDgitImport},) = $p_m =~ m/^\[dgit import ([0-9a-z]+) .*\]$/m;
501     }
502     my @orig_ps = grep { ($_->{IsDgitImport}//'X') eq 'orig' } @p;
503     my $m2 = $r->{Msg};
504     if (!(grep { !$_->{IsOrigin} } @p) and
505         (@orig_ps >= @p - 1) and
506         $m2 =~ s{^\[(dgit import unpatched .*)\]$}{[was: $1]}m) {
507         $r->{NewMsg} = $m2;
508         return $classify->(qw(DgitImportUnpatched),
509                            OrigParents => \@orig_ps);
510     }
511
512     return $unknown->("complex merge");
513 }
514
515 sub keycommits ($;$$$) {
516     my ($head, $furniture, $unclean, $trouble) = @_;
517     # => ($anchor, $breakwater)
518
519     # $unclean->("unclean-$tagsfx", $msg)
520     # $furniture->("unclean-$tagsfx", $msg)
521     # $dgitimport->("unclean-$tagsfx", $msg)
522     #   is callled for each situation or commit that
523     #   wouldn't be found in a laundered branch
524     # $furniture is for furniture commits such as might be found on an
525     #   interchange branch (pseudomerge, d/patches, changelog)
526     # $trouble is for things whnich prevent the return of
527     #   anchor and breakwater information; if that is ignored,
528     #   then keycommits returns (undef, undef) instead.
529     #
530     # If a callback is undef, fail is called instead.
531     # If a callback is defined but false, the situation is ignored.
532     # Callbacks may say:
533     #   no warnings qw(exiting); last;
534     # if the answer is no longer wanted.
535
536     my ($anchor, $breakwater);
537     my $clogonly;
538     my $x = sub {
539         my ($cb, $tagsfx, $why) = @_;
540         my $m = "branch needs laundering (run git-debrebase): $why";
541         fail $m unless defined $cb;
542         return unless $cb;
543         $cb->("unclean-$tagsfx", $why);
544     };
545     for (;;) {
546         my $cl = classify $head;
547         my $ty = $cl->{Type};
548         if ($ty eq 'Packaging') {
549             $breakwater //= $clogonly;
550             $breakwater //= $head;
551         } elsif ($ty eq 'Changelog') {
552             # this is going to count as the tip of the breakwater
553             # only if it has no upstream stuff before it
554             $clogonly //= $head;
555         } elsif ($ty eq 'Anchor' or
556                  $ty eq 'TreatAsAnchor' or
557                  $ty eq 'BreakwaterStart') {
558             $anchor = $head;
559             $breakwater //= $clogonly;
560             $breakwater //= $head;
561             last;
562         } elsif ($ty eq 'Upstream') {
563             $x->($unclean, 'ordering',
564  "packaging change ($breakwater) follows upstream change (eg $head)")
565                 if defined $breakwater;
566             $clogonly = undef;
567             $breakwater = undef;
568         } elsif ($ty eq 'Mixed') {
569             $x->($unclean, 'mixed',
570                  "found mixed upstream/packaging commit ($head)");
571             $clogonly = undef;
572             $breakwater = undef;
573         } elsif ($ty eq 'Pseudomerge' or
574                  $ty eq 'AddPatches') {
575             $x->($furniture, (lc $ty),
576                  "found interchange bureaucracy commit ($ty, $head)");
577         } elsif ($ty eq 'DgitImportUnpatched') {
578             $x->($trouble, 'dgitimport',
579                  "found dgit dsc import ($head)");
580             $breakwater = undef;
581             $anchor = undef;
582             no warnings qw(exiting);
583             last;
584         } else {
585             fail "found unprocessable commit, cannot cope: $head; $cl->{Why}";
586         }
587         $head = $cl->{Parents}[0]{CommitId};
588     }
589     return ($anchor, $breakwater);
590 }
591
592 sub walk ($;$$);
593 sub walk ($;$$) {
594     my ($input,
595         $nogenerate,$report) = @_;
596     # => ($tip, $breakwater_tip, $last_anchor)
597     # (or nothing, if $nogenerate)
598
599     printdebug "*** WALK $input ".($nogenerate//0)." ".($report//'-')."\n";
600
601     # go through commits backwards
602     # we generate two lists of commits to apply:
603     # breakwater branch and upstream patches
604     my (@brw_cl, @upp_cl, @processed);
605     my %found;
606     my $upp_limit;
607     my @pseudomerges;
608
609     my $cl;
610     my $xmsg = sub {
611         my ($prose, $info) = @_;
612         my $ms = $cl->{Msg};
613         chomp $ms;
614         $info //= '';
615         $ms .= "\n\n[git-debrebase$info: $prose]\n";
616         return (Msg => $ms);
617     };
618     my $rewrite_from_here = sub {
619         my ($cl) = @_;
620         my $sp_cl = { SpecialMethod => 'StartRewrite' };
621         push @$cl, $sp_cl;
622         push @processed, $sp_cl;
623     };
624     my $cur = $input;
625
626     my $prdelim = "";
627     my $prprdelim = sub { print $report $prdelim if $report; $prdelim=""; };
628
629     my $prline = sub {
630         return unless $report;
631         print $report $prdelim, @_;
632         $prdelim = "\n";
633     };
634
635     my $bomb = sub { # usage: return $bomb->();
636         print $report " Unprocessable" if $report;
637         print $report " ($cl->{Why})" if $report && defined $cl->{Why};
638         $prprdelim->();
639         if ($nogenerate) {
640             return (undef,undef);
641         }
642         die "commit $cur: Cannot cope with this commit (d.".
643             (join ' ', map { sprintf "%#x", $_->{Differs} }
644              @{ $cl->{Parents} }).
645             (defined $cl->{Why} ? "; $cl->{Why}": '').
646                  ")";
647     };
648
649     my $build;
650     my $breakwater;
651
652     my $build_start = sub {
653         my ($msg, $parent) = @_;
654         $prline->(" $msg");
655         $build = $parent;
656         no warnings qw(exiting); last;
657     };
658
659     my $last_anchor;
660
661     for (;;) {
662         $cl = classify $cur;
663         my $ty = $cl->{Type};
664         my $st = $cl->{SubType};
665         $prline->("$cl->{CommitId} $cl->{Type}");
666         $found{$ty. ( defined($st) ? "-$st" : '' )}++;
667         push @processed, $cl;
668         my $p0 = @{ $cl->{Parents} }==1 ? $cl->{Parents}[0]{CommitId} : undef;
669         if ($ty eq 'AddPatches') {
670             $cur = $p0;
671             $rewrite_from_here->(\@upp_cl);
672             next;
673         } elsif ($ty eq 'Packaging' or $ty eq 'Changelog') {
674             push @brw_cl, $cl;
675             $cur = $p0;
676             next;
677         } elsif ($ty eq 'BreakwaterStart') {
678             $last_anchor = $cur;
679             $build_start->('FirstPackaging', $cur);
680         } elsif ($ty eq 'Upstream') {
681             push @upp_cl, $cl;
682             $cur = $p0;
683             next;
684         } elsif ($ty eq 'Mixed') {
685             my $queue = sub {
686                 my ($q, $wh) = @_;
687                 my $cls = { %$cl, $xmsg->("split mixed commit: $wh part") };
688                 push @$q, $cls;
689             };
690             $queue->(\@brw_cl, "debian");
691             $queue->(\@upp_cl, "upstream");
692             $rewrite_from_here->(\@brw_cl);
693             $cur = $p0;
694             next;
695         } elsif ($ty eq 'Pseudomerge') {
696             my $contrib = $cl->{Contributor}{CommitId};
697             print $report " Contributor=$contrib" if $report;
698             push @pseudomerges, $cl;
699             $rewrite_from_here->(\@upp_cl);
700             $cur = $contrib;
701             next;
702         } elsif ($ty eq 'Anchor' or $ty eq 'TreatAsAnchor') {
703             $last_anchor = $cur;
704             $build_start->("Anchor", $cur);
705         } elsif ($ty eq 'DgitImportUnpatched') {
706             my $pm = $pseudomerges[-1];
707             if (defined $pm) {
708                 # To an extent, this is heuristic.  Imports don't have
709                 # a useful history of the debian/ branch.  We assume
710                 # that the first pseudomerge after an import has a
711                 # useful history of debian/, and ignore the histories
712                 # from later pseudomerges.  Often the first pseudomerge
713                 # will be the dgit import of the upload to the actual
714                 # suite intended by the non-dgit NMUer, and later
715                 # pseudomerges may represent in-archive copies.
716                 my $ovwrs = $pm->{Overwritten};
717                 printf $report " PM=%s \@Overwr:%d",
718                     $pm->{CommitId}, (scalar @$ovwrs)
719                     if $report;
720                 if (@$ovwrs != 1) {
721                     printdebug "*** WALK BOMB DgitImportUnpatched\n";
722                     return $bomb->();
723                 }
724                 my $ovwr = $ovwrs->[0]{CommitId};
725                 printf $report " Overwr=%s", $ovwr if $report;
726                 # This import has a tree which is just like a
727                 # breakwater tree, but it has the wrong history.  It
728                 # ought to have the previous breakwater (which the
729                 # pseudomerge overwrote) as an ancestor.  That will
730                 # make the history of the debian/ files correct.  As
731                 # for the upstream version: either it's the same as
732                 # was ovewritten (ie, same as the previous
733                 # breakwater), in which case that history is precisely
734                 # right; or, otherwise, it was a non-gitish upload of a
735                 # new upstream version.  We can tell these apart by
736                 # looking at the tree of the supposed upstream.
737                 push @brw_cl, {
738                     %$cl,
739                     SpecialMethod => 'DgitImportDebianUpdate',
740                     $xmsg->("convert dgit import: debian changes")
741                 }, {
742                     %$cl,
743                     SpecialMethod => 'DgitImportUpstreamUpdate',
744                     $xmsg->("convert dgit import: upstream update",
745                             " anchor")
746                 };
747                 $prline->(" Import");
748                 $rewrite_from_here->(\@brw_cl);
749                 $upp_limit //= $#upp_cl; # further, deeper, patches discarded
750                 $cur = $ovwr;
751                 next;
752             } else {
753                 # Everything is from this import.  This kind of import
754                 # is already in valid breakwater format, with the
755                 # patches as commits.
756                 printf $report " NoPM" if $report;
757                 # last thing we processed will have been the first patch,
758                 # if there is one; which is fine, so no need to rewrite
759                 # on account of this import
760                 $build_start->("ImportOrigin", $cur);
761             }
762             die "$ty ?";
763         } else {
764             printdebug "*** WALK BOMB unrecognised\n";
765             return $bomb->();
766         }
767     }
768     $prprdelim->();
769
770     printdebug "*** WALK prep done cur=$cur".
771         " brw $#brw_cl upp $#upp_cl proc $#processed pm $#pseudomerges\n";
772
773     return if $nogenerate;
774
775     # Now we build it back up again
776
777     fresh_workarea();
778
779     my $rewriting = 0;
780
781     my $read_tree_debian = sub {
782         my ($treeish) = @_;
783         read_tree_subdir 'debian', "$treeish:debian";
784         rm_subdir_cached 'debian/patches';
785     };
786     my $read_tree_upstream = sub {
787         my ($treeish) = @_;
788         runcmd @git, qw(read-tree), $treeish;
789         $read_tree_debian->($build);
790     };
791
792     $#upp_cl = $upp_limit if defined $upp_limit;
793  
794     my $committer_authline = calculate_committer_authline();
795
796     printdebug "WALK REBUILD $build ".(scalar @processed)."\n";
797
798     confess "internal error" unless $build eq (pop @processed)->{CommitId};
799
800     in_workarea sub {
801         mkdir $rd or $!==EEXIST or die $!;
802         my $current_method;
803         runcmd @git, qw(read-tree), $build;
804         foreach my $cl (qw(Debian), (reverse @brw_cl),
805                         { SpecialMethod => 'RecordBreakwaterTip' },
806                         qw(Upstream), (reverse @upp_cl)) {
807             if (!ref $cl) {
808                 $current_method = $cl;
809                 next;
810             }
811             my $method = $cl->{SpecialMethod} // $current_method;
812             my @parents = ($build);
813             my $cltree = $cl->{CommitId};
814             printdebug "WALK BUILD ".($cltree//'undef').
815                 " $method (rewriting=$rewriting)\n";
816             if ($method eq 'Debian') {
817                 $read_tree_debian->($cltree);
818             } elsif ($method eq 'Upstream') {
819                 $read_tree_upstream->($cltree);
820             } elsif ($method eq 'StartRewrite') {
821                 $rewriting = 1;
822                 next;
823             } elsif ($method eq 'RecordBreakwaterTip') {
824                 $breakwater = $build;
825                 next;
826             } elsif ($method eq 'DgitImportDebianUpdate') {
827                 $read_tree_debian->($cltree);
828             } elsif ($method eq 'DgitImportUpstreamUpdate') {
829                 confess unless $rewriting;
830                 my $differs = (get_differs $build, $cltree);
831                 next unless $differs & D_UPS;
832                 $read_tree_upstream->($cltree);
833                 push @parents, map { $_->{CommitId} } @{ $cl->{OrigParents} };
834             } else {
835                 confess "$method ?";
836             }
837             if (!$rewriting) {
838                 my $procd = (pop @processed) // 'UNDEF';
839                 if ($cl ne $procd) {
840                     $rewriting = 1;
841                     printdebug "WALK REWRITING NOW cl=$cl procd=$procd\n";
842                 }
843             }
844             my $newtree = cmdoutput @git, qw(write-tree);
845             my $ch = $cl->{Hdr};
846             $ch =~ s{^tree .*}{tree $newtree}m or confess "$ch ?";
847             $ch =~ s{^parent .*\n}{}mg;
848             $ch =~ s{(?=^author)}{
849                 join '', map { "parent $_\n" } @parents
850             }me or confess "$ch ?";
851             if ($rewriting) {
852                 $ch =~ s{^committer .*$}{$committer_authline}m
853                     or confess "$ch ?";
854             }
855             my $cf = "$rd/m$rewriting";
856             open CD, ">", $cf or die $!;
857             print CD $ch, "\n", $cl->{Msg} or die $!;
858             close CD or die $!;
859             my @cmd = (@git, qw(hash-object));
860             push @cmd, qw(-w) if $rewriting;
861             push @cmd, qw(-t commit), $cf;
862             my $newcommit = cmdoutput @cmd;
863             confess "$ch ?" unless $rewriting or $newcommit eq $cl->{CommitId};
864             $build = $newcommit;
865             if (grep { $method eq $_ } qw(DgitImportUpstreamUpdate)) {
866                 $last_anchor = $cur;
867             }
868         }
869     };
870
871     my $final_check = get_differs $build, $input;
872     die sprintf "internal error %#x %s %s", $final_check, $build, $input
873         if $final_check & ~D_PAT_ADD;
874
875     my @r = ($build, $breakwater, $last_anchor);
876     printdebug "*** WALK RETURN @r\n";
877     return @r
878 }
879
880 sub get_head () {
881     git_check_unmodified();
882     return git_rev_parse qw(HEAD);
883 }
884
885 sub update_head ($$$) {
886     my ($old, $new, $mrest) = @_;
887     push @deferred_updates, "update HEAD $new $old";
888     run_deferred_updates $mrest;
889 }
890
891 sub update_head_checkout ($$$) {
892     my ($old, $new, $mrest) = @_;
893     update_head $old, $new, $mrest;
894     runcmd @git, qw(reset --hard);
895 }
896
897 sub update_head_postlaunder ($$$) {
898     my ($old, $tip, $reflogmsg) = @_;
899     return if $tip eq $old;
900     print "git-debrebase: laundered (head was $old)\n";
901     update_head $old, $tip, $reflogmsg;
902     # no tree changes except debian/patches
903     runcmd @git, qw(rm --quiet --ignore-unmatch -rf debian/patches);
904 }
905
906 sub do_launder_head ($) {
907     my ($reflogmsg) = @_;
908     my $old = get_head();
909     record_ffq_auto();
910     my ($tip,$breakwater) = walk $old;
911     snags_maybe_bail();
912     update_head_postlaunder $old, $tip, $reflogmsg;
913     return ($tip,$breakwater);
914 }
915
916 sub cmd_launder_v0 () {
917     badusage "no arguments to launder-v0 allowed" if @ARGV;
918     my $old = get_head();
919     my ($tip,$breakwater,$last_anchor) = walk $old;
920     update_head_postlaunder $old, $tip, 'launder';
921     printf "# breakwater tip\n%s\n", $breakwater;
922     printf "# working tip\n%s\n", $tip;
923     printf "# last anchor\n%s\n", $last_anchor;
924 }
925
926 sub defaultcmd_rebase () {
927     push @ARGV, @{ $opt_defaultcmd_interactive // [] };
928     my ($tip,$breakwater) = do_launder_head 'launder for rebase';
929     runcmd @git, qw(rebase), @ARGV, $breakwater if @ARGV;
930 }
931
932 sub cmd_analyse () {
933     die if ($ARGV[0]//'') =~ m/^-/;
934     badusage "too many arguments to analyse" if @ARGV>1;
935     my ($old) = @ARGV;
936     if (defined $old) {
937         $old = git_rev_parse $old;
938     } else {
939         $old = git_rev_parse 'HEAD';
940     }
941     my ($dummy,$breakwater) = walk $old, 1,*STDOUT;
942     STDOUT->error and die $!;
943 }
944
945 sub ffq_prev_branchinfo () {
946     # => ('status', "message", [$current, $ffq_prev, $gdrlast])
947     # 'status' may be
948     #    branch         message is undef
949     #    weird-symref   } no $current,
950     #    notbranch      }  no $ffq_prev
951     my $current = git_get_symref();
952     return ('detached', 'detached HEAD') unless defined $current;
953     return ('weird-symref', 'HEAD symref is not to refs/')
954         unless $current =~ m{^refs/};
955     my $ffq_prev = "refs/$ffq_refprefix/$'";
956     my $gdrlast = "refs/$gdrlast_refprefix/$'";
957     printdebug "ffq_prev_branchinfo branch current $current\n";
958     return ('branch', undef, $current, $ffq_prev, $gdrlast);
959 }
960
961 sub record_ffq_prev_deferred () {
962     # => ('status', "message")
963     # 'status' may be
964     #    deferred          message is undef
965     #    exists
966     #    detached
967     #    weird-symref
968     #    notbranch
969     # if not ff from some branch we should be ff from, is an snag
970     # if "deferred", will have added something about that to
971     #   @deferred_update_messages, and also maybe printed (already)
972     #   some messages about ff checks
973     my ($status, $message, $current, $ffq_prev, $gdrlast)
974         = ffq_prev_branchinfo();
975     return ($status, $message) unless $status eq 'branch';
976
977     my $currentval = get_head();
978
979     my $exists = git_get_ref $ffq_prev;
980     return ('exists',"$ffq_prev already exists") if $exists;
981
982     return ('not-branch', 'HEAD symref is not to refs/heads/')
983         unless $current =~ m{^refs/heads/};
984     my $branch = $';
985
986     my @check_specs = split /\;/, (cfg "branch.$branch.ffq-ffrefs",1) // '*';
987     my %checked;
988
989     printdebug "ffq check_specs @check_specs\n";
990
991     my $check = sub {
992         my ($lrref, $desc) = @_;
993         printdebug "ffq might check $lrref ($desc)\n";
994         my $invert;
995         for my $chk (@check_specs) {
996             my $glob = $chk;
997             $invert = $glob =~ s{^[!^]}{};
998             last if fnmatch $glob, $lrref;
999         }
1000         return if $invert;
1001         my $lrval = git_get_ref $lrref;
1002         return unless defined $lrval;
1003
1004         if (is_fast_fwd $lrval, $currentval) {
1005             print "OK, you are ahead of $lrref\n" or die $!;
1006             $checked{$lrref} = 1;
1007         } elsif (is_fast_fwd $currentval, $lrval) {
1008             $checked{$lrref} = -1;
1009             snag 'behind', "you are behind $lrref, divergence risk";
1010         } else {
1011             $checked{$lrref} = -1;
1012             snag 'diverged', "you have diverged from $lrref";
1013         }
1014     };
1015
1016     my $merge = cfg "branch.$branch.merge",1;
1017     if (defined $merge and $merge =~ m{^refs/heads/}) {
1018         my $rhs = $';
1019         printdebug "ffq merge $rhs\n";
1020         my $check_remote = sub {
1021             my ($remote, $desc) = @_;
1022             printdebug "ffq check_remote ".($remote//'undef')." $desc\n";
1023             return unless defined $remote;
1024             $check->("refs/remotes/$remote/$rhs", $desc);
1025         };
1026         $check_remote->((scalar cfg "branch.$branch.remote",1),
1027                         'remote fetch/merge branch');
1028         $check_remote->((scalar cfg "branch.$branch.pushRemote",1) //
1029                         (scalar cfg "branch.$branch.pushDefault",1),
1030                         'remote push branch');
1031     }
1032     if ($branch =~ m{^dgit/}) {
1033         $check->("refs/remotes/dgit/$branch", 'remote dgit branch');
1034     } elsif ($branch =~ m{^master$}) {
1035         $check->("refs/remotes/dgit/dgit/sid", 'remote dgit branch for sid');
1036     }
1037
1038     snags_maybe_bail();
1039
1040     push @deferred_updates, "update $ffq_prev $currentval $git_null_obj";
1041     push @deferred_updates, "delete $gdrlast";
1042     push @deferred_update_messages, "Recorded current head for preservation";
1043     return ('deferred', undef);
1044 }
1045
1046 sub record_ffq_auto () {
1047     my ($status, $message) = record_ffq_prev_deferred();
1048     if ($status eq 'deferred' || $status eq 'exists') {
1049     } else {
1050         snag $status, "could not record ffq-prev: $message";
1051         snags_maybe_bail();
1052     }
1053 }
1054
1055 sub ffq_prev_info () {
1056     # => ($ffq_prev, $gdrlast, $ffq_prev_commitish)
1057     my ($status, $message, $current, $ffq_prev, $gdrlast)
1058         = ffq_prev_branchinfo();
1059     if ($status ne 'branch') {
1060         snag $status, "could not check ffq-prev: $message";
1061         snags_maybe_bail();
1062     }
1063     my $ffq_prev_commitish = $ffq_prev && git_get_ref $ffq_prev;
1064     return ($ffq_prev, $gdrlast, $ffq_prev_commitish);
1065 }
1066
1067 sub stitch ($$$$$) {
1068     my ($old_head, $ffq_prev, $gdrlast, $ffq_prev_commitish, $prose) = @_;
1069
1070     push @deferred_updates, "delete $ffq_prev $ffq_prev_commitish";
1071
1072     if (is_fast_fwd $old_head, $ffq_prev_commitish) {
1073         my $differs = get_differs $old_head, $ffq_prev_commitish;
1074         unless ($differs & ~D_PAT_ADD) {
1075             # ffq-prev is ahead of us, and the only tree changes it has
1076             # are possibly addition of things in debian/patches/.
1077             # Just wind forwards rather than making a pointless pseudomerge.
1078             push @deferred_updates,
1079                 "update $gdrlast $ffq_prev_commitish $git_null_obj";
1080             update_head_checkout $old_head, $ffq_prev_commitish,
1081                 "stitch (fast forward)";
1082             return;
1083         }
1084     }
1085     fresh_workarea();
1086     my $new_head = make_commit [ $old_head, $ffq_prev ], [
1087         'Declare fast forward / record previous work',
1088         "[git-debrebase pseudomerge: $prose]",
1089     ];
1090     push @deferred_updates, "update $gdrlast $new_head $git_null_obj";
1091     update_head $old_head, $new_head, "stitch: $prose";
1092 }
1093
1094 sub do_stitch ($;$) {
1095     my ($prose, $unclean) = @_;
1096
1097     my ($ffq_prev, $gdrlast, $ffq_prev_commitish) = ffq_prev_info();
1098     if (!$ffq_prev_commitish) {
1099         fail "No ffq-prev to stitch." unless $opt_noop_ok;
1100         return;
1101     }
1102     my $dangling_head = get_head();
1103
1104     keycommits $dangling_head, $unclean,$unclean,$unclean;
1105     snags_maybe_bail();
1106
1107     stitch($dangling_head, $ffq_prev, $gdrlast, $ffq_prev_commitish, $prose);
1108 }
1109
1110 sub cmd_new_upstream_v0 () {
1111     # automatically and unconditionally launders before rebasing
1112     # if rebase --abort is used, laundering has still been done
1113
1114     my %pieces;
1115
1116     badusage "need NEW-VERSION [UPS-COMMITTISH]" unless @ARGV >= 1;
1117
1118     # parse args - low commitment
1119     my $new_version = (new Dpkg::Version scalar(shift @ARGV), check => 1);
1120     my $new_upstream_version = $new_version->version();
1121
1122     my $new_upstream = git_rev_parse (shift @ARGV // 'upstream');
1123
1124     record_ffq_auto();
1125
1126     my $piece = sub {
1127         my ($n, @x) = @_; # may be ''
1128         my $pc = $pieces{$n} //= {
1129             Name => $n,
1130             Desc => ($n ? "upstream piece \`$n'" : "upstream (main piece"),
1131         };
1132         while (my $k = shift @x) { $pc->{$k} = shift @x; }
1133         $pc;
1134     };
1135
1136     my @newpieces;
1137     my $newpiece = sub {
1138         my ($n, @x) = @_; # may be ''
1139         my $pc = $piece->($n, @x, NewIx => (scalar @newpieces));
1140         push @newpieces, $pc;
1141     };
1142
1143     $newpiece->('',
1144         OldIx => 0,
1145         New => $new_upstream,
1146     );
1147     while (@ARGV && $ARGV[0] !~ m{^-}) {
1148         my $n = shift @ARGV;
1149
1150         badusage "for each EXTRA-UPS-NAME need EXTRA-UPS-COMMITISH"
1151             unless @ARGV && $ARGV[0] !~ m{^-};
1152
1153         my $c = git_rev_parse shift @ARGV;
1154         die unless $n =~ m/^$extra_orig_namepart_re$/;
1155         $newpiece->($n, New => $c);
1156     }
1157
1158     # now we need to investigate the branch this generates the
1159     # laundered version but we don't switch to it yet
1160     my $old_head = get_head();
1161     my ($old_laundered_tip,$old_bw,$old_anchor) = walk $old_head;
1162
1163     my $old_bw_cl = classify $old_bw;
1164     my $old_anchor_cl = classify $old_anchor;
1165     my $old_upstream;
1166     if (!$old_anchor_cl->{OrigParents}) {
1167         snag 'anchor-treated',
1168             'old anchor is recognised due to --anchor, cannot check upstream';
1169     } else {
1170         $old_upstream = parsecommit
1171             $old_anchor_cl->{OrigParents}[0]{CommitId};
1172         $piece->('', Old => $old_upstream->{CommitId});
1173     }
1174
1175     if ($old_upstream && $old_upstream->{Msg} =~ m{^\[git-debrebase }m) {
1176         if ($old_upstream->{Msg} =~
1177  m{^\[git-debrebase upstream-combine \.((?: $extra_orig_namepart_re)+)\:.*\]$}m
1178            ) {
1179             my @oldpieces = ('', split / /, $1);
1180             my $parentix = -1 + scalar @{ $old_upstream->{Parents} };
1181             foreach my $i (0..$#oldpieces) {
1182                 my $n = $oldpieces[$i];
1183                 $piece->($n, Old => $old_upstream->{CommitId}.'^'.$parentix);
1184             }
1185         } else {
1186             snag 'upstream-confusing',
1187                 "previous upstream $old_upstream->{CommitId} is from".
1188                " git-debrebase but not an \`upstream-combine' commit";
1189         }
1190     }
1191
1192     foreach my $pc (values %pieces) {
1193         if (!$old_upstream) {
1194             # we have complained already
1195         } elsif (!$pc->{Old}) {
1196             snag 'upstream-new-piece',
1197                 "introducing upstream piece \`$pc->{Name}'";
1198         } elsif (!$pc->{New}) {
1199             snag 'upstream-rm-piece',
1200                 "dropping upstream piece \`$pc->{Name}'";
1201         } elsif (!is_fast_fwd $pc->{Old}, $pc->{New}) {
1202             snag 'upstream-not-ff',
1203                 "not fast forward: $pc->{Name} $pc->{Old}..$pc->{New}";
1204         }
1205     }
1206
1207     printdebug "%pieces = ", (dd \%pieces), "\n";
1208     printdebug "\@newpieces = ", (dd \@newpieces), "\n";
1209
1210     snags_maybe_bail();
1211
1212     my $new_bw;
1213
1214     fresh_workarea();
1215     in_workarea sub {
1216         my @upstream_merge_parents;
1217
1218         if (!any_snags()) {
1219             push @upstream_merge_parents, $old_upstream->{CommitId};
1220         }
1221
1222         foreach my $pc (@newpieces) { # always has '' first
1223             if ($pc->{Name}) {
1224                 read_tree_subdir $pc->{Name}, $pc->{New};
1225             } else {
1226                 runcmd @git, qw(read-tree), $pc->{New};
1227             }
1228             push @upstream_merge_parents, $pc->{New};
1229         }
1230
1231         # index now contains the new upstream
1232
1233         if (@newpieces > 1) {
1234             # need to make the upstream subtree merge commit
1235             $new_upstream = make_commit \@upstream_merge_parents,
1236                 [ "Combine upstreams for $new_upstream_version",
1237  ("[git-debrebase upstream-combine . ".
1238  (join " ", map { $_->{Name} } @newpieces[1..$#newpieces]).
1239  ": new upstream]"),
1240                 ];
1241         }
1242
1243         # $new_upstream is either the single upstream commit, or the
1244         # combined commit we just made.  Either way it will be the
1245         # "upstream" parent of the anchor merge.
1246
1247         read_tree_subdir 'debian', "$old_bw:debian";
1248
1249         # index now contains the anchor merge contents
1250         $new_bw = make_commit [ $old_bw, $new_upstream ],
1251             [ "Update to upstream $new_upstream_version",
1252  "[git-debrebase anchor: new upstream $new_upstream_version, merge]",
1253             ];
1254
1255         my $clogsignoff = cmdoutput qw(git show),
1256             '--pretty=format:%an <%ae>  %aD',
1257             $new_bw;
1258
1259         # Now we have to add a changelog stanza so the Debian version
1260         # is right.
1261         die if unlink "debian";
1262         die $! unless $!==ENOENT or $!==ENOTEMPTY;
1263         unlink "debian/changelog" or $!==ENOENT or die $!;
1264         mkdir "debian" or die $!;
1265         open CN, ">", "debian/changelog" or die $!;
1266         my $oldclog = git_cat_file ":debian/changelog";
1267         $oldclog =~ m/^($package_re) \(\S+\) / or
1268             fail "cannot parse old changelog to get package name";
1269         my $p = $1;
1270         print CN <<END, $oldclog or die $!;
1271 $p ($new_version) UNRELEASED; urgency=medium
1272
1273   * Update to new upstream version $new_upstream_version.
1274
1275  -- $clogsignoff
1276
1277 END
1278         close CN or die $!;
1279         runcmd @git, qw(update-index --add --replace), 'debian/changelog';
1280
1281         # Now we have the final new breakwater branch in the index
1282         $new_bw = make_commit [ $new_bw ],
1283             [ "Update changelog for new upstream $new_upstream_version",
1284               "[git-debrebase: new upstream $new_upstream_version, changelog]",
1285             ];
1286     };
1287
1288     # we have constructed the new breakwater. we now need to commit to
1289     # the laundering output, because git-rebase can't easily be made
1290     # to make a replay list which is based on some other branch
1291
1292     update_head_postlaunder $old_head, $old_laundered_tip,
1293         'launder for new upstream';
1294
1295     my @cmd = (@git, qw(rebase --onto), $new_bw, $old_bw, @ARGV);
1296     runcmd @cmd;
1297     # now it's for the user to sort out
1298 }
1299
1300 sub cmd_record_ffq_prev () {
1301     badusage "no arguments allowed" if @ARGV;
1302     my ($status, $msg) = record_ffq_prev_deferred();
1303     if ($status eq 'exists' && $opt_noop_ok) {
1304         print "Previous head already recorded\n" or die $!;
1305     } elsif ($status eq 'deferred') {
1306         run_deferred_updates 'record-ffq-prev';
1307     } else {
1308         fail "Could not preserve: $msg";
1309     }
1310 }
1311
1312 sub cmd_anchor () {
1313     badusage "no arguments allowed" if @ARGV;
1314     my ($anchor, $bw) = keycommits +(git_rev_parse 'HEAD'), 0,0;
1315     print "$bw\n" or die $!;
1316 }
1317
1318 sub cmd_breakwater () {
1319     badusage "no arguments allowed" if @ARGV;
1320     my ($anchor, $bw) = keycommits +(git_rev_parse 'HEAD'), 0,0;
1321     print "$bw\n" or die $!;
1322 }
1323
1324 sub cmd_stitch () {
1325     my $prose = 'stitch';
1326     GetOptions('prose=s', \$prose) or die badusage("bad options to stitch");
1327     badusage "no arguments allowed" if @ARGV;
1328     do_stitch $prose, 0;
1329 }
1330 sub cmd_prepush () { cmd_stitch(); }
1331
1332 sub cmd_quick () {
1333     badusage "no arguments allowed" if @ARGV;
1334     do_launder_head 'launder for git-debrebase quick';
1335     do_stitch 'quick';
1336 }
1337
1338 sub cmd_conclude () {
1339     my ($ffq_prev, $gdrlast, $ffq_prev_commitish) = ffq_prev_info();
1340     if (!$ffq_prev_commitish) {
1341         fail "No ongoing git-debrebase session." unless $opt_noop_ok;
1342         return;
1343     }
1344     my $dangling_head = get_head();
1345     
1346     badusage "no arguments allowed" if @ARGV;
1347     do_launder_head 'launder for git-debrebase quick';
1348     do_stitch 'quick';
1349 }
1350
1351 sub make_patches_staged ($) {
1352     my ($head) = @_;
1353     # Produces the patches that would result from $head if it were
1354     # laundered.
1355     my ($secret_head, $secret_bw, $last_anchor) = walk $head;
1356     fresh_workarea();
1357     in_workarea sub {
1358         runcmd @git, qw(checkout -q -b bw), $secret_bw;
1359         runcmd @git, qw(checkout -q -b patch-queue/bw), $secret_head;
1360         runcmd qw(gbp pq export);
1361         runcmd @git, qw(add debian/patches);
1362     };
1363 }
1364
1365 sub make_patches ($) {
1366     my ($head) = @_;
1367     keycommits $head, 0, \&snag;
1368     make_patches_staged $head;
1369     my $out;
1370     in_workarea sub {
1371         my $ptree = cmdoutput @git, qw(write-tree --prefix=debian/patches/);
1372         runcmd @git, qw(read-tree), $head;
1373         read_tree_subdir 'debian/patches', $ptree;
1374         $out = make_commit [$head], [
1375             'Commit patch queue (exported by git-debrebase)',
1376             '[git-debrebase: export and commit patches]',
1377         ];
1378     };
1379     my $d = get_differs $head, $out;
1380     if ($d == 0) {
1381         return undef; # nothing to do
1382     } elsif ($d == D_PAT_ADD) {
1383         return $out; # OK
1384     } else {
1385         fail "Patch export produced patch amendments".
1386             " (abandoned output commit $out).".
1387             "  Try laundering first.";
1388     }
1389 }
1390
1391 sub cmd_make_patches () {
1392     badusage "no arguments allowed" if @ARGV;
1393     my $old_head = get_head();
1394     my $new = make_patches $old_head;
1395     snags_maybe_bail();
1396     if (!$new) {
1397         fail "No (more) patches to export." unless $opt_noop_ok;
1398         return;
1399     }
1400     update_head_checkout $old_head, $new, 'make-patches';
1401 }
1402
1403 sub cmd_convert_from_gbp () {
1404     badusage "needs 1 optional argument, the upstream git rev"
1405         unless @ARGV<=1;
1406     my ($upstream_spec) = @ARGV;
1407     $upstream_spec //= 'refs/heads/upstream';
1408     my $upstream = git_rev_parse $upstream_spec;
1409     my $old_head = get_head();
1410
1411     my $upsdiff = get_differs $upstream, $old_head;
1412     if ($upsdiff & D_UPS) {
1413         runcmd @git, qw(--no-pager diff),
1414             $upstream, $old_head,
1415             qw( -- :!/debian :/);
1416  fail "upstream ($upstream_spec) and HEAD are not identical in upstream files";
1417     }
1418
1419     if (!is_fast_fwd $upstream, $old_head) {
1420         snag 'upstream-not-ancestor',
1421             "upstream ($upstream) is not an ancestor of HEAD";
1422     } else {
1423         my $wrong = cmdoutput
1424             (@git, qw(rev-list --ancestry-path), "$upstream..HEAD",
1425              qw(-- :/ :!/debian));
1426         if (length $wrong) {
1427             snag 'unexpected-upstream-changes',
1428                 "history between upstream ($upstream) and HEAD contains direct changes to upstream files - are you sure this is a gbp (patches-unapplied) branch?";
1429             print STDERR "list expected changes with:  git log --stat --ancestry-path $upstream_spec..HEAD -- :/ ':!/debian'\n";
1430         }
1431     }
1432
1433     if ((git_cat_file "$upstream:debian")[0] ne 'missing') {
1434         snag 'upstream-has-debian',
1435             "upstream ($upstream) contains debian/ directory";
1436     }
1437
1438     snags_maybe_bail();
1439
1440     my $work;
1441
1442     fresh_workarea();
1443     in_workarea sub {
1444         runcmd @git, qw(checkout -q -b gdr-internal), $old_head;
1445         # make a branch out of the patch queue - we'll want this in a mo
1446         runcmd qw(gbp pq import);
1447         # strip the patches out
1448         runcmd @git, qw(checkout -q gdr-internal~0);
1449         rm_subdir_cached 'debian/patches';
1450         $work = make_commit ['HEAD'], [
1451  'git-debrebase convert-from-gbp: drop patches from tree',
1452  'Delete debian/patches, as part of converting to git-debrebase format.',
1453  '[git-debrebase convert-from-gbp: drop patches from tree]'
1454                               ];
1455         # make the anchor merge
1456         # the tree is already exactly right
1457         $work = make_commit [$work, $upstream], [
1458  'git-debrebase import: declare upstream',
1459  'First breakwater merge.',
1460  '[git-debrebase anchor: declare upstream]'
1461                               ];
1462
1463         # rebase the patch queue onto the new breakwater
1464         runcmd @git, qw(reset --quiet --hard patch-queue/gdr-internal);
1465         runcmd @git, qw(rebase --quiet --onto), $work, qw(gdr-internal);
1466         $work = git_rev_parse 'HEAD';
1467     };
1468
1469     update_head_checkout $old_head, $work, 'convert-from-gbp';
1470 }
1471
1472 sub cmd_convert_to_gbp () {
1473     badusage "no arguments allowed" if @ARGV;
1474     my $head = get_head();
1475     my (undef, undef, undef, $ffq, $gdrlast) = ffq_prev_branchinfo();
1476     keycommits $head, 0;
1477     my $out;
1478     make_patches_staged $head;
1479     in_workarea sub {
1480         $out = make_commit ['HEAD'], [
1481             'Commit patch queue (converted from git-debrebase format)',
1482             '[git-debrebase convert-to-gbp: commit patches]',
1483         ];
1484     };
1485     if (defined $ffq) {
1486         push @deferred_updates, "delete $ffq";
1487         push @deferred_updates, "delete $gdrlast";
1488     }
1489     snags_maybe_bail();
1490     update_head_checkout $head, $out, "convert to gbp (v0)";
1491     print <<END or die $!;
1492 git-debrebase: converted to git-buildpackage branch format
1493 git-debrebase: WARNING: do not now run "git-debrebase" any more
1494 git-debrebase: WARNING: doing so would drop all upstream patches!
1495 END
1496 }
1497
1498 sub cmd_downstream_rebase_launder_v0 () {
1499     badusage "needs 1 argument, the baseline" unless @ARGV==1;
1500     my ($base) = @ARGV;
1501     $base = git_rev_parse $base;
1502     my $old_head = get_head();
1503     my $current = $old_head;
1504     my $topmost_keep;
1505     for (;;) {
1506         if ($current eq $base) {
1507             $topmost_keep //= $current;
1508             print " $current BASE stop\n";
1509             last;
1510         }
1511         my $cl = classify $current;
1512         print " $current $cl->{Type}";
1513         my $keep = 0;
1514         my $p0 = $cl->{Parents}[0]{CommitId};
1515         my $next;
1516         if ($cl->{Type} eq 'Pseudomerge') {
1517             print " ^".($cl->{Contributor}{Ix}+1);
1518             $next = $cl->{Contributor}{CommitId};
1519         } elsif ($cl->{Type} eq 'AddPatches' or
1520                  $cl->{Type} eq 'Changelog') {
1521             print " strip";
1522             $next = $p0;
1523         } else {
1524             print " keep";
1525             $next = $p0;
1526             $keep = 1;
1527         }
1528         print "\n";
1529         if ($keep) {
1530             $topmost_keep //= $current;
1531         } else {
1532             die "to-be stripped changes not on top of the branch\n"
1533                 if $topmost_keep;
1534         }
1535         $current = $next;
1536     }
1537     if ($topmost_keep eq $old_head) {
1538         print "unchanged\n";
1539     } else {
1540         print "updating to $topmost_keep\n";
1541         update_head_checkout
1542             $old_head, $topmost_keep,
1543             'downstream-rebase-launder-v0';
1544     }
1545 }
1546
1547 GetOptions("D+" => \$debuglevel,
1548            'noop-ok', => \$opt_noop_ok,
1549            'f=s' => \@snag_force_opts,
1550            'anchor=s' => \@opt_anchors,
1551            'force!',
1552            '-i:s' => sub {
1553                my ($opt,$val) = @_;
1554                badusage "git-debrebase: no cuddling to -i for git-rebase"
1555                    if length $val;
1556                die if $opt_defaultcmd_interactive; # should not happen
1557                $opt_defaultcmd_interactive = [ qw(-i) ];
1558                # This access to @ARGV is excessive familiarity with
1559                # Getopt::Long, but there isn't another sensible
1560                # approach.  '-i=s{0,}' does not work with bundling.
1561                push @$opt_defaultcmd_interactive, @ARGV;
1562                @ARGV=();
1563            }) or die badusage "bad options\n";
1564 initdebug('git-debrebase ');
1565 enabledebug if $debuglevel;
1566
1567 my $toplevel = cmdoutput @git, qw(rev-parse --show-toplevel);
1568 chdir $toplevel or die "chdir $toplevel: $!";
1569
1570 $rd = fresh_playground "$playprefix/misc";
1571
1572 @opt_anchors = map { git_rev_parse $_ } @opt_anchors;
1573
1574 if (!@ARGV || $opt_defaultcmd_interactive || $ARGV[0] =~ m{^-}) {
1575     defaultcmd_rebase();
1576 } else {
1577     my $cmd = shift @ARGV;
1578     my $cmdfn = $cmd;
1579     $cmdfn =~ y/-/_/;
1580     $cmdfn = ${*::}{"cmd_$cmdfn"};
1581
1582     $cmdfn or badusage "unknown git-debrebase sub-operation $cmd";
1583     $cmdfn->();
1584 }