diff options
Diffstat (limited to 'dgit')
-rwxr-xr-x | dgit | 876 |
1 files changed, 664 insertions, 212 deletions
@@ -60,6 +60,7 @@ $SIG{INT} = 'DEFAULT'; # work around #932841 our @rpushprotovsn_support = qw(6 5 4); # Reverse order! our $protovsn; +our $rpush_verb; # "push" or "push-source" our $cmd; our $subcommand; @@ -82,13 +83,14 @@ our $existing_package = 'dpkg'; our $cleanmode; our $changes_since_version; our $rmchanges; +our $keep_playground; our $overwrite_version; # undef: not specified; '': check changelog our $quilt_mode; our $quilt_upstream_commitish; our $quilt_upstream_commitish_used; our $quilt_upstream_commitish_message; our $quilt_options_re = 'gbp|dpm|baredebian(?:\+tarball|\+git)?'; -our $quilt_modes_re = "linear|smash|auto|nofix|nocheck|unapplied|$quilt_options_re"; +our $quilt_modes_re = "linear|smash|try-linear|auto|single|nofix|nocheck|unapplied|$quilt_options_re"; our $splitview_mode; our $splitview_modes_re = qr{auto|always|never}; our $dodep14tag; @@ -102,8 +104,9 @@ our $chase_dsc_distro=1; our %forceopts = map { $_=>0 } qw(unrepresentable unsupported-source-format dsc-changes-mismatch changes-origs-exactly - uploading-binaries uploading-source-only + uploading-binaries uploading-old-version uploading-source-only reusing-version + push-tainted import-gitapply-absurd import-gitapply-no-absurd import-dsc-with-dgit-field); @@ -127,7 +130,7 @@ our (@curl) = (qw(curl --proto-redir), '-all,http,https', qw(-L)); our (@dput) = qw(dput); our (@debsign) = qw(debsign); our (@gpg) = qw(gpg); -our (@sbuild) = (qw(sbuild --no-source)); +our (@sbuild) = (qw(sbuild --no-source --no-source-only-changes)); our (@ssh) = 'ssh'; our (@dgit) = qw(dgit); our (@git_debrebase) = qw(git-debrebase); @@ -189,20 +192,20 @@ our $do_split_brain; # (currently, split brain only implemented iff # madformat_wantfixup && quiltmode_splitting) # -# source format sane `3.0 (quilt)' -# madformat_wantfixup() -# -# quilt mode normal quiltmode -# (eg linear) _splitbrain -# -# ------------ ------------------------------------------------ -# -# no split no q cache no q cache forbidden, -# brain PM on master q fixup on master prevented -# !do_split_brain() PM on master -# -# split brain no q cache q fixup cached, to dgit view -# PM in dgit view PM in dgit view +# source format | sane `3.0 (quilt)' +# | madformat_wantfixup() +# | +# quilt mode | normal quiltmode +# | (eg linear) _splitbrain +# | +# -------------------+------------------------------------------------- +# | +# no split | no q cache no q cache forbidden, +# brain | PM on master q fixup on master prevented +# !do_split_brain() | PM on master +# | +# split brain | no q cache q fixup cached, to dgit view +# | PM in dgit view PM in dgit view # # PM = pseudomerge to make ff, due to overwrite (or split view) # "no q cache" = do not record in cache on build, do not check cache @@ -292,12 +295,15 @@ sub no_such_package () { sub deliberately ($) { my ($enquiry) = @_; - return !!grep { $_ eq "--deliberately-$enquiry" } @deliberatelies; + return !!grep { + $_ eq "--deliberately-$enquiry" or + $_ eq "--deliberately-TEST-dgit-only-$enquiry" + } @deliberatelies; } sub deliberately_not_fast_forward () { foreach (qw(not-fast-forward fresh-repo)) { - return 1 if deliberately($_) || deliberately("TEST-dgit-only-$_"); + return 1 if deliberately($_); } } @@ -324,6 +330,16 @@ sub gbp_pq { return opts_opt_multi_cmd [], @gbp_pq; } +sub gbp_pq_pc_aside (&) { + my ($f) = @_; + my $undo = rename ".pc", "../pc-aside"; + confess "$!" unless $undo || $!==ENOENT; + $f->(); + if ($undo) { + rename "../pc-aside", ".pc", or confess $!; + } +} + sub dgit_privdir () { our $dgit_privdir_made //= ensure_a_playground 'dgit'; } @@ -433,9 +449,13 @@ sub branch_is_gdr ($) { !defined git_cat_file "$walk~:debian" and !quiltify_trees_differ "$walk~", $walk ) { - # (gdr classification of parent: BreakwaterStart - printdebug "branch_is_gdr $walk unmarked BreakwaterStart YES\n"; - return 1; + # (gdr classification of parent: BreakwaterStart We cannot + # process this using git-debrebase, because this can misrecognise + # other kinds of branch contents, eg as in #1025451. Not doing + # this via gdr is OK, because the normal quilt linearisation will + # do - doing it via gdr is just an optimisation. + printdebug "branch_is_gdr $walk unmarked BreakwaterStart NO\n"; + return 0; } # (gdr classification: Upstream Packaging Mixed Changelog) printdebug "branch_is_gdr $walk plain\n" @@ -447,8 +467,12 @@ sub branch_is_gdr ($) { #---------- remote protocol support, common ---------- # remote push initiator/responder protocol: -# $ dgit remote-push-build-host <n-rargs> <rargs>... <push-args>... +# $ dgit remote-push-source-build-host <n-rargs> <rargs>... <push-args>... # where <rargs> is <push-host-dir> <supported-proto-vsn>,... ... +# < dgit-remote-push-source-ready <actual-proto-vsn> +# +# Or for push-built, +# $ dgit remote-push-build-host <n-rargs> <rargs>... <push-args>... # < dgit-remote-push-ready <actual-proto-vsn> # # occasionally: @@ -482,6 +506,7 @@ sub branch_is_gdr ($) { # # > param buildinfo-filename P_V_X.buildinfo # zero or more times # > file buildinfo # for buildinfos to sign +# # not for push-source # # > previously REFNAME=OBJNAME # if --deliberately-not-fast-forward # # goes into tag, for replay prevention @@ -729,11 +754,13 @@ our %defcfg = ('dgit.default.distro' => 'debian', 'dgit.default.sshpsql-dbname' => 'service=projectb', 'dgit.default.aptget-components' => 'main', 'dgit.default.source-only-uploads' => 'ok', + 'dgit.default.policy-query-supported-ssh' => 'unknown', 'dgit.dsc-url-proto-ok.http' => 'true', 'dgit.dsc-url-proto-ok.https' => 'true', 'dgit.dsc-url-proto-ok.git' => 'true', 'dgit.vcs-git.suites', => 'sid', # ;-separated 'dgit.default.dsc-url-proto-ok' => 'false', + 'dgit.default.push-subcmd' => 'warn,built', # 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" @@ -744,6 +771,7 @@ our %defcfg = ('dgit.default.distro' => 'debian', 'dgit-distro.debian.git-check-suffix' => '/info/refs', 'dgit-distro.debian.new-private-pushers' => 't', 'dgit-distro.debian.source-only-uploads' => 'not-wholly-new', + 'dgit-distro.debian.policy-query-supported-ssh' => 'true', 'dgit-distro.debian/push.git-url' => '', 'dgit-distro.debian/push.git-host' => 'push.dgit.debian.org', 'dgit-distro.debian/push.git-user-force' => 'dgit', @@ -767,8 +795,8 @@ our %defcfg = ('dgit.default.distro' => 'debian', 'dgit-distro.debian.mirror' => 'http://ftp.debian.org/debian/', 'dgit-distro.debian-security.archive-query' => 'aptget:', 'dgit-distro.debian-security.mirror' => 'http://security.debian.org/debian-security/', - 'dgit-distro.debian-security.aptget-suite-map' => 's#-security$#/updates#', - 'dgit-distro.debian-security.aptget-suite-rmap' => 's#$#-security#', + 'dgit-distro.debian-security.aptget-suite-map' => 's#buster-security$#buster/updates#', + 'dgit-distro.debian-security.aptget-suite-rmap' => 's#buster$#buster-security#', 'dgit-distro.debian-security.nominal-distro' => 'debian', 'dgit-distro.debian.backports-quirk' => '(squeeze)-backports*', 'dgit-distro.debian-backports.mirror' => 'http://backports.debian.org/debian-backports/', @@ -1865,6 +1893,13 @@ sub git_add_write_tree () { return git_write_tree(); } +sub git_diff_programmatic (@) { + # Ideally we would unset various git.diff config options here, + # but there doesn't seem to be a way to *unset* + # something on the command line + (@git, qw(-c color.ui=never diff --no-ext-diff), @_) +} + sub remove_stray_gits ($) { my ($what) = @_; my @gitscmd = qw(find -name .git -prune -print0); @@ -1895,7 +1930,7 @@ sub mktree_in_ud_from_only_subdir ($;$) { remove_stray_gits($what); mktree_in_ud_here(); if (!$raw) { - my ($format, $fopts) = get_source_format(); + my $format = get_source_format(); if (madformat($format)) { rmtree '.pc'; } @@ -2035,10 +2070,12 @@ sub test_source_only_changes ($) { foreach my $l (split /\n/, getfield $changes, 'Files') { $l =~ m/\S+$/ or next; # \.tar\.[a-z0-9]+ covers orig.tar and the tarballs in native packages - unless ($& =~ m/(?:\.dsc|\.diff\.gz|$tarball_f_ext_re|_source\.buildinfo)$/) { - print f_ "purportedly source-only changes polluted by %s\n", $&; - return 0; - } + $_ = $&; + next if m/(?:\.dsc|\.diff\.gz|$tarball_f_ext_re)$/; + next if m/_source\.buildinfo$/; + print STDERR + f_ "purportedly source-only changes polluted by %s\n", $&; + return 0; } return 1; } @@ -2153,8 +2190,11 @@ sub clogp_authline ($) { # are by now preceded by @ (or "). It seems safer to punt on # "..." for now rather than attempting to dequote or something. $author =~ s#,.*##ms unless $author =~ m/"/; - my $date = cmdoutput qw(date), '+%s %z', qw(-d), getfield($clogp,'Date'); - my $authline = "$author $date"; + my $date = getfield($clogp,'Date'); + # try to pass through the changelog entry's timezone offset + my $tz = $date =~ m{ ([-+]\d{4})$} ? $1 : " +0000"; + $date = cmdoutput qw(date), '+%s', qw(-d), $date; + my $authline = "$author $date $tz"; $authline =~ m/$git_authline_re/o or fail f_ "unexpected commit author line format \`%s'". " (was generated from changelog Maintainer field)", @@ -2240,11 +2280,50 @@ sub dotdot_bpd_transfer_origs ($$$) { # checks is_orig_file_of_vsn and if # calls $wanted->{$leaf} and expects boolish + my $dotdot = $maindir; + $dotdot =~ s{/[^/]+$}{}; + + my %dupes; + my $dupe_scan = sub { + my ($dir, $why_token) = @_; + + if (!opendir SD, $dir) { + return if $! == ENOENT; + fail "opendir $why_token ($dir): $!"; + } + while ($!=0, defined(my $leaf = readdir SD)) { + next unless is_orig_file_of_vsn $leaf, $upstreamversion; + next if $leaf =~ m{$orig_f_sig_re$}; + next unless $leaf =~ m{\.tar(?:\.\w+)?$}; + my $base = "$`.tar"; + push @{ $dupes{$base}{$leaf} }, [$why_token, $dir]; + } + die "$dir; $!" if $!; + }; + $dupe_scan->($dotdot, ".."); + $dupe_scan->(bpd_abs(), 'build-products-dir') if $buildproductsdir ne '..'; + + my $dupes_found = 0; + foreach my $base (sort keys %dupes) { + my $leaves = $dupes{$base}; + next if keys(%$leaves) == 1; + $dupes_found = 1; + print STDERR f_ + "%s: multiple representations of similar orig %s:\n", + $us, $base; + foreach my $leaf (keys %$leaves) { + foreach my $found (@{ $leaves->{$leaf} }) { + print STDERR f_ " %s: in %s (%s)\n", + $leaf, @$found; + } + } + } + fail __ "Duplicate/inconsistent orig tarballs. Delete the spurious ones." + if $dupes_found; + return if $buildproductsdir eq '..'; my $warned; - my $dotdot = $maindir; - $dotdot =~ s{/[^/]+$}{}; opendir DD, $dotdot or fail "opendir .. ($dotdot): $!"; while ($!=0, defined(my $leaf = readdir DD)) { { @@ -2295,6 +2374,55 @@ sub dotdot_bpd_transfer_origs ($$$) { closedir DD; } +sub import_r1authline ($$) { + my ($clogp_r, $upstreamv) = @_; + my $r1clogp; + + my @clogcmd = qw(dpkg-parsechangelog --format rfc822 --all); + + printdebug "import clog search...\n"; + parsechangelog_loop \@clogcmd, (__ "package changelog"), sub { + my ($thisstanza, $desc) = @_; + no warnings qw(exiting); + + $$clogp_r //= $thisstanza; + + printdebug "import clog $thisstanza->{version} $desc...\n"; + + # We look for the first (most recent) changelog entry whose + # version number is lower than the upstream version of this + # package. Then the last (least recent) previous changelog + # entry is treated as the one which introduced this upstream + # version and used for the synthetic commits for the upstream + # tarballs. + + # One might think that a more sophisticated algorithm would be + # necessary. But: we do not want to scan the whole changelog + # file. Stopping when we see an earlier version, which + # necessarily then is an earlier upstream version, is the only + # realistic way to do that. Then, either the earliest + # changelog entry we have seen so far is indeed the earliest + # upload of this upstream version; or there are only changelog + # entries relating to later upstream versions (which is not + # possible unless the changelog and .dsc disagree about the + # version). Then it remains to choose between the physically + # last entry in the file, and the one with the lowest version + # number. If these are not the same, we guess that the + # versions were created in a non-monotonic order rather than + # that the changelog entries have been misordered. + + printdebug "import clog $thisstanza->{version} vs $upstreamv...\n"; + + last if version_compare($thisstanza->{version}, $upstreamv) < 0; + $r1clogp = $thisstanza; + + printdebug "import clog $r1clogp->{version} becomes r1\n"; + }; + + $r1clogp //= $$clogp_r; # maybe there's only one entry; + return clogp_authline $r1clogp; +} + sub import_tarball_tartrees ($$) { my ($upstreamv, $dfi) = @_; # cwd should be the playground @@ -2415,65 +2543,33 @@ sub import_tarball_commits ($$) { my $any_orig = grep { $_->{Orig} } @$tartrees; - my @clogcmd = qw(dpkg-parsechangelog --format rfc822 --all); my $clogp; - my $r1clogp; - - printdebug "import clog search...\n"; - parsechangelog_loop \@clogcmd, (__ "package changelog"), sub { - my ($thisstanza, $desc) = @_; - no warnings qw(exiting); - - $clogp //= $thisstanza; - - printdebug "import clog $thisstanza->{version} $desc...\n"; - - last if !$any_orig; # we don't need $r1clogp - - # We look for the first (most recent) changelog entry whose - # version number is lower than the upstream version of this - # package. Then the last (least recent) previous changelog - # entry is treated as the one which introduced this upstream - # version and used for the synthetic commits for the upstream - # tarballs. - - # One might think that a more sophisticated algorithm would be - # necessary. But: we do not want to scan the whole changelog - # file. Stopping when we see an earlier version, which - # necessarily then is an earlier upstream version, is the only - # realistic way to do that. Then, either the earliest - # changelog entry we have seen so far is indeed the earliest - # upload of this upstream version; or there are only changelog - # entries relating to later upstream versions (which is not - # possible unless the changelog and .dsc disagree about the - # version). Then it remains to choose between the physically - # last entry in the file, and the one with the lowest version - # number. If these are not the same, we guess that the - # versions were created in a non-monotonic order rather than - # that the changelog entries have been misordered. - - printdebug "import clog $thisstanza->{version} vs $upstreamv...\n"; - - last if version_compare($thisstanza->{version}, $upstreamv) < 0; - $r1clogp = $thisstanza; - - printdebug "import clog $r1clogp->{version} becomes r1\n"; - }; - - $clogp or fail __ "package changelog has no entries!"; - + my $r1authline; + if ($any_orig) { + if (!eval { + local $failmsg_prefix = ' '; + $r1authline = import_r1authline(\$clogp, $upstreamv); + $clogp or fail __ "package changelog has no entries!"; + 1; + }) { + chomp $@; + print STDERR f_ <<END, $upstreamv, $@; +warning: unable to find/parse changelog entry for first import of %s: +%s +END + } + } + # Runs if $any_orig clause didn't set $clogp + $clogp //= parsechangelog(); my $authline = clogp_authline $clogp; + # Runs if $any_orig clause didn't set $r1authline + $r1authline //= $authline; + my $changes = getfield $clogp, 'Changes'; $changes =~ s/^\n//; # Changes: \n my $cversion = getfield $clogp, 'Version'; - my $r1authline; if (@$tartrees) { - $r1clogp //= $clogp; # maybe there's only one entry; - $r1authline = clogp_authline $r1clogp; - # Strictly, r1authline might now be wrong if it's going to be - # unused because !$any_orig. Whatever. - printdebug "import tartrees authline $authline\n"; printdebug "import tartrees r1authline $r1authline\n"; @@ -2545,7 +2641,9 @@ sub generate_commits_from_dsc () { printdebug "considering saving $f: "; - if (rename_link_xf 1, $f, $upper_f) { + if (!act_local()) { + printdebug "no - dry run.\n"; + } elsif (rename_link_xf 1, $f, $upper_f) { printdebug "linked.\n"; } elsif ((printdebug "($@) "), $! != EEXIST) { @@ -2676,14 +2774,57 @@ END local $ENV{DGIT_ABSURD_DEBUG} = $debuglevel if $use_absurd; local $ENV{PATH} = $path if $use_absurd; + if ($use_absurd) { + # We filter the series file, to contain only things + # that are actually requests to apply a patch. + # + # This is needed because sometimes a series file can + # contain strange things that gbp pq cannot cope with. + # Eg, form feeds. See #1030093. + rename "debian/patches/series", "../series.orig" + or confess "$!"; + open OS, "../series.orig" or confess $!; + open NS, ">debian/patches/series" or confess $!; + while (<OS>) { + s/\#.*//; + s/^\s+//; + s/\s+$//; + next unless m/\S/; + print NS "$_\n" or confess $!; + } + confess $! if OS->error; + close NS or confess $!; + runcmd @git, qw(add debian/patches/series); + # This commit is spurious, but we must commit for gbp + # pq to work. We filter it out of the branch later. + runcmd @git, qw(commit --quiet --allow-empty -m), <<END; +INTERNAL commit to launder series file + +This commit should not escape into a public branch! +If you see it, this is due to a bug in dgit. + +[dgit ($our_version) INTERNAL-quilt-fixup-series] +END + } + my @showcmd = (gbp_pq, qw(import)); my @realcmd = shell_cmd - 'exec >/dev/null 2>>../../gbp-pq-output', @showcmd; - debugcmd "+",@realcmd; - if (system @realcmd) { - die f_ "%s failed: %s\n", - +(shellquote @showcmd), - failedcmd_waitstatus(); + 'exec >/dev/null 2>../../gbp-pq-output', @showcmd; + gbp_pq_pc_aside(sub { + debugcmd "+",@realcmd; + if (system @realcmd) { + die f_ "%s failed: %s\n", + +(shellquote @showcmd), + failedcmd_waitstatus(); + } + }); + + if ($use_absurd) { + # Perhaps we should be using git-filter-branch, + # but that's really considerably more awkward. + runcmd_quieten + @git, qw(rebase --keep-empty --allow-empty-message + --onto unpa~1 unpa); } my $gapplied = git_rev_parse('HEAD'); @@ -2699,7 +2840,7 @@ END if ($use_absurd) { File::Copy::copy("../../absurd-apply-warnings", \*STDERR) - or die $!; + or confess $!; } }; last unless $@; @@ -2968,10 +3109,10 @@ END my $want = $wantr{$rrefname}; next if $got eq $want; if (!defined $objgot{$want}) { - fail __ <<END unless act_local(); + fail f_ <<END, $rrefname unless act_local(); --dry-run specified but we actually wanted the results of git fetch, so this is not going to work. Try running dgit fetch first, -or using --damp-run instead of --dry-run. +or using --damp-run instead of --dry-run. (Wanted: %s.) END print STDERR f_ <<END, $lrefname, $want; warning: git ls-remote suggests we want %s @@ -3367,7 +3508,7 @@ END if ($lastfetch_mergeinput && !version_compare( (mergeinfo_version $lastfetch_mergeinput), (mergeinfo_version $mergeinputs[0]) )) { - @mergeinputs = ($lastfetch_mergeinput); + @mergeinputs = ($lastfetch_mergeinput); } } elsif ($lastpush_hash) { # only in git, not in the archive yet @@ -4034,19 +4175,13 @@ END } sub get_source_format () { - my %options; + my @options; if (open F, "debian/source/options") { while (<F>) { next if m/^\s*\#/; next unless m/\S/; s/\s+$//; # ignore missing final newline - if (m/\s*\#\s*/) { - my ($k, $v) = ($`, $'); #'); - $v =~ s/^"(.*)"$/$1/; - $options{$k} = $v; - } else { - $options{$_} = 1; - } + push @options, $_; } F->error and confess "$!"; close F; @@ -4062,7 +4197,7 @@ sub get_source_format () { F->error and confess "$!"; close F; chomp; - return ($_, \%options); + return wantarray ? ($_, \@options) : $_; } sub madformat_wantfixup ($) { @@ -4148,9 +4283,10 @@ sub pseudomerge_version_check ($$) { progress f_ "Checking package changelog for archive version %s ...", $v; my $cd; + my $vclogp; eval { my @xa = ("-f$v", "-t$v"); - my $vclogp = parsechangelog @xa; + $vclogp = parsechangelog @xa; my $gf = sub { my ($fn) = @_; [ (getfield $vclogp, $fn), @@ -4172,6 +4308,11 @@ sub pseudomerge_version_check ($$) { Your tree seems to based on earlier (not uploaded) %s. END if $cd->[0] =~ m/UNRELEASED/; + fail f_ <<END, $v, $v +d/changelog entry for %s is unfinalised! +Your tree seems to based on earlier (not uploaded) %s. +END + unless defined $vclogp->{Date}; } } @@ -4263,7 +4404,7 @@ sub splitbrain_pseudomerge ($$$$) { print STDERR <<END.(__ <<ENDT); $@ END -| Not fast forward; maybe --overwrite is needed ? Please see dgit(1). +| Not fast forward; maybe --trust-changelog is needed ? Please see dgit(1). ENDT finish -1; } @@ -4488,6 +4629,68 @@ sub sign_changes ($) { } } +sub tainted_objects_precheck ($$) { + my ($json, $dgithead) = @_; + my %taints; + ROW: foreach my $row (@{ decode_json $json }) { + foreach my $override (@{ $row->{overrides} }) { + if ($override =~ m{^--deliberately-} && deliberately($')) { + printdebug "overriding $row->{gitobjid} $override\n"; + next ROW; + } + } + my $objid = $row->{gitobjid}; + my ($gtype, $dummy) = git_cat_file $objid, undef; + next if $gtype eq 'missing'; + if ($row->{gitobjtype} and $gtype ne $row->{gitobjtype}) { + print STDERR f_ <<'END', $objid, $gtype, $row->{gitobjtype}; +warning: server says object %s type %s is tainted, but here it has type %s +END + } + $taints{$objid}{Type} = $gtype; + push @{ $taints{$objid}{Rows} }, $row; + } + + open GRL, "-|", + @git, qw(rev-list --objects --in-commit-order --pretty=format:), + $dgithead + or confess "$!"; + my $trouble = 0; + my %hinted; + my $found = sub { + my ($objid) = @_; + my $t = $taints{$objid}; + return unless $t; + + foreach my $row (@{ $t->{Rows} }) { + # If it was actually overridding we don't get here, asd + # don't call tainted_objects_message. Instead, the server + # will send such a message to our stderr (sadly, untranslated). + my $ovstatus = + (grep m{^--deliberately-}, @{ $row->{overrides} }) + ? '' : undef; + print STDERR tainted_objects_message $row, $ovstatus, \%hinted; + $trouble = 1; + } + }; + my $c_commit; + while (<GRL>) { + if (m{^commit (\w+)$}) { + $c_commit = $1; + $found->($1, __ 'commit'); + } elsif (m{(^\w{20}\w*) } && defined $c_commit) { + $found->($1, f_ 'object within commit %s', $c_commit); + } else { + confess "$_ ?"; + } + } + GRL->error and die $!; + close GRL or confess "$? $!"; + forceable_fail [qw(push-tainted)], + __ "pushing tainted objects (which server would reject)" + if $trouble; +} + sub dopush () { printdebug "actually entering push\n"; @@ -4499,6 +4702,7 @@ END git_fetch_us(); } my $archive_hash = fetch_from_archive(); + my $archive_dsc = $dsc; if (!$archive_hash) { $new_package or fail __ "package appears to be new in this suite;". @@ -4556,6 +4760,16 @@ END my $upstreamversion = upstreamversion $clogp->{Version}; + if (defined $archive_dsc && + version_compare($archive_dsc->{Version}, $cversion) >= 0 && + !forceing [qw(uploading-old-version)]) { + fail f_ <<'END', $archive_dsc->{Version}, $csuite, $cversion; +You seem to be trying to push an old version. +Version current in archive: %s (in suite %s) +Version you are trying to upload: %s +END + } + if (madformat_wantfixup($format)) { # user might have not used dgit build, so maybe do this now: if (do_split_brain()) { @@ -4603,7 +4817,7 @@ END fail __ "dgit push: HEAD is not a descendant". " of the archive's version.\n". "To overwrite the archive's contents,". - " pass --overwrite[=VERSION].\n". + " pass --trust-changelog, or --overwrite=VERSION.\n". "To rewrite history, if permitted by the archive,". " use --deliberately-not-fast-forward."; } @@ -4717,8 +4931,8 @@ ENDT if !$hasdebs; } elsif ($sourceonlypolicy eq 'not-wholly-new') { forceable_fail [qw(uploading-source-only)], - f_ "source-only upload, even though package is entirely NEW\n". - "(this is contrary to policy in %s)", + f_ "source-only upload, though package appears entirely NEW\n". + "(this is probably contrary to policy in %s)", access_nomdistro() if !$hasdebs && $new_package @@ -4728,6 +4942,32 @@ ENDT $sourceonlypolicy; } + # Try to detect if we're about to be rejected due to tainted objects + my $pq_supported = access_cfg 'policy-query-supported-ssh'; + $pq_supported =~ m{^(?:false|true|unknown)$} or badcfg f_ + "policy-query-supported-ssh value '%s' must be false/true/unknown", + $pq_supported; + if ($pq_supported !~ m/false/) { + my @cmd = + (access_cfg_ssh, access_gituserhost(), + access_runeinfo("policy-client-query $package tainted-objects ". + join " ", $csuite). + " true"); + my $json = cmdoutput_errok @cmd; + if (!defined $json) { + # "unknown" means try the call, but don't mind if it + # fails. (This is OK, as a best effort, because then the + # server will enforce the check and this machinery is just + # to prevent late failures.) + failedcmd @cmd unless $pq_supported =~ m/unknown/; + } else { + printdebug "tainted-objects: $json\n"; + if (length $json) { + tainted_objects_precheck $json, $dgithead; + } + } + } + # Perhaps adjust .dsc to contain right set of origs changes_update_origs_from_dsc($dsc, $changes, $upstreamversion, $changesfile) @@ -4944,9 +5184,9 @@ sub cmd_fetch { sub cmd_pull { parseopts(); fetchpullargs(); - determine_whether_split_brain get_source_format(); + my $format = get_source_format(); + determine_whether_split_brain $format; if (do_split_brain()) { - my ($format, $fopts) = get_source_format(); madformat($format) and fail f_ <<END, $quilt_mode dgit pull not yet supported in split view mode (including with view-splitting quilt modes) END @@ -5063,14 +5303,60 @@ sub prep_push () { } } -sub cmd_push { +sub cmd_push_built { prep_push(); + if (do_split_brain()) { + # We may need to make the split brain view now, bedcause if + # the user built the package other than with dgit they may + # have a correct .dsc, but not populated the cache. + # + # Do this only in split brain mode, since we don't actually + # want to update HEAD! + build_maybe_quilt_fixup(); + } dopush(); } +sub cmd_push { + some_push_alias('push', \&cmd_push_source, \&cmd_push_built, + [qw(dgit.default.push-subcmd)], sub { + my ($spec) = @_; + f_ 'dgit push, but dgit.default.push-subcmd set to %s', $spec + }); +} +sub cmd_rpush { + some_push_alias('rpush', \&cmd_rpush_source, \&cmd_rpush_built, + [qw(dgit.default.rpush-subcmd + dgit.default.push-subcmd)], sub { + my ($spec) = @_; + f_ 'dgit rpush, but dgit.default.[r]push-subcmd set to %s', $spec + }); +} +sub some_push_alias ($$@) { + my ($verb, $if_source, $if_built, $cfgs, $badvalue_msg) = @_; + my $spec = cfg @$cfgs; + + if ($spec eq 'source') { + $if_source->(); + } elsif ($spec eq 'built') { + $if_built->(); + } elsif ($spec eq 'warn,built') { + print STDERR f_ <<'END', $verb,$verb,$verb; +warning: "dgit %s" currently means "dgit %s-built" (by default) +warning: but is going to change to "dgit %s-source". See dgit!(1). +END + $if_built->(); + } else { + fail $badvalue_msg->($spec); + } +} + #---------- remote commands' implementation ---------- -sub pre_remote_push_build_host { +sub pre_remote_push_build_host { core_pre_rpush_bh('push'); } +sub pre_remote_push_source_build_host { core_pre_rpush_bh('push-source'); } +sub core_pre_rpush_bh ($) { + ($rpush_verb) = @_; my ($nrargs) = shift @ARGV; my (@rargs) = @ARGV[0..$nrargs-1]; @ARGV = @ARGV[$nrargs..$#ARGV]; @@ -5101,11 +5387,12 @@ sub pre_remote_push_build_host { unless defined $protovsn; changedir $dir; + + responder_send_command("dgit-remote-$rpush_verb-ready $protovsn"); } -sub cmd_remote_push_build_host { - responder_send_command("dgit-remote-push-ready $protovsn"); - &cmd_push; -} + +sub cmd_remote_push_build_host { &cmd_push_built; } +sub cmd_remote_push_source_build_host { &cmd_push_source; } sub pre_remote_push_responder { pre_remote_push_build_host(); } sub cmd_remote_push_responder { cmd_remote_push_build_host(); } @@ -5143,10 +5430,16 @@ sub i_method { { no strict qw(refs); &{"${base}_${selector}"}(@args); } } -sub pre_rpush () { - not_necessarily_a_tree(); -} -sub cmd_rpush { +sub pre_rpush_source () { not_necessarily_a_tree(); } +sub pre_rpush_built () { not_necessarily_a_tree(); } +sub pre_rpush () { not_necessarily_a_tree(); } + +sub cmd_rpush_source { rpush_core('push-source'); } +sub cmd_rpush_built { rpush_core('push'); } + +sub rpush_core ($) { + ($rpush_verb) = @_; + my $host = nextarg; my $dir; if ($host =~ m/^((?:[^][]|\[[^][]*\])*)\:/) { @@ -5161,7 +5454,7 @@ sub cmd_rpush { my @rdgit; push @rdgit, @dgit; push @rdgit, @ropts; - push @rdgit, qw(remote-push-build-host), (scalar @rargs), @rargs; + push @rdgit, "remote-$rpush_verb-build-host", (scalar @rargs), @rargs; push @rdgit, @ARGV; my @cmd = (@ssh, $host, shellquote @rdgit); debugcmd "+",@cmd; @@ -5178,7 +5471,7 @@ sub cmd_rpush { } $i_child_pid = open2(\*RO, \*RI, @cmd); changedir $i_tmp; - ($protovsn) = initiator_expect { m/^dgit-remote-push-ready (\S+)/ }; + ($protovsn) = initiator_expect { m/^dgit-remote-$rpush_verb-ready (\S+)/ }; die "$protovsn ?" unless grep { $_ eq $protovsn } @rpushprotovsn_support; for (;;) { @@ -5295,6 +5588,9 @@ sub i_localname_buildinfo ($) { return $&; } sub i_file_buildinfo { + $rpush_verb eq 'push' + or badproto \*RO, "buildinfo file but verb is $rpush_verb"; + my $bi = $i_param{'buildinfo-filename'}; my $bd = parsecontrol "$i_tmp/$bi", $bi; my $ch = parsecontrol "$i_tmp/$i_changesfn", 'changes'; @@ -5317,7 +5613,12 @@ sub i_localname_changes { $i_changesfn =~ s/\.dsc$/_dgit.changes/ or die; return $i_changesfn; } -sub i_file_changes { } +sub i_file_changes { + my $ch = parsecontrol "$i_tmp/$i_changesfn", 'changes'; + unless ($rpush_verb eq 'push' || test_source_only_changes($ch)) { + fail __ "build-host-supplied changes file is not source-only"; + } +} sub i_want_signed_tag { printdebug Dumper(\%i_param, $i_dscfn); @@ -5364,13 +5665,14 @@ our $dscfn; our $fakeeditorenv = 'DGIT_FAKE_EDITOR_QUILT'; -sub quiltify_dpkg_commit ($$$;$) { - my ($patchname,$author,$msg, $xinfo) = @_; +sub quiltify_make_dpkg_patch ($$$$$;$) { + my ($oldtreeish,$newtreeish, $patchname,$author,$msg, $xinfo) = @_; $xinfo //= ''; mkpath '.git/dgit'; # we are in playtree - my $descfn = ".git/dgit/quilt-description.tmp"; - open O, '>', $descfn or confess "$descfn: $!"; + my $patchfn = "debian/patches/$patchname"; + ensuredir dirname $patchfn; + open O, '>', $patchfn or confess "$patchfn: $!"; $msg =~ s/\n+/\n\n/; print O <<END or confess "$!"; From: $author @@ -5380,12 +5682,19 @@ ${xinfo}Subject: $msg END close O or confess "$!"; - { - local $ENV{'EDITOR'} = cmdoutput qw(realpath --), $0; - local $ENV{'VISUAL'} = $ENV{'EDITOR'}; - local $ENV{$fakeeditorenv} = cmdoutput qw(realpath --), $descfn; - runcmd @dpkgsource, qw(--commit --include-removal .), $patchname; - } + my @diffcmd = (git_diff_programmatic, $oldtreeish, $newtreeish, + '--', ':!/debian', ':!/.pc'); + runcmd qw(sh -ec), 'exec >>"$1"; shift; exec "$@"', 'x', $patchfn, + @diffcmd; + + open S, ">> debian/patches/series" or confess "$!"; + print S "$patchname\n" or confess "$!"; + close S or confess "$!"; +} + +sub normalise_mode_strip_exec ($) { + my ($m) = @_; + return $m eq '100755' ? '100644' : $m; } sub quiltify_trees_differ ($$;$$$) { @@ -5397,8 +5706,8 @@ sub quiltify_trees_differ ($$;$$$) { # if $ignorenamesr is defined, $ingorenamesr->{$fn} # is set for each modified .gitignore filename $fn # if $unrepres is defined, array ref to which is appeneded - # a list of unrepresentable changes (removals of upstream files - # (as messages) + # a list of unrepresentable changes (changes that dpkg-source + # cannot apply even just during unpack). local $/=undef; my @cmd = (@git, qw(diff-tree -z --no-renames)); push @cmd, qw(--name-only) unless $unrepres; @@ -5419,13 +5728,15 @@ sub quiltify_trees_differ ($$;$$$) { if ($unrepres) { eval { - die __ "not a plain file or symlink\n" + die __ "not a plain file\n" unless $newmode =~ m/^(?:10|12)\d{4}$/ || $oldmode =~ m/^(?:10|12)\d{4}$/; if ($oldmode =~ m/[^0]/ && $newmode =~ m/[^0]/) { # both old and new files exist - die __ "mode or type changed\n" if $oldmode ne $newmode; + die __ "mode or type changed in unsupported way\n" if + normalise_mode_strip_exec($oldmode) ne + normalise_mode_strip_exec($newmode); die __ "modified symlink\n" unless $newmode =~ m/^10/; } elsif ($oldmode =~ m/[^0]/) { # deletion @@ -5433,10 +5744,9 @@ sub quiltify_trees_differ ($$;$$$) { unless $oldmode =~ m/^10/; } else { # creation - die __ "creation with non-default mode\n" + die __ "creation with non-default mode, or symlink\n" unless $newmode =~ m/^100644$/ or - $newmode =~ m/^100755$/ or - $newmode =~ m/^120000$/; + $newmode =~ m/^100755$/; } }; if ($@) { @@ -5453,6 +5763,24 @@ sub quiltify_trees_differ ($$;$$$) { return $r; } +sub quiltify_check_unrepresentable ($) { + my ($unrepres) = @_; + return unless @$unrepres; + if ($quilt_mode =~ m/baredebian/) { + # With baredebian, even if the upstream commitish has this + # problem, we don't want to print this message, as nothing + # is going to try to make a patch out of it anyway. + return; + } + print STDERR f_ "dgit: cannot represent change: %s: %s\n", + $_->[1], $_->[0] + foreach @$unrepres; + + forceable_fail [qw(unrepresentable)], __ <<END; +HEAD has changes to .orig[s] which are not representable by `3.0 (quilt)' +END +} + sub quiltify_tree_sentinelfiles ($) { # lists the `sentinel' files present in the tree my ($x) = @_; @@ -5530,7 +5858,9 @@ ENDU if ($quilt_mode =~ m/gbp|unapplied|baredebian/ && ($diffbits->{O2A} & 01)) { # some patches progress __ "dgit view: creating patches-applied version using gbp pq"; - runcmd shell_cmd 'exec >/dev/null', gbp_pq, qw(import); + gbp_pq_pc_aside(sub { + runcmd shell_cmd 'exec >/dev/null', gbp_pq, qw(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); @@ -5555,6 +5885,7 @@ END " to record .gitignore changes", $gipatch if (stat _)[7]; + # TODO: The "Subject:" ought not to be translated print GIPATCH +(__ <<END).<<ENDU or die "$gipatch: $!"; Subject: Update .gitignore from Debian packaging branch @@ -5567,8 +5898,9 @@ END --- ENDU close GIPATCH or die "$gipatch: $!"; - runcmd shell_cmd "exec >>$gipatch", @git, qw(diff), - $unapplied, $headref, "--", sort keys %$editedignores; + runcmd shell_cmd "exec >>$gipatch", + git_diff_programmatic $unapplied, $headref, "--", + sort keys %$editedignores; open SERIES, "+>>", "debian/patches/series" or confess "$!"; defined seek SERIES, -1, 2 or $!==EINVAL or confess "$!"; my $newline; @@ -5733,7 +6065,7 @@ sub quiltify ($$$$) { fail __ "quilt history linearisation failed. Search \`quilt fixup' in dgit(7).\n"; } elsif ($quilt_mode eq 'smash') { - } elsif ($quilt_mode eq 'auto') { + } elsif ($quilt_mode eq 'try-linear') { progress __ "quilt fixup cannot be linear, smashing..."; } else { confess "$quilt_mode ?"; @@ -5744,7 +6076,9 @@ sub quiltify ($$$$) { my $ncommits = 3; my $msg = cmdoutput @git, qw(log), "-n$ncommits"; - quiltify_dpkg_commit "auto-$version-$target-$time", + quiltify_make_dpkg_patch + $oldtiptree, $target, + "auto-$version-$target-$time", (getfield $clogp, 'Maintainer'), (f_ "Automatically generated patch (%s)\n". "Last (up to) %s git changes, FYI:\n\n", @@ -5848,22 +6182,14 @@ sub quiltify ($$$$) { $index++) { } $!==ENOENT or confess "$patchname$index $!"; - runcmd @git, qw(checkout -q), $cc; - - # We use the tip's changelog so that dpkg-source doesn't - # produce complaining messages from dpkg-parsechangelog. None - # of the information dpkg-source gets from the changelog is - # actually relevant - it gets put into the original message - # which dpkg-source provides our stunt editor, and then - # overwritten. - runcmd @git, qw(checkout -q), $target, qw(debian/changelog); - - quiltify_dpkg_commit "$patchname$index", $author, $msg, + quiltify_make_dpkg_patch + $p->{Commit} ,$cc, + "$patchname$index", $author, $msg, "Date: $commitdate\n". "X-Dgit-Generated: $clogp->{Version} $cc\n"; - runcmd @git, qw(checkout -q), $cc, qw(debian/changelog); } + runcmd @git, qw(checkout -q), $target; } sub build_maybe_quilt_fixup () { @@ -5905,12 +6231,19 @@ sub build_maybe_quilt_fixup () { } chdir '..'; - if ($fopts->{'single-debian-patch'}) { + if (grep m{^single-debian-patch$}, @$fopts) { fail f_ "quilt mode %s does not make sense (or is not supported) with single-debian-patch", $quilt_mode if quiltmode_splitting(); - quilt_fixup_singlepatch($clogp, $headref, $upstreamversion); + + # We always use dpkg-source --commit in this case, because + # otherwise we can generate source packages that trigger horrible + # bugs in dpkg-source. + # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1018984 + quilt_fixup_dpkgsource_singlepatch($clogp, $headref, $upstreamversion); + } elsif ($quilt_mode =~ m/single/) { + quilt_fixup_git_singlepatch($clogp, $headref, $upstreamversion); } else { quilt_fixup_multipatch($clogp, $headref, $upstreamversion, $splitbrain_cachekey); @@ -5987,7 +6320,7 @@ END ENDU } -sub quilt_fixup_singlepatch ($$$) { +sub quilt_fixup_dpkgsource_singlepatch ($$$) { my ($clogp, $headref, $upstreamversion) = @_; progress __ "starting quiltify (single-debian-patch)"; @@ -5997,12 +6330,21 @@ sub quilt_fixup_singlepatch ($$$) { # get it to generate debian/patches/debian-changes, it is # necessary to build the source package. + # We "trust" dpkg-source to get this right, and in particular, to + # complain about things it can't represent. However, this is not + # always the case. See the test cases in tests/tests/unrepresentable + # which have as outcome "LATE-EP:...". When this happens, quilt fixup + # succeeds and dgit push fails. This is one of the reasons we + # deprecate this in the docs. In principle, we could do better by + # rejecting these situations earlier, but we would need another + # lot of recapitulation of dpkg-source behaviours. + unpack_playtree_linkorigs($upstreamversion, sub { }); unpack_playtree_need_cd_work($headref); rmtree("debian/patches"); - runcmd @dpkgsource, qw(-b .); + runcmd @dpkgsource, qw(--include-removal -b .); changedir ".."; runcmd @dpkgsource, qw(-x), (srcfn $version, ".dsc"); rename srcfn("$upstreamversion", "/debian/patches"), @@ -6014,6 +6356,53 @@ sub quilt_fixup_singlepatch ($$$) { commit_quilty_patch(); } +sub quilt_fixup_git_singlepatch ($$$) { + my ($clogp, $headref, $upstreamversion) = @_; + + progress f_ "regenerating patch using git diff (--quilt=%s)", + $quilt_mode; + + my $unapplied=quilt_fakedsc2unapplied($headref, $upstreamversion); + changedir '..'; + + my @unrepres; + my $o2h = quiltify_trees_differ($unapplied,$headref, 1, undef,\@unrepres); + unpack_playtree_need_cd_work($headref); + + quiltify_check_unrepresentable \@unrepres; + + rmtree("debian/patches"); + + if ($o2h) { + quiltify_make_dpkg_patch + $unapplied, $headref, + 'dgit-changes', '', <<END, ''; +Debian changes + +The Debian packaging of $package is maintained in git, using a workflow +similar to the one described in dgit-maint-merge(7). +The Debian delta is represented by this one combined patch; there isn't a +patch queue that can be represented as a quilt series. + +A detailed breakdown of the changes is available from their canonical +representation -- git commits in the packaging repository. +For example, to see the changes made by the Debian maintainer in the first +upload of upstream version 1.2.3, you could use: + + % git clone https://git.dgit.debian.org/$package + % cd $package + % git log --oneline 1.2.3..debian/1.2.3-1 -- . ':!debian' + +(If you have dgit, use `dgit clone $package`, rather than plain `git clone`.) + +We don't use debian/source/options single-debian-patch because it has bugs. +Therefore, NMUs etc. may nevertheless have made additional patches. +END + } + + commit_quilty_patch(); +} + sub quilt_need_fake_dsc ($) { # cwd should be playground my ($upstreamversion) = @_; @@ -6050,10 +6439,63 @@ END my @files=qw(debian/source/format debian/rules debian/control debian/changelog); foreach my $maybe (qw(debian/patches debian/source/options + debian/source/include-binaries debian/tests/control)) { next unless stat_exists "$maindir/$maybe"; push @files, $maybe; } + if (open IB, "$maindir/debian/source/include-binaries") { + BFILE: while (<IB>) { + s{^[ \t]*}{}; + s{[ \t\n]*$}{}; + next if m{^\#}; + next unless length; + our $include_binaries_warning; + $include_binaries_warning++ or + print STDERR __ + "warning: package uses dpkg-source include-binaries feature - not all changes are visible in patches!\n"; + + my @bpath; + my $bfile_in = $_; + my $bpath_chk; + foreach my $ent (split m{/}, $bfile_in) { + my $wrong = sub { + no warnings qw(exiting); + print STDERR f_ + "warning: ignoring bad include-binaries file %s: %s\n", $bfile_in, $_[0]; + next BFILE; + }; + $wrong->(f_ "forbidden path component '%s'", $ent) + if grep { $_ eq $ent } '', '.', '..'; + if (!@bpath) { # check first component + # dpkg-source doesn't like files in debian/ which it + # considers binary, so the user may have listed + # them. We should silently ignore this. #1026918. + if ($ent eq 'debian') { + no warnings qw(exiting); + next BFILE; + } + $wrong->(f_ "path starts with '%s'", $ent) + if grep { $_ eq $ent } qw(.git); + } + push @bpath, $ent; + $bpath_chk = join '/', @bpath; + if (!lstat "$maindir/$bpath_chk") { + confess "$maindir/$bpath_chk" unless $!==ENOENT; + next BFILE; + } elsif (-f _ || -d _) { + } else { + $wrong->(f_ "path to '%s' not a plain file or directory", + $bpath_chk); + } + }; + push @files, $bpath_chk; + } + IB->error and confess "$!"; + close IB; + } else { + $! == ENOENT || confess "$!"; + } my $debtar= srcfn $fakeversion,'.debian.tar'; runcmd qw(tar -cf), "./$debtar", qw(-C), $maindir, @files; @@ -6181,44 +6623,22 @@ sub quilt_fixup_multipatch ($$$) { # - determine the git commit corresponding to the tip of # the patch stack (if there is one) # - if there is such a git commit, convert each subsequent - # git commit into a quilt patch with dpkg-source --commit + # git commit into a quilt patch, simulating dpkg-source --commit # - otherwise convert all the differences in the tree into # a single git commit # # To do this we: - # Our git tree doesn't necessarily contain .pc. (Some versions of - # dgit would include the .pc in the git tree.) If there isn't - # one, we need to generate one by unpacking the patches that we - # have. - # - # We first look for a .pc in the git tree. If there is one, we - # will use it. (This is not the normal case.) - # - # Otherwise need to regenerate .pc so that dpkg-source --commit - # can work. We do this as follows: + # So we need to find out what the tree for the tip of the patch + # stack is. # 1. Collect all relevant .orig from parent directory # 2. Generate a debian.tar.gz out of # debian/{patches,rules,source/format,source/options} # 3. Generate a fake .dsc containing just these fields: # Format Source Version Files # 4. Extract the fake .dsc - # Now the fake .dsc has a .pc directory. - # (In fact we do this in every case, because in future we will - # want to search for a good base commit for generating patches.) # - # Then we can actually do the dpkg-source --commit - # 1. Make a new working tree with the same object - # store as our main tree and check out the main - # tree's HEAD. - # 2. Copy .pc from the fake's extraction, if necessary - # 3. Run dpkg-source --commit - # 4. If the result has changes to debian/, then - # - git add them them - # - git add .pc if we had a .pc in-tree - # - git commit - # 5. If we had a .pc in-tree, delete it, and git commit - # 6. Back in the main tree, fast forward to the new HEAD + # Then we can actually do the fake dpkg-source --commit. # Another situation we may have to cope with is gbp-style # patches-unapplied trees. @@ -6275,7 +6695,7 @@ sub quilt_fixup_multipatch ($$$) { } $headref = git_rev_parse('HEAD'); - chdir '..'; + changedir '..'; } my $unapplied=quilt_fakedsc2unapplied($headref, $upstreamversion); @@ -6284,6 +6704,7 @@ sub quilt_fixup_multipatch ($$$) { my @bbcmd = (qw(sh -ec), 'exec dpkg-source --before-build . >/dev/null'); $!=0; $?=-1; + debugcmd "+",@bbcmd; if (system @bbcmd) { failedcmd @bbcmd if $? < 0; fail __ <<END; @@ -6301,7 +6722,7 @@ END my $mustdeletepc=0; if (stat_exists ".pc") { -d _ or die; - progress __ "Tree already contains .pc - will use it then delete it."; + progress __ "Tree already contains .pc - will delete it."; $mustdeletepc=1; } else { rename '../fake/.pc','.pc' or confess "$!"; @@ -6435,17 +6856,7 @@ END $us, $dl[0], $dl[1], $dl[3], $dl[4], $us, $uhead_whatshort, $dl[2], $uhead_whatshort, $dl[5]; - if (@unrepres && $quilt_mode !~ m/baredebian/) { - # With baredebian, even if the upstream commitish has this - # problem, we don't want to print this message, as nothing - # is going to try to make a patch out of it anyway. - print STDERR f_ "dgit: cannot represent change: %s: %s\n", - $_->[1], $_->[0] - foreach @unrepres; - forceable_fail [qw(unrepresentable)], __ <<END; -HEAD has changes to .orig[s] which are not representable by `3.0 (quilt)' -END - } + quiltify_check_unrepresentable(\@unrepres); my @failsuggestion; if ($onlydebian) { @@ -6647,13 +7058,28 @@ sub build_or_push_prep_early () { } sub build_or_push_prep_modes () { - my ($format) = get_source_format(); + my ($format, $fopts) = get_source_format(); determine_whether_split_brain($format); fail __ "dgit: --include-dirty is not supported with split view". " (including with view-splitting quilt modes)" if do_split_brain() && $includedirty; + if (grep m{^tar-ignore$}, @$fopts) { + if ((cmdoutput qw(git ls-files :.gitignore :*/.gitignore)) ne '') { + # The source package won't be faithful; bail with an explanation. + fail __ <<'END'; +tree has .gitignore(s) but debian/source/options has 'tar-ignore' +Try 'tar-ignore=.git' in d/s/options instead. (See #908747.) +END + } else { + print STDERR f_ <<'END', $us; +%s: warning: debian/source/options contains bare 'tar-ignore' +This can cause .gitignore files to be improperly omitted. See #908747. +END + } + } + if (madformat_wantfixup $format and $quilt_mode =~ m/baredebian$/) { ($quilt_upstream_commitish, $quilt_upstream_commitish_used, $quilt_upstream_commitish_message) @@ -7021,7 +7447,7 @@ sub build_source { } # confess unless !!$made_split_brain == do_split_brain(); - my @cmd = (@dpkgsource, qw(-b --)); + my @cmd = (@dpkgsource, qw(-b --include-removal)); my $leafdir; if (building_source_in_playtree()) { $leafdir = 'work'; @@ -7037,6 +7463,14 @@ sub build_source { unpack_playtree_need_cd_work($headref); changedir '..'; } + + # We are presenting dpkg-source with a tree with no .pc directory. + # Without this option, dpkg-source tries to guess if it should + # mess about (un)applying patches. Depending on what precisely is + # in the patches, it can guess wrong. + push @cmd, qw(--no-preparation); + + runcmd @cmd, qw(--), $leafdir; } else { $leafdir = basename $maindir; @@ -7058,13 +7492,18 @@ sub build_source { } changedir '..'; + runcmd_ordryrun_local @cmd, qw(--), $leafdir; } - runcmd_ordryrun_local @cmd, $leafdir; changedir $leafdir; - runcmd_ordryrun_local qw(sh -ec), - 'exec >../$1; shift; exec "$@"','x', $sourcechanges, - @dpkggenchanges, qw(-S), changesopts(); + my @gencmd = (qw(sh -ec), + 'exec >../$1; shift; exec "$@"','x', $sourcechanges, + @dpkggenchanges, qw(-S), changesopts()); + if (building_source_in_playtree()) { + runcmd @gencmd; + } else { + runcmd_ordryrun_local @gencmd; + } changedir '..'; printdebug "moving $dscfn, $sourcechanges, etc. to ".bpd_abs()."\n"; @@ -7073,6 +7512,7 @@ sub build_source { my $mv = sub { my ($why, $l) = @_; printdebug " renaming ($why) $l\n"; + return unless act_local(); rename_link_xf 0, "$l", bpd_abs()."/$l" or fail f_ "put in place new built file (%s): %s", $l, $@; }; @@ -7263,7 +7703,7 @@ sub cmd_import_dsc { # we don't normally need this so import it here use Dpkg::Source::Package; my $dp = new Dpkg::Source::Package filename => $dscfn, - require_valid_signature => $needsig; + options => { require_valid_signature => $needsig }; { local $SIG{__WARN__} = sub { print STDERR $_[0]; @@ -7638,9 +8078,13 @@ sub parseopts () { } elsif (m/^--no-chase-dsc-distro$/s) { push @ropts, $_; $chase_dsc_distro = 0; - } elsif (m/^--overwrite$/s) { + } elsif (m/^--collab-non-dgit$/s) { push @ropts, $_; $overwrite_version = ''; + $splitview_mode = 'always'; + } elsif (m/^(?:--trust-changelog|--overwrite)$/s) { + push @ropts, '--overwrite'; # TODO, eventually, change this + $overwrite_version = ''; } elsif (m/^--split-(?:view|brain)$/s) { push @ropts, $_; $splitview_mode = 'always'; @@ -7663,6 +8107,9 @@ sub parseopts () { push @ropts, $_; $v =~ s#^(?!refs/)#refs/heads/#; $internal_object_save{$k} = $v; + } elsif (m/^--(no-)?keep-playground$/s) { + push @ropts, $_; + $keep_playground = !$1; } elsif (m/^--(no-)?rm-old-changes$/s) { push @ropts, $_; $rmchanges = !$1; @@ -7827,6 +8274,7 @@ sub parseopts_late_defaults () { $quilt_mode = $1; } $quilt_mode =~ s/^(baredebian)\+git$/$1/; + $quilt_mode =~ s/^auto$/try-linear/; foreach my $moc (@modeopt_cfgs) { local $access_forpush; @@ -7870,6 +8318,7 @@ if (!@ARGV) { finish 8; } $cmd = $subcommand = shift @ARGV; +my $orig_cmd = $cmd; $cmd =~ y/-/_/; my $pre_fn = ${*::}{"pre_$cmd"}; @@ -7882,7 +8331,10 @@ if ($invoked_in_git_tree) { git_slurp_config(); my $fn = ${*::}{"cmd_$cmd"}; -$fn or badusage f_ "unknown operation %s", $cmd; +$fn or badusage f_ "unknown operation %s", $orig_cmd; $fn->(); +changedir '/'; # rmtree complains if our cwd is inside what we remove +rmtree $playground if defined($playground) && !$keep_playground; + finish 0; |