X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=blobdiff_plain;f=git-debrebase;h=6211fb633d85ecee3fcb2f6173dde3f9184d0a1f;hb=acec8e4b8dc36601d94159bdac169b8f13e64913;hp=a5329b6b117ffe27211282038edcc40aefd4e32d;hpb=be8119133fb4de3173b0a39d28f09dd74a112d65;p=dgit.git
diff --git a/git-debrebase b/git-debrebase
index a5329b6b..6211fb63 100755
--- a/git-debrebase
+++ b/git-debrebase
@@ -18,66 +18,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-
-# usages:
-#
-# git-debrebase [] new-upstream-v0 \
-# \
-# [ ...] \
-# [...]
-#
-# git-debrebase [ --] []
-# git-debrebase [] analyse
-# git-debrebase [] breakwater # prints breakwater tip only
-# git-debrebase [] stitch [--prose=]
-# git-debrebase [] launder-v0 # prints breakwater tip etc.
-# git-debrebase [] downstream-rebase-launder-v0 # experimental
-#
-# git-debrebase [] convert-from-gbp []
-# git-debrebase [] convert-to-gbp
-
-# 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);
@@ -91,13 +31,15 @@ use Getopt::Long qw(:config posix_default gnu_compat bundling);
use Dpkg::Version;
use File::FnMatch qw(:fnmatch);
-our ($opt_force, $opt_noop_ok);
+our ($opt_force, $opt_noop_ok, @opt_anchors);
+our ($opt_defaultcmd_interactive);
our $us = qw(git-debrebase);
sub badusage ($) {
my ($m) = @_;
- die "bad usage: $m\n";
+ print STDERR "bad usage: $m\n";
+ exit 12;
}
sub cfg ($;$) {
@@ -157,6 +99,34 @@ sub fresh_workarea () {
in_workarea sub { playtree_setup };
}
+our $snags_forced;
+our $snags_tripped;
+our $snags_checked;
+our @deferred_updates;
+our @deferred_update_messages;
+
+sub run_deferred_updates ($) {
+ my ($mrest) = @_;
+
+ confess 'dangerous internal error' if
+ !$snags_checked || $snags_tripped || $snags_forced;
+
+ my @upd_cmd = (@git, qw(update-ref --stdin -m), "debrebase: $mrest");
+ debugcmd '>|', @upd_cmd;
+ open U, "|-", @upd_cmd or die $!;
+ foreach (@deferred_updates) {
+ printdebug ">= ", $_, "\n";
+ print U $_, "\n" or die $!;
+ }
+ printdebug ">\$\n";
+ close U or failedcmd @upd_cmd;
+
+ print $_, "\n" foreach @deferred_update_messages;
+
+ @deferred_updates = ();
+ @deferred_update_messages = ();
+}
+
sub get_differs ($$) {
my ($x,$y) = @_;
# This resembles quiltify_trees_differ, in dgit, a bit.
@@ -262,40 +232,41 @@ sub make_commit ($$) {
return cmdoutput @cmd;
}
-our @fproblem_force_opts;
-our $fproblems_forced;
-our $fproblems_tripped;
-sub fproblem ($$) {
+our @snag_force_opts;
+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 () {
+ $snags_checked++;
+ 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;
+ $snags_forced=0;
}
- 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;
+ $snags_tripped=0;
} 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
@@ -334,6 +305,8 @@ sub any_fproblems () {
# has additional entry in classification result
# OrigParents = [ subset of Parents ] # singleton list
#
+# TreatAsAnchor
+#
# BreakwaterStart
#
# Unknown
@@ -402,6 +375,10 @@ sub classify ($) {
return $r;
};
+ if (grep { $_ eq $objid } @opt_anchors) {
+ return $classify->('TreatAsAnchor');
+ }
+
my @identical = grep { !$_->{Differs} } @p;
my ($stype, $series) = git_cat_file "$t:debian/patches/series";
my $haspatches = $stype ne 'missing' && $series =~ m/^\s*[^#\n\t ]/m;
@@ -410,6 +387,32 @@ sub classify ($) {
# multi-orig upstreams are represented with an anchor merge
# from a single upstream commit which combines the orig tarballs
+ # Every anchor tagged this way must be a merge.
+ # We are relying on the
+ # [git-debrebase anchor: ...]
+ # commit message annotation in "declare" anchor merges (which
+ # do not have any upstream changes), to distinguish those
+ # anchor merges from ordinary pseudomerges (which we might
+ # just try to strip).
+ #
+ # However, the user is going to be doing git-rebase a lot. We
+ # really don't want them to rewrite an anchor commit.
+ # git-rebase trips up on merges, so that is a useful safety
+ # catch.
+ #
+ # BreakwaterStart commits are also anchors in the terminology
+ # of git-debrebase(5), but they are untagged (and always
+ # manually generated).
+ #
+ # We cannot not tolerate any tagged linear commit (ie,
+ # BreakwaterStart commits tagged `[anchor:') because such a
+ # thing could result from an erroneous linearising raw git
+ # rebase of a merge anchor. That would represent a corruption
+ # of the branch. and we want to detect and reject the results
+ # of such corruption before it makes it out anywhere. If we
+ # reject it here then we avoid making the pseudomerge which
+ # would be needed to push it.
+
my $badanchor = sub { $unknown->("git-debrebase \`anchor' but @_"); };
@p == 2 or return $badanchor->("has other than two parents");
$haspatches and return $badanchor->("contains debian/patches");
@@ -509,40 +512,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 ($;$$);
@@ -655,7 +699,7 @@ sub walk ($;$$) {
$rewrite_from_here->(\@upp_cl);
$cur = $contrib;
next;
- } elsif ($ty eq 'Anchor') {
+ } elsif ($ty eq 'Anchor' or $ty eq 'TreatAsAnchor') {
$last_anchor = $cur;
$build_start->("Anchor", $cur);
} elsif ($ty eq 'DgitImportUnpatched') {
@@ -840,7 +884,8 @@ sub get_head () {
sub update_head ($$$) {
my ($old, $new, $mrest) = @_;
- runcmd @git, qw(update-ref -m), "debrebase: $mrest", 'HEAD', $new, $old;
+ push @deferred_updates, "update HEAD $new $old";
+ run_deferred_updates $mrest;
}
sub update_head_checkout ($$$) {
@@ -858,6 +903,16 @@ 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;
+ snags_maybe_bail();
+ 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();
@@ -869,16 +924,9 @@ sub cmd_launder_v0 () {
}
sub defaultcmd_rebase () {
- my $old = get_head();
- my ($status, $message) = record_ffq_prev();
- if ($status eq 'written' || $status eq 'exists') {
- } else {
- fproblem $status, "could not record ffq-prev: $message";
- fproblems_maybe_bail();
- }
- my ($tip,$breakwater) = walk $old;
- update_head_postlaunder $old, $tip, 'launder for rebase';
- runcmd @git, qw(rebase), @ARGV, $breakwater;
+ push @ARGV, @{ $opt_defaultcmd_interactive // [] };
+ my ($tip,$breakwater) = do_launder_head 'launder for rebase';
+ runcmd @git, qw(rebase), @ARGV, $breakwater if @ARGV;
}
sub cmd_analyse () {
@@ -895,7 +943,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,
@@ -905,22 +953,25 @@ 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 () {
+sub record_ffq_prev_deferred () {
# => ('status', "message")
# 'status' may be
- # written message is undef
+ # deferred message is undef
# exists
# detached
# weird-symref
# notbranch
- # if not ff from some branch we should be ff from, is an fproblem
- # if "written", will have printed something about that to stdout,
- # and also some messages about ff checks
- my ($status, $message, $current, $ffq_prev) = ffq_prev_branchinfo();
+ # 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, $gdrlast)
+ = ffq_prev_branchinfo();
return ($status, $message) unless $status eq 'branch';
my $currentval = get_head();
@@ -955,10 +1006,10 @@ sub record_ffq_prev () {
$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";
}
};
@@ -984,11 +1035,76 @@ sub record_ffq_prev () {
$check->("refs/remotes/dgit/dgit/sid", 'remote dgit branch for sid');
}
- fproblems_maybe_bail();
- runcmd @git, qw(update-ref -m), "record current head for preservation",
- $ffq_prev, $currentval, $git_null_obj;
- print "Recorded current head for preservation\n" or die $!;
- return ('written', undef);
+ 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);
+}
+
+sub record_ffq_auto () {
+ my ($status, $message) = record_ffq_prev_deferred();
+ if ($status eq 'deferred' || $status eq 'exists') {
+ } else {
+ 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 do_stitch ($;$) {
+ my ($prose, $unclean) = @_;
+
+ 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;
+ }
+ my $dangling_head = get_head();
+
+ keycommits $dangling_head, $unclean,$unclean,$unclean;
+ snags_maybe_bail();
+
+ stitch($dangling_head, $ffq_prev, $gdrlast, $ffq_prev_commitish, $prose);
}
sub cmd_new_upstream_v0 () {
@@ -997,13 +1113,15 @@ sub cmd_new_upstream_v0 () {
my %pieces;
- badusage "need NEW-VERSION UPS-COMMITTISH" unless @ARGV >= 2;
+ badusage "need NEW-VERSION [UPS-COMMITTISH]" unless @ARGV >= 1;
# parse args - low commitment
my $new_version = (new Dpkg::Version scalar(shift @ARGV), check => 1);
my $new_upstream_version = $new_version->version();
- my $new_upstream = git_rev_parse shift @ARGV;
+ my $new_upstream = git_rev_parse (shift @ARGV // 'upstream');
+
+ record_ffq_auto();
my $piece = sub {
my ($n, @x) = @_; # may be ''
@@ -1040,17 +1158,21 @@ sub cmd_new_upstream_v0 () {
# now we need to investigate the branch this generates the
# laundered version but we don't switch to it yet
my $old_head = get_head();
- my ($old_laundered_tip,$old_bw,$old_upstream_update) = walk $old_head;
+ my ($old_laundered_tip,$old_bw,$old_anchor) = walk $old_head;
my $old_bw_cl = classify $old_bw;
- my $old_upstream_update_cl = classify $old_upstream_update;
- confess unless $old_upstream_update_cl->{OrigParents};
- my $old_upstream = parsecommit
- $old_upstream_update_cl->{OrigParents}[0]{CommitId};
-
- $piece->('', Old => $old_upstream->{CommitId});
+ my $old_anchor_cl = classify $old_anchor;
+ my $old_upstream;
+ if (!$old_anchor_cl->{OrigParents}) {
+ snag 'anchor-treated',
+ 'old anchor is recognised due to --anchor, cannot check upstream';
+ } else {
+ $old_upstream = parsecommit
+ $old_anchor_cl->{OrigParents}[0]{CommitId};
+ $piece->('', Old => $old_upstream->{CommitId});
+ }
- if ($old_upstream->{Msg} =~ m{^\[git-debrebase }m) {
+ if ($old_upstream && $old_upstream->{Msg} =~ m{^\[git-debrebase }m) {
if ($old_upstream->{Msg} =~
m{^\[git-debrebase upstream-combine \.((?: $extra_orig_namepart_re)+)\:.*\]$}m
) {
@@ -1061,21 +1183,23 @@ 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";
}
}
foreach my $pc (values %pieces) {
- if (!$pc->{Old}) {
- fproblem 'upstream-new-piece',
+ if (!$old_upstream) {
+ # we have complained already
+ } elsif (!$pc->{Old}) {
+ 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}";
}
}
@@ -1083,7 +1207,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;
@@ -1091,7 +1215,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};
}
@@ -1128,6 +1252,10 @@ sub cmd_new_upstream_v0 () {
"[git-debrebase anchor: new upstream $new_upstream_version, merge]",
];
+ my $clogsignoff = cmdoutput qw(git show),
+ '--pretty=format:%an <%ae> %aD',
+ $new_bw;
+
# Now we have to add a changelog stanza so the Debian version
# is right.
die if unlink "debian";
@@ -1144,7 +1272,7 @@ $p ($new_version) UNRELEASED; urgency=medium
* Update to new upstream version $new_upstream_version.
- --
+ -- $clogsignoff
END
close CN or die $!;
@@ -1171,63 +1299,53 @@ END
sub cmd_record_ffq_prev () {
badusage "no arguments allowed" if @ARGV;
- my ($status, $msg) = record_ffq_prev();
+ my ($status, $msg) = record_ffq_prev_deferred();
if ($status eq 'exists' && $opt_noop_ok) {
print "Previous head already recorded\n" or die $!;
- } elsif ($status eq 'written') {
+ } elsif ($status eq 'deferred') {
+ run_deferred_updates 'record-ffq-prev';
} else {
fail "Could not preserve: $msg";
}
}
+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) {
- fail "No ffq-prev to stitch." unless $opt_noop_ok;
- }
- 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,
- "debrebase: stitch (fast forward)";
- runcmd @git, qw(update-ref -d), $ffq_prev; # should be atomic
- return;
- }
+ do_stitch $prose, 0;
+}
+sub cmd_prepush () { cmd_stitch(); }
+
+sub cmd_quick () {
+ badusage "no arguments allowed" if @ARGV;
+ do_launder_head 'launder for git-debrebase quick';
+ do_stitch 'quick', \&snag;
+}
+
+sub cmd_conclude () {
+ my ($ffq_prev, $gdrlast, $ffq_prev_commitish) = ffq_prev_info();
+ if (!$ffq_prev_commitish) {
+ fail "No ongoing git-debrebase session." unless $opt_noop_ok;
+ return;
}
- fresh_workarea();
- my $new_head = make_commit [ $old_head, $ffq_prev ], [
- 'Declare fast forward / record previous work',
- "[git-debrebase pseudomerge: stitch$prose]",
- ];
- my @upd_cmd = (@git, qw(update-ref --stdin -m), "debrebase: stitch");
- debugcmd '>|', @upd_cmd;
- open U, "|-", @upd_cmd or die $!;
- my $u = <= ", $_, "\n" foreach split /\n/, $u;
- print U $u;
- printdebug ">\$\n";
- close U or failedcmd @upd_cmd;
+ my $dangling_head = get_head();
+
+ badusage "no arguments allowed" if @ARGV;
+ do_launder_head 'launder for git-debrebase quick';
+ do_stitch 'quick', \&snag;
}
sub cmd_convert_from_gbp () {
@@ -1247,25 +1365,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;
@@ -1302,8 +1420,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 {
@@ -1317,10 +1435,10 @@ sub cmd_convert_to_gbp () {
];
};
if (defined $ffq) {
- runcmd @git, qw(update-ref -m),
- "debrebase: converting corresponding main branch to gbp format",
- $ffq, $git_null_obj;
+ push @deferred_updates, "delete $ffq";
+ push @deferred_updates, "delete $gdrlast";
}
+ snags_maybe_bail();
update_head_checkout $head, $out, "convert to gbp (v0)";
print < \$debuglevel,
'noop-ok', => \$opt_noop_ok,
- 'f=s' => \@fproblem_force_opts,
- 'force!') or die badusage "bad options\n";
+ 'f=s' => \@snag_force_opts,
+ 'anchor=s' => \@opt_anchors,
+ 'force!',
+ '-i:s' => sub {
+ my ($opt,$val) = @_;
+ badusage "git-debrebase: no cuddling to -i for git-rebase"
+ if length $val;
+ die if $opt_defaultcmd_interactive; # should not happen
+ $opt_defaultcmd_interactive = [ qw(-i) ];
+ # This access to @ARGV is excessive familiarity with
+ # Getopt::Long, but there isn't another sensible
+ # approach. '-i=s{0,}' does not work with bundling.
+ push @$opt_defaultcmd_interactive, @ARGV;
+ @ARGV=();
+ }) or die badusage "bad options\n";
initdebug('git-debrebase ');
enabledebug if $debuglevel;
@@ -1390,7 +1521,9 @@ chdir $toplevel or die "chdir $toplevel: $!";
$rd = fresh_playground "$playprefix/misc";
-if (!@ARGV || $ARGV[0] =~ m{^-}) {
+@opt_anchors = map { git_rev_parse $_ } @opt_anchors;
+
+if (!@ARGV || $opt_defaultcmd_interactive || $ARGV[0] =~ m{^-}) {
defaultcmd_rebase();
} else {
my $cmd = shift @ARGV;