chiark / gitweb /
Revert "Dgit: Introduce in_workarea and fresh_workarea"
[dgit.git] / dgit
diff --git a/dgit b/dgit
index b17ed210119318196bfaa6ae26dc5ea94a341531..827dc62d0c866464b32123a44d92f6a9b78bba16 100755 (executable)
--- a/dgit
+++ b/dgit
@@ -34,8 +34,9 @@ use POSIX;
 use IPC::Open2;
 use Digest::SHA;
 use Digest::MD5;
 use IPC::Open2;
 use Digest::SHA;
 use Digest::MD5;
-use List::Util qw(any);
 use List::MoreUtils qw(pairwise);
 use List::MoreUtils qw(pairwise);
+use Text::Glob qw(match_glob);
+use Fcntl qw(:DEFAULT :flock);
 use Carp;
 
 use Debian::Dgit;
 use Carp;
 
 use Debian::Dgit;
@@ -46,7 +47,9 @@ our $absurdity = undef; ###substituted###
 our @rpushprotovsn_support = qw(4 3 2); # 4 is new tag format
 our $protovsn;
 
 our @rpushprotovsn_support = qw(4 3 2); # 4 is new tag format
 our $protovsn;
 
-our $isuite = 'unstable';
+our $cmd;
+our $subcommand;
+our $isuite;
 our $idistro;
 our $package;
 our @ropts;
 our $idistro;
 our $package;
 our @ropts;
@@ -67,13 +70,16 @@ our $rmchanges;
 our $overwrite_version; # undef: not specified; '': check changelog
 our $quilt_mode;
 our $quilt_modes_re = 'linear|smash|auto|nofix|nocheck|gbp|dpm|unapplied';
 our $overwrite_version; # undef: not specified; '': check changelog
 our $quilt_mode;
 our $quilt_modes_re = 'linear|smash|auto|nofix|nocheck|gbp|dpm|unapplied';
+our $dodep14tag;
 our $split_brain_save;
 our $we_are_responder;
 our $split_brain_save;
 our $we_are_responder;
+our $we_are_initiator;
 our $initiator_tempdir;
 our $patches_applied_dirtily = 00;
 our $tagformat_want;
 our $tagformat;
 our $tagformatfn;
 our $initiator_tempdir;
 our $patches_applied_dirtily = 00;
 our $tagformat_want;
 our $tagformat;
 our $tagformatfn;
