X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=dgit.git;a=blobdiff_plain;f=dgit;h=0e99eb5dbb197ce146cffa5b02853a89b0e3964c;hp=95398497b7c4d9ba77208da8297b33b5c949a851;hb=fbf96e6c1449973cf4a8375fa0b0fca40a2e3fa9;hpb=fe2c39524a6c74aed1f21f0e3eaafc332c46843c diff --git a/dgit b/dgit index 95398497..0e99eb5d 100755 --- 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 ($) { @@ -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 ($) { @@ -1802,8 +1818,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 +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} @@ -1921,10 +1940,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 +2016,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 +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) { @@ -2148,7 +2164,7 @@ END lrfetchref_used lrfetchref(); unshift @end, $del_lrfetchrefs; - return 1; + return $hash; } sub set_local_git_config ($$) { @@ -2345,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 <[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 ? <[0], $desc->[0]) or fail <[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 < $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 ? <[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, <[0] into your HEAD."; + return $r; +} + sub push_parse_changelog ($) { my ($clogpfn) = @_; @@ -2392,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; } @@ -2493,9 +2674,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 +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 @@ -2555,7 +2752,30 @@ END } } + if (defined $overwrite_version && !defined $maintviewhead) { + $dgithead = plain_overwrite_pseudomerge($clogp, + $dgithead, + $archive_hash); + } + 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,11 +2810,13 @@ 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"); responder_send_command("param tagformat $tagformat"); - if (quiltmode_splitbrain) { + if (defined $maintviewhead) { die unless ($protovsn//4) >= 4; responder_send_command("param maint-view $maintviewhead"); } @@ -2652,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}"; } @@ -2813,33 +3030,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 ---------- @@ -3192,6 +3383,13 @@ sub quiltify_splitbrain ($$$$$$) { } fail $msg; } + if ($quilt_mode =~ m/dpm/ && + ($diffbits->{H2A} & 01)) { + fail <{O2A} & 01)) { # some patches quiltify_splitbrain_needed(); @@ -3201,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 <{H2O} & 02) && # user has modified .gitignore !($diffbits->{O2A} & 02)) { # patches do not change .gitignore quiltify_splitbrain_needed();