chiark / gitweb /
git-debrebase: in new-upstream-v0 tolerate planned upstream-combine commits which...
[dgit.git] / git-debrebase
index c89e598f599a713a9903d7dedd159d840426b7d7..b467a56d17a03d92706b86ee34796c7b7ffef77e 100755 (executable)
@@ -3,7 +3,7 @@
 # Script helping make fast-forwarding histories while still rebasing
 # upstream deltas when working on Debian packaging
 #
-# Copyright (C)2017 Ian Jackson
+# Copyright (C)2017,2018 Ian Jackson
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-#    git-debrebase new-upstreams-v0 \
-#             NEW-VERSION ORIG-COMMITISH
-#            [EXTRA-ORIG-NAME EXTRA-ORIG-COMMITISH ...]
 
 # usages:
-#    git-debrebase status
-#    git-debrebase start       # like ffqrebase start + debrebase launder
-#    git-debrebase new-upstream [stuff]  # see below
-#    git-debrebase <git-rebase options>  # does debrebase start if necessary
 #
-#    git-debrebase analyse
-#    git-debrebase launder     # prints breakwater tip
-#    git-debrebase create-new-upstream-breakwater [-f] <upstreaminfo>...
+#    git-debrebase [<options>] new-upstream-v0 \
+#             <new-version> <orig-commitish> \
+#            [<extra-orig-name> <extra-orig-commitish> ...] \
+#            [<git-rebase options>...]
 #
-# <upstreaminfo> is
-#    [,][<subdir>:][+]<commitid>[,...]
+#    git-debrebase [<options> --] [<git-rebase options...>]
+#    git-debrebase [<options>] analyse
+#    git-debrebase [<options>] launder         # prints breakwater tip etc.
+#    git-debrebase [<options>] downstream-rebase-launder-v0  # experimental
 #
-# if initial comma is supplied, entries are not positional.  Unspecified
-# <subdir> means root (and there may be only one).
-# xxx want auto branch names
-# xxx too complicated
-# how about for now
-#    [+]<commit> [<subdir/> [+]<commit>...]
-# ?  plus options
-#     --new-upstream-different-subtrees
+#    git-debrebase [<options>] gbp2debrebase-v0 \
+#             <upstream>
+
+# 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 ?
 #
-#  automatic case
-#       git-debrebase new-upstream
-#             - previous breakwater merge must be gdr-generated
-#             - orig set is the same as before
-#             - implicitly uses upstream branches according to orig set
-#             - not all upstream branches need be updated
-#             - insists on fast-forward of each branch, unless
-#                  --force (or --force=<subdir>[/])
-#  branch set adjustments
-#       git-debrebase new-upstream --add <subdir>/
-#       git-debrebase new-upstream --rm <subdir>/
-#       git-debrebase new-upstream / [<subdir>/ ...]
-#             - orig set is adjusted
-#             - otherwise like auto (--add is not checked for ffness, obv)
-#             - multiple --add and --rm may be specified
-#             - --add makes new upstream the last contributor
-#  explicit
-#       git-debrebase / [<rootcommitid>] [<subdir>/ [<commitid>] ...]
-#             - orig set is precisely as specified now
-#             - previous breakwater merge is irrelevant
-#             - no fast forward checks
-#  for now only explicit with commitids
-
-#         implicitly uses `upstream'
-#                                     # (or multiple other branches)
-#       git-debrebase new-upstream \
-#             [<subdir>/]=<commitid>
-
-#    UPSTREAM[,[[SUBDIR:]SUBUPSTREAM]
-#    default for SUBDIR: is from previous upstream merge[xxx terminology]
-#    
+#  * Workflow is currently clumsy.  Lots of spurious runes to type.
+#    There's not even a guide.
 #
-#xxx
-# when starting must record original start (for ff)
-# and new rebase basis
+#  * There are no tests.
 #
-#    git-ffqrebase start [BASE]
-#                # records previous HEAD so it can be overwritten
-#                # records base for future git-ffqrebase
-#    git-ffqrebase set-base BASE
-#    git-ffqrebase <git-rebase options>
-#    git-ffqrebase finish
-#    git-ffqrebase status [BRANCH]
+#  * new-upstream-v0 has a terrible UI.  You end up with giant
+#    runic command lines.
 #
-#  refs/ffqrebase-prev/BRANCH    BRANCH may be refs/...; if not it means
-#  refs/ffqrebase-base/BRANCH      refs/heads/BRANCH
-#                               zero, one, or both of these may exist
+#    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.
 #
