chiark / gitweb /
git-debpush: Check upstream source is identical in the upstream tag
[dgit.git] / dgit
diff --git a/dgit b/dgit
index 02cf97d846cd695f158245e6b3a4057dd4ab7958..0e0ff6575c3e79bc67227b809e36c876b53fb4f1 100755 (executable)
--- a/dgit
+++ b/dgit
@@ -2,8 +2,9 @@
 # dgit
 # Integration between git and Debian-style archives
 #
-# Copyright (C)2013-2018 Ian Jackson
-# Copyright (C)2017-2018 Sean Whitton
+# Copyright (C)2013-2019 Ian Jackson
+# Copyright (C)2017-2019 Sean Whitton
+# Copyright (C)2019      Matthew Vernon / Sanger Institute
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -53,7 +54,7 @@ use Debian::Dgit;
 our $our_version = 'UNRELEASED'; ###substituted###
 our $absurdity = undef; ###substituted###
 
-our @rpushprotovsn_support = qw(4 5); # 5 drops tag format specification
+our @rpushprotovsn_support = qw(6 5 4); # Reverse order!
 our $protovsn;
 
 our $cmd;
@@ -80,7 +81,10 @@ our $rmchanges;
 our $overwrite_version; # undef: not specified; '': check changelog
 our $quilt_mode;
 our $quilt_upstream_commitish;
-our $quilt_modes_re = 'linear|smash|auto|nofix|nocheck|gbp|dpm|unapplied|baredebian';
+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 $splitview_mode;
 our $splitview_modes_re = qr{auto|always|never};
 our $dodep14tag;
@@ -101,7 +105,6 @@ our %forceopts = map { $_=>0 }
 
 our %format_ok = map { $_=>1 } ("1.0","3.0 (native)","3.0 (quilt)");
 
