chiark / gitweb /
dgit: Some extra error reports for wrong split brain mode
[dgit.git] / dgit
diff --git a/dgit b/dgit
index 5d1a95c532a19c0c49f6c9538e227c732cc02b76..77eae6b0dafedafb69b441a33c731c4478b571bf 100755 (executable)
--- a/dgit
+++ b/dgit
@@ -63,8 +63,9 @@ our $existing_package = 'dpkg';
 our $cleanmode;
 our $changes_since_version;
 our $rmchanges;
+our $overwrite_version;
 our $quilt_mode;
-our $quilt_modes_re = 'linear|smash|auto|nofix|nocheck|gbp|unapplied';
+our $quilt_modes_re = 'linear|smash|auto|nofix|nocheck|gbp|dpm|unapplied';
 our $we_are_responder;
 our $initiator_tempdir;
 our $patches_applied_dirtily = 00;
@@ -222,7 +223,7 @@ sub no_such_package () {
 sub changedir ($) {
     my ($newdir) = @_;
     printdebug "CD $newdir\n";
-    chdir $newdir or die "chdir: $newdir: $!";
+    chdir $newdir or confess "chdir: $newdir: $!";
 }
 
 sub deliberately ($) {
@@ -879,6 +880,19 @@ sub parsechangelog {
     return $c;
 }
 
+sub commit_getclogp ($) {
+    # Returns the parsed changelog hashref for a particular commit
+    my ($objid) = @_;
+    our %commit_getclogp_memo;
+    my $memo = $commit_getclogp_memo{$objid};
+    return $memo if $memo;
+    mkpath '.git/dgit';
+    my $mclog = ".git/dgit/clog-$objid";
+    runcmd shell_cmd "exec >$mclog", @git, qw(cat-file blob),
+       "$objid:debian/changelog";
+    $commit_getclogp_memo{$objid} = parsechangelog("-l$mclog");
+}
+
 sub must_getcwd () {
     my $d = getcwd();
     defined $d or fail "getcwd failed: $!";
@@ -1326,6 +1340,7 @@ sub prep_ud (;$) {
 
 sub mktree_in_ud_here () {
     runcmd qw(git init -q);
+    runcmd qw(git config gc.auto 0);
     rmtree('.git/objects');
     symlink '../../../../objects','.git/objects' or die $!;
 }
@@ -1550,10 +1565,7 @@ END
     my @output = ($rawimport_mergeinput);
     progress "synthesised git commit from .dsc $cversion";
     if ($lastpush_mergeinput) {
-       my $lastpush_hash = $lastpush_mergeinput->{Commit};
-       runcmd @git, qw(reset -q --hard), $lastpush_hash;
-       runcmd qw(sh -ec), 'dpkg-parsechangelog >>../changelogold.tmp';
-       my $oldclogp = parsecontrol('../changelogold.tmp','previous changelog');
+       my $oldclogp = mergeinfo_getclogp($lastpush_mergeinput);
        my $oversion = getfield $oldclogp, 'Version';
        my $vcmp =
            version_compare($oversion, $cversion);
@@ -1632,7 +1644,11 @@ sub git_fetch_us () {
     # deliberately-not-ff, in which case we must fetch everything.
 
     my @specs = deliberately_not_fast_forward ? qw(tags/*) :
-       map { "tags/$_" } debiantags('*',access_basedistro);
+       map { "tags/$_" }
+       (quiltmode_splitbrain
+        ? (map { $_->('*',access_basedistro) }
+           \&debiantag_new, \&debiantag_maintview)
+        : debiantags('*',access_basedistro));
     push @specs, server_branch($csuite);
     push @specs, qw(heads/*) if deliberately_not_fast_forward;
 
@@ -1787,14 +1803,9 @@ END
 }
 
 sub mergeinfo_getclogp ($) {
-    my ($mi) = @_;
     # Ensures thit $mi->{Clogp} exists and returns it
-    return $mi->{Clogp} if $mi->{Clogp};
-    my $mclog = ".git/dgit/clog-$mi->{Commit}";
-    mkpath '.git/dgit';
-    runcmd shell_cmd "exec >$mclog", @git, qw(cat-file blob),
-       "$mi->{Commit}:debian/changelog";
-    $mi->{Clogp} = parsechangelog("-l$mclog");
+    my ($mi) = @_;
+    $mi->{Clogp} = commit_getclogp($mi->{Commit});
 }
 
 sub mergeinfo_version ($) {
@@ -1802,8 +1813,11 @@ sub mergeinfo_version ($) {
 }
 
 sub fetch_from_archive () {
-    # ensures that lrref() is what is actually in the archive,
-    #  one way or another
+    # Ensures that lrref() is what is actually in the archive, one way
+    # or another, according to us - ie this client's
+    # appropritaely-updated archive view.  Also returns the commit id.
+    # If there is nothing in the archive, leaves lrref alone and
+    # returns undef.  git_fetch_us must have already been called.
     get_archive_dsc();
 
     if ($dsc) {
@@ -1882,7 +1896,7 @@ sub fetch_from_archive () {
     # Finally: we do not necessarily reify the public view (as
     # described above).  This is so that we do not end up stacking two
     # pseudo-merges.  So what we actually do is figure out the inputs
-    # to any public view psuedo-merge and put them in @mergeinputs.
+    # to any public view pseudo-merge and put them in @mergeinputs.
 
     my @mergeinputs;
     # $mergeinputs[]{Commit}
@@ -1921,10 +1935,10 @@ sub fetch_from_archive () {
     my $del_lrfetchrefs = sub {
        changedir $cwd;
        my $gur;
-       printdebug "del_lrfetchrefs\n";
+       printdebug "del_lrfetchrefs...\n";
        foreach my $fullrefname (sort keys %lrfetchrefs_d) {
            my $objid = $lrfetchrefs_d{$fullrefname};
-           printdebug "del_lrfetchrefs: $fullrefname=$objid.\n";
+           printdebug "del_lrfetchrefs: $objid $fullrefname\n";
            if (!$gur) {
                $gur ||= new IO::Handle;
                open $gur, "|-", qw(git update-ref --stdin) or die $!;
@@ -1997,7 +2011,7 @@ But we were not able to obtain any version from the archive or git.
 END
        }
        unshift @end, $del_lrfetchrefs;
-       return 0;
+       return undef;
     }
 
     if ($lastfetch_hash &&
@@ -2119,10 +2133,7 @@ END
     if (defined $skew_warning_vsn) {
        mkpath '.git/dgit';
        printdebug "SKEW CHECK WANT $skew_warning_vsn\n";
-       my $clogf = ".git/dgit/changelog.tmp";
-       runcmd shell_cmd "exec >$clogf",
-           @git, qw(cat-file blob), "$hash:debian/changelog";
-       my $gotclogp = parsechangelog("-l$clogf");
+       my $gotclogp = commit_getclogp($hash);
        my $got_vsn = getfield $gotclogp, 'Version';
        printdebug "SKEW CHECK GOT $got_vsn\n";
        if (version_compare($got_vsn, $skew_warning_vsn) < 0) {
@@ -2148,7 +2159,7 @@ END
     lrfetchref_used lrfetchref();
 
     unshift @end, $del_lrfetchrefs;
-    return 1;
+    return $hash;
 }
 
 sub set_local_git_config ($$) {
@@ -2345,6 +2356,114 @@ sub madformat ($) {
     return 1;
 }
 
+sub splitbrain_pseudomerge ($$$$) {
+    my ($clogp, $maintview, $dgitview, $archive_hash) = @_;
+    # => $merged_dgitview
+    printdebug "splitbrain_pseudomerge...\n";
+    #
+    #     We:      debian/PREVIOUS    HEAD($maintview)
+    # expect:          o ----------------- o
+    #                    \                   \
+    #                     o                   o
+    #                 a/d/PREVIOUS        $dgitview
+    #                $archive_hash              \
+    #  If so,                \                   \
+    #  we do:                 `------------------ o
+    #   this:                                   $dgitview'
+    #
+
+    # We work with tuples [ $thing, $what ]
+    # (often $thing is a commit hash; $what is a description)
+
+    my $tag_lookup = sub {
+       my ($tagname, $what) = @_;
+       printdebug "splitbrain_pseudomerge tag_lookup $what\n";
+       my $lrefname = lrfetchrefs."/tags/$tagname";
+       my $tagobj = $lrfetchrefs_f{$lrefname};
+       defined $tagobj or fail <<END;
+Wanted tag $tagname ($what) on dgit server, but not found
+END
+       printdebug "splitbrain_pseudomerge tag_lookup $tagobj $what\n";
+       return [ git_rev_parse($tagobj), $what ];
+    };
+
+    my $cond_equal = sub {
+       my ($x,$y) = @_;
+       $x->[0] eq $y->[0] or fail <<END;
+$x->[1] ($x->[0]) not equal to $y->[1] ($y->[0])
+END
+    };
+    my $cond_ff = sub {
+       my ($anc,$desc) = @_;
+       is_fast_fwd($anc->[0], $desc->[0]) or fail <<END;
+$anc->[1] ($anc->[0]) .. $desc->[1] ($desc->[0]) is not fast forward
+END
+    };
+
+    my $arch_clogp = commit_getclogp $archive_hash;
+    my $i_arch_v = [ (getfield $arch_clogp, 'Version'),
+                    'version currently in archive' ];
+    
+    printdebug "splitbrain_pseudomerge i_arch_v @$i_arch_v\n";
+
+    return $dgitview unless defined $archive_hash;
+
+    if ($overwrite_version) {
+       progress "Declaring that HEAD inciudes all changes in archive...";
+       progress "Checking that $overwrite_version does so...";
+       $cond_equal->([ $overwrite_version, '--overwrite= version' ],
+                     $i_arch_v);
+    } else {
+       progress "Checking that HEAD inciudes all changes in archive...";
+    }
+
+    return $dgitview if is_fast_fwd $archive_hash, $dgitview;
+
+    my $t_dep14 = debiantag_maintview $i_arch_v->[0], access_basedistro;
+    my $i_dep14 = $tag_lookup->($t_dep14, "maintainer view tag");
+    my $t_dgit = debiantag_new $i_arch_v->[0], access_basedistro;
+    my $i_dgit = $tag_lookup->($t_dgit, "dgit view tag");
+    my $i_archive = [ $archive_hash, "current archive contents" ];
+
+    printdebug "splitbrain_pseudomerge i_archive @$i_archive\n";
+
+    $cond_equal->($i_dgit, $i_archive);
+    $cond_ff->($i_dep14, $i_dgit);
+    $overwrite_version or $cond_ff->($i_dep14, [ $maintview, 'HEAD' ]);
+
+    my $tree = cmdoutput qw(git rev-parse), "${dgitview}:";
+    my $authline = clogp_authline $clogp;
+
+    mkpath '.git/dgit';
+    my $pmf = ".git/dgit/pseudomerge";
+    open MC, ">", $pmf or die "$pmf $!";
+    print MC <<END or die $!;
+tree $tree
+parent $dgitview
+parent $archive_hash
+author $authline
+commiter $authline
+
+END
+    if ($overwrite_version) {
+       print MC <<END;
+Declare fast forward from $overwrite_version
+
+[dgit --quilt=$quilt_mode --overwrite-version=$overwrite_version]
+END
+    } else {
+       print MC <<END;
+Make fast forward from $i_arch_v->[0]
+
+[dgit --quilt=$quilt_mode]
+END
+    }
+    close MC or die $!;
+
+    progress "Making pseudo-merge of $i_arch_v->[0] into dgit view.";
+    return make_commit($pmf);
+}      
+
 sub push_parse_changelog ($) {
     my ($clogpfn) = @_;
 
@@ -2392,6 +2511,7 @@ sub push_tagwants ($$$$) {
        $tw->{Tag} = $tw->{TagFn}($cversion, access_basedistro);
        $tw->{Tfn} = sub { $tfbase.$tw->{TfSuffix}.$_[0]; };
     }
+    printdebug 'push_tagwants: ', Dumper(\@_, \@tagwants);
     return @tagwants;
 }
 
@@ -2493,9 +2613,23 @@ sub sign_changes ($) {
     }
 }
 
-sub dopush ($) {
-    my ($forceflag) = @_;
+sub dopush () {
     printdebug "actually entering push\n";
+
+    supplementary_message(<<'END');
+Push failed, while checking state of the archive.
+You can retry the push, after fixing the problem, if you like.
+END
+    if (check_for_git()) {
+       git_fetch_us();
+    }
+    my $archive_hash = fetch_from_archive();
+    if (!$archive_hash) {
+       $new_package or
+           fail "package appears to be new in this suite;".
+               " if this is intentional, use --new";
+    }
+
     supplementary_message(<<'END');
 Push failed, while preparing your push.
 You can retry the push, after fixing the problem, if you like.
@@ -2546,7 +2680,9 @@ END
  "--quilt=$quilt_mode but no cached dgit view:
  perhaps tree changed since dgit build[-source] ?";
            $split_brain = 1;
-           $dgithead = $dgitview;
+           $dgithead = splitbrain_pseudomerge($clogp,
+                                              $actualhead, $dgitview,
+                                              $archive_hash);
            $maintviewhead = $actualhead;
            changedir '../../../..';
            prep_ud(); # so _only_subdir() works, below
@@ -2556,6 +2692,23 @@ END
     }
 
     check_not_dirty();
+
+    my $forceflag = '';
+    if ($archive_hash) {
+       if (is_fast_fwd($archive_hash, $dgithead)) {
+           # ok
+       } elsif (deliberately_not_fast_forward) {
+           $forceflag = '+';
+       } else {
+           fail "dgit push: HEAD is not a descendant".
+               " of the archive's version.\n".
+               "dgit: To overwrite its contents,".
+               " use git merge -s ours ".lrref().".\n".
+               "dgit: To rewind history, if permitted by the archive,".
+               " use --deliberately-not-fast-forward";
+       }
+    }
+
     changedir $ud;
     progress "checking that $dscfn corresponds to HEAD";
     runcmd qw(dpkg-source -x --),
@@ -2590,6 +2743,8 @@ END
        $changesfile = "$buildproductsdir/$changesfile";
     }
 
+    # Checks complete, we're going to try and go ahead:
+
     responder_send_file('changes',$changesfile);
     responder_send_command("param head $dgithead");
     responder_send_command("param csuite $csuite");
@@ -2650,11 +2805,8 @@ END
        create_remote_git_repo();
     }
 
-    my @pushrefs = $forceflag."HEAD:".rrref();
+    my @pushrefs = $forceflag.$dgithead.":".rrref();
     foreach my $tw (@tagwants) {
-       my $view = $tw->{View};
-       next unless $view eq 'dgit'
-           or any { $_ eq $view } access_cfg_tagformats();
        push @pushrefs, $forceflag."refs/tags/$tw->{Tag}";
     }
 
@@ -2811,33 +2963,7 @@ sub cmd_push {
            fail "dgit push: changelog specifies $isuite ($csuite)".
                " but command line specifies $specsuite";
     }
-    supplementary_message(<<'END');
-Push failed, while checking state of the archive.
-You can retry the push, after fixing the problem, if you like.
-END
-    if (check_for_git()) {
-       git_fetch_us();
-    }
-    my $forceflag = '';
-    if (fetch_from_archive()) {
-       if (is_fast_fwd(lrref(), 'HEAD')) {
-           # ok
-       } elsif (deliberately_not_fast_forward) {
-           $forceflag = '+';
-       } else {
-           fail "dgit push: HEAD is not a descendant".
-               " of the archive's version.\n".
-               "dgit: To overwrite its contents,".
-               " use git merge -s ours ".lrref().".\n".
-               "dgit: To rewind history, if permitted by the archive,".
-               " use --deliberately-not-fast-forward";
-       }
-    } else {
-       $new_package or
-           fail "package appears to be new in this suite;".
-               " if this is intentional, use --new";
-    }
-    dopush($forceflag);
+    dopush();
 }
 
 #---------- remote commands' implementation ----------
@@ -3190,6 +3316,13 @@ sub quiltify_splitbrain ($$$$$$) {
        }  
        fail $msg;
     }
+    if ($quilt_mode =~ m/dpm/ &&
+       ($diffbits->{H2A} & 01)) {
+       fail <<END;
+--quilt=$quilt_mode specified, implying patches-applied git tree
+ but git tree differs from result of applying debian/patches to upstream
+END
+    }
     if ($quilt_mode =~ m/gbp|unapplied/ &&
        ($diffbits->{O2A} & 01)) { # some patches
        quiltify_splitbrain_needed();
@@ -3199,6 +3332,14 @@ sub quiltify_splitbrain ($$$$$$) {
        runcmd @git, qw(update-ref refs/heads/dgit-view HEAD);
        runcmd @git, qw(checkout -q dgit-view);
     }
+    if ($quilt_mode =~ m/gbp|dpm/ &&
+       ($diffbits->{O2A} & 02)) {
+       fail <<END
+--quilt=$quilt_mode specified, implying that HEAD is for use with a
+ tool which does not create patches for changes to upstream
+ .gitignores: but, such patches exist in debian/patches.
+END
+    }
     if (($diffbits->{H2O} & 02) && # user has modified .gitignore
        !($diffbits->{O2A} & 02)) { # patches do not change .gitignore
        quiltify_splitbrain_needed();
@@ -3882,6 +4023,7 @@ sub maybe_unapply_patches_again () {
        if $patches_applied_dirtily & 01;
     rmtree '.pc'
        if $patches_applied_dirtily & 02;
+    $patches_applied_dirtily = 0;
 }
 
 #----- other building -----
@@ -4070,16 +4212,15 @@ sub cmd_gbp_build {
        }
        build_prep();
     }
+    maybe_unapply_patches_again();
     if ($wantsrc < 2) {
        unless (grep { m/^--git-debian-branch|^--git-ignore-branch/ } @ARGV) {
            canonicalise_suite();
            push @cmd, "--git-debian-branch=".lbranch();
        }
        push @cmd, changesopts();
-       maybe_apply_patches_dirtily();
        runcmd_ordryrun_local @cmd, @ARGV;
     }
-    maybe_unapply_patches_again();
     printdone "build successful\n";
 }
 sub cmd_git_build { cmd_gbp_build(); } # compatibility with <= 1.0
@@ -4162,6 +4303,7 @@ sub cmd_sbuild {
            " building would result in ambiguity about the intended results"
            if @unwanted;
     }
+    my $wasdir = must_getcwd();
     changedir "..";
     if (act_local()) {
        stat_exists $dscfn or fail "$dscfn (in parent directory): $!";
@@ -4190,6 +4332,7 @@ sub cmd_sbuild {
            rename "$cf", "$cf.inmulti" or fail "$cf\{,.inmulti}: $!";
        }
     }
+    changedir $wasdir;
     maybe_unapply_patches_again();
     printdone "build successful, results in $multichanges\n" or die $!;
 }