chiark / gitweb /
git-debrebase: record_gdrlast: Honour $oldvalue
[dgit.git] / git-debrebase
index 61507ea..630b8c2 100755 (executable)
@@ -50,11 +50,9 @@ usages:
 See git-debrebase(1), git-debrebase(5), dgit-maint-debrebase(7) (in dgit).
 END
 
-our ($opt_force, $opt_careful, $opt_noop_ok, @opt_anchors);
+our ($opt_force, $opt_noop_ok, $opt_merges, @opt_anchors);
 our ($opt_defaultcmd_interactive);
 
-$opt_careful = 0;
-
 our $us = qw(git-debrebase);
 
 our $wrecknoteprefix = 'refs/debrebase/wreckage';
@@ -183,23 +181,32 @@ sub run_deferred_updates ($) {
     @deferred_update_messages = ();
 }
 
-sub get_tree ($) {
+sub get_tree ($;$$) {
     # tree object name => ([ $name, $info ], ...)
     # where $name is the sort key, ie has / at end for subtrees
     # $info is the LHS from git-ls-tree (<mode> <type> <hash>)
-    # will crash if $x does not exist, so don't do that;
+    # without $precheck, will crash if $x does not exist, so don't do that;
     # instead pass '' to get ().
-    my ($x) = @_;
+    my ($x, $precheck, $recurse) = @_;
 
     return () if !length $x;
 
+    if ($precheck) {
+       my ($type, $dummy) = git_cat_file $x, [qw(tree missing)];
+       return () if $type eq 'missing';
+    }
+
+    confess "get_tree needs object not $x ?" unless $x =~ m{^[0-9a-f]+\:};
+
     our (@get_tree_memo, %get_tree_memo);
     my $memo = $get_tree_memo{$x};
     return @$memo if $memo;
 
     local $Debian::Dgit::debugcmd_when_debuglevel = 3;
     my @l;
-    my @cmd = (qw(git ls-tree -z --full-tree --), $x);
+    my @cmd = (qw(git ls-tree -z --full-tree));
+    push @cmd, qw(-r) if $recurse;
+    push @cmd, qw(--), $x;
     my $o = cmdoutput @cmd;
     $o =~ s/\0$//s;
     my $last = '';
@@ -421,6 +428,17 @@ sub any_snags () {
     return $snags_forced || $snags_tripped;
 }
 
+sub ffq_prev_branchinfo () {
+    my $current = git_get_symref();
+    return gdr_ffq_prev_branchinfo($current);
+}
+
+sub record_gdrlast ($$;$) {
+    my ($gdrlast, $newvalue, $oldvalue) = @_;
+    $oldvalue ||= $git_null_obj;
+    push @deferred_updates, "update $gdrlast $newvalue $oldvalue";
+}
+
 sub gbp_pq_export ($$$) {
     my ($bname, $base, $tip) = @_;
     # must be run in a workarea.  $bname and patch-queue/$bname
@@ -439,11 +457,7 @@ sub gbp_pq_export ($$$) {
 }
 
 
-# xxx allow merge resolution separately from laundering, before git merge
-#
-# xxx general gdr docs highlight forbidden things
-# xxx general gdr docs list allowable things ?
-# xxx general gdr docs explicitly forbid some rebase
+# MERGE-TODO allow merge resolution separately from laundering, before git merge
 
 # later/rework?
 #  use git-format-patch?
@@ -680,6 +694,8 @@ sub merge_series_patchqueue_convert ($$$) {
        runcmd @git, qw(checkout -q -b mergec), $merged_pq;
 
        merge_attempt_cmd($wrecknotes, qw(gbp pq import));
+       # MERGE-TODO consider git-format-patch etc. instead,
+       # since gbp pq doesn't always round-trip :-/
 
        # OK now we are on patch-queue/merge, and we need to rebase
        # onto the intended parent and drop the patches from each one
@@ -975,7 +991,6 @@ sub classify ($) {
 
     if (@p == 2 and
        $r->{Msg} =~ m{^\[git-debrebase merged-breakwater.*\]$}m) {
-       # xxx ^ metadata tag needs adding to (5)
        return $classify->("MergedBreakwaters");
     }
     if ($r->{Msg} =~ m{^\[(git-debrebase|dgit)[: ].*\]$}m) {
@@ -985,7 +1000,7 @@ sub classify ($) {
        return $unknown->("octopus merge");
     }
 
-    if (!$ENV{GIT_DEBREBASE_EXPERIMENTAL_MERGE}) {
+    if (!$opt_merges) {
        return $unknown->("general two-parent merge");
     }
 
@@ -1306,7 +1321,7 @@ sub walk ($;$$$) {
            # which was reachable via ffq-prev is no longer findable.
            # This is suboptimal, but if it all works we'll have done
            # the right thing.
-           # xxx we should warn the user in the docs about this
+           # MERGE-TODO we should warn the user in the docs about this
 
            my $ok=1;
            my $best_anchor;
@@ -1598,7 +1613,7 @@ sub walk ($;$$$) {
                    printdebug "WALK REWRITING NOW cl=$cl procd=$procd\n";
                }
            }
-           if ($rewriting || $opt_careful) {
+           if ($rewriting) {
                read_tree_upstream $want_upstream, 0, $want_debian;
 
                my $newtree = cmdoutput @git, qw(write-tree);
@@ -1672,7 +1687,7 @@ sub update_head_checkout ($$$) {
 
 sub update_head_postlaunder ($$$) {
     my ($old, $tip, $reflogmsg) = @_;
-    return if $tip eq $old;
+    return if $tip eq $old && !@deferred_updates;
     print "git-debrebase: laundered (head was $old)\n";
     update_head $old, $tip, $reflogmsg;
     # no tree changes except debian/patches
@@ -1731,11 +1746,6 @@ sub cmd_analyse () {
     STDOUT->error and die $!;
 }
 
-sub ffq_prev_branchinfo () {
-    my $current = git_get_symref();
-    return gdr_ffq_prev_branchinfo($current);
-}
-
 sub ffq_check ($;$$) {
     # calls $ff and/or $notff zero or more times
     # then returns either (status,message) where status is
@@ -1877,8 +1887,7 @@ sub stitch ($$$$$) {
            # 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";
+           record_gdrlast $gdrlast, $ffq_prev_commitish;
            update_head_checkout $old_head, $ffq_prev_commitish,
                "stitch (fast forward)";
            return;
@@ -1891,7 +1900,7 @@ sub stitch ($$$$$) {
        'Declare fast forward / record previous work',
         "[git-debrebase pseudomerge: $prose]",
     ];
-    push @deferred_updates, "update $gdrlast $new_head $git_null_obj";
+    record_gdrlast $gdrlast, $new_head;
     update_head $old_head, $new_head, "stitch: $prose";
 }
 
@@ -2275,7 +2284,10 @@ sub cmd_stitch () {
     badusage "no arguments allowed" if @ARGV;
     do_stitch $prose, 0;
 }
-sub cmd_prepush () { cmd_stitch(); }
+sub cmd_prepush () {
+    $opt_noop_ok = 1;
+    cmd_stitch();
+}
 
 sub cmd_quick () {
     badusage "no arguments allowed" if @ARGV;
@@ -2380,6 +2392,36 @@ sub cmd_make_patches () {
     }
 }
 
+sub check_series_has_all_patches ($) {
+    my ($head) = @_;
+    my $seriesfn = 'debian/patches/series';
+    my ($dummy, $series) = git_cat_file "$head:$seriesfn",
+       [qw(blob missing)];
+    $series //= '';
+    my %series;
+    foreach my $f (grep /\S/, grep {!m/^\s\#/} split /\n/, $series) {
+       fail "patch $f repeated in $seriesfn !" if $series{$f}++;
+    }
+    foreach my $patchfile (get_tree "$head:debian/patches", 1,1) {
+       my ($f,$i) = @$patchfile;
+       next if $series{$f};
+       next if $f eq 'series';
+       snag 'unused-patches', "Unused patch file $f will be discarded";
+    }
+}
+
+sub begin_convert_from () {
+    my $head = get_head();
+    return ($head, undef);
+}
+
+sub complete_convert_from ($$$$) {
+    my ($old_head, $new_head, $gi, $mrest) = @_;
+    ffq_check $new_head;
+    snags_maybe_bail();
+    update_head_checkout $old_head, $new_head, $mrest;
+}
+
 sub cmd_convert_from_gbp () {
     badusage "want only 1 optional argument, the upstream git commitish"
        unless @ARGV<=1;
@@ -2394,7 +2436,7 @@ sub cmd_convert_from_gbp () {
     my $upstream =
        resolve_upstream_version($upstream_spec, $upstream_version);
 
-    my $old_head = get_head();
+    my ($old_head, $gdrlastinfo) = begin_convert_from();
 
     my $upsdiff = get_differs $upstream, $old_head;
     if ($upsdiff & D_UPS) {
@@ -2427,6 +2469,8 @@ END
            "upstream ($upstream) contains debian/ directory";
     }
 
+    check_series_has_all_patches $old_head;
+
     my $previous_dgit_view = eval {
        my @clogcmd = qw(dpkg-parsechangelog --format rfc822 -n2);
        my ($lvsn, $suite);
@@ -2447,19 +2491,25 @@ END
        my $mtag = cmdoutput @git, qw(describe --always --abbrev=0 --match),
            $mtag_pat;
        die "could not find suitable maintainer view tag $mtag_pat\n"
-           unless $mtag_pat =~ m{/};
+           unless $mtag =~ m{/};
        is_fast_fwd $mtag, 'HEAD' or
            die "HEAD is not FF from maintainer tag $mtag!";
        my $dtag = "archive/$mtag";
+       git_get_ref "refs/tags/$dtag" or
+           die "dgit view tag $dtag not found\n";
        is_fast_fwd $mtag, $dtag or
-           die "dgit view tag $dtag is not FF from maintainer tag $mtag";
+           die "dgit view tag $dtag is not FF from maintainer tag $mtag\n";
        print "will stitch in dgit view, $dtag\n";
        git_rev_parse $dtag;
     };
     if (!$previous_dgit_view) {
        $@ =~ s/^\n+//;
        chomp $@;
-       print STDERR "cannot stitch in dgit view: $@\n";
+       print STDERR <<END;
+Cannot confirm dgit view: $@
+Failed to stitch in dgit view (see messages above).
+dgit --overwrite will be needed on the first dgit push after conversion.
+END
     }
 
     snags_maybe_bail_early();
@@ -2500,9 +2550,10 @@ END
        }
     };
 
-    ffq_check $work;
-    snags_maybe_bail();
-    update_head_checkout $old_head, $work, 'convert-from-gbp';
+    complete_convert_from $old_head, $work, $gdrlastinfo, 'convert-from-gbp';
+    print <<END or die $!;
+git-debrebase: converted from patched-unapplied (gbp) branch format, OK
+END
 }
 
 sub cmd_convert_to_gbp () {
@@ -2559,7 +2610,7 @@ sub cmd_convert_from_dgit_view () {
                         };
     }
 
-    my $head = get_head();
+    my ($head, $gdrlastinfo) = begin_convert_from();
 
     if (!$always) {
        my $troubles = 0;
@@ -2577,6 +2628,8 @@ END
        }
     }
 
+    check_series_has_all_patches $head;
+
     snags_maybe_bail_early();
 
     my $version = upstreamversion $clogp->{Version};
@@ -2713,15 +2766,13 @@ END
 
     printf STDERR "Yes, will base new branch on %s\n", $result->{Source};
 
-    ffq_check $result->{Result};
-    snags_maybe_bail();
-    update_head_checkout $head, $result->{Result},
+    complete_convert_from $head, $result->{Result}, $gdrlastinfo,
        'convert-from-dgit-view';
 }
 
 sub cmd_record_resolved_merge () {
     badusage "record-resolved-merge takes no further arguments" if @ARGV;
-    # xxx needs documentation
+    # MERGE-TODO needs documentation
     my $new = get_head();
     my $method;
 
@@ -2816,6 +2867,7 @@ getoptions_main
           'anchor=s' => \@opt_anchors,
           '--dgit=s' => \($dgit[0]),
           'force!',
+          'experimental-merge-resolution!', \$opt_merges,
           '-i:s' => sub {
               my ($opt,$val) = @_;
               badusage "git-debrebase: no cuddling to -i for git-rebase"