+our $chase_dsc_distro=1;
 
 our %forceopts = map { $_=>0 }
     qw(unrepresentable unsupported-source-format
 
 our %forceopts = map { $_=>0 }
     qw(unrepresentable unsupported-source-format
@@ -92,16 +98,19 @@ our $orig_f_tail_re = "$orig_f_comp_re\\.tar(?:\\.\\w+)?(?:$orig_f_sig_re)?";
 
 our $git_authline_re = '^([^<>]+) \<(\S+)\> (\d+ [-+]\d+)$';
 our $splitbraincache = 'dgit-intern/quilt-cache';
 
 our $git_authline_re = '^([^<>]+) \<(\S+)\> (\d+ [-+]\d+)$';
 our $splitbraincache = 'dgit-intern/quilt-cache';
+our $rewritemap = 'dgit-rewrite/map';
 
 our (@git) = qw(git);
 our (@dget) = qw(dget);
 
 our (@git) = qw(git);
 our (@dget) = qw(dget);
-our (@curl) = qw(curl);
+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);
 our (@ssh) = 'ssh';
 our (@dgit) = qw(dgit);
 our (@dput) = qw(dput);
 our (@debsign) = qw(debsign);
 our (@gpg) = qw(gpg);
 our (@sbuild) = qw(sbuild);
 our (@ssh) = 'ssh';
 our (@dgit) = qw(dgit);
+our (@aptget) = qw(apt-get);
+our (@aptcache) = qw(apt-cache);
 our (@dpkgbuildpackage) = qw(dpkg-buildpackage -i\.git/ -I.git);
 our (@dpkgsource) = qw(dpkg-source -i\.git/ -I.git);
 our (@dpkggenchanges) = qw(dpkg-genchanges);
 our (@dpkgbuildpackage) = qw(dpkg-buildpackage -i\.git/ -I.git);
 our (@dpkgsource) = qw(dpkg-source -i\.git/ -I.git);
 our (@dpkggenchanges) = qw(dpkg-genchanges);
@@ -119,6 +128,8 @@ our %opts_opt_map = ('dget' => \@dget, # accept for compatibility
                      'ssh' => \@ssh,
                      'dgit' => \@dgit,
                      'git' => \@git,
                      'ssh' => \@ssh,
                      'dgit' => \@dgit,
                      'git' => \@git,
+                     'apt-get' => \@aptget,
+                     'apt-cache' => \@aptcache,
                      'dpkg-source' => \@dpkgsource,
                      'dpkg-buildpackage' => \@dpkgbuildpackage,
                      'dpkg-genchanges' => \@dpkggenchanges,
                      'dpkg-source' => \@dpkgsource,
                      'dpkg-buildpackage' => \@dpkgbuildpackage,
                      'dpkg-genchanges' => \@dpkggenchanges,
@@ -133,7 +144,9 @@ our %opts_cfg_insertpos = map {
     scalar @{ $opts_opt_map{$_} }
 } keys %opts_opt_map;
 
     scalar @{ $opts_opt_map{$_} }
 } keys %opts_opt_map;
 
-sub finalise_opts_opts();
+sub parseopts_late_defaults();
+sub setup_gitattrs(;$);
+sub check_gitattrs($$);
 
 our $keyid;
 
 
 our $keyid;
 
@@ -145,6 +158,7 @@ our $split_brain = 0;
 
 END {
     local ($@, $?);
 
 END {
     local ($@, $?);
+    return unless forkcheck_mainprocess();
     print STDERR "! $_\n" foreach $supplementary_message =~ m/^.+$/mg;
 }
 
     print STDERR "! $_\n" foreach $supplementary_message =~ m/^.+$/mg;
 }
 
@@ -165,8 +179,7 @@ sub debiantag ($$) {
 
 sub debiantag_maintview ($$) { 
     my ($v,$distro) = @_;
 
 sub debiantag_maintview ($$) { 
     my ($v,$distro) = @_;
-    $v =~ y/~:/_%/;
-    return "$distro/$v";
+    return "$distro/".dep14_version_mangle $v;
 }
 
 sub madformat ($) { $_[0] eq '3.0 (quilt)' }
 }
 
 sub madformat ($) { $_[0] eq '3.0 (quilt)' }
@@ -177,30 +190,6 @@ sub lref () { return "refs/heads/".lbranch(); }
 sub lrref () { return "refs/remotes/$remotename/".server_branch($csuite); }
 sub rrref () { return server_ref($csuite); }
 
 sub lrref () { return "refs/remotes/$remotename/".server_branch($csuite); }
 sub rrref () { return server_ref($csuite); }
 
-sub lrfetchrefs () { return "refs/dgit-fetch/$csuite"; }
-sub lrfetchref () { return lrfetchrefs.'/'.server_branch($csuite); }
-
-# We fetch some parts of lrfetchrefs/*.  Ideally we delete these
-# locally fetched refs because they have unhelpful names and clutter
-# up gitk etc.  So we track whether we have "used up" head ref (ie,
-# whether we have made another local ref which refers to this object).
-#
-# (If we deleted them unconditionally, then we might end up
-# re-fetching the same git objects each time dgit fetch was run.)
-#
-# So, leach use of lrfetchrefs needs to be accompanied by arrangements
-# in git_fetch_us to fetch the refs in question, and possibly a call
-# to lrfetchref_used.
-
-our (%lrfetchrefs_f, %lrfetchrefs_d);
-# $lrfetchrefs_X{lrfetchrefs."/heads/whatever"} = $objid
-
-sub lrfetchref_used ($) {
-    my ($fullrefname) = @_;
-    my $objid = $lrfetchrefs_f{$fullrefname};
-    $lrfetchrefs_d{$fullrefname} = $objid if defined $objid;
-}
-
 sub stripepoch ($) {
     my ($vsn) = @_;
     $vsn =~ s/^\d+\://;
 sub stripepoch ($) {
     my ($vsn) = @_;
     $vsn =~ s/^\d+\://;
@@ -234,6 +223,7 @@ initdebug('');
 our @end;
 END { 
     local ($?);
 our @end;
 END { 
     local ($?);
+    return unless forkcheck_mainprocess();
     foreach my $f (@end) {
        eval { $f->(); };
        print STDERR "$us: cleanup: $@" if length $@;
     foreach my $f (@end) {
        eval { $f->(); };
        print STDERR "$us: cleanup: $@" if length $@;
@@ -261,12 +251,6 @@ sub no_such_package () {
     exit 4;
 }
 
     exit 4;
 }
 
-sub changedir ($) {
-    my ($newdir) = @_;
-    printdebug "CD $newdir\n";
-    chdir $newdir or confess "chdir: $newdir: $!";
-}
-
 sub deliberately ($) {
     my ($enquiry) = @_;
     return !!grep { $_ eq "--deliberately-$enquiry" } @deliberatelies;
 sub deliberately ($) {
     my ($enquiry) = @_;
     return !!grep { $_ eq "--deliberately-$enquiry" } @deliberatelies;
@@ -328,6 +312,9 @@ sub gbp_pq {
 #  > param tagformat old|new
 #  > param maint-view MAINT-VIEW-HEAD
 #
 #  > param tagformat old|new
 #  > param maint-view MAINT-VIEW-HEAD
 #
+#  > param buildinfo-filename P_V_X.buildinfo   # zero or more times
+#  > file buildinfo                             # for buildinfos to sign
+#
 #  > previously REFNAME=OBJNAME       # if --deliberately-not-fast-forward
 #                                     # goes into tag, for replay prevention
 #
 #  > previously REFNAME=OBJNAME       # if --deliberately-not-fast-forward
 #                                     # goes into tag, for replay prevention
 #
@@ -344,6 +331,9 @@ sub gbp_pq {
 #  [etc]
 #  < data-block NBYTES    [transfer of signed changes]
 #  [etc]
 #  [etc]
 #  < data-block NBYTES    [transfer of signed changes]
 #  [etc]
+#  < data-block NBYTES    [transfer of each signed buildinfo
+#  [etc]                   same number and order as "file buildinfo"]
+#  ...
 #  < files-end
 #
 #  > complete
 #  < files-end
 #
 #  > complete
@@ -499,18 +489,12 @@ sub url_get {
 
 our ($dscdata,$dscurl,$dsc,$dsc_checked,$skew_warning_vsn);
 
 
 our ($dscdata,$dscurl,$dsc,$dsc_checked,$skew_warning_vsn);
 
-sub runcmd {
-    debugcmd "+",@_;
-    $!=0; $?=-1;
-    failedcmd @_ if system @_;
-}
-
 sub act_local () { return $dryrun_level <= 1; }
 sub act_scary () { return !$dryrun_level; }
 
 sub printdone {
     if (!$dryrun_level) {
 sub act_local () { return $dryrun_level <= 1; }
 sub act_scary () { return !$dryrun_level; }
 
 sub printdone {
     if (!$dryrun_level) {
-       progress "dgit ok: @_";
+       progress "$us ok: @_";
     } else {
        progress "would be ok: @_ (but dry run only)";
     }
     } else {
        progress "would be ok: @_ (but dry run only)";
     }
@@ -572,6 +556,9 @@ sub nextarg {
     return scalar shift @ARGV;
 }
 
     return scalar shift @ARGV;
 }
 
+sub pre_help () {
+    no_local_git_cfg();
+}
 sub cmd_help () {
     print $helpmsg or die $!;
     exit 0;
 sub cmd_help () {
     print $helpmsg or die $!;
     exit 0;
@@ -580,12 +567,20 @@ sub cmd_help () {
 our $td = $ENV{DGIT_TEST_DUMMY_DIR} || "DGIT_TEST_DUMMY_DIR-unset";
 
 our %defcfg = ('dgit.default.distro' => 'debian',
 our $td = $ENV{DGIT_TEST_DUMMY_DIR} || "DGIT_TEST_DUMMY_DIR-unset";
 
 our %defcfg = ('dgit.default.distro' => 'debian',
+              'dgit.default.default-suite' => 'unstable',
+              'dgit.default.old-dsc-distro' => 'debian',
+              'dgit-suite.*-security.distro' => 'debian-security',
               'dgit.default.username' => '',
               'dgit.default.archive-query-default-component' => 'main',
               'dgit.default.ssh' => 'ssh',
               'dgit.default.archive-query' => 'madison:',
               'dgit.default.sshpsql-dbname' => 'service=projectb',
               'dgit.default.username' => '',
               'dgit.default.archive-query-default-component' => 'main',
               'dgit.default.ssh' => 'ssh',
               'dgit.default.archive-query' => 'madison:',
               'dgit.default.sshpsql-dbname' => 'service=projectb',
+              'dgit.default.aptget-components' => 'main',
               'dgit.default.dgit-tag-format' => 'new,old,maint',
               'dgit.default.dgit-tag-format' => 'new,old,maint',
+              'dgit.dsc-url-proto-ok.http'    => 'true',
+              'dgit.dsc-url-proto-ok.https'   => 'true',
+              'dgit.dsc-url-proto-ok.git'     => 'true',
+              'dgit.default.dsc-url-proto-ok' => 'false',
               # 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"
               # 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"
@@ -616,6 +611,11 @@ our %defcfg = ('dgit.default.distro' => 'debian',
               'dgit-distro.debian.git-url-suffix' => '',
               'dgit-distro.debian.upload-host' => 'ftp-master', # for dput
               'dgit-distro.debian.mirror' => 'http://ftp.debian.org/debian/',
               'dgit-distro.debian.git-url-suffix' => '',
               'dgit-distro.debian.upload-host' => 'ftp-master', # for dput
               '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.nominal-distro' => 'debian',
  'dgit-distro.debian.backports-quirk' => '(squeeze)-backports*',
  'dgit-distro.debian-backports.mirror' => 'http://backports.debian.org/debian-backports/',
               'dgit-distro.ubuntu.git-check' => 'false',
  'dgit-distro.debian.backports-quirk' => '(squeeze)-backports*',
  'dgit-distro.debian-backports.mirror' => 'http://backports.debian.org/debian-backports/',
               'dgit-distro.ubuntu.git-check' => 'false',
@@ -637,30 +637,14 @@ our %gitcfgs;
 our @gitcfgsources = qw(cmdline local global system);
 
 sub git_slurp_config () {
 our @gitcfgsources = qw(cmdline local global system);
 
 sub git_slurp_config () {
-    local ($debuglevel) = $debuglevel-2;
-    local $/="\0";
-
     # This algoritm is a bit subtle, but this is needed so that for
     # options which we want to be single-valued, we allow the
     # different config sources to override properly.  See #835858.
     foreach my $src (@gitcfgsources) {
        next if $src eq 'cmdline';
        # we do this ourselves since git doesn't handle it
     # This algoritm is a bit subtle, but this is needed so that for
     # options which we want to be single-valued, we allow the
     # different config sources to override properly.  See #835858.
     foreach my $src (@gitcfgsources) {
        next if $src eq 'cmdline';
        # we do this ourselves since git doesn't handle it
-       
-       my @cmd = (@git, qw(config -z --get-regexp), "--$src", qw(.*));
-       debugcmd "|",@cmd;
 
 
-       open GITS, "-|", @cmd or die $!;
-       while (<GITS>) {
-           chomp or die;
-           printdebug "=> ", (messagequote $_), "\n";
-           m/\n/ or die "$_ ?";
-           push @{ $gitcfgs{$src}{$`} }, $'; #';
-       }
-       $!=0; $?=0;
-       close GITS
-           or ($!==0 && $?==256)
-           or failedcmd @cmd;
+       $gitcfgs{$src} = git_slurp_config_src $src;
     }
 }
 
     }
 }
 
@@ -668,7 +652,10 @@ sub git_get_config ($) {
     my ($c) = @_;
     foreach my $src (@gitcfgsources) {
        my $l = $gitcfgs{$src}{$c};
     my ($c) = @_;
     foreach my $src (@gitcfgsources) {
        my $l = $gitcfgs{$src}{$c};
-       printdebug"C $c ".(defined $l ? messagequote "'$l'" : "undef")."\n"
+       confess "internal error ($l $c)" if $l && !ref $l;
+       printdebug"C $c ".(defined $l ?
+                          join " ", map { messagequote "'$_'" } @$l :
+                          "undef")."\n"
            if $debuglevel >= 4;
        $l or next;
        @$l==1 or badcfg "multiple values for $c".
            if $debuglevel >= 4;
        $l or next;
        @$l==1 or badcfg "multiple values for $c".
@@ -681,24 +668,58 @@ sub git_get_config ($) {
 sub cfg {
     foreach my $c (@_) {
        return undef if $c =~ /RETURN-UNDEF/;
 sub cfg {
     foreach my $c (@_) {
        return undef if $c =~ /RETURN-UNDEF/;
+       printdebug "C? $c\n" if $debuglevel >= 5;
        my $v = git_get_config($c);
        return $v if defined $v;
        my $dv = $defcfg{$c};
        my $v = git_get_config($c);
        return $v if defined $v;
        my $dv = $defcfg{$c};
-       return $dv if defined $dv;
+       if (defined $dv) {
+           printdebug "CD $c $dv\n" if $debuglevel >= 4;
+           return $dv;
+       }
     }
     badcfg "need value for one of: @_\n".
        "$us: distro or suite appears not to be (properly) supported";
 }
 
     }
     badcfg "need value for one of: @_\n".
        "$us: distro or suite appears not to be (properly) supported";
 }
 
-sub access_basedistro () {
+sub no_local_git_cfg () {
+    # needs to be called from pre_*
+    @gitcfgsources = grep { $_ ne 'local' } @gitcfgsources;
+}
+
+sub access_basedistro__noalias () {
     if (defined $idistro) {
        return $idistro;
     } else {   
     if (defined $idistro) {
        return $idistro;
     } else {   
-       return cfg("dgit-suite.$isuite.distro",
-                  "dgit.default.distro");
+       my $def = cfg("dgit-suite.$isuite.distro", 'RETURN-UNDEF');
+       return $def if defined $def;
+       foreach my $src (@gitcfgsources, 'internal') {
+           my $kl = $src eq 'internal' ? \%defcfg : $gitcfgs{$src};
+           next unless $kl;
+           foreach my $k (keys %$kl) {
+               next unless $k =~ m#^dgit-suite\.(.*)\.distro$#;
+               my $dpat = $1;
+               next unless match_glob $dpat, $isuite;
+               return $kl->{$k};
+           }
+       }
+       return cfg("dgit.default.distro");
     }
 }
 
     }
 }
 
+sub access_basedistro () {
+    my $noalias = access_basedistro__noalias();
+    my $canon = cfg("dgit-distro.$noalias.alias-canon",'RETURN-UNDEF');
+    return $canon // $noalias;
+}
+
+sub access_nomdistro () {
+    my $base = access_basedistro();
+    my $r = cfg("dgit-distro.$base.nominal-distro",'RETURN-UNDEF') // $base;
+    $r =~ m/^$distro_re$/ or badcfg
+ "bad syntax for (nominal) distro \`$r' (does not match /^$distro_re$/)";
+    return $r;
+}
+
 sub access_quirk () {
     # returns (quirk name, distro to use instead or undef, quirk-specific info)
     my $basedistro = access_basedistro();
 sub access_quirk () {
     # returns (quirk name, distro to use instead or undef, quirk-specific info)
     my $basedistro = access_basedistro();
@@ -760,11 +781,11 @@ sub pushing () {
 Push failed, before we got started.
 You can retry the push, after fixing the problem, if you like.
 END
 Push failed, before we got started.
 You can retry the push, after fixing the problem, if you like.
 END
-    finalise_opts_opts();
+    parseopts_late_defaults();
 }
 
 sub notpushing () {
 }
 
 sub notpushing () {
-    finalise_opts_opts();
+    parseopts_late_defaults();
 }
 
 sub supplementary_message ($) {
 }
 
 sub supplementary_message ($) {
@@ -794,6 +815,8 @@ sub access_distros () {
     unshift @l, $instead_distro;
     @l = grep { defined } @l;
 
     unshift @l, $instead_distro;
     @l = grep { defined } @l;
 
+    push @l, access_nomdistro();
+
     if (access_forpush()) {
        @l = map { ("$_/push", $_) } @l;
     }
     if (access_forpush()) {
        @l = map { ("$_/push", $_) } @l;
     }
@@ -923,10 +946,10 @@ sub parsecontrolfh ($$;$) {
 }
 
 sub parsecontrol {
 }
 
 sub parsecontrol {
-    my ($file, $desc) = @_;
+    my ($file, $desc, $allowsigned) = @_;
     my $fh = new IO::Handle;
     open $fh, '<', $file or die "$file: $!";
     my $fh = new IO::Handle;
     open $fh, '<', $file or die "$file: $!";
-    my $c = parsecontrolfh($fh,$desc);
+    my $c = parsecontrolfh($fh,$desc,$allowsigned);
     $fh->error and die $!;
     close $fh;
     return $c;
     $fh->error and die $!;
     close $fh;
     return $c;
@@ -962,12 +985,6 @@ sub commit_getclogp ($) {
     $commit_getclogp_memo{$objid} = parsechangelog("-l$mclog");
 }
 
     $commit_getclogp_memo{$objid} = parsechangelog("-l$mclog");
 }
 
-sub must_getcwd () {
-    my $d = getcwd();
-    defined $d or fail "getcwd failed: $!";
-    return $d;
-}
-
 sub parse_dscdata () {
     my $dscfh = new IO::File \$dscdata, '<' or die $!;
     printdebug Dumper($dscdata) if $debuglevel>1;
 sub parse_dscdata () {
     my $dscfh = new IO::File \$dscdata, '<' or die $!;
     printdebug Dumper($dscdata) if $debuglevel>1;
@@ -979,6 +996,8 @@ our %rmad;
 
 sub archive_query ($;@) {
     my ($method) = shift @_;
 
 sub archive_query ($;@) {
     my ($method) = shift @_;
+    fail "this operation does not support multiple comma-separated suites"
+       if $isuite =~ m/,/;
     my $query = access_cfg('archive-query','RETURN-UNDEF');
     $query =~ s/^(\w+):// or badcfg "invalid archive-query method \`$query'";
     my $proto = $1;
     my $query = access_cfg('archive-query','RETURN-UNDEF');
     $query =~ s/^(\w+):// or badcfg "invalid archive-query method \`$query'";
     my $proto = $1;
@@ -986,12 +1005,28 @@ sub archive_query ($;@) {
     { no strict qw(refs); &{"${method}_${proto}"}($proto,$data,@_); }
 }
 
     { no strict qw(refs); &{"${method}_${proto}"}($proto,$data,@_); }
 }
 
+sub archive_query_prepend_mirror {
+    my $m = access_cfg('mirror');
+    return map { [ $_->[0], $m.$_->[1], @$_[2..$#$_] ] } @_;
+}
+
 sub pool_dsc_subpath ($$) {
     my ($vsn,$component) = @_; # $package is implict arg
     my $prefix = substr($package, 0, $package =~ m/^l/ ? 4 : 1);
     return "/pool/$component/$prefix/$package/".dscfn($vsn);
 }
 
 sub pool_dsc_subpath ($$) {
     my ($vsn,$component) = @_; # $package is implict arg
     my $prefix = substr($package, 0, $package =~ m/^l/ ? 4 : 1);
     return "/pool/$component/$prefix/$package/".dscfn($vsn);
 }
 
+sub cfg_apply_map ($$$) {
+    my ($varref, $what, $mapspec) = @_;
+    return unless $mapspec;
+
+    printdebug "config $what EVAL{ $mapspec; }\n";
+    $_ = $$varref;
+    eval "package Dgit::Config; $mapspec;";
+    die $@ if $@;
+    $$varref = $_;
+}
+
 #---------- `ftpmasterapi' archive query method (nascent) ----------
 
 sub archive_api_query_cmd ($) {
 #---------- `ftpmasterapi' archive query method (nascent) ----------
 
 sub archive_api_query_cmd ($) {
@@ -1092,7 +1127,7 @@ sub archive_query_ftpmasterapi {
            if length $@;
     }
     @rows = sort { -version_compare($a->[0],$b->[0]) } @rows;
            if length $@;
     }
     @rows = sort { -version_compare($a->[0],$b->[0]) } @rows;
-    return @rows;
+    return archive_query_prepend_mirror @rows;
 }
 
 sub file_in_archive_ftpmasterapi {
 }
 
 sub file_in_archive_ftpmasterapi {
@@ -1104,6 +1139,170 @@ sub file_in_archive_ftpmasterapi {
     my $info = api_query($data, "file_in_archive/$pat", 1);
 }
 
     my $info = api_query($data, "file_in_archive/$pat", 1);
 }
 
+#---------- `aptget' archive query method ----------
+
+our $aptget_base;
+our $aptget_releasefile;
+our $aptget_configpath;
+
+sub aptget_aptget   () { return @aptget,   qw(-c), $aptget_configpath; }
+sub aptget_aptcache () { return @aptcache, qw(-c), $aptget_configpath; }
+
+sub aptget_cache_clean {
+    runcmd_ordryrun_local qw(sh -ec),
+       'cd "$1"; find -atime +30 -type f -print0 | xargs -0r rm --',
+       'x', $aptget_base;
+}
+
+sub aptget_lock_acquire () {
+    my $lockfile = "$aptget_base/lock";
+    open APTGET_LOCK, '>', $lockfile or die "open $lockfile: $!";
+    flock APTGET_LOCK, LOCK_EX or die "lock $lockfile: $!";
+}
+
+sub aptget_prep ($) {
+    my ($data) = @_;
+    return if defined $aptget_base;
+
+    badcfg "aptget archive query method takes no data part"
+       if length $data;
+
+    my $cache = $ENV{XDG_CACHE_DIR} // "$ENV{HOME}/.cache";
+
+    ensuredir $cache;
+    ensuredir "$cache/dgit";
+    my $cachekey =
+       access_cfg('aptget-cachekey','RETURN-UNDEF')
+       // access_nomdistro();
+
+    $aptget_base = "$cache/dgit/aptget";
+    ensuredir $aptget_base;
+
+    my $quoted_base = $aptget_base;
+    die "$quoted_base contains bad chars, cannot continue"
+       if $quoted_base =~ m/["\\]/; # apt.conf(5) says no escaping :-/
+
+    ensuredir $aptget_base;
+
+    aptget_lock_acquire();
+
+    aptget_cache_clean();
+
+    $aptget_configpath = "$aptget_base/apt.conf#$cachekey";
+    my $sourceslist = "source.list#$cachekey";
+
+    my $aptsuites = $isuite;
+    cfg_apply_map(\$aptsuites, 'suite map',
+                 access_cfg('aptget-suite-map', 'RETURN-UNDEF'));
+
+    open SRCS, ">", "$aptget_base/$sourceslist" or die $!;
+    printf SRCS "deb-src %s %s %s\n",
+       access_cfg('mirror'),
+       $aptsuites,
+       access_cfg('aptget-components')
+       or die $!;
+
+    ensuredir "$aptget_base/cache";
+    ensuredir "$aptget_base/lists";
+
+    open CONF, ">", $aptget_configpath or die $!;
+    print CONF <<END;
+Debug::NoLocking "true";
+APT::Get::List-Cleanup "false";
+#clear APT::Update::Post-Invoke-Success;
+Dir::Etc::SourceList "$quoted_base/$sourceslist";
+Dir::State::Lists "$quoted_base/lists";
+Dir::Etc::preferences "$quoted_base/preferences";
+Dir::Cache::srcpkgcache "$quoted_base/cache/srcs#$cachekey";
+Dir::Cache::pkgcache "$quoted_base/cache/pkgs#$cachekey";
+END
+
+    foreach my $key (qw(
+                       Dir::Cache
+                       Dir::State
+                       Dir::Cache::Archives
+                       Dir::Etc::SourceParts
+                       Dir::Etc::preferencesparts
+                     )) {
+       ensuredir "$aptget_base/$key";
+       print CONF "$key \"$quoted_base/$key\";\n" or die $!;
+    };
+
+    my $oldatime = (time // die $!) - 1;
+    foreach my $oldlist (<$aptget_base/lists/*Release>) {
+       next unless stat_exists $oldlist;
+       my ($mtime) = (stat _)[9];
+       utime $oldatime, $mtime, $oldlist or die "$oldlist $!";
+    }
+
+    runcmd_ordryrun_local aptget_aptget(), qw(update);
+
+    my @releasefiles;
+    foreach my $oldlist (<$aptget_base/lists/*Release>) {
+       next unless stat_exists $oldlist;
+       my ($atime) = (stat _)[8];
+       next if $atime == $oldatime;
+       push @releasefiles, $oldlist;
+    }
+    my @inreleasefiles = grep { m#/InRelease$# } @releasefiles;
+    @releasefiles = @inreleasefiles if @inreleasefiles;
+    die "apt updated wrong number of Release files (@releasefiles), erk"
+       unless @releasefiles == 1;
+
+    ($aptget_releasefile) = @releasefiles;
+}
+
+sub canonicalise_suite_aptget {
+    my ($proto,$data) = @_;
+    aptget_prep($data);
+
+    my $release = parsecontrol $aptget_releasefile, "Release file", 1;
+
+    foreach my $name (qw(Codename Suite)) {
+       my $val = $release->{$name};
+       if (defined $val) {
+           printdebug "release file $name: $val\n";
+           $val =~ m/^$suite_re$/o or fail
+ "Release file ($aptget_releasefile) specifies intolerable $name";
+           cfg_apply_map(\$val, 'suite rmap',
+                         access_cfg('aptget-suite-rmap', 'RETURN-UNDEF'));
+           return $val
+       }
+    }
+    return $isuite;
+}
+
+sub archive_query_aptget {
+    my ($proto,$data) = @_;
+    aptget_prep($data);
+
+    ensuredir "$aptget_base/source";
+    foreach my $old (<$aptget_base/source/*.dsc>) {
+       unlink $old or die "$old: $!";
+    }
+
+    my $showsrc = cmdoutput aptget_aptcache(), qw(showsrc), $package;
+    return () unless $showsrc =~ m/^package:\s*\Q$package\E\s*$/mi;
+    # avoids apt-get source failing with ambiguous error code
+
+    runcmd_ordryrun_local
+       shell_cmd 'cd "$1"/source; shift', $aptget_base,
+       aptget_aptget(), qw(--download-only --only-source source), $package;
+
+    my @dscs = <$aptget_base/source/*.dsc>;
+    fail "apt-get source did not produce a .dsc" unless @dscs;
+    fail "apt-get source produced several .dscs (@dscs)" unless @dscs==1;
+
+    my $pre_dsc = parsecontrol $dscs[0], $dscs[0], 1;
+
+    use URI::Escape;
+    my $uri = "file://". uri_escape $dscs[0];
+    $uri =~ s{\%2f}{/}gi;
+    return [ (getfield $pre_dsc, 'Version'), $uri ];
+}
+
+sub file_in_archive_aptget () { return undef; }
+
 #---------- `dummyapicat' archive query method ----------
 
 sub archive_query_dummycatapi { archive_query_ftpmasterapi @_; }
 #---------- `dummyapicat' archive query method ----------
 
 sub archive_query_dummycatapi { archive_query_ftpmasterapi @_; }
@@ -1134,7 +1333,8 @@ sub file_in_archive_dummycatapi ($$$) {
 #---------- `madison' archive query method ----------
 
 sub archive_query_madison {
 #---------- `madison' archive query method ----------
 
 sub archive_query_madison {
-    return map { [ @$_[0..1] ] } madison_get_parse(@_);
+    return archive_query_prepend_mirror
+       map { [ @$_[0..1] ] } madison_get_parse(@_);
 }
 
 sub madison_get_parse {
 }
 
 sub madison_get_parse {
@@ -1240,7 +1440,7 @@ END
        my ($vsn,$component,$filename,$sha256sum) = @$_;
        [ $vsn, "/pool/$component/$filename",$digester,$sha256sum ];
     } @rows;
        my ($vsn,$component,$filename,$sha256sum) = @$_;
        [ $vsn, "/pool/$component/$filename",$digester,$sha256sum ];
     } @rows;
-    return @rows;
+    return archive_query_prepend_mirror @rows;
 }
 
 sub canonicalise_suite_sshpsql ($$) {
 }
 
 sub canonicalise_suite_sshpsql ($$) {
@@ -1296,7 +1496,8 @@ sub archive_query_dummycat ($$) {
     }
     C->error and die "$dpath: $!";
     close C;
     }
     C->error and die "$dpath: $!";
     close C;
-    return sort { -version_compare($a->[0],$b->[0]); } @rows;
+    return archive_query_prepend_mirror
+       sort { -version_compare($a->[0],$b->[0]); } @rows;
 }
 
 sub file_in_archive_dummycat () { return undef; }
 }
 
 sub file_in_archive_dummycat () { return undef; }
@@ -1307,6 +1508,15 @@ sub access_cfg_tagformats () {
     split /\,/, access_cfg('dgit-tag-format');
 }
 
     split /\,/, access_cfg('dgit-tag-format');
 }
 
+sub access_cfg_tagformats_can_splitbrain () {
+    my %y = map { $_ => 1 } access_cfg_tagformats;
+    foreach my $needtf (qw(new maint)) {
+       next if $y{$needtf};
+       return 0;
+    }
+    return 1;
+}
+
 sub need_tagformat ($$) {
     my ($fmt, $why) = @_;
     fail "need to use tag format $fmt ($why) but also need".
 sub need_tagformat ($$) {
     my ($fmt, $why) = @_;
     fail "need to use tag format $fmt ($why) but also need".
@@ -1351,6 +1561,8 @@ sub canonicalise_suite () {
     $csuite = archive_query('canonicalise_suite');
     if ($isuite ne $csuite) {
        progress "canonical suite name for $isuite is $csuite";
     $csuite = archive_query('canonicalise_suite');
     if ($isuite ne $csuite) {
        progress "canonical suite name for $isuite is $csuite";
+    } else {
+       progress "canonical suite name is $csuite";
     }
 }
 
     }
 }
 
@@ -1358,8 +1570,8 @@ sub get_archive_dsc () {
     canonicalise_suite();
     my @vsns = archive_query('archive_query');
     foreach my $vinfo (@vsns) {
     canonicalise_suite();
     my @vsns = archive_query('archive_query');
     foreach my $vinfo (@vsns) {
-       my ($vsn,$subpath,$digester,$digest) = @$vinfo;
-       $dscurl = access_cfg('mirror').$subpath;
+       my ($vsn,$vsn_dscurl,$digester,$digest) = @$vinfo;
+       $dscurl = $vsn_dscurl;
        $dscdata = url_get($dscurl);
        if (!$dscdata) {
            $skew_warning_vsn = $vsn if !defined $skew_warning_vsn;
        $dscdata = url_get($dscurl);
        if (!$dscdata) {
            $skew_warning_vsn = $vsn if !defined $skew_warning_vsn;
@@ -1454,6 +1666,7 @@ sub create_remote_git_repo () {
 }
 
 our ($dsc_hash,$lastpush_mergeinput);
 }
 
 our ($dsc_hash,$lastpush_mergeinput);
+our ($dsc_distro, $dsc_hint_tag, $dsc_hint_url);
 
 our $ud = '.git/dgit/unpack';
 
 
 our $ud = '.git/dgit/unpack';
 
@@ -1466,10 +1679,7 @@ sub prep_ud (;$) {
 }
 
 sub mktree_in_ud_here () {
 }
 
 sub mktree_in_ud_here () {
-    runcmd qw(git init -q);
-    runcmd qw(git config gc.auto 0);
-    rmtree('.git/objects');
-    symlink '../../../../objects','.git/objects' or die $!;
+    playtree_setup $gitcfgs{local};
 }
 
 sub git_write_tree () {
 }
 
 sub git_write_tree () {
@@ -1478,7 +1688,13 @@ sub git_write_tree () {
     return $tree;
 }
 
     return $tree;
 }
 
-sub remove_stray_gits () {
+sub git_add_write_tree () {
+    runcmd @git, qw(add -Af .);
+    return git_write_tree();
+}
+
+sub remove_stray_gits ($) {
+    my ($what) = @_;
     my @gitscmd = qw(find -name .git -prune -print0);
     debugcmd "|",@gitscmd;
     open GITS, "-|", @gitscmd or die $!;
     my @gitscmd = qw(find -name .git -prune -print0);
     debugcmd "|",@gitscmd;
     open GITS, "-|", @gitscmd or die $!;
@@ -1486,7 +1702,7 @@ sub remove_stray_gits () {
        local $/="\0";
        while (<GITS>) {
            chomp or die;
        local $/="\0";
        while (<GITS>) {
            chomp or die;
-           print STDERR "$us: warning: removing from source package: ",
+           print STDERR "$us: warning: removing from $what: ",
                (messagequote $_), "\n";
            rmtree $_;
        }
                (messagequote $_), "\n";
            rmtree $_;
        }
@@ -1494,8 +1710,8 @@ sub remove_stray_gits () {
     $!=0; $?=0; close GITS or failedcmd @gitscmd;
 }
 
     $!=0; $?=0; close GITS or failedcmd @gitscmd;
 }
 
-sub mktree_in_ud_from_only_subdir (;$) {
-    my ($raw) = @_;
+sub mktree_in_ud_from_only_subdir ($;$) {
+    my ($what,$raw) = @_;
 
     # changes into the subdir
     my (@dirs) = <*/.>;
 
     # changes into the subdir
     my (@dirs) = <*/.>;
@@ -1504,7 +1720,7 @@ sub mktree_in_ud_from_only_subdir (;$) {
     my $dir = $1;
     changedir $dir;
 
     my $dir = $1;
     changedir $dir;
 
-    remove_stray_gits();
+    remove_stray_gits($what);
     mktree_in_ud_here();
     if (!$raw) {
        my ($format, $fopts) = get_source_format();
     mktree_in_ud_here();
     if (!$raw) {
        my ($format, $fopts) = get_source_format();
@@ -1513,8 +1729,7 @@ sub mktree_in_ud_from_only_subdir (;$) {
        }
     }
 
        }
     }
 
-    runcmd @git, qw(add -Af);
-    my $tree=git_write_tree();
+    my $tree=git_add_write_tree();
     return ($tree,$dir);
 }
 
     return ($tree,$dir);
 }
 
@@ -1676,7 +1891,8 @@ END
            push @found_differ, "archive $h->{filename}: ".join "; ", @differ
                if @differ;
        }
            push @found_differ, "archive $h->{filename}: ".join "; ", @differ
                if @differ;
        }
-       print "origs $file f.same=$found_same #f._differ=$#found_differ\n";
+       printdebug "origs $file f.same=$found_same".
+           " #f._differ=$#found_differ\n";
        if (@found_differ && !$found_same) {
            fail join "\n",
                "archive contains $file with different checksum",
        if (@found_differ && !$found_same) {
            fail join "\n",
                "archive contains $file with different checksum",
@@ -1752,7 +1968,14 @@ sub make_commit_text ($) {
 sub clogp_authline ($) {
     my ($clogp) = @_;
     my $author = getfield $clogp, 'Maintainer';
 sub clogp_authline ($) {
     my ($clogp) = @_;
     my $author = getfield $clogp, 'Maintainer';
-    $author =~ s#,.*##ms;
+    if ($author =~ m/^[^"\@]+\,/) {
+       # single entry Maintainer field with unquoted comma
+       $author = ($& =~ y/,//rd).$'; # strip the comma
+    }
+    # git wants a single author; any remaining commas in $author
+    # 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";
     $authline =~ m/$git_authline_re/o or
     my $date = cmdoutput qw(date), '+%s %z', qw(-d), getfield($clogp,'Date');
     my $authline = "$author $date";
     $authline =~ m/$git_authline_re/o or
@@ -1822,7 +2045,9 @@ sub check_for_vendor_patches () {
     vendor_patches_distro(Dpkg::Vendor::get_current_vendor(),
                         "Dpkg::Vendor \`current vendor'");
     vendor_patches_distro(access_basedistro(),
     vendor_patches_distro(Dpkg::Vendor::get_current_vendor(),
                         "Dpkg::Vendor \`current vendor'");
     vendor_patches_distro(access_basedistro(),
-                         "distro being accessed");
+                         "(base) distro being accessed");
+    vendor_patches_distro(access_nomdistro(),
+                         "(nominal) distro being accessed");
 }
 
 sub generate_commits_from_dsc () {
 }
 
 sub generate_commits_from_dsc () {
@@ -1835,23 +2060,44 @@ sub generate_commits_from_dsc () {
     foreach my $fi (@dfi) {
        my $f = $fi->{Filename};
        die "$f ?" if $f =~ m#/|^\.|\.dsc$|\.tmp$#;
     foreach my $fi (@dfi) {
        my $f = $fi->{Filename};
        die "$f ?" if $f =~ m#/|^\.|\.dsc$|\.tmp$#;
+       my $upper_f = "../../../../$f";
+
+       printdebug "considering reusing $f: ";
+
+       if (link_ltarget "$upper_f,fetch", $f) {
+           printdebug "linked (using ...,fetch).\n";
+       } elsif ((printdebug "($!) "),
+                $! != ENOENT) {
+           fail "accessing ../$f,fetch: $!";
+       } elsif (link_ltarget $upper_f, $f) {
+           printdebug "linked.\n";
+       } elsif ((printdebug "($!) "),
+                $! != ENOENT) {
+           fail "accessing ../$f: $!";
+       } else {
+           printdebug "absent.\n";
+       }
 
 
-       printdebug "considering linking $f: ";
-
-       link_ltarget "../../../../$f", $f
-           or ((printdebug "($!) "), 0)
-           or $!==&ENOENT
-           or die "$f $!";
-
-       printdebug "linked.\n";
-
-       complete_file_from_dsc('.', $fi)
+       my $refetched;
+       complete_file_from_dsc('.', $fi, \$refetched)
            or next;
 
            or next;
 
-       if (is_orig_file_in_dsc($f, \@dfi)) {
-           link $f, "../../../../$f"
-               or $!==&EEXIST
-               or die "$f $!";
+       printdebug "considering saving $f: ";
+
+       if (link $f, $upper_f) {
+           printdebug "linked.\n";
+       } elsif ((printdebug "($!) "),
+                $! != EEXIST) {
+           fail "saving ../$f: $!";
+       } elsif (!$refetched) {
+           printdebug "no need.\n";
+       } elsif (link $f, "$upper_f,fetch") {
+           printdebug "linked (using ...,fetch).\n";
+       } elsif ((printdebug "($!) "),
+                $! != EEXIST) {
+           fail "saving ../$f,fetch: $!";
+       } else {
+           printdebug "cannot.\n";
        }
     }
 
        }
     }
 
@@ -1894,7 +2140,7 @@ sub generate_commits_from_dsc () {
                if defined $compr_ext && !defined $cname;
            my $compr_proc =
                new Dpkg::Compression::Process compression => $cname;
                if defined $compr_ext && !defined $cname;
            my $compr_proc =
                new Dpkg::Compression::Process compression => $cname;
-           my @compr_cmd = $compr_proc->get_uncompress_cmdline();
+           @compr_cmd = $compr_proc->get_uncompress_cmdline();
            my $compr_fh = new IO::Handle;
            my $compr_pid = open $compr_fh, "-|" // die $!;
            if (!$compr_pid) {
            my $compr_fh = new IO::Handle;
            my $compr_pid = open $compr_fh, "-|" // die $!;
            if (!$compr_pid) {
@@ -1905,14 +2151,14 @@ sub generate_commits_from_dsc () {
            $input = $compr_fh;
        }
 
            $input = $compr_fh;
        }
 
-       rmtree "../unpack-tar";
-       mkdir "../unpack-tar" or die $!;
+       rmtree "_unpack-tar";
+       mkdir "_unpack-tar" or die $!;
        my @tarcmd = qw(tar -x -f -
                        --no-same-owner --no-same-permissions
                        --no-acls --no-xattrs --no-selinux);
        my $tar_pid = fork // die $!;
        if (!$tar_pid) {
        my @tarcmd = qw(tar -x -f -
                        --no-same-owner --no-same-permissions
                        --no-acls --no-xattrs --no-selinux);
        my $tar_pid = fork // die $!;
        if (!$tar_pid) {
-           chdir "../unpack-tar" or die $!;
+           chdir "_unpack-tar" or die $!;
            open STDIN, "<&", $input or die $!;
            exec @tarcmd;
            die "dgit (child): exec $tarcmd[0]: $!";
            open STDIN, "<&", $input or die $!;
            exec @tarcmd;
            die "dgit (child): exec $tarcmd[0]: $!";
@@ -1921,16 +2167,26 @@ sub generate_commits_from_dsc () {
        !$? or failedcmd @tarcmd;
 
        close $input or
        !$? or failedcmd @tarcmd;
 
        close $input or
-           (@compr_cmd ? failedcmd @compr_cmd
+           (@compr_cmd ? ($?==SIGPIPE || failedcmd @compr_cmd)
             : die $!);
        # finally, we have the results in "tarball", but maybe
        # with the wrong permissions
 
             : die $!);
        # finally, we have the results in "tarball", but maybe
        # with the wrong permissions
 
-       runcmd qw(chmod -R +rwX ../unpack-tar);
-       changedir "../unpack-tar";
-       my ($tree) = mktree_in_ud_from_only_subdir(1);
-       changedir "../../unpack";
-       rmtree "../unpack-tar";
+       runcmd qw(chmod -R +rwX _unpack-tar);
+       changedir "_unpack-tar";
+       remove_stray_gits($f);
+       mktree_in_ud_here();
+       
+       my ($tree) = git_add_write_tree();
+       my $tentries = cmdoutput @git, qw(ls-tree -z), $tree;
+       if ($tentries =~ m/^\d+ tree (\w+)\t[^\000]+\000$/s) {
+           $tree = $1;
+           printdebug "one subtree $1\n";
+       } else {
+           printdebug "multiple subtrees\n";
+       }
+       changedir "..";
+       rmtree "_unpack-tar";
 
        my $ent = [ $f, $tree ];
        push @tartrees, {
 
        my $ent = [ $f, $tree ];
        push @tartrees, {
@@ -1969,7 +2225,7 @@ sub generate_commits_from_dsc () {
     push @cmd, qw(-x --), $dscfn;
     runcmd @cmd;
 
     push @cmd, qw(-x --), $dscfn;
     runcmd @cmd;
 
-    my ($tree,$dir) = mktree_in_ud_from_only_subdir();
+    my ($tree,$dir) = mktree_in_ud_from_only_subdir("source package");
     if (madformat $dsc->{format}) { 
        check_for_vendor_patches();
     }
     if (madformat $dsc->{format}) { 
        check_for_vendor_patches();
     }
@@ -1979,8 +2235,7 @@ sub generate_commits_from_dsc () {
        my @pcmd = qw(dpkg-source --before-build .);
        runcmd shell_cmd 'exec >/dev/null', @pcmd;
        rmtree '.pc';
        my @pcmd = qw(dpkg-source --before-build .);
        runcmd shell_cmd 'exec >/dev/null', @pcmd;
        rmtree '.pc';
-       runcmd @git, qw(add -Af);
-       $dappliedtree = git_write_tree();
+       $dappliedtree = git_add_write_tree();
     }
 
     my @clogcmd = qw(dpkg-parsechangelog --format rfc822 --all);
     }
 
     my @clogcmd = qw(dpkg-parsechangelog --format rfc822 --all);
@@ -2042,6 +2297,7 @@ sub generate_commits_from_dsc () {
 
     my $authline = clogp_authline $clogp;
     my $changes = getfield $clogp, 'Changes';
 
     my $authline = clogp_authline $clogp;
     my $changes = getfield $clogp, 'Changes';
+    $changes =~ s/^\n//; # Changes: \n
     my $cversion = getfield $clogp, 'Version';
 
     if (@tartrees) {
     my $cversion = getfield $clogp, 'Version';
 
     if (@tartrees) {
@@ -2125,6 +2381,8 @@ END
        my $path = $ENV{PATH} or die;
 
        foreach my $use_absurd (qw(0 1)) {
        my $path = $ENV{PATH} or die;
 
        foreach my $use_absurd (qw(0 1)) {
+           runcmd @git, qw(checkout -q unpa);
+           runcmd @git, qw(update-ref -d refs/heads/patch-queue/unpa);
            local $ENV{PATH} = $path;
            if ($use_absurd) {
                chomp $@;
            local $ENV{PATH} = $path;
            if ($use_absurd) {
                chomp $@;
@@ -2141,11 +2399,12 @@ END
                die "only absurd git-apply!\n" if !$use_absurd
                    && forceing [qw(import-gitapply-absurd)];
 
                die "only absurd git-apply!\n" if !$use_absurd
                    && forceing [qw(import-gitapply-absurd)];
 
-               local $ENV{PATH} = $path if $use_absurd;
+               local $ENV{DGIT_ABSURD_DEBUG} = $debuglevel if $use_absurd;
+               local $ENV{PATH} = $path                    if $use_absurd;
 
                my @showcmd = (gbp_pq, qw(import));
                my @realcmd = shell_cmd
 
                my @showcmd = (gbp_pq, qw(import));
                my @realcmd = shell_cmd
-                   'exec >/dev/null 2>../../gbp-pq-output', @showcmd;
+                   'exec >/dev/null 2>>../../gbp-pq-output', @showcmd;
                debugcmd "+",@realcmd;
                if (system @realcmd) {
                    die +(shellquote @showcmd).
                debugcmd "+",@realcmd;
                if (system @realcmd) {
                    die +(shellquote @showcmd).
@@ -2210,39 +2469,56 @@ END
     return @output;
 }
 
     return @output;
 }
 
-sub complete_file_from_dsc ($$) {
-    our ($dstdir, $fi) = @_;
-    # Ensures that we have, in $dir, the file $fi, with the correct
+sub complete_file_from_dsc ($$;$) {
+    our ($dstdir, $fi, $refetched) = @_;
+    # Ensures that we have, in $dstdir, the file $fi, with the correct
     # contents.  (Downloading it from alongside $dscurl if necessary.)
     # contents.  (Downloading it from alongside $dscurl if necessary.)
+    # If $refetched is defined, can overwrite "$dstdir/$fi->{Filename}"
+    # and will set $$refetched=1 if it did so (or tried to).
 
     my $f = $fi->{Filename};
     my $tf = "$dstdir/$f";
     my $downloaded = 0;
 
 
     my $f = $fi->{Filename};
     my $tf = "$dstdir/$f";
     my $downloaded = 0;
 
+    my $got;
+    my $checkhash = sub {
+       open F, "<", "$tf" or die "$tf: $!";
+       $fi->{Digester}->reset();
+       $fi->{Digester}->addfile(*F);
+       F->error and die $!;
+       $got = $fi->{Digester}->hexdigest();
+       return $got eq $fi->{Hash};
+    };
+
     if (stat_exists $tf) {
     if (stat_exists $tf) {
-       progress "using existing $f";
+       if ($checkhash->()) {
+           progress "using existing $f";
+           return 1;
+       }
+       if (!$refetched) {
+           fail "file $f has hash $got but .dsc".
+               " demands hash $fi->{Hash} ".
+               "(perhaps you should delete this file?)";
+       }
+       progress "need to fetch correct version of $f";
+       unlink $tf or die "$tf $!";
+       $$refetched = 1;
     } else {
        printdebug "$tf does not exist, need to fetch\n";
     } else {
        printdebug "$tf does not exist, need to fetch\n";
-       my $furl = $dscurl;
-       $furl =~ s{/[^/]+$}{};
-       $furl .= "/$f";
-       die "$f ?" unless $f =~ m/^\Q${package}\E_/;
-       die "$f ?" if $f =~ m#/#;
-       runcmd_ordryrun_local @curl,qw(-f -o),$tf,'--',"$furl";
-       return 0 if !act_local();
-       $downloaded = 1;
-    }
-
-    open F, "<", "$tf" or die "$tf: $!";
-    $fi->{Digester}->reset();
-    $fi->{Digester}->addfile(*F);
-    F->error and die $!;
-    my $got = $fi->{Digester}->hexdigest();
-    $got eq $fi->{Hash} or
+    }
+
+    my $furl = $dscurl;
+    $furl =~ s{/[^/]+$}{};
+    $furl .= "/$f";
+    die "$f ?" unless $f =~ m/^\Q${package}\E_/;
+    die "$f ?" if $f =~ m#/#;
+    runcmd_ordryrun_local @curl,qw(-f -o),$tf,'--',"$furl";
+    return 0 if !act_local();
+
+    $checkhash->() or
        fail "file $f has hash $got but .dsc".
            " demands hash $fi->{Hash} ".
        fail "file $f has hash $got but .dsc".
            " demands hash $fi->{Hash} ".
-           ($downloaded ? "(got wrong file from archive!)"
-            : "(perhaps you should delete this file?)");
+           "(got wrong file from archive!)";
 
     return 1;
 }
 
     return 1;
 }
@@ -2257,18 +2533,41 @@ sub ensure_we_have_orig () {
     }
 }
 
     }
 }
 
-sub git_fetch_us () {
-    # Want to fetch only what we are going to use, unless
-    # deliberately-not-ff, in which case we must fetch everything.
+#---------- git fetch ----------
 
 
-    my @specs = deliberately_not_fast_forward ? qw(tags/*) :
-       map { "tags/$_" }
-       (quiltmode_splitbrain
-        ? (map { $_->('*',access_basedistro) }
-           \&debiantag_new, \&debiantag_maintview)
-        : debiantags('*',access_basedistro));
-    push @specs, server_branch($csuite);
-    push @specs, qw(heads/*) if deliberately_not_fast_forward;
+sub lrfetchrefs () { return "refs/dgit-fetch/".access_basedistro(); }
+sub lrfetchref () { return lrfetchrefs.'/'.server_branch($csuite); }
+
+# We fetch some parts of lrfetchrefs/*.  Ideally we delete these
+# locally fetched refs because they have unhelpful names and clutter
+# up gitk etc.  So we track whether we have "used up" head ref (ie,
+# whether we have made another local ref which refers to this object).
+#
+# (If we deleted them unconditionally, then we might end up
+# re-fetching the same git objects each time dgit fetch was run.)
+#
+# So, each use of lrfetchrefs needs to be accompanied by arrangements
+# in git_fetch_us to fetch the refs in question, and possibly a call
+# to lrfetchref_used.
+
+our (%lrfetchrefs_f, %lrfetchrefs_d);
+# $lrfetchrefs_X{lrfetchrefs."/heads/whatever"} = $objid
+
+sub lrfetchref_used ($) {
+    my ($fullrefname) = @_;
+    my $objid = $lrfetchrefs_f{$fullrefname};
+    $lrfetchrefs_d{$fullrefname} = $objid if defined $objid;
+}
+
+sub git_lrfetch_sane {
+    my ($url, $supplementary, @specs) = @_;
+    # Make a 'refs/'.lrfetchrefs.'/*' be just like on server,
+    # at least as regards @specs.  Also leave the results in
+    # %lrfetchrefs_f, and arrange for lrfetchref_used to be
+    # able to clean these up.
+    #
+    # With $supplementary==1, @specs must not contain wildcards
+    # and we add to our previous fetches (non-atomically).
 
     # This is rather miserable:
     # When git fetch --prune is passed a fetchspec ending with a *,
 
     # This is rather miserable:
     # When git fetch --prune is passed a fetchspec ending with a *,
@@ -2292,30 +2591,31 @@ sub git_fetch_us () {
     # git fetch to try to generate it.  If we don't manage to generate
     # the target state, we try again.
 
     # git fetch to try to generate it.  If we don't manage to generate
     # the target state, we try again.
 
-    printdebug "git_fetch_us specs @specs\n";
+    printdebug "git_lrfetch_sane suppl=$supplementary specs @specs\n";
 
     my $specre = join '|', map {
        my $x = $_;
        $x =~ s/\W/\\$&/g;
 
     my $specre = join '|', map {
        my $x = $_;
        $x =~ s/\W/\\$&/g;
-       $x =~ s/\\\*$/.*/;
+       my $wildcard = $x =~ s/\\\*$/.*/;
+       die if $wildcard && $supplementary;
        "(?:refs/$x)";
     } @specs;
        "(?:refs/$x)";
     } @specs;
-    printdebug "git_fetch_us specre=$specre\n";
+    printdebug "git_lrfetch_sane specre=$specre\n";
     my $wanted_rref = sub {
        local ($_) = @_;
     my $wanted_rref = sub {
        local ($_) = @_;
-       return m/^(?:$specre)$/o;
+       return m/^(?:$specre)$/;
     };
 
     my $fetch_iteration = 0;
     FETCH_ITERATION:
     for (;;) {
     };
 
     my $fetch_iteration = 0;
     FETCH_ITERATION:
     for (;;) {
-       printdebug "git_fetch_us iteration $fetch_iteration\n";
+       printdebug "git_lrfetch_sane iteration $fetch_iteration\n";
         if (++$fetch_iteration > 10) {
            fail "too many iterations trying to get sane fetch!";
        }
 
        my @look = map { "refs/$_" } @specs;
         if (++$fetch_iteration > 10) {
            fail "too many iterations trying to get sane fetch!";
        }
 
        my @look = map { "refs/$_" } @specs;
-       my @lcmd = (@git, qw(ls-remote -q --refs), access_giturl(), @look);
+       my @lcmd = (@git, qw(ls-remote -q --refs), $url, @look);
        debugcmd "|",@lcmd;
 
        my %wantr;
        debugcmd "|",@lcmd;
 
        my %wantr;
@@ -2337,17 +2637,18 @@ END
 
        # OK, now %want is exactly what we want for refs in @specs
        my @fspecs = map {
 
        # OK, now %want is exactly what we want for refs in @specs
        my @fspecs = map {
-           return () if !m/\*$/ && !exists $wantr{"refs/$_"};
+           !m/\*$/ && !exists $wantr{"refs/$_"} ? () :
            "+refs/$_:".lrfetchrefs."/$_";
        } @specs;
 
            "+refs/$_:".lrfetchrefs."/$_";
        } @specs;
 
-       printdebug "git_fetch_us fspecs @fspecs\n";
+       printdebug "git_lrfetch_sane fspecs @fspecs\n";
 
 
-       my @fcmd = (@git, qw(fetch -p -n -q), access_giturl(), @fspecs);
-       runcmd_ordryrun_local @git, qw(fetch -p -n -q), access_giturl(),
-           @fspecs;
+       my @fcmd = (@git, qw(fetch -p -n -q), $url, @fspecs);
+       runcmd_ordryrun_local @fcmd if @fspecs;
 
 
-       %lrfetchrefs_f = ();
+       if (!$supplementary) {
+           %lrfetchrefs_f = ();
+       }
        my %objgot;
 
        git_for_each_ref(lrfetchrefs, sub {
        my %objgot;
 
        git_for_each_ref(lrfetchrefs, sub {
@@ -2356,6 +2657,10 @@ END
            $objgot{$objid} = 1;
        });
 
            $objgot{$objid} = 1;
        });
 
+       if ($supplementary) {
+           last;
+       }
+
        foreach my $lrefname (sort keys %lrfetchrefs_f) {
            my $rrefname = 'refs'.substr($lrefname, length lrfetchrefs);
            if (!exists $wantr{$rrefname}) {
        foreach my $lrefname (sort keys %lrfetchrefs_f) {
            my $rrefname = 'refs'.substr($lrefname, length lrfetchrefs);
            if (!exists $wantr{$rrefname}) {
@@ -2397,11 +2702,39 @@ END
        }
        last;
     }
        }
        last;
     }
-    printdebug "git_fetch_us: git fetch --no-insane emulation complete\n",
+
+    if (defined $csuite) {
+       printdebug "git_lrfetch_sane: tidying any old suite lrfetchrefs\n";
+       git_for_each_ref("refs/dgit-fetch/$csuite", sub {
+           my ($objid,$objtype,$lrefname,$reftail) = @_;
+           next if $lrfetchrefs_f{$lrefname}; # $csuite eq $distro ?
+           runcmd_ordryrun_local @git, qw(update-ref -d), $lrefname;
+       });
+    }
+
+    printdebug "git_lrfetch_sane: git fetch --no-insane emulation complete\n",
        Dumper(\%lrfetchrefs_f);
        Dumper(\%lrfetchrefs_f);
+}
+
+sub git_fetch_us () {
+    # Want to fetch only what we are going to use, unless
+    # deliberately-not-ff, in which case we must fetch everything.
+
+    my @specs = deliberately_not_fast_forward ? qw(tags/*) :
+       map { "tags/$_" }
+       (quiltmode_splitbrain
+        ? (map { $_->('*',access_nomdistro) }
+           \&debiantag_new, \&debiantag_maintview)
+        : debiantags('*',access_nomdistro));
+    push @specs, server_branch($csuite);
+    push @specs, $rewritemap;
+    push @specs, qw(heads/*) if deliberately_not_fast_forward;
+
+    my $url = access_giturl();
+    git_lrfetch_sane $url, 0, @specs;
 
     my %here;
 
     my %here;
-    my @tagpats = debiantags('*',access_basedistro);
+    my @tagpats = debiantags('*',access_nomdistro);
 
     git_for_each_ref([map { "refs/tags/$_" } @tagpats], sub {
        my ($objid,$objtype,$fullrefname,$reftail) = @_;
 
     git_for_each_ref([map { "refs/tags/$_" } @tagpats], sub {
        my ($objid,$objtype,$fullrefname,$reftail) = @_;
@@ -2419,12 +2752,14 @@ END
        } elsif ($here{$lref} eq $objid) {
            lrfetchref_used $fullrefname;
        } else {
        } elsif ($here{$lref} eq $objid) {
            lrfetchref_used $fullrefname;
        } else {
-           print STDERR \
-               "Not updateting $lref from $here{$lref} to $objid.\n";
+           print STDERR
+               "Not updating $lref from $here{$lref} to $objid.\n";
        }
     });
 }
 
        }
     });
 }
 
+#---------- dsc and archive handling ----------
+
 sub mergeinfo_getclogp ($) {
     # Ensures thit $mi->{Clogp} exists and returns it
     my ($mi) = @_;
 sub mergeinfo_getclogp ($) {
     # Ensures thit $mi->{Clogp} exists and returns it
     my ($mi) = @_;
@@ -2435,6 +2770,150 @@ sub mergeinfo_version ($) {
     return getfield( (mergeinfo_getclogp $_[0]), 'Version' );
 }
 
     return getfield( (mergeinfo_getclogp $_[0]), 'Version' );
 }
 
+sub fetch_from_archive_record_1 ($) {
+    my ($hash) = @_;
+    runcmd @git, qw(update-ref -m), "dgit fetch $csuite",
+           'DGIT_ARCHIVE', $hash;
+    cmdoutput @git, qw(log -n2), $hash;
+    # ... gives git a chance to complain if our commit is malformed
+}
+
+sub fetch_from_archive_record_2 ($) {
+    my ($hash) = @_;
+    my @upd_cmd = (@git, qw(update-ref -m), 'dgit fetch', lrref(), $hash);
+    if (act_local()) {
+       cmdoutput @upd_cmd;
+    } else {
+       dryrun_report @upd_cmd;
+    }
+}
+
+sub parse_dsc_field_def_dsc_distro () {
+    $dsc_distro //= cfg qw(dgit.default.old-dsc-distro
+                          dgit.default.distro);
+}
+
+sub parse_dsc_field ($$) {
+    my ($dsc, $what) = @_;
+    my $f;
+    foreach my $field (@ourdscfield) {
+       $f = $dsc->{$field};
+       last if defined $f;
+    }
+
+    if (!defined $f) {
+       progress "$what: NO git hash";
+       parse_dsc_field_def_dsc_distro();
+    } elsif (($dsc_hash, $dsc_distro, $dsc_hint_tag, $dsc_hint_url)
+            = $f =~ m/^(\w+)\s+($distro_re)\s+($versiontag_re)\s+(\S+)(?:\s|$)/) {
+       progress "$what: specified git info ($dsc_distro)";
+       $dsc_hint_tag = [ $dsc_hint_tag ];
+    } elsif ($f =~ m/^\w+\s*$/) {
+       $dsc_hash = $&;
+       parse_dsc_field_def_dsc_distro();
+       $dsc_hint_tag = [ debiantags +(getfield $dsc, 'Version'),
+                         $dsc_distro ];
+       progress "$what: specified git hash";
+    } else {
+       fail "$what: invalid Dgit info";
+    }
+}
+
+sub resolve_dsc_field_commit ($$) {
+    my ($already_distro, $already_mapref) = @_;
+
+    return unless defined $dsc_hash;
+
+    my $mapref =
+       defined $already_mapref &&
+       ($already_distro eq $dsc_distro || !$chase_dsc_distro)
+       ? $already_mapref : undef;
+
+    my $do_fetch;
+    $do_fetch = sub {
+       my ($what, @fetch) = @_;
+
+       local $idistro = $dsc_distro;
+       my $lrf = lrfetchrefs;
+
+       if (!$chase_dsc_distro) {
+           progress
+               "not chasing .dsc distro $dsc_distro: not fetching $what";
+           return 0;
+       }
+
+       progress
+           ".dsc names distro $dsc_distro: fetching $what";
+
+       my $url = access_giturl();
+       if (!defined $url) {
+           defined $dsc_hint_url or fail <<END;
+.dsc Dgit metadata is in context of distro $dsc_distro
+for which we have no configured url and .dsc provides no hint
+END
+           my $proto =
+               $dsc_hint_url =~ m#^([-+0-9a-zA-Z]+):# ? $1 :
+               $dsc_hint_url =~ m#^/# ? 'file' : 'bad-syntax';
+           parse_cfg_bool "dsc-url-proto-ok", 'false',
+               cfg("dgit.dsc-url-proto-ok.$proto",
+                   "dgit.default.dsc-url-proto-ok")
+               or fail <<END;
+.dsc Dgit metadata is in context of distro $dsc_distro
+for which we have no configured url;
+.dsc provides hinted url with protocol $proto which is unsafe.
+(can be overridden by config - consult documentation)
+END
+           $url = $dsc_hint_url;
+       }
+
+       git_lrfetch_sane $url, 1, @fetch;
+
+       return $lrf;
+    };
+
+    my $rewrite_enable = do {
+       local $idistro = $dsc_distro;
+       access_cfg('rewrite-map-enable', 'RETURN-UNDEF');
+    };
+
+    if (parse_cfg_bool 'rewrite-map-enable', 'true', $rewrite_enable) {
+       if (!defined $mapref) {
+           my $lrf = $do_fetch->("rewrite map", $rewritemap) or return;
+           $mapref = $lrf.'/'.$rewritemap;
+       }
+       my $rewritemapdata = git_cat_file $mapref.':map';
+       if (defined $rewritemapdata
+           && $rewritemapdata =~ m/^$dsc_hash(?:[ \t](\w+))/m) {
+           progress
+               "server's git history rewrite map contains a relevant entry!";
+
+           $dsc_hash = $1;
+           if (defined $dsc_hash) {
+               progress "using rewritten git hash in place of .dsc value";
+           } else {
+               progress "server data says .dsc hash is to be disregarded";
+           }
+       }
+    }
+
+    if (!defined git_cat_file $dsc_hash) {
+       my @tags = map { "tags/".$_ } @$dsc_hint_tag;
+       my $lrf = $do_fetch->("additional commits", @tags) &&
+           defined git_cat_file $dsc_hash
+           or fail <<END;
+.dsc Dgit metadata requires commit $dsc_hash
+but we could not obtain that object anywhere.
+END
+       foreach my $t (@tags) {
+           my $fullrefname = $lrf.'/'.$t;
+#          print STDERR "CHK $t $fullrefname ".Dumper(\%lrfetchrefs_f);
+           next unless $lrfetchrefs_f{$fullrefname};
+           next unless is_fast_fwd "$fullrefname~0", $dsc_hash;
+           lrfetchref_used $fullrefname;
+       }
+    }
+}
+
 sub fetch_from_archive () {
     ensure_setup_existing_tree();
 
 sub fetch_from_archive () {
     ensure_setup_existing_tree();
 
@@ -2446,17 +2925,9 @@ sub fetch_from_archive () {
     get_archive_dsc();
 
     if ($dsc) {
     get_archive_dsc();
 
     if ($dsc) {
-       foreach my $field (@ourdscfield) {
-           $dsc_hash = $dsc->{$field};
-           last if defined $dsc_hash;
-       }
-       if (defined $dsc_hash) {
-           $dsc_hash =~ m/\w+/ or fail "invalid hash in .dsc \`$dsc_hash'";
-           $dsc_hash = $&;
-           progress "last upload to archive specified git hash";
-       } else {
-           progress "last upload to archive has NO git hash";
-       }
+       parse_dsc_field($dsc, 'last upload to archive');
+       resolve_dsc_field_commit access_basedistro,
+           lrfetchrefs."/".$rewritemap
     } else {
        progress "no version available from the archive";
     }
     } else {
        progress "no version available from the archive";
     }
@@ -2748,10 +3219,7 @@ END
        if $lastpush_hash;
     $chkff->($lastfetch_hash, 'local tracking tip (last fetch)');
 
        if $lastpush_hash;
     $chkff->($lastfetch_hash, 'local tracking tip (last fetch)');
 
-    runcmd @git, qw(update-ref -m), "dgit fetch $csuite",
-           'DGIT_ARCHIVE', $hash;
-    cmdoutput @git, qw(log -n2), $hash;
-    # ... gives git a chance to complain if our commit is malformed
+    fetch_from_archive_record_1($hash);
 
     if (defined $skew_warning_vsn) {
        mkpath '.git/dgit';
 
     if (defined $skew_warning_vsn) {
        mkpath '.git/dgit';
@@ -2771,16 +3239,13 @@ END
     }
 
     if ($lastfetch_hash ne $hash) {
     }
 
     if ($lastfetch_hash ne $hash) {
-       my @upd_cmd = (@git, qw(update-ref -m), 'dgit fetch', lrref(), $hash);
-       if (act_local()) {
-           cmdoutput @upd_cmd;
-       } else {
-           dryrun_report @upd_cmd;
-       }
+       fetch_from_archive_record_2($hash);
     }
 
     lrfetchref_used lrfetchref();
 
     }
 
     lrfetchref_used lrfetchref();
 
+    check_gitattrs($hash, "fetched source tree");
+
     unshift @end, $del_lrfetchrefs;
     return $hash;
 }
     unshift @end, $del_lrfetchrefs;
     return $hash;
 }
@@ -2842,24 +3307,296 @@ sub ensure_setup_existing_tree () {
     set_local_git_config $k, 'true';
 }
 
     set_local_git_config $k, 'true';
 }
 
+sub open_gitattrs () {
+    my $gai = new IO::File ".git/info/attributes"
+       or $!==ENOENT
+       or die "open .git/info/attributes: $!";
+    return $gai;
+}
+
+sub is_gitattrs_setup () {
+    my $gai = open_gitattrs();
+    return 0 unless $gai;
+    while (<$gai>) {
+       return 1 if m{^\[attr\]dgit-defuse-attrs\s};
+    }
+    $gai->error and die $!;
+    return 0;
+}    
+
+sub setup_gitattrs (;$) {
+    my ($always) = @_;
+    return unless $always || access_cfg_bool(1, 'setup-gitattributes');
+
+    if (is_gitattrs_setup()) {
+       progress <<END;
+[attr]dgit-defuse-attrs already found in .git/info/attributes
+ not doing further gitattributes setup
+END
+       return;
+    }
+    my $af = ".git/info/attributes";
+    ensuredir '.git/info';
+    open GAO, "> $af.new" or die $!;
+    print GAO <<END or die $!;
+*      dgit-defuse-attrs
+[attr]dgit-defuse-attrs        $negate_harmful_gitattrs
+# ^ see GITATTRIBUTES in dgit(7) and dgit setup-new-tree in dgit(1)
+END
+    my $gai = open_gitattrs();
+    if ($gai) {
+       while (<$gai>) {
+           chomp;
+           print GAO $_, "\n" or die $!;
+       }
+       $gai->error and die $!;
+    }
+    close GAO or die $!;
+    rename "$af.new", "$af" or die "install $af: $!";
+}
+
 sub setup_new_tree () {
     setup_mergechangelogs();
     setup_useremail();
 sub setup_new_tree () {
     setup_mergechangelogs();
     setup_useremail();
+    setup_gitattrs();
+}
+
+sub check_gitattrs ($$) {
+    my ($treeish, $what) = @_;
+
+    return if is_gitattrs_setup;
+
+    local $/="\0";
+    my @cmd = (@git, qw(ls-tree -lrz --), "${treeish}:");
+    debugcmd "|",@cmd;
+    my $gafl = new IO::File;
+    open $gafl, "-|", @cmd or die $!;
+    while (<$gafl>) {
+       chomp or die;
+       s/^\d+\s+\w+\s+\w+\s+(\d+)\t// or die;
+       next if $1 == 0;
+       next unless m{(?:^|/)\.gitattributes$};
+
+       # oh dear, found one
+       print STDERR <<END;
+dgit: warning: $what contains .gitattributes
+dgit: .gitattributes have not been defused.  Recommended: dgit setup-new-tree.
+END
+       close $gafl;
+       return;
+    }
+    # tree contains no .gitattributes files
+    $?=0; $!=0; close $gafl or failedcmd @cmd;
+}
+
+
+sub multisuite_suite_child ($$$) {
+    my ($tsuite, $merginputs, $fn) = @_;
+    # in child, sets things up, calls $fn->(), and returns undef
+    # in parent, returns canonical suite name for $tsuite
+    my $canonsuitefh = IO::File::new_tmpfile;
+    my $pid = fork // die $!;
+    if (!$pid) {
+       forkcheck_setup();
+       $isuite = $tsuite;
+       $us .= " [$isuite]";
+       $debugprefix .= " ";
+       progress "fetching $tsuite...";
+       canonicalise_suite();
+       print $canonsuitefh $csuite, "\n" or die $!;
+       close $canonsuitefh or die $!;
+       $fn->();
+       return undef;
+    }
+    waitpid $pid,0 == $pid or die $!;
+    fail "failed to obtain $tsuite: ".waitstatusmsg() if $? && $?!=256*4;
+    seek $canonsuitefh,0,0 or die $!;
+    local $csuite = <$canonsuitefh>;
+    die $! unless defined $csuite && chomp $csuite;
+    if ($? == 256*4) {
+       printdebug "multisuite $tsuite missing\n";
+       return $csuite;
+    }
+    printdebug "multisuite $tsuite ok (canon=$csuite)\n";
+    push @$merginputs, {
+        Ref => lrref,
+        Info => $csuite,
+    };
+    return $csuite;
+}
+
+sub fork_for_multisuite ($) {
+    my ($before_fetch_merge) = @_;
+    # if nothing unusual, just returns ''
+    #
+    # if multisuite:
+    # returns 0 to caller in child, to do first of the specified suites
+    # in child, $csuite is not yet set
+    #
+    # returns 1 to caller in parent, to finish up anything needed after
+    # in parent, $csuite is set to canonicalised portmanteau
+
+    my $org_isuite = $isuite;
+    my @suites = split /\,/, $isuite;
+    return '' unless @suites > 1;
+    printdebug "fork_for_multisuite: @suites\n";
+
+    my @mergeinputs;
+
+    my $cbasesuite = multisuite_suite_child($suites[0], \@mergeinputs,
+                                           sub { });
+    return 0 unless defined $cbasesuite;
+
+    fail "package $package missing in (base suite) $cbasesuite"
+       unless @mergeinputs;
+
+    my @csuites = ($cbasesuite);
+
+    $before_fetch_merge->();
+
+    foreach my $tsuite (@suites[1..$#suites]) {
+       $tsuite =~ s/^-/$cbasesuite-/;
+       my $csubsuite = multisuite_suite_child($tsuite, \@mergeinputs,
+                                              sub {
+            @end = ();
+            fetch();
+           exit 0;
+       });
+       # xxx collecte the ref here
+
+       $csubsuite =~ s/^\Q$cbasesuite\E-/-/;
+       push @csuites, $csubsuite;
+    }
+
+    foreach my $mi (@mergeinputs) {
+       my $ref = git_get_ref $mi->{Ref};
+       die "$mi->{Ref} ?" unless length $ref;
+       $mi->{Commit} = $ref;
+    }
+
+    $csuite = join ",", @csuites;
+
+    my $previous = git_get_ref lrref;
+    if ($previous) {
+       unshift @mergeinputs, {
+            Commit => $previous,
+            Info => "local combined tracking branch",
+            Warning =>
+ "archive seems to have rewound: local tracking branch is ahead!",
+        };
+    }
+
+    foreach my $ix (0..$#mergeinputs) {
+       $mergeinputs[$ix]{Index} = $ix;
+    }
+
+    @mergeinputs = sort {
+       -version_compare(mergeinfo_version $a,
+                        mergeinfo_version $b) # highest version first
+           or
+       $a->{Index} <=> $b->{Index}; # earliest in spec first
+    } @mergeinputs;
+
+    my @needed;
+
+  NEEDED:
+    foreach my $mi (@mergeinputs) {
+       printdebug "multisuite merge check $mi->{Info}\n";
+       foreach my $previous (@needed) {
+           next unless is_fast_fwd $mi->{Commit}, $previous->{Commit};
+           printdebug "multisuite merge un-needed $previous->{Info}\n";
+           next NEEDED;
+       }
+       push @needed, $mi;
+       printdebug "multisuite merge this-needed\n";
+       $mi->{Character} = '+';
+    }
+
+    $needed[0]{Character} = '*';
+
+    my $output = $needed[0]{Commit};
+
+    if (@needed > 1) {
+       printdebug "multisuite merge nontrivial\n";
+       my $tree = cmdoutput qw(git rev-parse), $needed[0]{Commit}.':';
+
+       my $commit = "tree $tree\n";
+       my $msg = "Combine archive branches $csuite [dgit]\n\n".
+           "Input branches:\n";
+
+       foreach my $mi (sort { $a->{Index} <=> $b->{Index} } @mergeinputs) {
+           printdebug "multisuite merge include $mi->{Info}\n";
+           $mi->{Character} //= ' ';
+           $commit .= "parent $mi->{Commit}\n";
+           $msg .= sprintf " %s  %-25s %s\n",
+               $mi->{Character},
+               (mergeinfo_version $mi),
+               $mi->{Info};
+       }
+       my $authline = clogp_authline mergeinfo_getclogp $needed[0];
+       $msg .= "\nKey\n".
+           " * marks the highest version branch, which choose to use\n".
+           " + marks each branch which was not already an ancestor\n\n".
+           "[dgit multi-suite $csuite]\n";
+       $commit .=
+           "author $authline\n".
+           "committer $authline\n\n";
+       $output = make_commit_text $commit.$msg;
+       printdebug "multisuite merge generated $output\n";
+    }
+
+    fetch_from_archive_record_1($output);
+    fetch_from_archive_record_2($output);
+
+    progress "calculated combined tracking suite $csuite";
+
+    return 1;
+}
+
+sub clone_set_head () {
+    open H, "> .git/HEAD" or die $!;
+    print H "ref: ".lref()."\n" or die $!;
+    close H or die $!;
+}
+sub clone_finish ($) {
+    my ($dstdir) = @_;
+    runcmd @git, qw(reset --hard), lrref();
+    runcmd qw(bash -ec), <<'END';
+        set -o pipefail
+        git ls-tree -r --name-only -z HEAD | \
+        xargs -0r touch -h -r . --
+END
+    printdone "ready for work in $dstdir";
 }
 
 sub clone ($) {
 }
 
 sub clone ($) {
+    # in multisuite, returns twice!
+    # once in parent after first suite fetched,
+    # and then again in child after everything is finished
     my ($dstdir) = @_;
     my ($dstdir) = @_;
-    canonicalise_suite();
     badusage "dry run makes no sense with clone" unless act_local();
     badusage "dry run makes no sense with clone" unless act_local();
+
+    my $multi_fetched = fork_for_multisuite(sub {
+        printdebug "multi clone before fetch merge\n";
+        changedir $dstdir;
+    });
+    if ($multi_fetched) {
+        printdebug "multi clone after fetch merge\n";
+       clone_set_head();
+       clone_finish($dstdir);
+       return;
+    }
+    printdebug "clone main body\n";
+
+    canonicalise_suite();
     my $hasgit = check_for_git();
     mkdir $dstdir or fail "create \`$dstdir': $!";
     changedir $dstdir;
     runcmd @git, qw(init -q);
     my $hasgit = check_for_git();
     mkdir $dstdir or fail "create \`$dstdir': $!";
     changedir $dstdir;
     runcmd @git, qw(init -q);
+    setup_new_tree();
+    clone_set_head();
     my $giturl = access_giturl(1);
     if (defined $giturl) {
     my $giturl = access_giturl(1);
     if (defined $giturl) {
-       open H, "> .git/HEAD" or die $!;
-       print H "ref: ".lref()."\n" or die $!;
-       close H or die $!;
        runcmd @git, qw(remote add), 'origin', $giturl;
     }
     if ($hasgit) {
        runcmd @git, qw(remote add), 'origin', $giturl;
     }
     if ($hasgit) {
@@ -2875,17 +3612,11 @@ sub clone ($) {
        $vcsgiturl =~ s/\s+-b\s+\S+//g;
        runcmd @git, qw(remote add vcs-git), $vcsgiturl;
     }
        $vcsgiturl =~ s/\s+-b\s+\S+//g;
        runcmd @git, qw(remote add vcs-git), $vcsgiturl;
     }
-    setup_new_tree();
-    runcmd @git, qw(reset --hard), lrref();
-    runcmd qw(bash -ec), <<'END';
-        set -o pipefail
-        git ls-tree -r --name-only -z HEAD | \
-        xargs -0r touch -r . --
-END
-    printdone "ready for work in $dstdir";
+    clone_finish($dstdir);
 }
 
 sub fetch () {
 }
 
 sub fetch () {
+    canonicalise_suite();
     if (check_for_git()) {
        git_fetch_us();
     }
     if (check_for_git()) {
        git_fetch_us();
     }
@@ -2894,7 +3625,9 @@ sub fetch () {
 }
 
 sub pull () {
 }
 
 sub pull () {
-    fetch();
+    my $multi_fetched = fork_for_multisuite(sub { });
+    fetch() unless $multi_fetched; # parent
+    return if $multi_fetched eq '0'; # child
     runcmd_ordryrun_local @git, qw(merge -m),"Merge from $csuite [dgit]",
         lrref();
     printdone "fetched to ".lrref()." and merged into HEAD";
     runcmd_ordryrun_local @git, qw(merge -m),"Merge from $csuite [dgit]",
         lrref();
     printdone "fetched to ".lrref()." and merged into HEAD";
@@ -3057,18 +3790,28 @@ sub pseudomerge_version_check ($$) {
        } else {
            my $v = $i_arch_v->[0];
            progress "Checking package changelog for archive version $v ...";
        } else {
            my $v = $i_arch_v->[0];
            progress "Checking package changelog for archive version $v ...";
+           my $cd;
            eval {
                my @xa = ("-f$v", "-t$v");
                my $vclogp = parsechangelog @xa;
            eval {
                my @xa = ("-f$v", "-t$v");
                my $vclogp = parsechangelog @xa;
-               my $cv = [ (getfield $vclogp, 'Version'),
-                          "Version field from dpkg-parsechangelog @xa" ];
+               my $gf = sub {
+                   my ($fn) = @_;
+                   [ (getfield $vclogp, $fn),
+                     "$fn field from dpkg-parsechangelog @xa" ];
+               };
+               my $cv = $gf->('Version');
                infopair_cond_equal($i_arch_v, $cv);
                infopair_cond_equal($i_arch_v, $cv);
+               $cd = $gf->('Distribution');
            };
            if ($@) {
                $@ =~ s/^dgit: //gm;
                fail "$@".
                    "Perhaps debian/changelog does not mention $v ?";
            }
            };
            if ($@) {
                $@ =~ s/^dgit: //gm;
                fail "$@".
                    "Perhaps debian/changelog does not mention $v ?";
            }
+           fail <<END if $cd->[0] =~ m/UNRELEASED/;
+$cd->[1] is $cd->[0]
+Your tree seems to based on earlier (not uploaded) $v.
+END
        }
     }
     
        }
     }
     
@@ -3098,7 +3841,7 @@ tree $tree
 parent $dgitview
 parent $archive_hash
 author $authline
 parent $dgitview
 parent $archive_hash
 author $authline
-commiter $authline
+committer $authline
 
 $msg_msg
 
 
 $msg_msg
 
@@ -3126,6 +3869,7 @@ sub splitbrain_pseudomerge ($$$$) {
     #
 
     return $dgitview unless defined $archive_hash;
     #
 
     return $dgitview unless defined $archive_hash;
+    return $dgitview if deliberately_not_fast_forward();
 
     printdebug "splitbrain_pseudomerge...\n";
 
 
     printdebug "splitbrain_pseudomerge...\n";
 
@@ -3139,9 +3883,9 @@ sub splitbrain_pseudomerge ($$$$) {
 
     if (defined $overwrite_version) {
     } elsif (!eval {
 
     if (defined $overwrite_version) {
     } elsif (!eval {
-       my $t_dep14 = debiantag_maintview $i_arch_v->[0], access_basedistro;
+       my $t_dep14 = debiantag_maintview $i_arch_v->[0], access_nomdistro;
        my $i_dep14 = infopair_lrf_tag_lookup($t_dep14, "maintainer view tag");
        my $i_dep14 = infopair_lrf_tag_lookup($t_dep14, "maintainer view tag");
-       my $t_dgit = debiantag_new $i_arch_v->[0], access_basedistro;
+       my $t_dgit = debiantag_new $i_arch_v->[0], access_nomdistro;
        my $i_dgit = infopair_lrf_tag_lookup($t_dgit, "dgit view tag");
        my $i_archive = [ $archive_hash, "current archive contents" ];
 
        my $i_dgit = infopair_lrf_tag_lookup($t_dgit, "dgit view tag");
        my $i_archive = [ $archive_hash, "current archive contents" ];
 
@@ -3200,10 +3944,17 @@ sub push_parse_changelog ($) {
     my $clogp = Dpkg::Control::Hash->new();
     $clogp->load($clogpfn) or die;
 
     my $clogp = Dpkg::Control::Hash->new();
     $clogp->load($clogpfn) or die;
 
-    $package = getfield $clogp, 'Source';
+    my $clogpackage = getfield $clogp, 'Source';
+    $package //= $clogpackage;
+    fail "-p specified $package but changelog specified $clogpackage"
+       unless $package eq $clogpackage;
     my $cversion = getfield $clogp, 'Version';
     my $cversion = getfield $clogp, 'Version';
-    my $tag = debiantag($cversion, access_basedistro);
-    runcmd @git, qw(check-ref-format), $tag;
+
+    if (!$we_are_initiator) {
+       # rpush initiator can't do this because it doesn't have $isuite yet
+       my $tag = debiantag($cversion, access_nomdistro);
+       runcmd @git, qw(check-ref-format), $tag;
+    }
 
     my $dscfn = dscfn($cversion);
 
 
     my $dscfn = dscfn($cversion);
 
@@ -3236,9 +3987,23 @@ sub push_tagwants ($$$$) {
            TfSuffix => '-maintview',
             View => 'maint',
         };
            TfSuffix => '-maintview',
             View => 'maint',
         };
-    }
+    } elsif ($dodep14tag eq 'no' ? 0
+            : $dodep14tag eq 'want' ? access_cfg_tagformats_can_splitbrain
+            : $dodep14tag eq 'always'
+            ? (access_cfg_tagformats_can_splitbrain or fail <<END)
+--dep14tag-always (or equivalent in config) means server must support
+ both "new" and "maint" tag formats, but config says it doesn't.
+END
+           : die "$dodep14tag ?") {
+       push @tagwants, {
+           TagFn => \&debiantag_maintview,
+           Objid => $dgithead,
+           TfSuffix => '-dgit',
+           View => 'dgit',
+        };
+    };
     foreach my $tw (@tagwants) {
     foreach my $tw (@tagwants) {
-       $tw->{Tag} = $tw->{TagFn}($cversion, access_basedistro);
+       $tw->{Tag} = $tw->{TagFn}($cversion, access_nomdistro);
        $tw->{Tfn} = sub { $tfbase.$tw->{TfSuffix}.$_[0]; };
     }
     printdebug 'push_tagwants: ', Dumper(\@_, \@tagwants);
        $tw->{Tfn} = sub { $tfbase.$tw->{TfSuffix}.$_[0]; };
     }
     printdebug 'push_tagwants: ', Dumper(\@_, \@tagwants);
@@ -3252,7 +4017,11 @@ sub push_mktags ($$ $$ $) {
 
     die unless $tagwants->[0]{View} eq 'dgit';
 
 
     die unless $tagwants->[0]{View} eq 'dgit';
 
-    $dsc->{$ourdscfield[0]} = $tagwants->[0]{Objid};
+    my $declaredistro = access_nomdistro();
+    my $reader_giturl = do { local $access_forpush=0; access_giturl(); };
+    $dsc->{$ourdscfield[0]} = join " ",
+       $tagwants->[0]{Objid}, $declaredistro, $tagwants->[0]{Tag},
+       $reader_giturl;
     $dsc->save("$dscfn.tmp") or die $!;
 
     my $changes = parsecontrol($changesfile,$changesfilewhat);
     $dsc->save("$dscfn.tmp") or die $!;
 
     my $changes = parsecontrol($changesfile,$changesfilewhat);
@@ -3269,7 +4038,6 @@ sub push_mktags ($$ $$ $) {
     # to control the "tagger" (b) we can do remote signing
     my $authline = clogp_authline $clogp;
     my $delibs = join(" ", "",@deliberatelies);
     # to control the "tagger" (b) we can do remote signing
     my $authline = clogp_authline $clogp;
     my $delibs = join(" ", "",@deliberatelies);
-    my $declaredistro = access_basedistro();
 
     my $mktag = sub {
        my ($tw) = @_;
 
     my $mktag = sub {
        my ($tw) = @_;
@@ -3371,6 +4139,7 @@ END
     prep_ud();
 
     access_giturl(); # check that success is vaguely likely
     prep_ud();
 
     access_giturl(); # check that success is vaguely likely
+    rpush_handle_protovsn_bothends() if $we_are_initiator;
     select_tagformat();
 
     my $clogpfn = ".git/dgit/changelog.822.tmp";
     select_tagformat();
 
     my $clogpfn = ".git/dgit/changelog.822.tmp";
@@ -3383,7 +4152,7 @@ END
 
     my $dscpath = "$buildproductsdir/$dscfn";
     stat_exists $dscpath or
 
     my $dscpath = "$buildproductsdir/$dscfn";
     stat_exists $dscpath or
-       fail "looked for .dsc $dscfn, but $!;".
+       fail "looked for .dsc $dscpath, but $!;".
            " maybe you forgot to build";
 
     responder_send_file('dsc', $dscpath);
            " maybe you forgot to build";
 
     responder_send_file('dsc', $dscpath);
@@ -3450,7 +4219,7 @@ END
     progress "checking that $dscfn corresponds to HEAD";
     runcmd qw(dpkg-source -x --),
         $dscpath =~ m#^/# ? $dscpath : "../../../$dscpath";
     progress "checking that $dscfn corresponds to HEAD";
     runcmd qw(dpkg-source -x --),
         $dscpath =~ m#^/# ? $dscpath : "../../../$dscpath";
-    my ($tree,$dir) = mktree_in_ud_from_only_subdir();
+    my ($tree,$dir) = mktree_in_ud_from_only_subdir("source package");
     check_for_vendor_patches() if madformat($dsc->{format});
     changedir '../../../..';
     my @diffcmd = (@git, qw(diff --quiet), $tree, $dgithead);
     check_for_vendor_patches() if madformat($dsc->{format});
     changedir '../../../..';
     my @diffcmd = (@git, qw(diff --quiet), $tree, $dgithead);
@@ -3499,12 +4268,21 @@ END
     responder_send_file('changes',$changesfile);
     responder_send_command("param head $dgithead");
     responder_send_command("param csuite $csuite");
     responder_send_file('changes',$changesfile);
     responder_send_command("param head $dgithead");
     responder_send_command("param csuite $csuite");
+    responder_send_command("param isuite $isuite");
     responder_send_command("param tagformat $tagformat");
     if (defined $maintviewhead) {
        die unless ($protovsn//4) >= 4;
        responder_send_command("param maint-view $maintviewhead");
     }
 
     responder_send_command("param tagformat $tagformat");
     if (defined $maintviewhead) {
        die unless ($protovsn//4) >= 4;
        responder_send_command("param maint-view $maintviewhead");
     }
 
+    # Perhaps send buildinfo(s) for signing
+    my $changes_files = getfield $changes, 'Files';
+    my @buildinfos = ($changes_files =~ m/ .* (\S+\.buildinfo)$/mg);
+    foreach my $bi (@buildinfos) {
+       responder_send_command("param buildinfo-filename $bi");
+       responder_send_file('buildinfo', "$buildproductsdir/$bi");
+    }
+
     if (deliberately_not_fast_forward) {
        git_for_each_ref(lrfetchrefs, sub {
            my ($objid,$objtype,$lrfetchrefname,$reftail) = @_;
     if (deliberately_not_fast_forward) {
        git_for_each_ref(lrfetchrefs, sub {
            my ($objid,$objtype,$lrfetchrefname,$reftail) = @_;
@@ -3566,14 +4344,19 @@ END
     runcmd_ordryrun @git, qw(update-ref -m), 'dgit push', lrref(), $dgithead;
 
     supplementary_message(<<'END');
     runcmd_ordryrun @git, qw(update-ref -m), 'dgit push', lrref(), $dgithead;
 
     supplementary_message(<<'END');
-Push failed, after updating the remote git repository.
-If you want to try again, you must use a new version number.
+Push failed, while obtaining signatures on the .changes and .dsc.
+If it was just that the signature failed, you may try again by using
+debsign by hand to sign the changes
+   $changesfile
+and then dput to complete the upload.
+If you need to change the package, you must use a new version number.
 END
     if ($we_are_responder) {
        my $dryrunsuffix = act_local() ? "" : ".tmp";
 END
     if ($we_are_responder) {
        my $dryrunsuffix = act_local() ? "" : ".tmp";
+       my @rfiles = ($dscpath, $changesfile);
+       push @rfiles, map { "$buildproductsdir/$_" } @buildinfos;
        responder_receive_files('signed-dsc-changes',
        responder_receive_files('signed-dsc-changes',
-                               "$dscpath$dryrunsuffix",
-                               "$changesfile$dryrunsuffix");
+                               map { "$_$dryrunsuffix" } @rfiles);
     } else {
        if (act_local()) {
            rename "$dscpath.tmp",$dscpath or die "$dscfn $!";
     } else {
        if (act_local()) {
            rename "$dscpath.tmp",$dscpath or die "$dscfn $!";
@@ -3599,9 +4382,11 @@ END
     responder_send_command("complete");
 }
 
     responder_send_command("complete");
 }
 
+sub pre_clone () {
+    no_local_git_cfg();
+}
 sub cmd_clone {
     parseopts();
 sub cmd_clone {
     parseopts();
-    notpushing();
     my $dstdir;
     badusage "-p is not allowed with clone; specify as argument instead"
        if defined $package;
     my $dstdir;
     badusage "-p is not allowed with clone; specify as argument instead"
        if defined $package;
@@ -3616,8 +4401,9 @@ sub cmd_clone {
     } else {
        badusage "incorrect arguments to dgit clone";
     }
     } else {
        badusage "incorrect arguments to dgit clone";
     }
-    $dstdir ||= "$package";
+    notpushing();
 
 
+    $dstdir ||= "$package";
     if (stat_exists $dstdir) {
        fail "$dstdir already exists";
     }
     if (stat_exists $dstdir) {
        fail "$dstdir already exists";
     }
@@ -3631,6 +4417,7 @@ sub cmd_clone {
                return if $!==&ENOENT;
                die "chdir $cwd_remove: $!";
            }
                return if $!==&ENOENT;
                die "chdir $cwd_remove: $!";
            }
+           printdebug "clone rmonerror removing $dstdir\n";
            if (stat $dstdir) {
                rmtree($dstdir) or die "remove $dstdir: $!\n";
            } elsif (grep { $! == $_ }
            if (stat $dstdir) {
                rmtree($dstdir) or die "remove $dstdir: $!\n";
            } elsif (grep { $! == $_ }
@@ -3646,7 +4433,12 @@ sub cmd_clone {
 }
 
 sub branchsuite () {
 }
 
 sub branchsuite () {
-    my $branch = cmdoutput_errok @git, qw(symbolic-ref HEAD);
+    my @cmd = (@git, qw(symbolic-ref -q HEAD));
+    my $branch = cmdoutput_errok @cmd;
+    if (!defined $branch) {
+       $?==256 or failedcmd @cmd;
+       return undef;
+    }
     if ($branch =~ m#$lbranch_re#o) {
        return $1;
     } else {
     if ($branch =~ m#$lbranch_re#o) {
        return $1;
     } else {
@@ -3655,30 +4447,30 @@ sub branchsuite () {
 }
 
 sub fetchpullargs () {
 }
 
 sub fetchpullargs () {
-    notpushing();
     if (!defined $package) {
        my $sourcep = parsecontrol('debian/control','debian/control');
        $package = getfield $sourcep, 'Source';
     }
     if (@ARGV==0) {
     if (!defined $package) {
        my $sourcep = parsecontrol('debian/control','debian/control');
        $package = getfield $sourcep, 'Source';
     }
     if (@ARGV==0) {
-#      $isuite = branchsuite();  # this doesn't work because dak hates canons
+       $isuite = branchsuite();
        if (!$isuite) {
            my $clogp = parsechangelog();
        if (!$isuite) {
            my $clogp = parsechangelog();
-           $isuite = getfield $clogp, 'Distribution';
+           my $clogsuite = getfield $clogp, 'Distribution';
+           $isuite= $clogsuite if $clogsuite ne 'UNRELEASED';
        }
        }
-       canonicalise_suite();
-       progress "fetching from suite $csuite";
     } elsif (@ARGV==1) {
        ($isuite) = @ARGV;
     } elsif (@ARGV==1) {
        ($isuite) = @ARGV;
-       canonicalise_suite();
     } else {
        badusage "incorrect arguments to dgit fetch or dgit pull";
     }
     } else {
        badusage "incorrect arguments to dgit fetch or dgit pull";
     }
+    notpushing();
 }
 
 sub cmd_fetch {
     parseopts();
     fetchpullargs();
 }
 
 sub cmd_fetch {
     parseopts();
     fetchpullargs();
+    my $multi_fetched = fork_for_multisuite(sub { });
+    exit 0 if $multi_fetched;
     fetch();
 }
 
     fetch();
 }
 
@@ -3694,21 +4486,18 @@ END
     pull();
 }
 
     pull();
 }
 
-sub cmd_push {
+sub prep_push () {
     parseopts();
     parseopts();
+    build_or_push_prep_early();
     pushing();
     pushing();
-    badusage "-p is not allowed with dgit push" if defined $package;
     check_not_dirty();
     check_not_dirty();
-    my $clogp = parsechangelog();
-    $package = getfield $clogp, 'Source';
     my $specsuite;
     if (@ARGV==0) {
     } elsif (@ARGV==1) {
        ($specsuite) = (@ARGV);
     } else {
     my $specsuite;
     if (@ARGV==0) {
     } elsif (@ARGV==1) {
        ($specsuite) = (@ARGV);
     } else {
-       badusage "incorrect arguments to dgit push";
+       badusage "incorrect arguments to dgit $subcommand";
     }
     }
-    $isuite = getfield $clogp, 'Distribution';
     if ($new_package) {
        local ($package) = $existing_package; # this is a hack
        canonicalise_suite();
     if ($new_package) {
        local ($package) = $existing_package; # this is a hack
        canonicalise_suite();
@@ -3718,15 +4507,19 @@ sub cmd_push {
     if (defined $specsuite &&
        $specsuite ne $isuite &&
        $specsuite ne $csuite) {
     if (defined $specsuite &&
        $specsuite ne $isuite &&
        $specsuite ne $csuite) {
-           fail "dgit push: changelog specifies $isuite ($csuite)".
+           fail "dgit $subcommand: changelog specifies $isuite ($csuite)".
                " but command line specifies $specsuite";
     }
                " but command line specifies $specsuite";
     }
+}
+
+sub cmd_push {
+    prep_push();
     dopush();
 }
 
 #---------- remote commands' implementation ----------
 
     dopush();
 }
 
 #---------- remote commands' implementation ----------
 
-sub cmd_remote_push_build_host {
+sub pre_remote_push_build_host {
     my ($nrargs) = shift @ARGV;
     my (@rargs) = @ARGV[0..$nrargs-1];
     @ARGV = @ARGV[$nrargs..$#ARGV];
     my ($nrargs) = shift @ARGV;
     my (@rargs) = @ARGV[0..$nrargs-1];
     @ARGV = @ARGV[$nrargs..$#ARGV];
@@ -3739,8 +4532,6 @@ sub cmd_remote_push_build_host {
     $we_are_responder = 1;
     $us .= " (build host)";
 
     $we_are_responder = 1;
     $us .= " (build host)";
 
-    pushing();
-
     open PI, "<&STDIN" or die $!;
     open STDIN, "/dev/null" or die $!;
     open PO, ">&STDOUT" or die $!;
     open PI, "<&STDIN" or die $!;
     open STDIN, "/dev/null" or die $!;
     open PO, ">&STDOUT" or die $!;
@@ -3758,12 +4549,14 @@ sub cmd_remote_push_build_host {
         " but invocation host has $vsnwant"
        unless defined $protovsn;
 
         " but invocation host has $vsnwant"
        unless defined $protovsn;
 
-    responder_send_command("dgit-remote-push-ready $protovsn");
-    rpush_handle_protovsn_bothends();
     changedir $dir;
     changedir $dir;
+}
+sub cmd_remote_push_build_host {
+    responder_send_command("dgit-remote-push-ready $protovsn");
     &cmd_push;
 }
 
     &cmd_push;
 }
 
+sub pre_remote_push_responder { pre_remote_push_build_host(); }
 sub cmd_remote_push_responder { cmd_remote_push_build_host(); }
 # ... for compatibility with proto vsn.1 dgit (just so that user gets
 #     a good error message)
 sub cmd_remote_push_responder { cmd_remote_push_build_host(); }
 # ... for compatibility with proto vsn.1 dgit (just so that user gets
 #     a good error message)
@@ -3792,7 +4585,10 @@ sub i_cleanup {
     }
 }
 
     }
 }
 
-END { i_cleanup(); }
+END {
+    return unless forkcheck_mainprocess();
+    i_cleanup();
+}
 
 sub i_method {
     my ($base,$selector,@args) = @_;
 
 sub i_method {
     my ($base,$selector,@args) = @_;
@@ -3800,8 +4596,10 @@ sub i_method {
     { no strict qw(refs); &{"${base}_${selector}"}(@args); }
 }
 
     { no strict qw(refs); &{"${base}_${selector}"}(@args); }
 }
 
+sub pre_rpush () {
+    no_local_git_cfg();
+}
 sub cmd_rpush {
 sub cmd_rpush {
-    pushing();
     my $host = nextarg;
     my $dir;
     if ($host =~ m/^((?:[^][]|\[[^][]*\])*)\:/) {
     my $host = nextarg;
     my $dir;
     if ($host =~ m/^((?:[^][]|\[[^][]*\])*)\:/) {
@@ -3821,6 +4619,8 @@ sub cmd_rpush {
     my @cmd = (@ssh, $host, shellquote @rdgit);
     debugcmd "+",@cmd;
 
     my @cmd = (@ssh, $host, shellquote @rdgit);
     debugcmd "+",@cmd;
 
+    $we_are_initiator=1;
+
     if (defined $initiator_tempdir) {
        rmtree $initiator_tempdir;
        mkdir $initiator_tempdir, 0700 or die "$initiator_tempdir: $!";
     if (defined $initiator_tempdir) {
        rmtree $initiator_tempdir;
        mkdir $initiator_tempdir, 0700 or die "$initiator_tempdir: $!";
@@ -3834,11 +4634,6 @@ sub cmd_rpush {
     die "$protovsn ?" unless grep { $_ eq $protovsn } @rpushprotovsn_support;
     $supplementary_message = '' unless $protovsn >= 3;
 
     die "$protovsn ?" unless grep { $_ eq $protovsn } @rpushprotovsn_support;
     $supplementary_message = '' unless $protovsn >= 3;
 
-    fail "rpush negotiated protocol version $protovsn".
-       " which does not support quilt mode $quilt_mode"
-       if quiltmode_splitbrain;
-
-    rpush_handle_protovsn_bothends();
     for (;;) {
        my ($icmd,$iargs) = initiator_expect {
            m/^(\S+)(?: (.*))?$/;
     for (;;) {
        my ($icmd,$iargs) = initiator_expect {
            m/^(\S+)(?: (.*))?$/;
@@ -3902,6 +4697,18 @@ our %i_wanted;
 sub i_resp_want ($) {
     my ($keyword) = @_;
     die "$keyword ?" if $i_wanted{$keyword}++;
 sub i_resp_want ($) {
     my ($keyword) = @_;
     die "$keyword ?" if $i_wanted{$keyword}++;
+    
+    defined $i_param{'csuite'} or badproto \*RO, "premature desire, no csuite";
+    $isuite = $i_param{'isuite'} // $i_param{'csuite'};
+    die unless $isuite =~ m/^$suite_re$/;
+
+    pushing();
+    rpush_handle_protovsn_bothends();
+
+    fail "rpush negotiated protocol version $protovsn".
+       " which does not support quilt mode $quilt_mode"
+       if quiltmode_splitbrain;
+
     my @localpaths = i_method "i_want", $keyword;
     printdebug "[[  $keyword @localpaths\n";
     foreach my $localpath (@localpaths) {
     my @localpaths = i_method "i_want", $keyword;
     printdebug "[[  $keyword @localpaths\n";
     foreach my $localpath (@localpaths) {
@@ -3910,7 +4717,7 @@ sub i_resp_want ($) {
     print RI "files-end\n" or die $!;
 }
 
     print RI "files-end\n" or die $!;
 }
 
-our ($i_clogp, $i_version, $i_dscfn, $i_changesfn);
+our ($i_clogp, $i_version, $i_dscfn, $i_changesfn, @i_buildinfos);
 
 sub i_localname_parsed_changelog {
     return "remote-changelog.822";
 
 sub i_localname_parsed_changelog {
     return "remote-changelog.822";
@@ -3927,6 +4734,31 @@ sub i_localname_dsc {
 }
 sub i_file_dsc { }
 
 }
 sub i_file_dsc { }
 
+sub i_localname_buildinfo ($) {
+    my $bi = $i_param{'buildinfo-filename'};
+    defined $bi or badproto \*RO, "buildinfo before filename";
+    defined $i_changesfn or badproto \*RO, "buildinfo before changes";
+    $bi =~ m{^\Q$package\E_[!-.0-~]*\.buildinfo$}s
+       or badproto \*RO, "improper buildinfo filename";
+    return $&;
+}
+sub i_file_buildinfo {
+    my $bi = $i_param{'buildinfo-filename'};
+    my $bd = parsecontrol "$i_tmp/$bi", $bi;
+    my $ch = parsecontrol "$i_tmp/$i_changesfn", 'changes';
+    if (!forceing [qw(buildinfo-changes-mismatch)]) {
+       files_compare_inputs($bd, $ch);
+       (getfield $bd, $_) eq (getfield $ch, $_) or
+           fail "buildinfo mismatch $_"
+           foreach qw(Source Version);
+       !defined $bd->{$_} or
+           fail "buildinfo contains $_"
+           foreach qw(Changes Changed-by Distribution);
+    }
+    push @i_buildinfos, $bi;
+    delete $i_param{'buildinfo-filename'};
+}
+
 sub i_localname_changes {
     defined $i_dscfn or badproto \*RO, "dsc (before parsed-changelog)";
     $i_changesfn = $i_dscfn;
 sub i_localname_changes {
     defined $i_dscfn or badproto \*RO, "dsc (before parsed-changelog)";
     $i_changesfn = $i_dscfn;
@@ -3968,7 +4800,7 @@ sub i_want_signed_tag {
 sub i_want_signed_dsc_changes {
     rename "$i_dscfn.tmp","$i_dscfn" or die "$i_dscfn $!";
     sign_changes $i_changesfn;
 sub i_want_signed_dsc_changes {
     rename "$i_dscfn.tmp","$i_dscfn" or die "$i_dscfn $!";
     sign_changes $i_changesfn;
-    return ($i_dscfn, $i_changesfn);
+    return ($i_dscfn, $i_changesfn, @i_buildinfos);
 }
 
 #---------- building etc. ----------
 }
 
 #---------- building etc. ----------
@@ -4001,7 +4833,7 @@ END
        local $ENV{'EDITOR'} = cmdoutput qw(realpath --), $0;
        local $ENV{'VISUAL'} = $ENV{'EDITOR'};
        local $ENV{$fakeeditorenv} = cmdoutput qw(realpath --), $descfn;
        local $ENV{'EDITOR'} = cmdoutput qw(realpath --), $0;
        local $ENV{'VISUAL'} = $ENV{'EDITOR'};
        local $ENV{$fakeeditorenv} = cmdoutput qw(realpath --), $descfn;
-       runcmd @dpkgsource, qw(--commit .), $patchname;
+       runcmd @dpkgsource, qw(--commit --include-removal .), $patchname;
     }
 }
 
     }
 }
 
@@ -4017,7 +4849,7 @@ sub quiltify_trees_differ ($$;$$$) {
     #  a list of unrepresentable changes (removals of upstream files
     #  (as messages)
     local $/=undef;
     #  a list of unrepresentable changes (removals of upstream files
     #  (as messages)
     local $/=undef;
-    my @cmd = (@git, qw(diff-tree -z));
+    my @cmd = (@git, qw(diff-tree -z --no-renames));
     push @cmd, qw(--name-only) unless $unrepres;
     push @cmd, qw(-r) if $finegrained || $unrepres;
     push @cmd, $x, $y;
     push @cmd, qw(--name-only) unless $unrepres;
     push @cmd, qw(-r) if $finegrained || $unrepres;
     push @cmd, $x, $y;
@@ -4036,17 +4868,28 @@ sub quiltify_trees_differ ($$;$$$) {
 
        if ($unrepres) {
            eval {
 
        if ($unrepres) {
            eval {
-               die "deleted\n" unless $newmode =~ m/[^0]/;
-               die "not a plain file\n" unless $newmode =~ m/^10\d{4}$/;
-               if ($oldmode =~ m/[^0]/) {
-                   die "mode changed\n" if $oldmode ne $newmode;
+               die "not a plain file or symlink\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 "modified symlink\n" unless $newmode =~ m/^10/;
+               } elsif ($oldmode =~ m/[^0]/) {
+                   # deletion
+                   die "deletion of symlink\n"
+                       unless $oldmode =~ m/^10/;
                } else {
                } else {
-                   die "non-default mode\n" unless $newmode =~ m/^100644$/;
+                   # creation
+                   die "creation with non-default mode\n"
+                       unless $newmode =~ m/^100644$/ or
+                              $newmode =~ m/^120000$/;
                }
            };
            if ($@) {
                local $/="\n"; chomp $@;
                }
            };
            if ($@) {
                local $/="\n"; chomp $@;
-               push @$unrepres, [ $f, $@ ];
+               push @$unrepres, [ $f, "$@ ($oldmode->$newmode)" ];
            }
        }
 
            }
        }
 
@@ -4396,6 +5239,7 @@ sub quiltify ($$$$) {
                die "contains unexpected slashes\n" if m{//} || m{/$};
                die "contains leading punctuation\n" if m{^\W} || m{/\W};
                die "contains bad character(s)\n" if m{[^-a-z0-9_.+=~/]}i;
                die "contains unexpected slashes\n" if m{//} || m{/$};
                die "contains leading punctuation\n" if m{^\W} || m{/\W};
                die "contains bad character(s)\n" if m{[^-a-z0-9_.+=~/]}i;
+               die "is series file\n" if m{$series_filename_re}o;
                die "too long" if length > 200;
            };
            return $_ unless $@;
                die "too long" if length > 200;
            };
            return $_ unless $@;
@@ -4434,6 +5278,7 @@ sub quiltify ($$$$) {
            $patchname =~ y/-a-z0-9_.+=~//cd;
            $patchname =~ s/^\W/x-$&/;
            $patchname = substr($patchname,0,40);
            $patchname =~ y/-a-z0-9_.+=~//cd;
            $patchname =~ s/^\W/x-$&/;
            $patchname = substr($patchname,0,40);
+           $patchname .= ".patch";
        }
        if (!defined $patchdir) {
            $patchdir = '';
        }
        if (!defined $patchdir) {
            $patchdir = '';
@@ -4479,13 +5324,10 @@ sub build_maybe_quilt_fixup () {
     check_for_vendor_patches();
 
     if (quiltmode_splitbrain) {
     check_for_vendor_patches();
 
     if (quiltmode_splitbrain) {
-       foreach my $needtf (qw(new maint)) {
-           next if grep { $_ eq $needtf } access_cfg_tagformats;
-           fail <<END
+       fail <<END unless access_cfg_tagformats_can_splitbrain;
 quilt mode $quilt_mode requires split view so server needs to support
  both "new" and "maint" tag formats, but config says it doesn't.
 END
 quilt mode $quilt_mode requires split view so server needs to support
  both "new" and "maint" tag formats, but config says it doesn't.
 END
-       }
     }
 
     my $clogp = parsechangelog();
     }
 
     my $clogp = parsechangelog();
@@ -4774,13 +5616,13 @@ sub quilt_fixup_multipatch ($$$) {
 
     changedir 'fake';
 
 
     changedir 'fake';
 
-    remove_stray_gits();
+    remove_stray_gits("source package");
     mktree_in_ud_here();
 
     rmtree '.pc';
 
     mktree_in_ud_here();
 
     rmtree '.pc';
 
-    runcmd @git, qw(add -Af .);
-    my $unapplied=git_write_tree();
+    runcmd @git, qw(checkout -f), $headref, qw(-- debian);
+    my $unapplied=git_add_write_tree();
     printdebug "fake orig tree object $unapplied\n";
 
     ensuredir '.pc';
     printdebug "fake orig tree object $unapplied\n";
 
     ensuredir '.pc';
@@ -4812,8 +5654,7 @@ END
 
     changedir '../fake';
     rmtree '.pc';
 
     changedir '../fake';
     rmtree '.pc';
-    runcmd @git, qw(add -Af .);
-    my $oldtiptree=git_write_tree();
+    my $oldtiptree=git_add_write_tree();
     printdebug "fake o+d/p tree object $unapplied\n";
     changedir '../work';
 
     printdebug "fake o+d/p tree object $unapplied\n";
     changedir '../work';
 
@@ -4972,15 +5813,19 @@ sub cmd_clean () {
     maybe_unapply_patches_again();
 }
 
     maybe_unapply_patches_again();
 }
 
-sub build_prep_early () {
-    our $build_prep_early_done //= 0;
-    return if $build_prep_early_done++;
-    notpushing();
-    badusage "-p is not allowed when building" if defined $package;
+sub build_or_push_prep_early () {
+    our $build_or_push_prep_early_done //= 0;
+    return if $build_or_push_prep_early_done++;
+    badusage "-p is not allowed with dgit $subcommand" if defined $package;
     my $clogp = parsechangelog();
     $isuite = getfield $clogp, 'Distribution';
     $package = getfield $clogp, 'Source';
     $version = getfield $clogp, 'Version';
     my $clogp = parsechangelog();
     $isuite = getfield $clogp, 'Distribution';
     $package = getfield $clogp, 'Source';
     $version = getfield $clogp, 'Version';
+}
+
+sub build_prep_early () {
+    build_or_push_prep_early();
+    notpushing();
     check_not_dirty();
 }
 
     check_not_dirty();
 }
 
@@ -5163,6 +6008,7 @@ sub postbuild_mergechanges_vanilla ($) {
 }
 
 sub cmd_build {
 }
 
 sub cmd_build {
+    build_prep_early();
     my @dbp = (@dpkgbuildpackage, qw(-us -uc), changesopts_initial(), @ARGV);
     my $wantsrc = massage_dbp_args \@dbp;
     if ($wantsrc > 0) {
     my @dbp = (@dpkgbuildpackage, qw(-us -uc), changesopts_initial(), @ARGV);
     my $wantsrc = massage_dbp_args \@dbp;
     if ($wantsrc > 0) {
@@ -5255,6 +6101,7 @@ sub cmd_gbp_build {
 sub cmd_git_build { cmd_gbp_build(); } # compatibility with <= 1.0
 
 sub build_source {
 sub cmd_git_build { cmd_gbp_build(); } # compatibility with <= 1.0
 
 sub build_source {
+    build_prep_early();
     my $our_cleanmode = $cleanmode;
     if ($need_split_build_invocation) {
        # Pretend that clean is being done some other way.  This
     my $our_cleanmode = $cleanmode;
     if ($need_split_build_invocation) {
        # Pretend that clean is being done some other way.  This
@@ -5315,6 +6162,7 @@ sub build_source {
 }
 
 sub cmd_build_source {
 }
 
 sub cmd_build_source {
+    build_prep_early();
     badusage "build-source takes no additional arguments" if @ARGV;
     build_source();
     maybe_unapply_patches_again();
     badusage "build-source takes no additional arguments" if @ARGV;
     build_source();
     maybe_unapply_patches_again();
@@ -5343,14 +6191,20 @@ END
 
 sub cmd_quilt_fixup {
     badusage "incorrect arguments to dgit quilt-fixup" if @ARGV;
 
 sub cmd_quilt_fixup {
     badusage "incorrect arguments to dgit quilt-fixup" if @ARGV;
-    my $clogp = parsechangelog();
-    $version = getfield $clogp, 'Version';
-    $package = getfield $clogp, 'Source';
-    check_not_dirty();
+    build_prep_early();
     clean_tree();
     build_maybe_quilt_fixup();
 }
 
     clean_tree();
     build_maybe_quilt_fixup();
 }
 
+sub import_dsc_result {
+    my ($dstref, $newhash, $what_log, $what_msg) = @_;
+    my @cmd = (@git, qw(update-ref -m), $what_log, $dstref, $newhash);
+    runcmd @cmd;
+    check_gitattrs($newhash, "source tree");
+
+    progress "dgit: import-dsc: $what_msg";
+}
+
 sub cmd_import_dsc {
     my $needsig = 0;
 
 sub cmd_import_dsc {
     my $needsig = 0;
 
@@ -5414,32 +6268,42 @@ sub cmd_import_dsc {
 
     parse_dscdata();
 
 
     parse_dscdata();
 
-    my $dgit_commit = $dsc->{$ourdscfield[0]};
-    if (defined $dgit_commit && 
-       !forceing [qw(import-dsc-with-dgit-field)]) {
-       $dgit_commit =~ m/\w+/ or fail "invalid hash in .dsc";
+    $package = getfield $dsc, 'Source';
+
+    parse_dsc_field($dsc, "Dgit metadata in .dsc")
+       unless forceing [qw(import-dsc-with-dgit-field)];
+    parse_dsc_field_def_dsc_distro();
+
+    $isuite = 'DGIT-IMPORT-DSC';
+    $idistro //= $dsc_distro;
+
+    notpushing();
+
+    if (defined $dsc_hash) {
        progress "dgit: import-dsc of .dsc with Dgit field, using git hash";
        progress "dgit: import-dsc of .dsc with Dgit field, using git hash";
+       resolve_dsc_field_commit undef, undef;
+    }
+    if (defined $dsc_hash) {
        my @cmd = (qw(sh -ec),
        my @cmd = (qw(sh -ec),
-                  "echo $dgit_commit | git cat-file --batch-check");
+                  "echo $dsc_hash | git cat-file --batch-check");
        my $objgot = cmdoutput @cmd;
        if ($objgot =~ m#^\w+ missing\b#) {
            fail <<END
        my $objgot = cmdoutput @cmd;
        if ($objgot =~ m#^\w+ missing\b#) {
            fail <<END
-.dsc contains Dgit field referring to object $dgit_commit
+.dsc contains Dgit field referring to object $dsc_hash
 Your git tree does not have that object.  Try `git fetch' from a
 plausible server (browse.dgit.d.o? alioth?), and try the import-dsc again.
 END
        }
 Your git tree does not have that object.  Try `git fetch' from a
 plausible server (browse.dgit.d.o? alioth?), and try the import-dsc again.
 END
        }
-       if ($oldhash && !is_fast_fwd $oldhash, $dgit_commit) {
+       if ($oldhash && !is_fast_fwd $oldhash, $dsc_hash) {
            if ($force > 0) {
                progress "Not fast forward, forced update.";
            } else {
            if ($force > 0) {
                progress "Not fast forward, forced update.";
            } else {
-               fail "Not fast forward to $dgit_commit";
+               fail "Not fast forward to $dsc_hash";
            }
        }
            }
        }
-       @cmd = (@git, qw(update-ref -m), "dgit import-dsc (Dgit): $info",
-               $dstbranch, $dgit_commit);
-       runcmd @cmd;
-       progress "dgit: import-dsc updated git ref $dstbranch";
+       import_dsc_result $dstbranch, $dsc_hash,
+           "dgit import-dsc (Dgit): $info",
+           "updated git ref $dstbranch";
        return 0;
     }
 
        return 0;
     }
 
@@ -5450,12 +6314,14 @@ Specify  +$specbranch to overwrite, discarding existing history
 END
        if $oldhash && !$force;
 
 END
        if $oldhash && !$force;
 
-    $package = getfield $dsc, 'Source';
     my @dfi = dsc_files_info();
     foreach my $fi (@dfi) {
        my $f = $fi->{Filename};
        my $here = "../$f";
     my @dfi = dsc_files_info();
     foreach my $fi (@dfi) {
        my $f = $fi->{Filename};
        my $here = "../$f";
-       next if lstat $here;
+       if (lstat $here) {
+           next if stat $here;
+           fail "lstat $here works but stat gives $! !";
+       }
        fail "stat $here: $!" unless $! == ENOENT;
        my $there = $dscfn;
        if ($dscfn =~ m#^(?:\./+)?\.\./+#) {
        fail "stat $here: $!" unless $! == ENOENT;
        my $there = $dscfn;
        if ($dscfn =~ m#^(?:\./+)?\.\./+#) {
@@ -5466,11 +6332,13 @@ END
            fail "cannot import $dscfn which seems to be inside working tree!";
        }
        $there =~ s#/+[^/]+$## or
            fail "cannot import $dscfn which seems to be inside working tree!";
        }
        $there =~ s#/+[^/]+$## or
-           fail "cannot import $dscfn which seems to not have a basename";
+           fail "import $dscfn requires ../$f, but it does not exist";
        $there .= "/$f";
        $there .= "/$f";
+       my $test = $there =~ m{^/} ? $there : "../$there";
+       stat $test or fail "import $dscfn requires $test, but: $!";
        symlink $there, $here or fail "symlink $there to $here: $!";
        progress "made symlink $here -> $there";
        symlink $there, $here or fail "symlink $there to $here: $!";
        progress "made symlink $here -> $there";
-       print STDERR Dumper($fi);
+#      print STDERR Dumper($fi);
     }
     my @mergeinputs = generate_commits_from_dsc();
     die unless @mergeinputs == 1;
     }
     my @mergeinputs = generate_commits_from_dsc();
     die unless @mergeinputs == 1;
@@ -5484,10 +6352,14 @@ END
            progress "Import, merging.";
            my $tree = cmdoutput @git, qw(rev-parse), "$newhash:";
            my $version = getfield $dsc, 'Version';
            progress "Import, merging.";
            my $tree = cmdoutput @git, qw(rev-parse), "$newhash:";
            my $version = getfield $dsc, 'Version';
+           my $clogp = commit_getclogp $newhash;
+           my $authline = clogp_authline $clogp;
            $newhash = make_commit_text <<END;
 tree $tree
 parent $newhash
 parent $oldhash
            $newhash = make_commit_text <<END;
 tree $tree
 parent $newhash
 parent $oldhash
+author $authline
+committer $authline
 
 Merge $package ($version) import into $dstbranch
 END
 
 Merge $package ($version) import into $dstbranch
 END
@@ -5496,12 +6368,14 @@ END
        }
     }
 
        }
     }
 
-    my @cmd = (@git, qw(update-ref -m), "dgit import-dsc: $info",
-              $dstbranch, $newhash);
-    runcmd @cmd;
-    progress "dgit: import-dsc results are in in git ref $dstbranch";
+    import_dsc_result $dstbranch, $newhash,
+       "dgit import-dsc: $info",
+       "results are in in git ref $dstbranch";
 }
 
 }
 
+sub pre_archive_api_query () {
+    no_local_git_cfg();
+}
 sub cmd_archive_api_query {
     badusage "need only 1 subpath argument" unless @ARGV==1;
     my ($subpath) = @ARGV;
 sub cmd_archive_api_query {
     badusage "need only 1 subpath argument" unless @ARGV==1;
     my ($subpath) = @ARGV;
@@ -5511,27 +6385,56 @@ sub cmd_archive_api_query {
     exec @cmd or fail "exec curl: $!\n";
 }
 
     exec @cmd or fail "exec curl: $!\n";
 }
 
+sub repos_server_url () {
+    $package = '_dgit-repos-server';
+    local $access_forpush = 1;
+    local $isuite = 'DGIT-REPOS-SERVER';
+    my $url = access_giturl();
+}    
+
+sub pre_clone_dgit_repos_server () {
+    no_local_git_cfg();
+}
 sub cmd_clone_dgit_repos_server {
     badusage "need destination argument" unless @ARGV==1;
     my ($destdir) = @ARGV;
 sub cmd_clone_dgit_repos_server {
     badusage "need destination argument" unless @ARGV==1;
     my ($destdir) = @ARGV;
-    $package = '_dgit-repos-server';
-    my @cmd = (@git, qw(clone), access_giturl(), $destdir);
+    my $url = repos_server_url();
+    my @cmd = (@git, qw(clone), $url, $destdir);
     debugcmd ">",@cmd;
     exec @cmd or fail "exec git clone: $!\n";
 }
 
     debugcmd ">",@cmd;
     exec @cmd or fail "exec git clone: $!\n";
 }
 
+sub pre_print_dgit_repos_server_source_url () {
+    no_local_git_cfg();
+}
+sub cmd_print_dgit_repos_server_source_url {
+    badusage "no arguments allowed to dgit print-dgit-repos-server-source-url"
+       if @ARGV;
+    my $url = repos_server_url();
+    print $url, "\n" or die $!;
+}
+
 sub cmd_setup_mergechangelogs {
     badusage "no arguments allowed to dgit setup-mergechangelogs" if @ARGV;
 sub cmd_setup_mergechangelogs {
     badusage "no arguments allowed to dgit setup-mergechangelogs" if @ARGV;
+    local $isuite = 'DGIT-SETUP-TREE';
     setup_mergechangelogs(1);
 }
 
 sub cmd_setup_useremail {
     setup_mergechangelogs(1);
 }
 
 sub cmd_setup_useremail {
-    badusage "no arguments allowed to dgit setup-mergechangelogs" if @ARGV;
+    badusage "no arguments allowed to dgit setup-useremail" if @ARGV;
+    local $isuite = 'DGIT-SETUP-TREE';
     setup_useremail(1);
 }
 
     setup_useremail(1);
 }
 
+sub cmd_setup_gitattributes {
+    badusage "no arguments allowed to dgit setup-useremail" if @ARGV;
+    local $isuite = 'DGIT-SETUP-TREE';
+    setup_gitattrs(1);
+}
+
 sub cmd_setup_new_tree {
     badusage "no arguments allowed to dgit setup-tree" if @ARGV;
 sub cmd_setup_new_tree {
     badusage "no arguments allowed to dgit setup-tree" if @ARGV;
+    local $isuite = 'DGIT-SETUP-TREE';
     setup_new_tree();
 }
 
     setup_new_tree();
 }
 
@@ -5543,7 +6446,9 @@ sub cmd_version {
 }
 
 our (%valopts_long, %valopts_short);
 }
 
 our (%valopts_long, %valopts_short);
+our (%funcopts_long);
 our @rvalopts;
 our @rvalopts;
+our (@modeopt_cfgs);
 
 sub defvalopt ($$$$) {
     my ($long,$short,$val_re,$how) = @_;
 
 sub defvalopt ($$$$) {
     my ($long,$short,$val_re,$how) = @_;
@@ -5562,6 +6467,7 @@ defvalopt '',                '-k', '.+',      \$keyid;
 defvalopt '--existing-package','', '.*',      \$existing_package;
 defvalopt '--build-products-dir','','.*',     \$buildproductsdir;
 defvalopt '--clean',       '', $cleanmode_re, \$cleanmode;
 defvalopt '--existing-package','', '.*',      \$existing_package;
 defvalopt '--build-products-dir','','.*',     \$buildproductsdir;
 defvalopt '--clean',       '', $cleanmode_re, \$cleanmode;
+defvalopt '--package',   '-p',   $package_re, \$package;
 defvalopt '--quilt',     '', $quilt_modes_re, \$quilt_mode;
 
 defvalopt '', '-C', '.+', sub {
 defvalopt '--quilt',     '', $quilt_modes_re, \$quilt_mode;
 
 defvalopt '', '-C', '.+', sub {
@@ -5578,6 +6484,26 @@ defvalopt '--initiator-tempdir','','.*', sub {
        " absolute, not relative, directory."
 };
 
        " absolute, not relative, directory."
 };
 
+sub defoptmodes ($@) {
+    my ($varref, $cfgkey, $default, %optmap) = @_;
+    my %permit;
+    while (my ($opt,$val) = each %optmap) {
+       $funcopts_long{$opt} = sub { $$varref = $val; };
+       $permit{$val} = $val;
+    }
+    push @modeopt_cfgs, {
+        Var => $varref,
+        Key => $cfgkey,
+        Default => $default,
+        Vals => \%permit
+    };
+}
+
+defoptmodes \$dodep14tag, qw( dep14tag          want
+                             --dep14tag        want
+                             --no-dep14tag     no
+                             --always-dep14tag always );
+
 sub parseopts () {
     my $om;
 
 sub parseopts () {
     my $om;
 
@@ -5651,6 +6577,9 @@ sub parseopts () {
            } elsif (m/^--no-rm-on-error$/s) {
                push @ropts, $_;
                $rmonerror = 0;
            } elsif (m/^--no-rm-on-error$/s) {
                push @ropts, $_;
                $rmonerror = 0;
+           } elsif (m/^--no-chase-dsc-distro$/s) {
+               push @ropts, $_;
+               $chase_dsc_distro = 0;
            } elsif (m/^--overwrite$/s) {
                push @ropts, $_;
                $overwrite_version = '';
            } elsif (m/^--overwrite$/s) {
                push @ropts, $_;
                $overwrite_version = '';
@@ -5687,9 +6616,17 @@ sub parseopts () {
                # undocumented, for testing
                push @ropts, $_;
                $need_split_build_invocation = 1;
                # undocumented, for testing
                push @ropts, $_;
                $need_split_build_invocation = 1;
+           } elsif (m/^--config-lookup-explode=(.+)$/s) {
+               # undocumented, for testing
+               push @ropts, $_;
+               $gitcfgs{cmdline}{$1} = 'CONFIG-LOOKUP-EXPLODE';
+               # ^ it's supposed to be an array ref
            } elsif (m/^(--[-0-9a-z]+)(=|$)/ && ($oi = $valopts_long{$1})) {
                $val = $2 ? $' : undef; #';
                $valopt->($oi->{Long});
            } elsif (m/^(--[-0-9a-z]+)(=|$)/ && ($oi = $valopts_long{$1})) {
                $val = $2 ? $' : undef; #';
                $valopt->($oi->{Long});
+           } elsif ($funcopts_long{$_}) {
+               push @ropts, $_;
+               $funcopts_long{$_}();
            } else {
                badusage "unknown long option \`$_'";
            }
            } else {
                badusage "unknown long option \`$_'";
            }
@@ -5775,7 +6712,11 @@ END
 }
 
 
 }
 
 
-sub finalise_opts_opts () {
+sub parseopts_late_defaults () {
+    $isuite //= cfg("dgit-distro.$idistro.default-suite", 'RETURN-UNDEF')
+       if defined $idistro;
+    $isuite //= cfg('dgit.default.default-suite');
+
     foreach my $k (keys %opts_opt_map) {
        my $om = $opts_opt_map{$k};
 
     foreach my $k (keys %opts_opt_map) {
        my $om = $opts_opt_map{$k};
 
@@ -5802,6 +6743,42 @@ sub finalise_opts_opts () {
                     @$om[$insertpos..$#$om] );
        }
     }
                     @$om[$insertpos..$#$om] );
        }
     }
+
+    if (!defined $rmchanges) {
+       local $access_forpush;
+       $rmchanges = access_cfg_bool(0, 'rm-old-changes');
+    }
+
+    if (!defined $quilt_mode) {
+       local $access_forpush;
+       $quilt_mode = cfg('dgit.force.quilt-mode', 'RETURN-UNDEF')
+           // access_cfg('quilt-mode', 'RETURN-UNDEF')
+           // 'linear';
+       $quilt_mode =~ m/^($quilt_modes_re)$/ 
+           or badcfg "unknown quilt-mode \`$quilt_mode'";
+       $quilt_mode = $1;
+    }
+
+    foreach my $moc (@modeopt_cfgs) {
+       local $access_forpush;
+       my $vr = $moc->{Var};
+       next if defined $$vr;
+       $$vr = access_cfg($moc->{Key}, 'RETURN-UNDEF') // $moc->{Default};
+       my $v = $moc->{Vals}{$$vr};
+       badcfg "unknown $moc->{Key} setting \`$$vr'" unless defined $v;
+       $$vr = $v;
+    }
+
+    $need_split_build_invocation ||= quiltmode_splitbrain();
+
+    if (!defined $cleanmode) {
+       local $access_forpush;
+       $cleanmode = access_cfg('clean-mode', 'RETURN-UNDEF');
+       $cleanmode //= 'dpkg-source';
+
+       badcfg "unknown clean-mode \`$cleanmode'" unless
+           $cleanmode =~ m/^($cleanmode_re)$(?!\n)/s;
+    }
 }
 
 if ($ENV{$fakeeditorenv}) {
 }
 
 if ($ENV{$fakeeditorenv}) {
@@ -5811,7 +6788,6 @@ if ($ENV{$fakeeditorenv}) {
 
 parseopts();
 check_env_sanity();
 
 parseopts();
 check_env_sanity();
-git_slurp_config();
 
 print STDERR "DRY RUN ONLY\n" if $dryrun_level > 1;
 print STDERR "DAMP RUN - WILL MAKE LOCAL (UNSIGNED) CHANGES\n"
 
 print STDERR "DRY RUN ONLY\n" if $dryrun_level > 1;
 print STDERR "DAMP RUN - WILL MAKE LOCAL (UNSIGNED) CHANGES\n"
@@ -5820,37 +6796,13 @@ if (!@ARGV) {
     print STDERR $helpmsg or die $!;
     exit 8;
 }
     print STDERR $helpmsg or die $!;
     exit 8;
 }
-my $cmd = shift @ARGV;
+$cmd = $subcommand = shift @ARGV;
 $cmd =~ y/-/_/;
 
 my $pre_fn = ${*::}{"pre_$cmd"};
 $pre_fn->() if $pre_fn;
 
 $cmd =~ y/-/_/;
 
 my $pre_fn = ${*::}{"pre_$cmd"};
 $pre_fn->() if $pre_fn;
 
-if (!defined $rmchanges) {
-    local $access_forpush;
-    $rmchanges = access_cfg_bool(0, 'rm-old-changes');
-}
-
-if (!defined $quilt_mode) {
-    local $access_forpush;
-    $quilt_mode = cfg('dgit.force.quilt-mode', 'RETURN-UNDEF')
-       // access_cfg('quilt-mode', 'RETURN-UNDEF')
-       // 'linear';
-    $quilt_mode =~ m/^($quilt_modes_re)$/ 
-       or badcfg "unknown quilt-mode \`$quilt_mode'";
-    $quilt_mode = $1;
-}
-
-$need_split_build_invocation ||= quiltmode_splitbrain();
-
-if (!defined $cleanmode) {
-    local $access_forpush;
-    $cleanmode = access_cfg('clean-mode', 'RETURN-UNDEF');
-    $cleanmode //= 'dpkg-source';
-
-    badcfg "unknown clean-mode \`$cleanmode'" unless
-       $cleanmode =~ m/^($cleanmode_re)$(?!\n)/s;
-}
+git_slurp_config();
 
 my $fn = ${*::}{"cmd_$cmd"};
 $fn or badusage "unknown operation $cmd";
 
 my $fn = ${*::}{"cmd_$cmd"};
 $fn or badusage "unknown operation $cmd";