chiark / gitweb /
Ovewrite: Honour $overwrite_version in non-split-brain mode
[dgit.git] / dgit
diff --git a/dgit b/dgit
index f60e15fcd9666ca4d70f65af07829aca108256f4..0e99eb5dbb197ce146cffa5b02853a89b0e3964c 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;
@@ -533,6 +534,11 @@ our %defcfg = ('dgit.default.distro' => 'debian',
               'dgit.default.archive-query' => 'madison:',
               'dgit.default.sshpsql-dbname' => 'service=projectb',
               'dgit.default.dgit-tag-format' => 'old,new,maint',
+              # old means "repo server accepts pushes with old dgit tags"
+              # new means "repo server accepts pushes with new dgit tags"
+              # maint means "repo server accepts split brain pushes"
+              # hist means "repo server may have old pushes without new tag"
+              #   ("hist" is implied by "old")
               'dgit-distro.debian.archive-query' => 'ftpmasterapi:',
               'dgit-distro.debian.git-check' => 'url',
               'dgit-distro.debian.git-check-suffix' => '/info/refs',
@@ -879,6 +885,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: $!";
@@ -1180,7 +1199,7 @@ sub select_tagformat () {
     die 'bug' if $tagformatfn && $tagformat_want;
     # ... $tagformat_want assigned after previous select_tagformat
 
-    my (@supported) = grep { $_ ne 'maint' } access_cfg_tagformats();
+    my (@supported) = grep { $_ =~ m/^(?:old|new)$/ } access_cfg_tagformats();
     printdebug "select_tagformat supported @supported\n";
 
     $tagformat_want //= [ $supported[0], "distro access configuration", 0 ];
@@ -1326,6 +1345,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 +1570,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 +1649,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 +1808,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 ($) {
@@ -1885,7 +1901,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}
@@ -2122,10 +2138,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) {
@@ -2348,6 +2361,170 @@ sub madformat ($) {
     return 1;
 }
 
+# An "infopair" is a tuple [ $thing, $what ]
+# (often $thing is a commit hash; $what is a description)
+
+sub infopair_cond_equal ($$) {
+    my ($x,$y) = @_;
+    $x->[0] eq $y->[0] or fail <<END;
+$x->[1] ($x->[0]) not equal to $y->[1] ($y->[0])
+END
+};
+
+sub infopair_lrf_tag_lookup ($$) {
+    my ($tagnames, $what) = @_;
+    # $tagname may be an array ref
+    my @tagnames = ref $tagnames ? @$tagnames : ($tagnames);
+    printdebug "infopair_lrfetchref_tag_lookup $what @tagnames\n";
+    foreach my $tagname (@tagnames) {
+       my $lrefname = lrfetchrefs."/tags/$tagname";
+       my $tagobj = $lrfetchrefs_f{$lrefname};
+       next unless defined $tagobj;
+       printdebug "infopair_lrfetchref_tag_lookup $tagobj $tagname $what\n";
+       return [ git_rev_parse($tagobj), $what ];
+    }
+    fail @tagnames==1 ? <<END : <<END;
+Wanted tag $what (@tagnames) on dgit server, but not found
+END
+Wanted tag $what (one of: @tagnames) on dgit server, but not found
+END
+}
+
+sub infopair_cond_ff ($$) {
+    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
+};
+
+sub pseudomerge_version_check ($$) {
+    my ($clogp, $archive_hash) = @_;
+
+    my $arch_clogp = commit_getclogp $archive_hash;
+    my $i_arch_v = [ (getfield $arch_clogp, 'Version'),
+                    'version currently in archive' ];
+    if (defined $overwrite_version) {
+       infopair_cond_equal([ $overwrite_version, '--overwrite= version' ],
+                           $i_arch_v);
+    }
+    
+    printdebug "pseudomerge_version_check i_arch_v @$i_arch_v\n";
+    return $i_arch_v;
+}
+
+sub pseudomerge_make_commit ($$$$$) {
+    my ($clogp, $dgitview, $archive_hash, $i_arch_v, $msg) = @_;
+    progress "Declaring that HEAD inciudes all changes in $i_arch_v->[0]...";
+
+    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, $msg or die $!;
+tree $tree
+parent $dgitview
+parent $archive_hash
+author $authline
+commiter $authline
+
+END
+    close MC or die $!;
+
+    return make_commit($pmf);
+}
+
+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'
+    #
+
+    printdebug "splitbrain_pseudomerge...\n";
+
+    my $i_arch_v = pseudomerge_version_check($clogp, $archive_hash);
+
+    return $dgitview unless defined $archive_hash;
+
+    if (!defined $overwrite_version) {
+       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 = infopair_lrf_tag_lookup($t_dep14, "maintainer view tag");
+    my $t_dgit = debiantag_new $i_arch_v->[0], access_basedistro;
+    my $i_dgit = infopair_lrf_tag_lookup($t_dgit, "dgit view tag");
+    my $i_archive = [ $archive_hash, "current archive contents" ];
+
+    printdebug "splitbrain_pseudomerge i_archive @$i_archive\n";
+
+    infopair_cond_equal($i_dgit, $i_archive);
+    infopair_cond_ff($i_dep14, $i_dgit);
+    $overwrite_version // infopair_cond_ff($i_dep14, [ $maintview, 'HEAD' ]);
+
+    my $r = pseudomerge_make_commit
+       $clogp, $dgitview, $archive_hash, $i_arch_v,
+       (defined $overwrite_version ? <<END_OVERWR : <<END_MAKEFF);
+Declare fast forward from $overwrite_version
+
+[dgit --quilt=$quilt_mode --overwrite-version=$overwrite_version]
+END_OVERWR
+Make fast forward from $i_arch_v->[0]
+
+[dgit --quilt=$quilt_mode]
+END_MAKEFF
+
+    progress "Made pseudo-merge of $i_arch_v->[0] into dgit view.";
+    return $r;
+}      
+
+sub plain_overwrite_pseudomerge ($$$) {
+    my ($clogp, $head, $archive_hash) = @_;
+
+    printdebug "plain_overwrite_pseudomerge...";
+
+    my $i_arch_v = pseudomerge_version_check($clogp, $archive_hash);
+
+    my @tagformats = access_cfg_tagformats();
+    my @t_overwr =
+       map { $_->($overwrite_version, access_basedistro) }
+       (grep { m/^(?:old|hist)$/ } @tagformats)
+       ? \&debiantags : \&debiantag_new;
+    my $i_overwr = infopair_lrf_tag_lookup \@t_overwr, "previous version tag";
+    my $i_archive = [ $archive_hash, "current archive contents" ];
+
+    infopair_cond_equal($i_overwr, $i_archive);
+
+    return $head if is_fast_fwd $archive_hash, $head;
+
+    my $m = "Declare fast forward from $overwrite_version";
+
+    my $r = pseudomerge_make_commit
+       $clogp, $head, $archive_hash, $i_arch_v, <<END;
+$m
+
+[dgit --overwrite-version=$overwrite_version]
+END
+
+    runcmd @git, qw(update-ref -m), $m, 'HEAD', $r, $head;
+
+    progress "Make pseudo-merge of $i_arch_v->[0] into your HEAD.";
+    return $r;
+}
+
 sub push_parse_changelog ($) {
     my ($clogpfn) = @_;
 
@@ -2395,6 +2572,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;
 }
 
@@ -2563,7 +2741,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
@@ -2572,6 +2752,12 @@ END
        }
     }
 
+    if (defined $overwrite_version && !defined $maintviewhead) {
+       $dgithead = plain_overwrite_pseudomerge($clogp,
+                                               $dgithead,
+                                               $archive_hash);
+    }
+
     check_not_dirty();
 
     my $forceflag = '';
@@ -2630,7 +2816,7 @@ END
     responder_send_command("param head $dgithead");
     responder_send_command("param csuite $csuite");
     responder_send_command("param tagformat $tagformat");
-    if (quiltmode_splitbrain) {
+    if (defined $maintviewhead) {
        die unless ($protovsn//4) >= 4;
        responder_send_command("param maint-view $maintviewhead");
     }
@@ -2688,11 +2874,6 @@ END
 
     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();
-           # ^ $view is "dgit" or "maint" so this looks for "maint"
-           # in archive supported tagformats.
        push @pushrefs, $forceflag."refs/tags/$tw->{Tag}";
     }
 
@@ -3202,6 +3383,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();
@@ -3211,6 +3399,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();