-# git-debrebase without start, if already started, is willing
-# to strip pseudomerges provided that they overwrite exactly
-# the previous HEAD
-#  xxxx is this right ?  what matters is have we pushed
-#    I think in fact the right answer is:
-#       git-debrebase always strips out pseudomerges from its branch
-#       a pseudomerge is put in at the time we want to push
-#       at that time, we make a pseudomerge of the remote tracking
-#           branch (if raw git) or the dgit view (if dgit)
-#       for raw git git-ffqrebase, do want preciseley to record
-#           value of remote tracking branch or our branch, on start, so we
-#           overwrite only things we intend to
-#  the previous pseudomerge    check for tags and remote branches ?
+#  * 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;
 
@@ -282,6 +252,26 @@ sub make_commit ($$) {
     return cmdoutput @cmd;
 }
 
+our $fproblems;
+sub fproblem ($) {
+    my ($msg) = @_;
+    $fproblems++;
+    print STDERR "git-debrebase: safety catch tripped: $msg\n";
+}
+sub fproblems_maybe_bail () {
+    if ($fproblems) {
+       if ($opt_force) {
+           printf STDERR
+               "safety catch trips (%d) overriden by --force\n",
+               $fproblems;
+       } else {
+           fail sprintf
+               "safety catch trips (%d) (you could --force)",
+               $fproblems;
+       }
+    }
+}
+
 # classify returns an info hash like this
 #   CommitId => $objid
 #   Hdr => # commit headers, including 1 final newline
