X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=dgit.git;a=blobdiff_plain;f=git-debrebase;h=171324671faed440b06338d41208b3eeda52e47b;hp=cdd6348a122efc405a6de50d3338d6b1356da12d;hb=5ccc45c714e700c97d52fa291daef03cd2492780;hpb=4b9de539daea1c590687fcf144b0f36cc99609ed
diff --git a/git-debrebase b/git-debrebase
index cdd6348a..17132467 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);
@@ -438,6 +378,23 @@ 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).
+
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");
@@ -537,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 forfurniture 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 ($;$$);
@@ -920,7 +917,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,
@@ -930,8 +927,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,7 +944,8 @@ sub record_ffq_prev_deferred () {
# 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();
@@ -1013,6 +1012,7 @@ sub record_ffq_prev_deferred () {
fproblems_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);
}
@@ -1026,6 +1026,33 @@ sub record_ffq_auto () {
}
}
+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: stitch$prose]",
+ ];
+ push @deferred_updates, "update $gdrlast $new_head $git_null_obj";
+ update_head $old_head, $new_head, "stitch";
+}
+
sub cmd_new_upstream_v0 () {
# automatically and unconditionally launders before rebasing
# if rebase --abort is used, laundering has still been done
@@ -1224,9 +1251,15 @@ 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 $!;
}
@@ -1234,34 +1267,22 @@ sub cmd_stitch () {
my $prose = '';
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();
+ my ($status, $message, $current, $ffq_prev, $gdrlast)
+ = 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_commitish = $ffq_prev && git_get_ref $ffq_prev;
+ 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, \&fproblem, \&fproblem, \&fproblem;
+
+ stitch($old_head, $ffq_prev, $gdrlast, $ffq_prev_commitish, $prose);
}
sub cmd_convert_from_gbp () {
@@ -1336,8 +1357,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 {
@@ -1352,6 +1373,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 <