-our $suite_re = '[-+.0-9a-z]+';
 our $cleanmode_re = qr{(?: dpkg-source (?: -d )? (?: ,no-check | ,all-check )?
                      | (?: git | git-ff ) (?: ,always )?
                          | check (?: ,ignores )?
@@ -297,6 +300,10 @@ sub deliberately_not_fast_forward () {
 sub quiltmode_splitting () {
     $quilt_mode =~ m/gbp|dpm|unapplied|baredebian/;
 }
+sub format_quiltmode_splitting ($) {
+    my ($format) = @_;
+    return madformat_wantfixup($format) && quiltmode_splitting();
+}
 
 sub do_split_brain () { !!($do_split_brain // confess) }
 
@@ -466,6 +473,7 @@ sub branch_is_gdr ($) {
 #  > param head DGIT-VIEW-HEAD
 #  > param csuite SUITE
 #  > param tagformat new              # $protovsn == 4
+#  > param splitbrain 0|1             # $protovsn >= 6
 #  > param maint-view MAINT-VIEW-HEAD
 #
 #  > param buildinfo-filename P_V_X.buildinfo   # zero or more times
@@ -776,6 +784,12 @@ our %defcfg = ('dgit.default.distro' => 'debian',
  'dgit-distro.debian-backports.mirror' => 'http://backports.debian.org/debian-backports/',
               'dgit-distro.ubuntu.git-check' => 'false',
  'dgit-distro.ubuntu.mirror' => 'http://archive.ubuntu.com/ubuntu',
+              'dgit-distro.ubuntucloud.git-check' => 'false',
+ 'dgit-distro.ubuntucloud.nominal-distro' => 'ubuntu',
+ 'dgit-distro.ubuntucloud.archive-query' => 'aptget:',
+ 'dgit-distro.ubuntucloud.mirror' => 'http://ubuntu-cloud.archive.canonical.com/ubuntu',
+ 'dgit-distro.ubuntucloud.aptget-suite-map' => 's#^([^-]+):([^:]+)$#${1}-updates/$2#; s#^(.+)-(.+):(.+)#$1-$2/$3#;',
+ 'dgit-distro.ubuntucloud.aptget-suite-rmap' => 's#/(.+)$#-$1#',
               'dgit-distro.test-dummy.ssh' => "$td/ssh",
               'dgit-distro.test-dummy.username' => "alice",
               'dgit-distro.test-dummy.git-check' => "ssh-cmd",
@@ -970,9 +984,8 @@ sub notpushing () {
     parseopts_late_defaults();
 }
 
-sub determine_whether_split_brain () {
-    my ($format,) = get_source_format();
-
+sub determine_whether_split_brain ($) {
+    my ($format) = @_;
     {
        local $access_forpush;
        default_from_access_cfg(\$splitview_mode, 'split-view', 'auto',
@@ -982,7 +995,7 @@ sub determine_whether_split_brain () {
 
     printdebug "format $format, quilt mode $quilt_mode\n";
 
-    if (madformat_wantfixup($format) && quiltmode_splitting()) {
+    if (format_quiltmode_splitting $format) {
        $splitview_mode ne 'never' or
            fail f_ "dgit: quilt mode \`%s' (for format \`%s')".
                    " implies split view, but split-view set to \`%s'",
@@ -990,8 +1003,6 @@ sub determine_whether_split_brain () {
        $do_split_brain = 1;
     }
     $do_split_brain //= 0;
-
-    return ($format);
 }
 
 sub supplementary_message ($) {
@@ -1431,11 +1442,11 @@ sub canonicalise_suite_aptget {
        my $val = $release->{$name};
        if (defined $val) {
            printdebug "release file $name: $val\n";
+           cfg_apply_map(\$val, 'suite rmap',
+                         access_cfg('aptget-suite-rmap', 'RETURN-UNDEF'));
            $val =~ m/^$suite_re$/o or fail f_
                "Release file (%s) specifies intolerable %s",
                $aptget_releasefile, $name;
-           cfg_apply_map(\$val, 'suite rmap',
-                         access_cfg('aptget-suite-rmap', 'RETURN-UNDEF'));
            return $val
        }
     }
@@ -2268,62 +2279,9 @@ sub dotdot_bpd_transfer_origs ($$$) {
     closedir DD;
 }
 
-sub generate_commits_from_dsc () {
-    # See big comment in fetch_from_archive, below.
-    # See also README.dsc-import.
-    prep_ud();
-    changedir $playground;
-
-    my $bpd_abs = bpd_abs();
-    my $upstreamv = upstreamversion $dsc->{version};
-    my @dfi = dsc_files_info();
-
-    dotdot_bpd_transfer_origs $bpd_abs, $upstreamv,
-       sub { grep { $_->{Filename} eq $_[0] } @dfi };
-
-    foreach my $fi (@dfi) {
-       my $f = $fi->{Filename};
-       die "$f ?" if $f =~ m#/|^\.|\.dsc$|\.tmp$#;
-       my $upper_f = "$bpd_abs/$f";
-
-       printdebug "considering reusing $f: ";
-
-       if (link_ltarget "$upper_f,fetch", $f) {
-           printdebug "linked (using ...,fetch).\n";
-       } elsif ((printdebug "($!) "),
-                $! != ENOENT) {
-           fail f_ "accessing %s: %s", "$buildproductsdir/$f,fetch", $!;
-       } elsif (link_ltarget $upper_f, $f) {
-           printdebug "linked.\n";
-       } elsif ((printdebug "($!) "),
-                $! != ENOENT) {
-           fail f_ "accessing %s: %s", "$buildproductsdir/$f", $!;
-       } else {
-           printdebug "absent.\n";
-       }
-
-       my $refetched;
-       complete_file_from_dsc('.', $fi, \$refetched)
-           or next;
-
-       printdebug "considering saving $f: ";
-
-       if (rename_link_xf 1, $f, $upper_f) {
-           printdebug "linked.\n";
-       } elsif ((printdebug "($@) "),
-                $! != EEXIST) {
-           fail f_ "saving %s: %s", "$buildproductsdir/$f", $@;
-       } elsif (!$refetched) {
-           printdebug "no need.\n";
-       } elsif (rename_link_xf 1, $f, "$upper_f,fetch") {
-           printdebug "linked (using ...,fetch).\n";
-       } elsif ((printdebug "($@) "),
-                $! != EEXIST) {
-           fail f_ "saving %s: %s", "$buildproductsdir/$f,fetch", $@;
-       } else {
-           printdebug "cannot.\n";
-       }
-    }
+sub import_tarball_tartrees ($$) {
+    my ($upstreamv, $dfi) = @_;
+    # cwd should be the playground
 
     # We unpack and record the orig tarballs first, so that we only
     # need disk space for one private copy of the unpacked source.
@@ -2333,14 +2291,13 @@ sub generate_commits_from_dsc () {
     my @tartrees;
     my $orig_f_base = srcfn $upstreamv, '';
 
-    foreach my $fi (@dfi) {
+    foreach my $fi (@$dfi) {
        # We actually import, and record as a commit, every tarball
        # (unless there is only one file, in which case there seems
        # little point.
 
        my $f = $fi->{Filename};
        printdebug "import considering $f ";
-       (printdebug "only one dfi\n"), next if @dfi == 1;
        (printdebug "not tar\n"), next unless $f =~ m/\.tar(\.\w+)?$/;
        (printdebug "signature\n"), next if $f =~ m/$orig_f_sig_re$/o;
        my $compr_ext = $1;
@@ -2352,6 +2309,7 @@ sub generate_commits_from_dsc () {
                          $compr_ext, $orig_f_part
                         ), "\n";
 
+       my $path = $fi->{Path} // $f;
        my $input = new IO::File $f, '<' or die "$f $!";
        my $compr_pid;
        my @compr_cmd;
@@ -2417,6 +2375,7 @@ sub generate_commits_from_dsc () {
             Sort => (!$orig_f_part         ? 2 :
                     $orig_f_part =~ m/-/g ? 1 :
                                             0),
+            OrigPart => $orig_f_part, # 'orig', 'orig-XXX', or undef 
             F => $f,
             Tree => $tree,
         };
@@ -2430,36 +2389,15 @@ sub generate_commits_from_dsc () {
        $a->{F}    cmp $b->{F}
     } @tartrees;
 
-    my $any_orig = grep { $_->{Orig} } @tartrees;
-
-    my $dscfn = "$package.dsc";
-
-    my $treeimporthow = 'package';
-
-    open D, ">", $dscfn or die "$dscfn: $!";
-    print D $dscdata or die "$dscfn: $!";
-    close D or die "$dscfn: $!";
-    my @cmd = qw(dpkg-source);
-    push @cmd, '--no-check' if $dsc_checked;
-    if (madformat $dsc->{format}) {
-       push @cmd, '--skip-patches';
-       $treeimporthow = 'unpatched';
-    }
-    push @cmd, qw(-x --), $dscfn;
-    runcmd @cmd;
+    @tartrees;
+}
 
-    my ($tree,$dir) = mktree_in_ud_from_only_subdir(__ "source package");
-    if (madformat $dsc->{format}) { 
-       check_for_vendor_patches();
-    }
+sub import_tarball_commits ($$) {
+    my ($tartrees, $upstreamv) = @_;
+    # cwd should be a playtree which has a relevant debian/changelog
+    # fills in $tt->{Commit} for each one
 
-    my $dappliedtree;
-    if (madformat $dsc->{format}) {
-       my @pcmd = qw(dpkg-source --before-build .);
-       runcmd shell_cmd 'exec >/dev/null', @pcmd;
-       rmtree '.pc';
-       $dappliedtree = git_add_write_tree();
-    }
+    my $any_orig = grep { $_->{Orig} } @$tartrees;
 
     my @clogcmd = qw(dpkg-parsechangelog --format rfc822 --all);
     my $clogp;
@@ -2513,19 +2451,21 @@ sub generate_commits_from_dsc () {
     $changes =~ s/^\n//; # Changes: \n
     my $cversion = getfield $clogp, 'Version';
 
-    if (@tartrees) {
+    my $r1authline;
+    if (@$tartrees) {
        $r1clogp //= $clogp; # maybe there's only one entry;
-       my $r1authline = clogp_authline $r1clogp;
+        $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";
 
-       foreach my $tt (@tartrees) {
+       foreach my $tt (@$tartrees) {
            printdebug "import tartree $tt->{F} $tt->{Tree}\n";
 
-           my $mbody = f_ "Import %s", $tt->{F};
+           # untranslated so that different people's imports are identical
+           my $mbody = sprintf "Import %s", $tt->{F};
            $tt->{Commit} = hash_commit_text($tt->{Orig} ? <<END_O : <<END_T);
 tree $tt->{Tree}
 author $r1authline
@@ -2546,6 +2486,104 @@ END_T
        }
     }
 
+    return ($authline, $r1authline, $clogp, $changes);
+}
+
+sub generate_commits_from_dsc () {
+    # See big comment in fetch_from_archive, below.
+    # See also README.dsc-import.
+    prep_ud();
+    changedir $playground;
+
+    my $bpd_abs = bpd_abs();
+    my $upstreamv = upstreamversion $dsc->{version};
+    my @dfi = dsc_files_info();
+
+    dotdot_bpd_transfer_origs $bpd_abs, $upstreamv,
+       sub { grep { $_->{Filename} eq $_[0] } @dfi };
+
+    foreach my $fi (@dfi) {
+       my $f = $fi->{Filename};
+       die "$f ?" if $f =~ m#/|^\.|\.dsc$|\.tmp$#;
+       my $upper_f = "$bpd_abs/$f";
+
+       printdebug "considering reusing $f: ";
+
+       if (link_ltarget "$upper_f,fetch", $f) {
+           printdebug "linked (using ...,fetch).\n";
+       } elsif ((printdebug "($!) "),
+                $! != ENOENT) {
+           fail f_ "accessing %s: %s", "$buildproductsdir/$f,fetch", $!;
+       } elsif (link_ltarget $upper_f, $f) {
+           printdebug "linked.\n";
+       } elsif ((printdebug "($!) "),
+                $! != ENOENT) {
+           fail f_ "accessing %s: %s", "$buildproductsdir/$f", $!;
+       } else {
+           printdebug "absent.\n";
+       }
+
+       my $refetched;
+       complete_file_from_dsc('.', $fi, \$refetched)
+           or next;
+
+       printdebug "considering saving $f: ";
+
+       if (rename_link_xf 1, $f, $upper_f) {
+           printdebug "linked.\n";
+       } elsif ((printdebug "($@) "),
+                $! != EEXIST) {
+           fail f_ "saving %s: %s", "$buildproductsdir/$f", $@;
+       } elsif (!$refetched) {
+           printdebug "no need.\n";
+       } elsif (rename_link_xf 1, $f, "$upper_f,fetch") {
+           printdebug "linked (using ...,fetch).\n";
+       } elsif ((printdebug "($@) "),
+                $! != EEXIST) {
+           fail f_ "saving %s: %s", "$buildproductsdir/$f,fetch", $@;
+       } else {
+           printdebug "cannot.\n";
+       }
+    }
+
+    my @tartrees;
+    @tartrees = import_tarball_tartrees($upstreamv, \@dfi)
+       unless @dfi == 1; # only one file in .dsc
+
+    my $dscfn = "$package.dsc";
+
+    my $treeimporthow = 'package';
+
+    open D, ">", $dscfn or die "$dscfn: $!";
+    print D $dscdata or die "$dscfn: $!";
+    close D or die "$dscfn: $!";
+    my @cmd = qw(dpkg-source);
+    push @cmd, '--no-check' if $dsc_checked;
+    if (madformat $dsc->{format}) {
+       push @cmd, '--skip-patches';
+       $treeimporthow = 'unpatched';
+    }
+    push @cmd, qw(-x --), $dscfn;
+    runcmd @cmd;
+
+    my ($tree,$dir) = mktree_in_ud_from_only_subdir(__ "source package");
+    if (madformat $dsc->{format}) { 
+       check_for_vendor_patches();
+    }
+
+    my $dappliedtree;
+    if (madformat $dsc->{format}) {
+       my @pcmd = qw(dpkg-source --before-build .);
+       runcmd shell_cmd 'exec >/dev/null', @pcmd;
+       rmtree '.pc';
+       $dappliedtree = git_add_write_tree();
+    }
+
+    my ($authline, $r1authline, $clogp, $changes) =
+       import_tarball_commits(\@tartrees, $upstreamv);
+
+    my $cversion = getfield $clogp, 'Version';
+
     printdebug "import main commit\n";
 
     open C, ">../commit.tmp" or confess "$!";
@@ -2665,7 +2703,10 @@ END
        if ($vcmp < 0) {
            @output = ($rawimport_mergeinput, $lastpush_mergeinput,
                { ReverseParents => 1,
-                 Message => (f_ <<END, $package, $cversion, $csuite) });
+                 # untranslated so that different people's pseudomerges
+                 # are not needlessly different (although they will
+                 # still differ if the series of pulls is different)
+                 Message => (sprintf <<END, $package, $cversion, $csuite) });
 Record %s (%s) in archive suite %s
 END
        } elsif ($vcmp > 0) {
@@ -4324,11 +4365,11 @@ sub push_mktags ($$ $$ $) {
 
     my $cversion = getfield $clogp, 'Version';
     my $clogsuite = getfield $clogp, 'Distribution';
+    my $format = getfield $dsc, 'Format';
 
     # We make the git tag by hand because (a) that makes it easier
     # to control the "tagger" (b) we can do remote signing
     my $authline = clogp_authline $clogp;
-    my $delibs = join(" ", "",@deliberatelies);
 
     my $mktag = sub {
        my ($tw) = @_;
@@ -4344,22 +4385,33 @@ tag $tag
 tagger $authline
 
 END
+
+       my @dtxinfo = @deliberatelies;
+       unshift @dtxinfo, "--quilt=$quilt_mode" if madformat($format);
+       unshift @dtxinfo, do_split_brain() ? "split" : "no-split"
+           # rpush protocol 5 and earlier don't tell us
+           unless $we_are_initiator && $protovsn < 6;
+       my $dtxinfo = join(" ", "",@dtxinfo);
+       my $tag_metadata = <<END;
+[dgit distro=$declaredistro$dtxinfo]
+END
+       foreach my $ref (sort keys %previously) {
+           $tag_metadata .= <<END or confess "$!";
+[dgit previously:$ref=$previously{$ref}]
+END
+       }
+
        if ($tw->{View} eq 'dgit') {
-           print TO f_ <<ENDT, $package, $cversion, $clogsuite, $csuite
+           print TO sprintf <<ENDT, $package, $cversion, $clogsuite, $csuite
 %s release %s for %s (%s) [dgit]
 ENDT
                or confess "$!";
-           print TO <<END or confess "$!";
-[dgit distro=$declaredistro$delibs]
-END
-           foreach my $ref (sort keys %previously) {
-               print TO <<END or confess "$!";
-[dgit previously:$ref=$previously{$ref}]
-END
-           }
        } elsif ($tw->{View} eq 'maint') {
-           print TO f_ <<END, $package, $cversion, $clogsuite, $csuite,
+           print TO sprintf <<END, $package, $cversion, $clogsuite, $csuite;
 %s release %s for %s (%s)
+
+END
+           print TO f_ <<END,
 (maintainer view tag generated by dgit --quilt=%s)
 END
                $quilt_mode
@@ -4367,6 +4419,7 @@ END
        } else {
            confess Dumper($tw)."?";
        }
+       print TO "\n", $tag_metadata;
 
        close TO or confess "$!";
 
@@ -4645,6 +4698,7 @@ ENDT
     responder_send_command("param csuite $csuite");
     responder_send_command("param isuite $isuite");
     responder_send_command("param tagformat new"); # needed in $protovsn==4
+    responder_send_command("param splitbrain $do_split_brain");
     if (defined $maintviewhead) {
        responder_send_command("param maint-view $maintviewhead");
     }
@@ -4848,7 +4902,7 @@ sub cmd_fetch {
 sub cmd_pull {
     parseopts();
     fetchpullargs();
-    determine_whether_split_brain();
+    determine_whether_split_brain get_source_format();
     if (do_split_brain()) {
        my ($format, $fopts) = get_source_format();
        madformat($format) and fail f_ <<END, $quilt_mode
@@ -5143,6 +5197,7 @@ sub i_resp_previously ($) {
 }
 
 our %i_wanted;
+our ($i_clogp, $i_version, $i_dscfn, $i_changesfn, @i_buildinfos);
 
 sub i_resp_want ($) {
     my ($keyword) = @_;
@@ -5152,8 +5207,18 @@ sub i_resp_want ($) {
     $isuite = $i_param{'isuite'} // $i_param{'csuite'};
     die unless $isuite =~ m/^$suite_re$/;
 
-    pushing();
-    rpush_handle_protovsn_bothends();
+    if (!defined $dsc) {
+       pushing();
+       rpush_handle_protovsn_bothends();
+       push_parse_dsc $i_dscfn, 'remote dsc', $i_version;
+       if ($protovsn >= 6) {
+           determine_whether_split_brain getfield $dsc, 'Format';
+           $do_split_brain eq ($i_param{'splitbrain'} // '<unsent>')
+               or badproto \*RO,
+ "split brain mismatch, $do_split_brain != $i_param{'split_brain'}";
+           printdebug "rpush split brain $do_split_brain\n";
+       }
+    }
 
     my @localpaths = i_method "i_want", $keyword;
     printdebug "[[  $keyword @localpaths\n";
@@ -5163,8 +5228,6 @@ sub i_resp_want ($) {
     print RI "files-end\n" or confess "$!";
 }
 
-our ($i_clogp, $i_version, $i_dscfn, $i_changesfn, @i_buildinfos);
-
 sub i_localname_parsed_changelog {
     return "remote-changelog.822";
 }
@@ -5232,7 +5295,7 @@ sub i_want_signed_tag {
 
     die unless $i_param{'csuite'} =~ m/^$suite_re$/;
     $csuite = $&;
-    push_parse_dsc $i_dscfn, 'remote dsc', $i_version;
+    defined $dsc or badproto \*RO, "dsc (before parsed-changelog)";
 
     my @tagwants = push_tagwants $i_version, $head, $maintview, "tag";
 
@@ -5409,13 +5472,14 @@ END
        # upstream and debian/ files from HEAD.
 
        read_tree_upstream $quilt_upstream_commitish, 1, $headref;
-       my $upsversion = upstreamversion getfield $clogp, 'Version';
+       my $version = getfield $clogp, 'Version';
+       my $upsversion = upstreamversion $version;
        my $merge = make_commit
            [ $headref, $quilt_upstream_commitish ],
          [ +(f_ <<ENDT, $upsversion), <<ENDU ];
[ +(f_ <<ENDT, $upsversion), $quilt_upstream_commitish_message, <<ENDU ];
 Combine debian/ with upstream source for %s
 ENDT
-[dgit ($our_version) baredebian-merge]
+[dgit ($our_version) baredebian-merge $version $quilt_upstream_commitish_used]
 ENDU
        runcmd @git, qw(reset -q --hard), $merge;
     }
@@ -6037,6 +6101,30 @@ sub quilt_check_splitbrain_cache ($$) {
     return (undef, $splitbrain_cachekey);
 }
 
+sub baredebian_origtarballs_scan ($$$) {
+    my ($fakedfi, $upstreamversion, $dir) = @_;
+    if (!opendir OD, $dir) {
+       return if $! == ENOENT;
+       fail "opendir $dir (origs): $!";
+    }
+
+    while ($!=0, defined(my $leaf = readdir OD)) {
+       {
+           local ($debuglevel) = $debuglevel-1;
+           printdebug "BDOS $dir $leaf ?\n";
+       }
+       next unless is_orig_file_of_vsn $leaf, $upstreamversion;
+       next if grep { $_->{Filename} eq $leaf } @$fakedfi;
+       push @$fakedfi, {
+            Filename => $leaf,
+            Path => "$dir/$leaf",
+                       };
+    }
+
+    die "$dir; $!" if $!;
+    closedir OD;
+}
+
 sub quilt_fixup_multipatch ($$$) {
     my ($clogp, $headref, $upstreamversion, $splitbrain_cachekey) = @_;
 
@@ -6190,8 +6278,79 @@ END
     my $uheadref = $headref;
     my $uhead_whatshort = 'HEAD';
 
-    if ($quilt_mode =~ m/baredebian/) {
+    if ($quilt_mode =~ m/baredebian\+tarball/) {
+       # We need to make a tarball import.  Yuk.
+       # We want to do this here so that we have a $uheadref value
+
+       my @fakedfi;
+       baredebian_origtarballs_scan \@fakedfi, $upstreamversion, bpd_abs();
+       baredebian_origtarballs_scan \@fakedfi, $upstreamversion,
+           "$maindir/.." unless $buildproductsdir eq '..';
+       changedir '..';
+
+       my @tartrees = import_tarball_tartrees $upstreamversion, \@fakedfi;
+
+       fail __ "baredebian quilt fixup: could not find any origs"
+           unless @tartrees;
+
+       changedir 'work';
+       my ($authline, $r1authline, $clogp,) =
+           import_tarball_commits \@tartrees, $upstreamversion;
+
+       if (@tartrees == 1) {
+           $uheadref = $tartrees[0]{Commit};
+           # TRANSLATORS: this translation must fit in the ASCII art
+           # quilt differences display.  The untranslated display
+           # says %9.9s, so with that display it must be at most 9
+           # characters.
+           $uhead_whatshort = __ 'tarball';
+       } else {
+           # on .dsc import we do not make a separate commit, but
+           # here we need to do so
+           rm_subdir_cached '.';
+           my $parents;
+           foreach my $ti (@tartrees) {
+               my $c = $ti->{Commit};
+               if ($ti->{OrigPart} eq 'orig') {
+                   runcmd qw(git read-tree), $c;
+               } elsif ($ti->{OrigPart} =~ m/orig-/) {
+                   read_tree_subdir $', $c;
+               } else {
+                   confess "$ti->OrigPart} ?"
+               }
+               $parents .= "parent $c\n";
+           }
+           my $tree = git_write_tree();
+           my $mbody = f_ 'Combine orig tarballs for %s %s',
+               $package, $upstreamversion;
+           $uheadref = hash_commit_text <<END;
+tree $tree
+${parents}author $r1authline
+committer $r1authline
+
+$mbody
+
+[dgit import tarballs combine $package $upstreamversion]
+END
+           # TRANSLATORS: this translation must fit in the ASCII art
+           # quilt differences display.  The untranslated display
+           # says %9.9s, so with that display it must be at most 9
+           # characters.  This fragmentt is referring to multiple
+           # orig tarballs in a source package.
+           $uhead_whatshort = __ 'tarballs';
+
+           runcmd @git, qw(reset -q);
+       }
+       $quilt_upstream_commitish = $uheadref;
+       $quilt_upstream_commitish_used = '*orig*';
+       $quilt_upstream_commitish_message = '';
+    }
+    if ($quilt_mode =~ m/baredebian$/) {
        $uheadref = $quilt_upstream_commitish;
+       # TRANSLATORS: this translation must fit in the ASCII art
+       # quilt differences display.  The untranslated display
+       # says %9.9s, so with that display it must be at most 9
+       # characters.
        $uhead_whatshort = __ 'upstream';
     }
 
@@ -6218,6 +6377,13 @@ END
     progress f_
 "%s: base trees orig=%.20s o+d/p=%.20s",
               $us, $unapplied, $oldtiptree;
+    # TRANSLATORS: Try to keep this ascii-art layout right.  The 0s in
+    # %9.00009s will be ignored and are there to make the format the
+    # same length (9 characters) as the output it generates.  If you
+    # change the value 9, your translations of "upstream" and
+    # 'tarball' must fit into the new length, and you should change
+    # the number of 0s.  Do not reduce it below 4 as HEAD has to fit
+    # too.
     progress f_
 "%s: quilt differences: src:  %s orig %s     gitignores:  %s orig %s\n".
 "%s: quilt differences: %9.00009s %s o+d/p          %9.00009s %s o+d/p",
@@ -6376,6 +6542,18 @@ sub clean_tree_check () {
 sub clean_tree () {
     # We always clean the tree ourselves, rather than leave it to the
     # builder (dpkg-source, or soemthing which calls dpkg-source).
+    if ($quilt_mode =~ m/baredebian/ and $cleanmode =~ m/git/) {
+       fail f_ <<END, $quilt_mode, $cleanmode;
+quilt mode %s (generally needs untracked upstream files)
+contradicts clean mode %s (which would delete them)
+END
+       # This is not 100% true: dgit build-source and push-source
+       # (for example) could operate just fine with no upstream
+       # source in the working tree.  But it doesn't seem likely that
+       # the user wants dgit to proactively delete such things.
+       # -wn, for example, would produce identical output without
+       # deleting anything from the working tree.
+    }
     if ($cleanmode =~ m{^dpkg-source}) {
        my @cmd = @dpkgbuildpackage;
        push @cmd, qw(-d) if $cleanmode =~ m{^dpkg-source-d};
@@ -6410,28 +6588,33 @@ sub WANTSRC_BUILDER () { 02; } # caller should run dpkg-buildpackage
 sub build_or_push_prep_early () {
     our $build_or_push_prep_early_done //= 0;
     return if $build_or_push_prep_early_done++;
-    badusage f_ "-p is not allowed with dgit %s", $subcommand
-       if defined $package;
     my $clogp = parsechangelog();
     $isuite = getfield $clogp, 'Distribution';
-    $package = getfield $clogp, 'Source';
+    my $gotpackage = getfield $clogp, 'Source';
     $version = getfield $clogp, 'Version';
+    $package //= $gotpackage;
+    if ($package ne $gotpackage) {
+       fail f_ "-p specified package %s, but changelog says %s",
+           $package, $gotpackage;
+    }
     $dscfn = dscfn($version);
 }
 
 sub build_or_push_prep_modes () {
-    my ($format,) = determine_whether_split_brain();
+    my ($format) = 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 (madformat_wantfixup $format and $quilt_mode =~ m/baredebian$/) {
-       my ($dummy, $umessage);
-       ($quilt_upstream_commitish, $dummy, $umessage) =
-       resolve_upstream_version
+       ($quilt_upstream_commitish, $quilt_upstream_commitish_used,
+        $quilt_upstream_commitish_message)
+           = resolve_upstream_version
            $quilt_upstream_commitish, upstreamversion $version;
-       progress f_ "dgit: --quilt=%s, %s", $quilt_mode, $umessage;
+       progress f_ "dgit: --quilt=%s, %s", $quilt_mode,
+           $quilt_upstream_commitish_message;
     } elsif (defined $quilt_upstream_commitish) {
        fail __
  "dgit: --upstream-commitish only makes sense with --quilt=baredebian"
@@ -7367,7 +7550,7 @@ sub parseopts () {
                push @ropts, $_;
                my $cmd = shift @$om;
                @$om = ($cmd, grep { $_ ne $2 } @$om);
-           } elsif (m/^--(gbp|dpm|baredebian)$/s) {
+           } elsif (m/^--($quilt_options_re)$/s) {
                push @ropts, "--quilt=$1";
                $quilt_mode = $1;
            } elsif (m/^--(?:ignore|include)-dirty$/s) {
@@ -7567,6 +7750,7 @@ sub parseopts_late_defaults () {
            or badcfg f_ "unknown quilt-mode \`%s'", $quilt_mode;
        $quilt_mode = $1;
     }
+    $quilt_mode =~ s/^(baredebian)\+git$/$1/;
 
     foreach my $moc (@modeopt_cfgs) {
        local $access_forpush;