# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-# undocumented usages:
-#
-# git-debrebase [<options>] 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);
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 ($;$$);
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();
}
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;
}
}
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,
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 () {
# 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();
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);
}
}
}
+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
}
}
+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 $!;
}
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 () {
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 {
};
if (defined $ffq) {
push @deferred_updates, "delete $ffq";
+ push @deferred_updates, "delete $gdrlast";
}
update_head_checkout $head, $out, "convert to gbp (v0)";
print <<END or die $!;