X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=dgit.git;a=blobdiff_plain;f=git-debrebase;h=599fe150d12cdbae0038183976cc1f4f594b1b62;hp=75a8f51a96b1638bd6d4847c0b6f721bd113a51a;hb=e221ca98a0eb6ad2601f58002e0abdb0ea4f7374;hpb=67db3561da79c090d84228d06adb11546881bba8 diff --git a/git-debrebase b/git-debrebase index 75a8f51a..599fe150 100755 --- a/git-debrebase +++ b/git-debrebase @@ -18,53 +18,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . - -# undocumented usages: -# -# git-debrebase [] downstream-rebase-launder-v0 # experimental - -# problems / outstanding questions: -# -# * dgit push with a `3.0 (quilt)' package means doing quilt -# fixup. Usually this involves recommitting the whole patch -# series, one at a time, with dpkg-source --commit. This is -# terribly terribly slow. (Maybe this should be fixed in dgit.) -# -# * dgit push usually needs to (re)make a pseudomerge. The "first" -# git-debrebase stripped out the previous pseudomerge and could -# have remembeed the HEAD. But it's not quite clear what history -# ought to be preserved and what should be discarded. For now -# the user will have to tell dgit --overwrite. -# -# To fix this, do we need a new push hook for dgit ? -# -# * Workflow is currently clumsy. Lots of spurious runes to type. -# There's not even a guide. -# -# * There are no tests. -# -# * new-upstream-v0 has a terrible UI. You end up with giant -# runic command lines. -# -# One consequence of the lack of richness it can need --force in -# fairly sensible situations and there is no way to tell it what -# you are really trying to do, other than just --force. There -# should be an interface with some default branch names. -# -# * There should be a standard convention for the version number, -# and unfinalised or not changelog, after new-upstream. -# -# * Handing of multi-orig dgit new-upstream .dsc imports is known to -# be broken. They may be not recognised, improperly converted, or -# their conversion may be unrecognised. -# -# * Docs need writing and updating. Even README.git-debrebase -# describes a design but may not reflect the implementation. -# -# * We need to develop a plausible model that works for derivatives, -# who probably want to maintain their stack on top of Debian's. -# downstream-rebase-launder-v0 may be a starting point? - use strict; use Debian::Dgit qw(:DEFAULT :playground); @@ -271,40 +224,40 @@ sub make_commit ($$) { return cmdoutput @cmd; } -our @fproblem_force_opts; -our $fproblems_forced; -our $fproblems_tripped; -sub fproblem ($$) { +our @snag_force_opts; +our $snags_forced; +our $snags_tripped; +sub snag ($$) { my ($tag,$msg) = @_; - if (grep { $_ eq $tag } @fproblem_force_opts) { - $fproblems_forced++; - print STDERR "git-debrebase: safety catch overridden (-f$tag): $msg\n"; + if (grep { $_ eq $tag } @snag_force_opts) { + $snags_forced++; + print STDERR "git-debrebase: snag ignored (-f$tag): $msg\n"; } else { - $fproblems_tripped++; - print STDERR "git-debrebase: safety catch tripped (-f$tag): $msg\n"; + $snags_tripped++; + print STDERR "git-debrebase: snag detected (-f$tag): $msg\n"; } } -sub fproblems_maybe_bail () { - if ($fproblems_forced) { +sub snags_maybe_bail () { + if ($snags_forced) { printf STDERR - "%s: safety catch trips: %d overriden by individual -f options\n", - $us, $fproblems_forced; + "%s: snags: %d overriden by individual -f options\n", + $us, $snags_forced; } - if ($fproblems_tripped) { + if ($snags_tripped) { if ($opt_force) { printf STDERR - "%s: safety catch trips: %d overriden by global --force\n", - $us, $fproblems_tripped; + "%s: snags: %d overriden by global --force\n", + $us, $snags_tripped; } else { fail sprintf - "%s: safety catch trips: %d blockers (you could -f, or --force)", - $us, $fproblems_tripped; + "%s: snags: %d blockers (you could -f, or --force)", + $us, $snags_tripped; } } } -sub any_fproblems () { - return $fproblems_forced || $fproblems_tripped; +sub any_snags () { + return $snags_forced || $snags_tripped; } # classify returns an info hash like this @@ -541,41 +494,81 @@ sub classify ($) { return $unknown->("complex merge"); } -sub breakwater_of ($) { - my ($head) = @_; # must be laundered - my $breakwater; - my $unclean = sub { - my ($why) = @_; - fail "branch needs laundering (run git-debrebase): $why"; +sub keycommits ($;$$$) { + my ($head, $furniture, $unclean, $trouble) = @_; + # => ($anchor, $breakwater) + + # $unclean->("unclean-$tagsfx", $msg) + # $furniture->("unclean-$tagsfx", $msg) + # $dgitimport->("unclean-$tagsfx", $msg) + # is callled for each situation or commit that + # wouldn't be found in a laundered branch + # $furniture is for furniture commits such as might be found on an + # interchange branch (pseudomerge, d/patches, changelog) + # $trouble is for things whnich prevent the return of + # anchor and breakwater information; if that is ignored, + # then keycommits returns (undef, undef) instead. + # + # If a callback is undef, fail is called instead. + # If a callback is defined but false, the situation is ignored. + # Callbacks may say: + # no warnings qw(exiting); last; + # if the answer is no longer wanted. + + my ($anchor, $breakwater); + my $clogonly; + my $x = sub { + my ($cb, $tagsfx, $why) = @_; + my $m = "branch needs laundering (run git-debrebase): $why"; + fail $m unless defined $cb; + return unless $cb; + $cb->("unclean-$tagsfx", $why); }; for (;;) { my $cl = classify $head; my $ty = $cl->{Type}; - if ($ty eq 'Packaging' or - $ty eq 'Changelog') { + if ($ty eq 'Packaging') { + $breakwater //= $clogonly; $breakwater //= $head; + } elsif ($ty eq 'Changelog') { + # this is going to count as the tip of the breakwater + # only if it has no upstream stuff before it + $clogonly //= $head; } elsif ($ty eq 'Anchor' or $ty eq 'TreatAsAnchor' or $ty eq 'BreakwaterStart') { + $anchor = $head; + $breakwater //= $clogonly; $breakwater //= $head; last; } elsif ($ty eq 'Upstream') { - $unclean->("packaging change ($breakwater)". - " follows upstream change (eg $head)") + $x->($unclean, 'ordering', + "packaging change ($breakwater) follows upstream change (eg $head)") if defined $breakwater; + $clogonly = undef; + $breakwater = undef; } elsif ($ty eq 'Mixed') { - $unclean->('found mixed upstream/packaging commit ($head)'); + $x->($unclean, 'mixed', + 'found mixed upstream/packaging commit ($head)'); + $clogonly = undef; + $breakwater = undef; } elsif ($ty eq 'Pseudomerge' or $ty eq 'AddPatches') { - $unclean->("found interchange conversion commit ($ty, $head)"); + $x->($furniture, (lc $ty), + "found interchange bureaucracy commit ($ty, $head)"); } elsif ($ty eq 'DgitImportUnpatched') { - $unclean->("found dgit dsc import ($head)"); + $x->($trouble, 'dgitimport', + "found dgit dsc import ($head)"); + $breakwater = undef; + $anchor = undef; + no warnings qw(exiting); + last; } else { fail "found unprocessable commit, cannot cope: $head; $cl->{Why}"; } $head = $cl->{Parents}[0]{CommitId}; } - return $breakwater; + return ($anchor, $breakwater); } sub walk ($;$$); @@ -892,6 +885,15 @@ sub update_head_postlaunder ($$$) { runcmd @git, qw(rm --quiet --ignore-unmatch -rf debian/patches); } +sub do_launder_head ($) { + my ($reflogmsg) = @_; + my $old = get_head(); + record_ffq_auto(); + my ($tip,$breakwater) = walk $old; + update_head_postlaunder $old, $tip, $reflogmsg; + return ($tip,$breakwater); +} + sub cmd_launder_v0 () { badusage "no arguments to launder-v0 allowed" if @ARGV; my $old = get_head(); @@ -903,10 +905,7 @@ sub cmd_launder_v0 () { } sub defaultcmd_rebase () { - my $old = get_head(); - record_ffq_auto(); - my ($tip,$breakwater) = walk $old; - update_head_postlaunder $old, $tip, 'launder for rebase'; + my ($tip,$breakwater) = do_launder_head 'launder for rebase'; runcmd @git, qw(rebase), @ARGV, $breakwater; } @@ -924,7 +923,7 @@ sub cmd_analyse () { } sub ffq_prev_branchinfo () { - # => ('status', "message", [$current, $ffq_prev]) + # => ('status', "message", [$current, $ffq_prev, $gdrlast]) # 'status' may be # branch message is undef # weird-symref } no $current, @@ -934,8 +933,9 @@ sub ffq_prev_branchinfo () { return ('weird-symref', 'HEAD symref is not to refs/') unless $current =~ m{^refs/}; my $ffq_prev = "refs/$ffq_refprefix/$'"; + my $gdrlast = "refs/$gdrlast_refprefix/$'"; printdebug "ffq_prev_branchinfo branch current $current\n"; - return ('branch', undef, $current, $ffq_prev); + return ('branch', undef, $current, $ffq_prev, $gdrlast); } sub record_ffq_prev_deferred () { @@ -946,11 +946,12 @@ sub record_ffq_prev_deferred () { # detached # weird-symref # notbranch - # if not ff from some branch we should be ff from, is an fproblem + # if not ff from some branch we should be ff from, is an snag # if "deferred", will have added something about that to # @deferred_update_messages, and also maybe printed (already) # some messages about ff checks - my ($status, $message, $current, $ffq_prev) = ffq_prev_branchinfo(); + my ($status, $message, $current, $ffq_prev, $gdrlast) + = ffq_prev_branchinfo(); return ($status, $message) unless $status eq 'branch'; my $currentval = get_head(); @@ -985,10 +986,10 @@ sub record_ffq_prev_deferred () { $checked{$lrref} = 1; } elsif (is_fast_fwd $currentval, $lrval) { $checked{$lrref} = -1; - fproblem 'behind', "you are behind $lrref, divergence risk"; + snag 'behind', "you are behind $lrref, divergence risk"; } else { $checked{$lrref} = -1; - fproblem 'diverged', "you have diverged from $lrref"; + snag 'diverged', "you have diverged from $lrref"; } }; @@ -1014,9 +1015,10 @@ sub record_ffq_prev_deferred () { $check->("refs/remotes/dgit/dgit/sid", 'remote dgit branch for sid'); } - fproblems_maybe_bail(); + snags_maybe_bail(); push @deferred_updates, "update $ffq_prev $currentval $git_null_obj"; + push @deferred_updates, "delete $gdrlast"; push @deferred_update_messages, "Recorded current head for preservation"; return ('deferred', undef); } @@ -1025,9 +1027,48 @@ sub record_ffq_auto () { my ($status, $message) = record_ffq_prev_deferred(); if ($status eq 'deferred' || $status eq 'exists') { } else { - fproblem $status, "could not record ffq-prev: $message"; - fproblems_maybe_bail(); + snag $status, "could not record ffq-prev: $message"; + snags_maybe_bail(); + } +} + +sub ffq_prev_info () { + # => ($ffq_prev, $gdrlast, $ffq_prev_commitish) + my ($status, $message, $current, $ffq_prev, $gdrlast) + = ffq_prev_branchinfo(); + if ($status ne 'branch') { + snag $status, "could not check ffq-prev: $message"; + snags_maybe_bail(); + } + my $ffq_prev_commitish = $ffq_prev && git_get_ref $ffq_prev; + return ($ffq_prev, $gdrlast, $ffq_prev_commitish); +} + +sub stitch ($$$$$) { + my ($old_head, $ffq_prev, $gdrlast, $ffq_prev_commitish, $prose) = @_; + + push @deferred_updates, "delete $ffq_prev $ffq_prev_commitish"; + + if (is_fast_fwd $old_head, $ffq_prev_commitish) { + my $differs = get_differs $old_head, $ffq_prev_commitish; + unless ($differs & ~D_PAT_ADD) { + # ffq-prev is ahead of us, and the only tree changes it has + # are possibly addition of things in debian/patches/. + # Just wind forwards rather than making a pointless pseudomerge. + push @deferred_updates, + "update $gdrlast $ffq_prev_commitish $git_null_obj"; + update_head_checkout $old_head, $ffq_prev_commitish, + "stitch (fast forward)"; + return; + } } + fresh_workarea(); + my $new_head = make_commit [ $old_head, $ffq_prev ], [ + 'Declare fast forward / record previous work', + "[git-debrebase pseudomerge: $prose]", + ]; + push @deferred_updates, "update $gdrlast $new_head $git_null_obj"; + update_head $old_head, $new_head, "stitch: $prose"; } sub cmd_new_upstream_v0 () { @@ -1087,7 +1128,7 @@ sub cmd_new_upstream_v0 () { my $old_anchor_cl = classify $old_anchor; my $old_upstream; if (!$old_anchor_cl->{OrigParents}) { - fproblem 'anchor-treated', + snag 'anchor-treated', 'old anchor is recognised due to --anchor, cannot check upstream'; } else { $old_upstream = parsecommit @@ -1106,7 +1147,7 @@ sub cmd_new_upstream_v0 () { $piece->($n, Old => $old_upstream->{CommitId}.'^'.$parentix); } } else { - fproblem 'upstream-confusing', + snag 'upstream-confusing', "previous upstream $old_upstream->{CommitId} is from". " git-debrebase but not an \`upstream-combine' commit"; } @@ -1116,13 +1157,13 @@ sub cmd_new_upstream_v0 () { if (!$old_upstream) { # we have complained already } elsif (!$pc->{Old}) { - fproblem 'upstream-new-piece', + snag 'upstream-new-piece', "introducing upstream piece \`$pc->{Name}'"; } elsif (!$pc->{New}) { - fproblem 'upstream-rm-piece', + snag 'upstream-rm-piece', "dropping upstream piece \`$pc->{Name}'"; } elsif (!is_fast_fwd $pc->{Old}, $pc->{New}) { - fproblem 'upstream-not-ff', + snag 'upstream-not-ff', "not fast forward: $pc->{Name} $pc->{Old}..$pc->{New}"; } } @@ -1130,7 +1171,7 @@ sub cmd_new_upstream_v0 () { printdebug "%pieces = ", (dd \%pieces), "\n"; printdebug "\@newpieces = ", (dd \@newpieces), "\n"; - fproblems_maybe_bail(); + snags_maybe_bail(); my $new_bw; @@ -1138,7 +1179,7 @@ sub cmd_new_upstream_v0 () { in_workarea sub { my @upstream_merge_parents; - if (!any_fproblems()) { + if (!any_snags()) { push @upstream_merge_parents, $old_upstream->{CommitId}; } @@ -1228,44 +1269,33 @@ sub cmd_record_ffq_prev () { } } +sub cmd_anchor () { + badusage "no arguments allowed" if @ARGV; + my ($anchor, $bw) = keycommits +(git_rev_parse 'HEAD'), 0,0; + print "$bw\n" or die $!; +} + sub cmd_breakwater () { badusage "no arguments allowed" if @ARGV; - my $bw = breakwater_of git_rev_parse 'HEAD'; + my ($anchor, $bw) = keycommits +(git_rev_parse 'HEAD'), 0,0; print "$bw\n" or die $!; } sub cmd_stitch () { - my $prose = ''; + my $prose = 'stitch'; GetOptions('prose=s', \$prose) or die badusage("bad options to stitch"); badusage "no arguments allowed" if @ARGV; - my ($status, $message, $current, $ffq_prev) = ffq_prev_branchinfo(); - if ($status ne 'branch') { - fproblem $status, "could not check ffq-prev: $message"; - fproblems_maybe_bail(); - } - my $prev = $ffq_prev && git_get_ref $ffq_prev; - if (!$prev) { + + my ($ffq_prev, $gdrlast, $ffq_prev_commitish) = ffq_prev_info(); + if (!$ffq_prev_commitish) { fail "No ffq-prev to stitch." unless $opt_noop_ok; + return; } - push @deferred_updates, "delete $ffq_prev $prev"; - my $old_head = get_head(); - if (is_fast_fwd $old_head, $prev) { - my $differs = get_differs $old_head, $prev; - unless ($differs & ~D_PAT_ADD) { - # ffq-prev is ahead of us, and the only tree changes it has - # are possibly addition of things in debian/patches/. - # Just wind forwards rather than making a pointless pseudomerge. - update_head_checkout $old_head, $prev, "stitch (fast forward)"; - return; - } - } - fresh_workarea(); - my $new_head = make_commit [ $old_head, $ffq_prev ], [ - 'Declare fast forward / record previous work', - "[git-debrebase pseudomerge: stitch$prose]", - ]; - update_head $old_head, $new_head, "stitch"; + + keycommits $old_head, \&snag, \&snag, \&snag; + + stitch($old_head, $ffq_prev, $gdrlast, $ffq_prev_commitish, $prose); } sub cmd_convert_from_gbp () { @@ -1285,25 +1315,25 @@ sub cmd_convert_from_gbp () { } if (!is_fast_fwd $upstream, $old_head) { - fproblem 'upstream-not-ancestor', + snag 'upstream-not-ancestor', "upstream ($upstream) is not an ancestor of HEAD"; } else { my $wrong = cmdoutput (@git, qw(rev-list --ancestry-path), "$upstream..HEAD", qw(-- :/ :!/debian)); if (length $wrong) { - fproblem 'unexpected-upstream-changes', + snag 'unexpected-upstream-changes', "history between upstream ($upstream) and HEAD contains direct changes to upstream files - are you sure this is a gbp (patches-unapplied) branch?"; print STDERR "list expected changes with: git log --stat --ancestry-path $upstream_spec..HEAD -- :/ ':!/debian'\n"; } } if ((git_cat_file "$upstream:debian")[0] ne 'missing') { - fproblem 'upstream-has-debian', + snag 'upstream-has-debian', "upstream ($upstream) contains debian/ directory"; } - fproblems_maybe_bail(); + snags_maybe_bail(); my $work; @@ -1340,8 +1370,8 @@ sub cmd_convert_from_gbp () { sub cmd_convert_to_gbp () { badusage "no arguments allowed" if @ARGV; my $head = get_head(); - my $ffq = (ffq_prev_branchinfo())[3]; - my $bw = breakwater_of $head; + my (undef, undef, undef, $ffq, $gdrlast) = ffq_prev_branchinfo(); + my ($anchor, $bw) = keycommits $head, 0; fresh_workarea(); my $out; in_workarea sub { @@ -1356,6 +1386,7 @@ sub cmd_convert_to_gbp () { }; if (defined $ffq) { push @deferred_updates, "delete $ffq"; + push @deferred_updates, "delete $gdrlast"; } update_head_checkout $head, $out, "convert to gbp (v0)"; print < \$debuglevel, 'noop-ok', => \$opt_noop_ok, - 'f=s' => \@fproblem_force_opts, + 'f=s' => \@snag_force_opts, 'anchor=s' => \@opt_anchors, 'force!') or die badusage "bad options\n"; initdebug('git-debrebase ');