@@ -787,6 +777,8 @@ sub update_head_checkout ($$$) {
 
 sub update_head_postlaunder ($$$) {
     my ($old, $tip, $reflogmsg) = @_;
+    return if $tip eq $old;
+    print "git-debrebase: laundered (head was $old)\n";
     update_head $old, $tip, $reflogmsg;
     # no tree changes except debian/patches
     runcmd @git, qw(rm --quiet --ignore-unmatch -rf debian/patches);
@@ -802,6 +794,14 @@ sub cmd_launder () {
     printf "# last upstream merge\n%s\n", $last_upstream_merge;
 }
 
+sub defaultcmd_rebase () {
+    my $old = get_head();
+    my ($tip,$breakwater) = walk $old;
+    update_head_postlaunder $old, $tip, 'launder for rebase';
+    @ARGV = qw(-i) unless @ARGV; # make configurable
+    runcmd @git, qw(rebase), @ARGV, $breakwater;
+}
+
 sub cmd_analyse () {
     die if ($ARGV[0]//'') =~ m/^-/;
     badusage "too many arguments to analyse" if @ARGV>1;
@@ -816,16 +816,14 @@ sub cmd_analyse () {
 }
 
 sub cmd_new_upstream_v0 () {
-    # xxx would like to support more git-rebase options
-    badusage
- "need NEW-VERSION UPS-COMMITISH [EXTRA-UPS-NAME EXTRA-UPS-COMMITISH...]"
-       unless @ARGV % 2 == 0 and @ARGV >= 2;
     # tree should be clean and this is not checked
     # automatically and unconditionally launders before rebasing
     # if rebase --abort is used, laundering has still been done
 
     my %pieces;
 
+    badusage "need NEW-VERSION UPS-COMMITTISH" unless @ARGV >= 2;
+
     # parse args - low commitment
     my $new_version = (new Dpkg::Version scalar(shift @ARGV), check => 1);
     my $new_upstream_version = $new_version->version();
@@ -853,8 +851,12 @@ sub cmd_new_upstream_v0 () {
         OldIx => 0,
         New => $new_upstream,
     );
-    while (@ARGV) {
+    while (@ARGV && $ARGV[0] !~ m{^-}) {
        my $n = shift @ARGV;
+
+        badusage "for each EXTRA-UPS-NAME need EXTRA-UPS-COMMITISH"
+            unless @ARGV && $ARGV[0] !~ m{^-};
+
        my $c = git_rev_parse shift @ARGV;
        die unless $n =~ m/^$extra_orig_namepart_re$/;
        $newpiece->($n, New => $c);
@@ -871,18 +873,11 @@ sub cmd_new_upstream_v0 () {
     my $old_upstream = parsecommit
         $old_upstream_update_cl->{OrigParents}[0]{CommitId};
 
-    my $problems = 0;
-    my $problem = sub {
-       my ($msg) = @_;
-       $problems++;
-       print STDERR "preflight check failed: $msg\n";
-    };
-
     $piece->('', Old => $old_upstream->{CommitId});
 
     if ($old_upstream->{Msg} =~ m{^\[git-debrebase }m) {
        if ($old_upstream->{Msg} =~
- m{^\[git-debrebase new-upstream combine \.((?: $extra_orig_namepart_re)+)\]}
+ m{^\[git-debrebase (?:\w*-)?upstream combine \.((?: $extra_orig_namepart_re)+)\]}
           ) {
            my @oldpieces = ('', split / /, $1);
            my $parentix = -1 + scalar @{ $old_upstream->{Parents} };
@@ -891,35 +886,25 @@ sub cmd_new_upstream_v0 () {
                $piece->($n, Old => $old_upstream->{CommitId}.'^'.$parentix);
            }
        } else {
-           $problem->("previous upstream $old_upstream->{CommitId} is from".
-                 " git-debrebase but not a \`new-upstream combine' commit");
+           fproblem "previous upstream $old_upstream->{CommitId} is from".
+                 " git-debrebase but not an \`upstream combine' commit";
        }
     }
 
     foreach my $pc (values %pieces) {
        if (!$pc->{Old}) {
-           $problem->("introducing upstream piece \`$pc->{Name}'");
+           fproblem "introducing upstream piece \`$pc->{Name}'";
        } elsif (!$pc->{New}) {
-           $problem->("dropping upstream piece \`$pc->{Name}'");
+           fproblem "dropping upstream piece \`$pc->{Name}'";
        } elsif (!is_fast_fwd $pc->{Old}, $pc->{New}) {
-           $problem->("not fast forward: $pc->{Name} $pc->{Old}..$pc->{New}");
+           fproblem "not fast forward: $pc->{Name} $pc->{Old}..$pc->{New}";
        }
     }
 
     printdebug "%pieces = ", (dd \%pieces), "\n";
     printdebug "\@newpieces = ", (dd \@newpieces), "\n";
 
-    if ($problems) {
-       if ($opt_force) {
-           printf STDERR
-               "preflight check failures (%d) overriden by --force\n",
-               $problems;
-       } else {
-           fail sprintf
-               "preflight check failures (%d) (you could --force)",
-               $problems;
-       }
-    }
+    fproblems_maybe_bail();
 
     my $new_bw;
 
@@ -967,8 +952,9 @@ sub cmd_new_upstream_v0 () {
        # Now we have to add a changelog stanza so the Debian version
        # is right.
        die if unlink "debian";
-       die $! unless $!==ENOTEMPTY;
-       unlink "debian/changelog" or die $!;
+       die $! unless $!==ENOENT or $!==ENOTEMPTY;
+       unlink "debian/changelog" or $!==ENOENT or die $!;
+        mkdir "debian" or die $!;
        open CN, ">", "debian/changelog" or die $!;
        my $oldclog = git_cat_file ":debian/changelog";
        $oldclog =~ m/^($package_re) \(\S+\) / or
@@ -996,9 +982,10 @@ END
     # the laundering output, because git-rebase can't easily be made
     # to make a replay list which is based on some other branch
 
-    update_head $old_head, $old_laundered_tip, 'launder for new upstream';
+    update_head_postlaunder $old_head, $old_laundered_tip,
+        'launder for new upstream';
 
-    my @cmd = (@git, qw(rebase --onto), $new_bw, $old_bw);
+    my @cmd = (@git, qw(rebase --onto), $new_bw, $old_bw, @ARGV);
     runcmd @cmd;
     # now it's for the user to sort out
 }
@@ -1062,10 +1049,14 @@ chdir $toplevel or die "chdir $toplevel: $!";
 
 $rd = fresh_playground "$playprefix/misc";
 
-my $cmd = shift @ARGV;
-my $cmdfn = $cmd;
-$cmdfn =~ y/-/_/;
-$cmdfn = ${*::}{"cmd_$cmdfn"};
+if (!@ARGV || $ARGV[0] =~ m{^-}) {
+    defaultcmd_rebase();
+} else {
+    my $cmd = shift @ARGV;
+    my $cmdfn = $cmd;
+    $cmdfn =~ y/-/_/;
+    $cmdfn = ${*::}{"cmd_$cmdfn"};
 
-$cmdfn or badusage "unknown git-debrebase sub-operation $cmd";
-$cmdfn->();
+    $cmdfn or badusage "unknown git-debrebase sub-operation $cmd";
+    $cmdfn->();
+}