X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=blobdiff_plain;f=git-debrebase;h=f79aef5ff2241478bb98e452fe134d30492208a1;hb=2e4434dbec72dd4a042d2fc3beab9db88943b2c8;hp=bc98756f514a4e0b786329d20ef00e4d38a774d1;hpb=9157ed980b82c0664685e1cf8941c360fb2b11e1;p=dgit.git
diff --git a/git-debrebase b/git-debrebase
index bc98756f..f79aef5f 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 [] launder # prints breakwater tip etc.
-# git-debrebase [] stitch [--prose=]
-# 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,7 +31,7 @@ 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 $us = qw(git-debrebase);
@@ -157,6 +97,28 @@ sub fresh_workarea () {
in_workarea sub { playtree_setup };
}
+our @deferred_updates;
+our @deferred_update_messages;
+
+sub run_deferred_updates ($) {
+ my ($mrest) = @_;
+
+ 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.
@@ -334,6 +296,8 @@ sub any_fproblems () {
# has additional entry in classification result
# OrigParents = [ subset of Parents ] # singleton list
#
+# TreatAsAnchor
+#
# BreakwaterStart
#
# Unknown
@@ -402,6 +366,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 +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");
@@ -509,12 +494,19 @@ sub classify ($) {
return $unknown->("complex merge");
}
-sub breakwater_of ($) {
- my ($head) = @_; # must be laundered
+sub breakwater_of ($;$) {
+ my ($head, $unclean_fproblem_tag) = @_;
+ # $head should be laundered; if not, $unclean_fproblem_tag controls:
+ # if falseish, calls fail; otherwise, calls fproblem and returns undef
my $breakwater;
my $unclean = sub {
my ($why) = @_;
- fail "branch needs laundering (run git-debrebase): $why";
+ my $m = "branch needs laundering (run git-debrebase): $why";
+ fail $m unless $unclean_fproblem_tag;
+ fproblem $unclean_fproblem_tag, $m;
+ $breakwater = undef;
+ no warnings qw(exiting);
+ last;
};
for (;;) {
my $cl = classify $head;
@@ -523,6 +515,7 @@ sub breakwater_of ($) {
$ty eq 'Changelog') {
$breakwater //= $head;
} elsif ($ty eq 'Anchor' or
+ $ty eq 'TreatAsAnchor' or
$ty eq 'BreakwaterStart') {
$breakwater //= $head;
last;
@@ -572,8 +565,9 @@ sub walk ($;$$) {
return (Msg => $ms);
};
my $rewrite_from_here = sub {
+ my ($cl) = @_;
my $sp_cl = { SpecialMethod => 'StartRewrite' };
- push @brw_cl, $sp_cl;
+ push @$cl, $sp_cl;
push @processed, $sp_cl;
};
my $cur = $input;
@@ -623,7 +617,7 @@ sub walk ($;$$) {
my $p0 = @{ $cl->{Parents} }==1 ? $cl->{Parents}[0]{CommitId} : undef;
if ($ty eq 'AddPatches') {
$cur = $p0;
- $rewrite_from_here->();
+ $rewrite_from_here->(\@upp_cl);
next;
} elsif ($ty eq 'Packaging' or $ty eq 'Changelog') {
push @brw_cl, $cl;
@@ -644,17 +638,17 @@ sub walk ($;$$) {
};
$queue->(\@brw_cl, "debian");
$queue->(\@upp_cl, "upstream");
- $rewrite_from_here->();
+ $rewrite_from_here->(\@brw_cl);
$cur = $p0;
next;
} elsif ($ty eq 'Pseudomerge') {
my $contrib = $cl->{Contributor}{CommitId};
print $report " Contributor=$contrib" if $report;
push @pseudomerges, $cl;
- $rewrite_from_here->();
+ $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') {
@@ -700,7 +694,7 @@ sub walk ($;$$) {
" anchor")
};
$prline->(" Import");
- $rewrite_from_here->();
+ $rewrite_from_here->(\@brw_cl);
$upp_limit //= $#upp_cl; # further, deeper, patches discarded
$cur = $ovwr;
next;
@@ -839,7 +833,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 ($$$) {
@@ -857,8 +852,8 @@ sub update_head_postlaunder ($$$) {
runcmd @git, qw(rm --quiet --ignore-unmatch -rf debian/patches);
}
-sub cmd_launder () {
- badusage "no arguments to launder allowed" if @ARGV;
+sub cmd_launder_v0 () {
+ badusage "no arguments to launder-v0 allowed" if @ARGV;
my $old = get_head();
my ($tip,$breakwater,$last_anchor) = walk $old;
update_head_postlaunder $old, $tip, 'launder';
@@ -869,12 +864,7 @@ sub cmd_launder () {
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();
- }
+ record_ffq_auto();
my ($tip,$breakwater) = walk $old;
update_head_postlaunder $old, $tip, 'launder for rebase';
runcmd @git, qw(rebase), @ARGV, $breakwater;
@@ -894,7 +884,7 @@ sub cmd_analyse () {
}
sub ffq_prev_branchinfo () {
- # => ('status', "message", [$current, $ffq_prev])
+ # => ('status', "message", [$current, $ffq_prev, $drlast])
# 'status' may be
# branch message is undef
# weird-symref } no $current,
@@ -904,22 +894,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 $drlast = "refs/$gdrlast_refprefix/$'";
printdebug "ffq_prev_branchinfo branch current $current\n";
- return ('branch', undef, $current, $ffq_prev);
+ return ('branch', undef, $current, $ffq_prev, $drlast);
}
-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 "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, $drlast)
+ = ffq_prev_branchinfo();
return ($status, $message) unless $status eq 'branch';
my $currentval = get_head();
@@ -984,10 +977,20 @@ sub record_ffq_prev () {
}
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);
+
+ push @deferred_updates, "update $ffq_prev $currentval $git_null_obj";
+ push @deferred_updates, "delete $drlast";
+ 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 {
+ fproblem $status, "could not record ffq-prev: $message";
+ fproblems_maybe_bail();
+ }
}
sub cmd_new_upstream_v0 () {
@@ -996,13 +999,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 ''
@@ -1039,17 +1044,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}) {
+ fproblem '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
) {
@@ -1067,7 +1076,9 @@ sub cmd_new_upstream_v0 () {
}
foreach my $pc (values %pieces) {
- if (!$pc->{Old}) {
+ if (!$old_upstream) {
+ # we have complained already
+ } elsif (!$pc->{Old}) {
fproblem 'upstream-new-piece',
"introducing upstream piece \`$pc->{Name}'";
} elsif (!$pc->{New}) {
@@ -1170,10 +1181,11 @@ 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";
}
@@ -1189,7 +1201,8 @@ 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, $drlast)
+ = ffq_prev_branchinfo();
if ($status ne 'branch') {
fproblem $status, "could not check ffq-prev: $message";
fproblems_maybe_bail();
@@ -1198,23 +1211,27 @@ sub cmd_stitch () {
if (!$prev) {
fail "No ffq-prev to stitch." unless $opt_noop_ok;
}
- fresh_workarea();
+ 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.
+ push @deferred_updates, "update $drlast $prev $git_null_obj";
+ 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]",
];
- my @upd_cmd = (@git, qw(update-ref --stdin));
- 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;
+ push @deferred_updates, "update $drlast $new_head $git_null_obj";
+ update_head $old_head, $new_head, "stitch";
}
sub cmd_convert_from_gbp () {
@@ -1289,7 +1306,7 @@ 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 (undef, undef, undef, $ffq, $drlast) = ffq_prev_branchinfo();
my $bw = breakwater_of $head;
fresh_workarea();
my $out;
@@ -1304,9 +1321,8 @@ 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 $drlast";
}
update_head_checkout $head, $out, "convert to gbp (v0)";
print < \$debuglevel,
'noop-ok', => \$opt_noop_ok,
'f=s' => \@fproblem_force_opts,
+ 'anchor=s' => \@opt_anchors,
'force!') or die badusage "bad options\n";
initdebug('git-debrebase ');
enabledebug if $debuglevel;
@@ -1377,6 +1394,8 @@ chdir $toplevel or die "chdir $toplevel: $!";
$rd = fresh_playground "$playprefix/misc";
+@opt_anchors = map { git_rev_parse $_ } @opt_anchors;
+
if (!@ARGV || $ARGV[0] =~ m{^-}) {
defaultcmd_rebase();
} else {