chiark / gitweb /
Split brain: Use `gbp pq' (which exists) not `gbp-pq' (which doesn't)
[dgit.git] / dgit
diff --git a/dgit b/dgit
index b3071b71347d9dec2ae3c14d09e56e02cc92f07a..455b375e96e13a7c0f790514e8169fe75ab39490 100755 (executable)
--- a/dgit
+++ b/dgit
@@ -71,6 +71,7 @@ our $suite_re = '[-+.0-9a-z]+';
 our $cleanmode_re = 'dpkg-source(?:-d)?|git|git-ff|check|none';
 
 our $git_authline_re = '^([^<>]+) \<(\S+)\> (\d+ [-+]\d+)$';
 our $cleanmode_re = 'dpkg-source(?:-d)?|git|git-ff|check|none';
 
 our $git_authline_re = '^([^<>]+) \<(\S+)\> (\d+ [-+]\d+)$';
+our $splitbraincache = 'dgit-intern/quilt-cache';
 
 our (@git) = qw(git);
 our (@dget) = qw(dget);
 
 our (@git) = qw(git);
 our (@dget) = qw(dget);
@@ -85,7 +86,7 @@ our (@dpkgbuildpackage) = qw(dpkg-buildpackage -i\.git/ -I.git);
 our (@dpkgsource) = qw(dpkg-source -i\.git/ -I.git);
 our (@dpkggenchanges) = qw(dpkg-genchanges);
 our (@mergechanges) = qw(mergechanges -f);
 our (@dpkgsource) = qw(dpkg-source -i\.git/ -I.git);
 our (@dpkggenchanges) = qw(dpkg-genchanges);
 our (@mergechanges) = qw(mergechanges -f);
-our (@gbppq) = qw(gbp-pq);
+our (@gbp) = qw(gbp);
 our (@changesopts) = ('');
 
 our %opts_opt_map = ('dget' => \@dget, # accept for compatibility
 our (@changesopts) = ('');
 
 our %opts_opt_map = ('dget' => \@dget, # accept for compatibility
@@ -199,6 +200,10 @@ sub deliberately_not_fast_forward () {
     }
 }
 
     }
 }
 
+sub quiltmode_splitbrain () {
+    $quilt_mode =~ m/gbp|dpm|unapplied/;
+}
+
 #---------- remote protocol support, common ----------
 
 # remote push initiator/responder protocol:
 #---------- remote protocol support, common ----------
 
 # remote push initiator/responder protocol:
@@ -1983,10 +1988,14 @@ END
 
     my $format = getfield $dsc, 'Format';
     printdebug "format $format\n";
 
     my $format = getfield $dsc, 'Format';
     printdebug "format $format\n";
+
     if (madformat($format)) {
        # user might have not used dgit build, so maybe do this now:
        commit_quilty_patch();
     }
     if (madformat($format)) {
        # user might have not used dgit build, so maybe do this now:
        commit_quilty_patch();
     }
+
+    die 'xxx fast forward (should not depend on quilt mode, but will always be needed if we did $split_brain)' if $split_brain;
+
     check_not_dirty();
     changedir $ud;
     progress "checking that $dscfn corresponds to HEAD";
     check_not_dirty();
     changedir $ud;
     progress "checking that $dscfn corresponds to HEAD";
@@ -2515,12 +2524,14 @@ END
     }
 }
 
     }
 }
 
