X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=dgit.git;a=blobdiff_plain;f=dgit;h=8404a19c2509021f1b8242bac094dc86d0d6e523;hp=3e4741f74a48ebbc7219cbb0505ccdf1d78523fa;hb=b7120584994325d4f6d2963ed336110355349fc6;hpb=40f91b11738a9d84bd8b329c40003d5ac5f08ec3 diff --git a/dgit b/dgit index 3e4741f7..8404a19c 100755 --- a/dgit +++ b/dgit @@ -64,12 +64,16 @@ our $quilt_mode; our $quilt_modes_re = 'linear|smash|auto|nofix|nocheck|gbp|unapplied'; our $we_are_responder; our $initiator_tempdir; +our $patches_applied_dirtily = 00; our %format_ok = map { $_=>1 } ("1.0","3.0 (native)","3.0 (quilt)"); 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 $splitbraincache = 'dgit-intern/quilt-cache'; + our (@git) = qw(git); our (@dget) = qw(dget); our (@curl) = qw(curl -f); @@ -83,6 +87,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 (@gbp) = qw(gbp); our (@changesopts) = (''); our %opts_opt_map = ('dget' => \@dget, # accept for compatibility @@ -97,6 +102,7 @@ our %opts_opt_map = ('dget' => \@dget, # accept for compatibility 'dpkg-source' => \@dpkgsource, 'dpkg-buildpackage' => \@dpkgbuildpackage, 'dpkg-genchanges' => \@dpkggenchanges, + 'gbp' => \@gbp, 'ch' => \@changesopts, 'mergechanges' => \@mergechanges); @@ -114,6 +120,7 @@ autoflush STDOUT 1; our $supplementary_message = ''; our $need_split_build_invocation = 0; +our $split_brain = 0; END { local ($@, $?); @@ -195,6 +202,10 @@ sub deliberately_not_fast_forward () { } } +sub quiltmode_splitbrain () { + $quilt_mode =~ m/gbp|dpm|unapplied/; +} + #---------- remote protocol support, common ---------- # remote push initiator/responder protocol: @@ -1314,9 +1325,10 @@ sub clogp_authline ($) { $author =~ s#,.*##ms; my $date = cmdoutput qw(date), '+%s %z', qw(-d), getfield($clogp,'Date'); my $authline = "$author $date"; - $authline =~ m/^[^<>]+ \<\S+\> \d+ [-+]\d+$/ or + $authline =~ m/$git_authline_re/o or fail "unexpected commit author line format \`$authline'". " (was generated from changelog Maintainer field)"; + return ($1,$2,$3) if wantarray; return $authline; } @@ -1436,7 +1448,7 @@ END my $cversion = getfield $clogp, 'Version'; progress "synthesised git commit from .dsc $cversion"; if ($lastpush_hash) { - runcmd @git, qw(reset --hard), $lastpush_hash; + runcmd @git, qw(reset -q --hard), $lastpush_hash; runcmd qw(sh -ec), 'dpkg-parsechangelog >>../changelogold.tmp'; my $oldclogp = parsecontrol('../changelogold.tmp','previous changelog'); my $oversion = getfield $oldclogp, 'Version'; @@ -1978,10 +1990,14 @@ END 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(); } + + 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"; @@ -2510,12 +2526,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 + # 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; @@ -2524,7 +2542,9 @@ sub quiltify_trees_differ ($$;$) { 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; @@ -2537,14 +2557,96 @@ sub quiltify_tree_sentinelfiles ($) { qw(-- debian/rules debian/control); $r =~ s/\n/,/g; return $r; -} + } + +sub quiltify_splitbrain_needed () { + if (!$split_brain) { + progress "dgit view: changes are required..."; + runcmd @git, qw(checkout -q -b dgit-view); + $split_brain = 1; + } +} + +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 }; + $_ = !!$_ foreach values %$diffbits; + } + # We would like any commits we generate to be reproducible + my @authline = clogp_authline($clogp); + 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/ && + ($diffbits->{O2A} & 01)) { # some patches + quiltify_splitbrain_needed(); + progress "dgit view: creating patches-applied version using gbp pq"; + 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 "dgit view: 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: $!"; + fail "$gipatch already exists; but want to create it". + " to record .gitignore changes" if (stat _)[7]; + print GIPATCH <>$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; + + progress "dgit view: created (commit id $dgitview)"; -sub quiltify_splitbrain () { - # memoisation via git-reflog - my $may_apply = $quilt_mode =~ m/gbp|unapplied/; - die "xxx not yet implemented"; -# if ($may_apply && -# quiltify_trees_differ($userhead,)) {} + changedir '.git/dgit/unpack/work'; } sub quiltify ($$$$) { @@ -2780,6 +2882,8 @@ sub build_maybe_quilt_fixup () { 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); @@ -2846,7 +2950,7 @@ sub quilt_fixup_singlepatch ($$$) { sub quilt_fixup_multipatch ($$$) { my ($clogp, $headref, $upstreamversion) = @_; - progress "starting quiltify (multiple patches, $quilt_mode mode)"; + progress "examining quilt state (multiple patches, $quilt_mode mode)"; # Our objective is: # - honour any existing .pc in case it has any strangeness @@ -2951,11 +3055,70 @@ END } my $debtar= srcfn $fakeversion,'.debian.tar.gz'; - runcmd qw(env GZIP=-1 tar -zcf), "./$debtar", qw(-C ../../..), @files; + runcmd qw(env GZIP=-1n tar -zcf), "./$debtar", qw(-C ../../..), @files; $dscaddfile->($debtar); close $fakedsc or die $!; + my $splitbrain_cachekey; + if (quiltmode_splitbrain()) { + progress + "dgit: split brain (separate dgit view) may needed (--quilt=$quilt_mode)."; + # 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 () { + 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 "dgit view: found cached (commit id $cachehit)"; + runcmd @git, qw(checkout -q -b dgit-view), $cachehit; + $split_brain = 1; + return; + } + progress "dgit view: found cached, no changes required"; + 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'; @@ -2995,20 +3158,27 @@ END rmtree '.pc'; runcmd @git, qw(add -Af .); my $oldtiptree=git_write_tree(); + printdebug "fake o+d/p tree object $unapplied\n"; changedir '../work'; # We calculate some guesswork now about what kind of tree this might # be. This is mostly for error reporting. - my $user2unapplied = quiltify_trees_differ($headref, $unapplied, 1); - my $user2applied = quiltify_trees_differ($headref, $oldtiptree,1); - my $applied2unapplied = quiltify_trees_differ($oldtiptree,$unapplied, 1); + my %editedignores; + 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,\%editedignores), + H2A => quiltify_trees_differ($headref, $oldtiptree,1), + O2A => quiltify_trees_differ($unapplied,$oldtiptree,1), + }; my @dl; foreach my $b (qw(01 02)) { - foreach my $v ($user2unapplied, $applied2unapplied, $user2applied) { - push @dl, ($v & $b) ? '##' : '=='; + foreach my $v (qw(H2O O2A H2A)) { + push @dl, ($diffbits->{$v} & $b) ? '##' : '=='; } } printdebug "differences \@dl @dl.\n"; @@ -3020,19 +3190,22 @@ END $dl[2], $dl[5]; my @failsuggestion; - if (!($user2unapplied & $applied2unapplied)) { + if (!($diffbits->{H2O} & $diffbits->{O2A})) { push @failsuggestion, "This might be a patches-unapplied branch."; - } elsif (!($user2applied & $applied2unapplied)) { + } elsif (!($diffbits->{H2A} & $diffbits->{O2A})) { push @failsuggestion, "This might be a patches-applied branch."; } push @failsuggestion, "Maybe you need to specify one of". " --quilt=gbp --quilt=dpm --quilt=unapplied ?"; - if ($quilt_mode =~ m/gbp|dpm|apply/) { - quiltify_splitbrain(); + if (quiltmode_splitbrain()) { + quiltify_splitbrain($clogp, $unapplied, $headref, + $diffbits, \%editedignores, + $splitbrain_cachekey); return; } + progress "starting quiltify (multiple patches, $quilt_mode mode)"; quiltify($clogp,$headref,$oldtiptree,\@failsuggestion); if (!open P, '>>', ".pc/applied-patches") { @@ -3067,15 +3240,44 @@ sub quilt_fixup_editor () { exit 0; } +sub maybe_apply_patches_dirtily () { + return unless $quilt_mode =~ m/gbp|unapplied/; + print STDERR <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 @@ -3196,11 +3399,13 @@ sub massage_dbp_args ($;$) { #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 ?"; } + printdebug "massage done $r $dmode.\n"; push @$cmd, $dmode; #print STDERR "MASS2 ",Dumper($cmd, $xargs, $r); return $r; @@ -3216,8 +3421,10 @@ sub cmd_build { } if ($wantsrc < 2) { push @dbp, changesopts_version(); + maybe_apply_patches_dirtily(); runcmd_ordryrun_local @dbp; } + maybe_unapply_patches_again(); printdone "build successful\n"; } @@ -3237,7 +3444,7 @@ sub cmd_gbp_build { if ($wantsrc > 0) { build_source(); } else { - if (!$suppress_clean) { + if (!$clean_using_builder) { push @cmd, '--git-cleaner=true'; } build_prep(); @@ -3248,16 +3455,27 @@ sub cmd_gbp_build { 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 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'; @@ -3266,18 +3484,38 @@ sub build_source { or fail "remove $sourcechanges: $!"; } $dscfn = dscfn($version); - if ($cleanmode eq 'dpkg-source') { + if ($our_cleanmode eq 'dpkg-source') { + maybe_apply_patches_dirtily(); runcmd_ordryrun_local @dpkgbuildpackage, qw(-us -uc -S), - changesopts(); - } elsif ($cleanmode eq 'dpkg-source-d') { + changesopts(); + } elsif ($our_cleanmode eq 'dpkg-source-d') { + maybe_apply_patches_dirtily(); runcmd_ordryrun_local @dpkgbuildpackage, qw(-us -uc -S -d), - changesopts(); + changesopts(); } 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", @@ -3288,6 +3526,7 @@ sub build_source { sub cmd_build_source { badusage "build-source takes no additional arguments" if @ARGV; build_source(); + maybe_unapply_patches_again(); printdone "source built, results in $dscfn and $sourcechanges"; } @@ -3330,6 +3569,7 @@ sub cmd_sbuild { rename "$cf", "$cf.inmulti" or fail "$cf\{,.inmulti}: $!"; } } + maybe_unapply_patches_again(); printdone "build successful, results in $multichanges\n" or die $!; } @@ -3618,6 +3858,8 @@ if (!defined $quilt_mode) { $quilt_mode = $1; } +$need_split_build_invocation ||= quiltmode_splitbrain(); + if (!defined $cleanmode) { local $access_forpush; $cleanmode = access_cfg('clean-mode', 'RETURN-UNDEF');