-sub quiltify_trees_differ ($$;$) {
-    my ($x,$y,$finegrained) = @_;
+sub quiltify_trees_differ ($$;$$) {
+    my ($x,$y,$finegrained,$ignorenamesr) = @_;
     # returns true iff the two tree objects differ other than in debian/
     # with $finegrained,
     # returns bitmask 01 - differ in upstream files except .gitignore
     #                 02 - differ in .gitignore
     # returns true iff the two tree objects differ other than in debian/
     # with $finegrained,
     # returns bitmask 01 - differ in upstream files except .gitignore
     #                 02 - differ in .gitignore
+    # if $ignorenamesr is defined, $ingorenamesr->{$fn}
+    #  is set for each modified .gitignore filename $fn
     local $/=undef;
     my @cmd = (@git, qw(diff-tree --name-only -z));
     push @cmd, qw(-r) if $finegrained;
     local $/=undef;
     my @cmd = (@git, qw(diff-tree --name-only -z));
     push @cmd, qw(-r) if $finegrained;
@@ -2529,7 +2540,9 @@ sub quiltify_trees_differ ($$;$) {
     my $r = 0;
     foreach my $f (split /\0/, $diffs) {
        next if $f =~ m#^debian(?:/.*)?$#s;
     my $r = 0;
     foreach my $f (split /\0/, $diffs) {
        next if $f =~ m#^debian(?:/.*)?$#s;
-       $r |= ($f =~ m#^(?:.*/)?.gitignore$#s) ? 02 : 01;
+       my $isignore = $f =~ m#^(?:.*/)?.gitignore$#s;
+       $r |= $isignore ? 02 : 01;
+       $ignorenamesr->{$f}=1 if $ignorenamesr && $isignore;
     }
     printdebug "quiltify_trees_differ $x $y => $r\n";
     return $r;
     }
     printdebug "quiltify_trees_differ $x $y => $r\n";
     return $r;
@@ -2552,8 +2565,9 @@ sub quiltify_splitbrain_needed () {
     }
 }
 
     }
 }
 
-sub quiltify_splitbrain ($$) {
-    my ($clogp, $diffbits) = @_;
+sub quiltify_splitbrain ($$$$$$) {
+    my ($clogp, $unapplied, $headref, $diffbits,
+       $editedignores, $cachekey) = @_;
     if ($quilt_mode !~ m/gbp|dpm/) {
        # treat .gitignore just like any other upstream file
        $diffbits = { %$diffbits };
     if ($quilt_mode !~ m/gbp|dpm/) {
        # treat .gitignore just like any other upstream file
        $diffbits = { %$diffbits };
@@ -2564,18 +2578,33 @@ sub quiltify_splitbrain ($$) {
     local $ENV{GIT_COMMITTER_NAME} =  $authline[0];
     local $ENV{GIT_COMMITTER_EMAIL} = $authline[1];
     local $ENV{GIT_COMMITTER_DATE} =  $authline[2];
     local $ENV{GIT_COMMITTER_NAME} =  $authline[0];
     local $ENV{GIT_COMMITTER_EMAIL} = $authline[1];
     local $ENV{GIT_COMMITTER_DATE} =  $authline[2];
+       
+    if ($quilt_mode =~ m/gbp|unapplied/ &&
+       ($diffbits->{H2O} & 01)) {
+       my $msg =
+ "--quilt=$quilt_mode specified, implying patches-unapplied git tree\n".
+ " but git tree differs from orig in upstream files.";
+       if (!stat_exists "debian/patches") {
+           $msg .=
+ "\n ... debian/patches is missing; perhaps this is a patch queue branch?";
+       }  
+       fail $msg;
+    }
     if ($quilt_mode =~ m/gbp|unapplied/ &&
     if ($quilt_mode =~ m/gbp|unapplied/ &&
-       ($diffbits->{O2A} & 01) && # some patches
-       !($diffbits->{H2O} & 01)) { # but HEAD is like orig
+       ($diffbits->{O2A} & 01)) { # some patches
        quiltify_splitbrain_needed();
        quiltify_splitbrain_needed();
-       progress "creating patches-applied version using gbp-pq";
+       progress "creating patches-applied version using gbp pq";
        open STDOUT, ">/dev/null" or die $!;
        open STDOUT, ">/dev/null" or die $!;
-       runcmd shell_cmd 'exec >/dev/null', @gbppq, qw(import);
+       runcmd shell_cmd 'exec >/dev/null', @gbp, qw(pq import);
+       # gbp pq import creates a fresh branch; push back to dgit-view
+       runcmd @git, qw(update-ref refs/heads/dgit-view HEAD);
+       runcmd @git, qw(checkout -q dgit-view);
     }
     if (($diffbits->{H2O} & 02) && # user has modified .gitignore
        !($diffbits->{O2A} & 02)) { # patches do not change .gitignore
        quiltify_splitbrain_needed();
        progress "creating patch to represent .gitignore changes";
     }
     if (($diffbits->{H2O} & 02) && # user has modified .gitignore
        !($diffbits->{O2A} & 02)) { # patches do not change .gitignore
        quiltify_splitbrain_needed();
        progress "creating patch to represent .gitignore changes";
+        ensuredir "debian/patches";
        my $gipatch = "debian/patches/auto-gitignore";
        open GIPATCH, ">>", "$gipatch" or die "$gipatch: $!";
        stat GIPATCH or die "$gipatch: $!";
        my $gipatch = "debian/patches/auto-gitignore";
        open GIPATCH, ">>", "$gipatch" or die "$gipatch: $!";
        stat GIPATCH or die "$gipatch: $!";
@@ -2588,13 +2617,33 @@ The Debian packaging git branch contains these updates to the upstream
 .gitignore file(s).  This patch is autogenerated, to provide these
 updates to users of the official Debian archive view of the package.
 
 .gitignore file(s).  This patch is autogenerated, to provide these
 updates to users of the official Debian archive view of the package.
 
+[dgit version $our_version]
 ---
 END
 ---
 END
-    die 'xxx gitignore';
-       
-    }
-    die 'xxx memoisation via git-reflog';
-    die 'xxx fast forward (should not depend on quilt mode, but will always be needed if we did $split_brain)';
+        close GIPATCH or die "$gipatch: $!";
+        runcmd shell_cmd "exec >>$gipatch", @git, qw(diff),
+            $unapplied, $headref, "--", sort keys %$editedignores;
+        open SERIES, "+>>", "debian/patches/series" or die $!;
+        defined seek SERIES, -1, 2 or $!==EINVAL or die $!;
+        my $newline;
+        defined read SERIES, $newline, 1 or die $!;
+       print SERIES "\n" or die $! unless $newline eq "\n";
+       print SERIES "auto-gitignore\n" or die $!;
+       close SERIES or die  $!;
+        runcmd @git, qw(add -- debian/patches/series), $gipatch;
+        commit_admin "Commit patch to update .gitignore";
+    }
+
+    my $dgitview = git_rev_parse 'refs/heads/dgit-view';
+
+    changedir '../../../..';
+    ensuredir ".git/logs/refs/dgit-intern";
+    my $makelogfh = new IO::File ".git/logs/refs/$splitbraincache", '>>'
+      or die $!;
+    runcmd @git, qw(update-ref -m), $cachekey, "refs/$splitbraincache",
+       $dgitview;
+
+    changedir '.git/dgit/unpack/work';
 }
 
 sub quiltify ($$$$) {
 }
 
 sub quiltify ($$$$) {
@@ -2830,6 +2879,8 @@ sub build_maybe_quilt_fixup () {
        quilt_fixup_multipatch($clogp, $headref, $upstreamversion);
     }
 
        quilt_fixup_multipatch($clogp, $headref, $upstreamversion);
     }
 
+    die 'bug' if $split_brain && !$need_split_build_invocation;
+
     changedir '../../../..';
     runcmd_ordryrun_local
         @git, qw(pull --ff-only -q .git/dgit/unpack/work master);
     changedir '../../../..';
     runcmd_ordryrun_local
         @git, qw(pull --ff-only -q .git/dgit/unpack/work master);
@@ -3006,6 +3057,64 @@ END
     $dscaddfile->($debtar);
     close $fakedsc or die $!;
 
     $dscaddfile->($debtar);
     close $fakedsc or die $!;
 
+    my $splitbrain_cachekey;
+    if (quiltmode_splitbrain()) {
+       # we look in the reflog of dgit-intern/quilt-cache
+       # we look for an entry whose message is the key for the cache lookup
+       my @cachekey = (qw(dgit), $our_version);
+       push @cachekey, $upstreamversion;
+       push @cachekey, $quilt_mode;
+       push @cachekey, $headref;
+
+       push @cachekey, hashfile('fake.dsc');
+
+       my $srcshash = Digest::SHA->new(256);
+       my %sfs = ( %INC, '$0(dgit)' => $0 );
+       foreach my $sfk (sort keys %sfs) {
+           $srcshash->add($sfk,"  ");
+           $srcshash->add(hashfile($sfs{$sfk}));
+           $srcshash->add("\n");
+       }
+       push @cachekey, $srcshash->hexdigest();
+       $splitbrain_cachekey = "@cachekey";
+
+       my @cmd = (@git, qw(reflog), '--pretty=format:%H %gs',
+                  $splitbraincache);
+       printdebug "splitbrain cachekey $splitbrain_cachekey\n";
+       debugcmd "|(probably)",@cmd;
+       my $child = open GC, "-|";  defined $child or die $!;
+       if (!$child) {
+           chdir '../../..' or die $!;
+           if (!stat ".git/logs/refs/$splitbraincache") {
+               $! == ENOENT or die $!;
+               printdebug ">(no reflog)\n";
+               exit 0;
+           }
+           exec @cmd; die $!;
+       }
+       while (<GC>) {
+           chomp;
+           printdebug ">| ", $_, "\n" if $debuglevel > 1;
+           next unless m/^(\w+) (\S.*\S)$/ && $2 eq $splitbrain_cachekey;
+           
+           my $cachehit = $1;
+           quilt_fixup_mkwork($headref);
+           if ($cachehit ne $headref) {
+               progress "quilt fixup ($quilt_mode mode) found cached tree";
+               runcmd @git, qw(checkout -q -b dgit-view), $cachehit;
+               $split_brain = 1;
+               return;
+           }
+           progress "quilt fixup ($quilt_mode mode)".
+             " found cached indication that no changes needed";
+           return;
+       }
+       die $! if GC->error;
+       failedcmd unless close GC;
+
+       printdebug "splitbrain cache miss\n";
+    }
+
     runcmd qw(sh -ec),
         'exec dpkg-source --no-check --skip-patches -x fake.dsc >/dev/null';
 
     runcmd qw(sh -ec),
         'exec dpkg-source --no-check --skip-patches -x fake.dsc >/dev/null';
 
@@ -3052,11 +3161,12 @@ END
     # We calculate some guesswork now about what kind of tree this might
     # be.  This is mostly for error reporting.
 
     # We calculate some guesswork now about what kind of tree this might
     # be.  This is mostly for error reporting.
 
+    my %editedignores;
     my $diffbits = {
         # H = user's HEAD
         # O = orig, without patches applied
         # A = "applied", ie orig with H's debian/patches applied
     my $diffbits = {
         # H = user's HEAD
         # O = orig, without patches applied
         # A = "applied", ie orig with H's debian/patches applied
-        H2O => quiltify_trees_differ($headref,  $unapplied, 1),
+        H2O => quiltify_trees_differ($headref,  $unapplied, 1,\%editedignores),
         H2A => quiltify_trees_differ($headref,  $oldtiptree,1),
         O2A => quiltify_trees_differ($unapplied,$oldtiptree,1),
     };
         H2A => quiltify_trees_differ($headref,  $oldtiptree,1),
         O2A => quiltify_trees_differ($unapplied,$oldtiptree,1),
     };
@@ -3084,8 +3194,10 @@ END
     push @failsuggestion, "Maybe you need to specify one of".
         " --quilt=gbp --quilt=dpm --quilt=unapplied ?";
 
     push @failsuggestion, "Maybe you need to specify one of".
         " --quilt=gbp --quilt=dpm --quilt=unapplied ?";
 
-    if ($quilt_mode =~ m/gbp|dpm|unapplied/) {
-       quiltify_splitbrain($clogp, $diffbits);
+    if (quiltmode_splitbrain()) {
+       quiltify_splitbrain($clogp, $unapplied, $headref,
+                            $diffbits, \%editedignores,
+                           $splitbrain_cachekey);
        return;
     }
 
        return;
     }
 
@@ -3125,10 +3237,14 @@ sub quilt_fixup_editor () {
 
 #----- other building -----
 
 
 #----- other building -----
 
-our $suppress_clean;
+our $clean_using_builder;
+# ^ tree is to be cleaned by dpkg-source's builtin idea that it should
+#   clean the tree before building (perhaps invoked indirectly by
+#   whatever we are using to run the build), rather than separately
+#   and explicitly by us.
 
 sub clean_tree () {
 
 sub clean_tree () {
-    return if $suppress_clean;
+    return if $clean_using_builder;
     if ($cleanmode eq 'dpkg-source') {
        runcmd_ordryrun_local @dpkgbuildpackage, qw(-T clean);
     } elsif ($cleanmode eq 'dpkg-source-d') {
     if ($cleanmode eq 'dpkg-source') {
        runcmd_ordryrun_local @dpkgbuildpackage, qw(-T clean);
     } elsif ($cleanmode eq 'dpkg-source-d') {
@@ -3233,7 +3349,7 @@ sub massage_dbp_args ($;$) {
     debugcmd '#massaging#', @$cmd if $debuglevel>1;
 #print STDERR "MASS0 ",Dumper($cmd, $xargs, $need_split_build_invocation);
     if ($cleanmode eq 'dpkg-source' && !$need_split_build_invocation) {
     debugcmd '#massaging#', @$cmd if $debuglevel>1;
 #print STDERR "MASS0 ",Dumper($cmd, $xargs, $need_split_build_invocation);
     if ($cleanmode eq 'dpkg-source' && !$need_split_build_invocation) {
-       $suppress_clean = 1;
+       $clean_using_builder = 1;
        return 0;
     }
     # -nc has the side effect of specifying -b if nothing else specified
        return 0;
     }
     # -nc has the side effect of specifying -b if nothing else specified
@@ -3252,11 +3368,13 @@ sub massage_dbp_args ($;$) {
 #print STDERR "MASS1 ",Dumper($cmd, $xargs, $dmode);
     my $r = 0;
     if ($need_split_build_invocation) {
 #print STDERR "MASS1 ",Dumper($cmd, $xargs, $dmode);
     my $r = 0;
     if ($need_split_build_invocation) {
+       printdebug "massage split $dmode.\n";
        $r = $dmode =~ m/[S]/     ? +2 :
             $dmode =~ y/gGF/ABb/ ? +1 :
             $dmode =~ m/[ABb]/   ?  0 :
             die "$dmode ?";
     }
        $r = $dmode =~ m/[S]/     ? +2 :
             $dmode =~ y/gGF/ABb/ ? +1 :
             $dmode =~ m/[ABb]/   ?  0 :
             die "$dmode ?";
     }
+    printdebug "massage done $r $dmode.\n";
     push @$cmd, $dmode;
 #print STDERR "MASS2 ",Dumper($cmd, $xargs, $r);
     return $r;
     push @$cmd, $dmode;
 #print STDERR "MASS2 ",Dumper($cmd, $xargs, $r);
     return $r;
@@ -3293,7 +3411,7 @@ sub cmd_gbp_build {
     if ($wantsrc > 0) {
        build_source();
     } else {
     if ($wantsrc > 0) {
        build_source();
     } else {
-       if (!$suppress_clean) {
+       if (!$clean_using_builder) {
            push @cmd, '--git-cleaner=true';
        }
        build_prep();
            push @cmd, '--git-cleaner=true';
        }
        build_prep();
@@ -3311,9 +3429,18 @@ sub cmd_gbp_build {
 sub cmd_git_build { cmd_gbp_build(); } # compatibility with <= 1.0
 
 sub build_source {
 sub cmd_git_build { cmd_gbp_build(); } # compatibility with <= 1.0
 
 sub build_source {
-    if ($cleanmode =~ m/^dpkg-source/) {
-       # dpkg-source will clean, so we shouldn't
-       $suppress_clean = 1;
+    my $our_cleanmode = $cleanmode;
+    if ($need_split_build_invocation) {
+       # Pretend that clean is being done some other way.  This
+       # forces us not to try to use dpkg-buildpackage to clean and
+       # build source all in one go; and instead we run dpkg-source
+       # (and build_prep() will do the clean since $clean_using_builder
+       # is false).
+       $our_cleanmode = 'ELSEWHERE';
+    }
+    if ($our_cleanmode =~ m/^dpkg-source/) {
+       # dpkg-source invocation (below) will clean, so build_prep shouldn't
+       $clean_using_builder = 1;
     }
     build_prep();
     $sourcechanges = changespat $version,'source';
     }
     build_prep();
     $sourcechanges = changespat $version,'source';
@@ -3322,18 +3449,36 @@ sub build_source {
            or fail "remove $sourcechanges: $!";
     }
     $dscfn = dscfn($version);
            or fail "remove $sourcechanges: $!";
     }
     $dscfn = dscfn($version);
-    if ($cleanmode eq 'dpkg-source') {
+    if ($our_cleanmode eq 'dpkg-source') {
        runcmd_ordryrun_local @dpkgbuildpackage, qw(-us -uc -S),
        runcmd_ordryrun_local @dpkgbuildpackage, qw(-us -uc -S),
-                              changesopts();
-    } elsif ($cleanmode eq 'dpkg-source-d') {
+           changesopts();
+    } elsif ($our_cleanmode eq 'dpkg-source-d') {
        runcmd_ordryrun_local @dpkgbuildpackage, qw(-us -uc -S -d),
        runcmd_ordryrun_local @dpkgbuildpackage, qw(-us -uc -S -d),
-                              changesopts();
+           changesopts();
     } else {
     } else {
-       my $pwd = must_getcwd();
-       my $leafdir = basename $pwd;
-       changedir "..";
-       runcmd_ordryrun_local @dpkgsource, qw(-b --), $leafdir;
-       changedir $pwd;
+       my @cmd = (@dpkgsource, qw(-b --));
+       if ($split_brain) {
+           changedir $ud;
+           runcmd_ordryrun_local @cmd, "work";
+           my @udfiles = <${package}_*>;
+           changedir "../../..";
+           foreach my $f (@udfiles) {
+               printdebug "source copy, found $f\n";
+               next unless
+                   $f eq $dscfn or
+                   ($f =~ m/\.debian\.tar(?:\.\w+)$/ &&
+                    $f eq srcfn($version, $&));
+               printdebug "source copy, found $f - renaming\n";
+               rename "$ud/$f", "../$f" or $!==ENOENT
+                   or fail "put in place new source file ($f): $!";
+           }
+       } else {
+           my $pwd = must_getcwd();
+           my $leafdir = basename $pwd;
+           changedir "..";
+           runcmd_ordryrun_local @cmd, $leafdir;
+           changedir $pwd;
+       }
        runcmd_ordryrun_local qw(sh -ec),
            'exec >$1; shift; exec "$@"','x',
            "../$sourcechanges",
        runcmd_ordryrun_local qw(sh -ec),
            'exec >$1; shift; exec "$@"','x',
            "../$sourcechanges",
@@ -3674,6 +3819,8 @@ if (!defined $quilt_mode) {
     $quilt_mode = $1;
 }
 
     $quilt_mode = $1;
 }
 
+$need_split_build_invocation ||= quiltmode_splitbrain();
+
 if (!defined $cleanmode) {
     local $access_forpush;
     $cleanmode = access_cfg('clean-mode', 'RETURN-UNDEF');
 if (!defined $cleanmode) {
     local $access_forpush;
     $cleanmode = access_cfg('clean-mode', 'RETURN-UNDEF');