chiark / gitweb /
Merge branch stable into master
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Sun, 7 Jan 2018 12:39:49 +0000 (12:39 +0000)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Sun, 7 Jan 2018 14:41:18 +0000 (14:41 +0000)
Also regenerate debian/tests/control

Signed-off-by: Ian Jackson <ijackson@chiark.greenend.org.uk>
1  2 
debian/changelog
debian/tests/control
dgit

diff --combined debian/changelog
index 591ee6779baae62402b72502acab8ad0d696183f,4209e71a43ee3ab9ac69b1943f8848806458ae4a..9fd6eddf35d3e289771e045864bda7066e0be471
@@@ -1,80 -1,14 +1,91 @@@
 +dgit (4.2~) experimental; urgency=medium
 +
 +  Documentation improvements:
 +  * dgit(1): Add a bit more rationale (polemic, even).  Closes:#874221.
 +  * Recommend mk-build-deps rather than apt-get build-dep.
 +    Suggestion from Nikolaus Rath.  Closes:#863361.
 +  * dgit-maint-merge(7): many updates.  [Sean Whitton]
 +    Closes:#864873,#878433.
 +  * dgit-*(7): Mention first upload trick.  [Andrew Shadura,
 +    Sean Whitton]  Closes:#856402.
 +
 +  Minor fixes:
 +  * When source discrepancy involves file mode changes, report them
 +    specially.  Closes:#886442.
 +  * In split brain mode, with unexpected diffs, print dgit view
 +    commitid in suggested diff rune.  (HEAD is wrong.)  Closes:#886443.
 +  * Fix message about missing quilt cache entry to refer to
 +    HEAD rather than tree, since dgit needs a commit.  Closes:#884646.
 +  * Fix grammar error in 4.1 changelog entry. [Sean Whitton]
 +  * Remove some whitespace "errors". [Sean Whitton]
 +
 +  Packaging:
 +  * Remove dependency alternative on realpath (package last existed in
 +    Debian wheezy).  Closes:#877552.
 +
 +  Test suite:
 +  * dpkgsourceignores-docs: Correct restriction (so autopkgtest
 +    won't try to run it).
 +
 + --
 +
+ dgit (3.13) unstable; urgency=high
+   Important bugfixes to dgit:
+   * Add missing `use' for Dpkg::Compression et al.
+     Thanks to report from Didier 'OdyX' Raboud.  (Closes:#879526.)
+   Test suite:
+   * Add missing `chiark-utils-bin' to Test-Depends.
+  -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Sun, 22 Oct 2017 17:51:12 +0100
 +dgit (4.1) experimental; urgency=medium
 +
 +  Important improvements to dgit:
 +  * Support for `git worktree' worktrees.  There may still be
 +    bugs; the tests for this are not very comprehensive.  And
 +    worktrees on different filesystems may not work; that's a
 +    matter for the future.  Closes:#868515.
 +  * Change the dpkg-source -i argument to exclude exactly the right
 +    set of things.  (Sadly this is not a simple rune.)
 +
 +  Other improvements to dgit:
 +  * New print-dpkg-source-ignores option to print the big rune
 +    you need to pass to dpkg-source to make it work exactly right.
 +  * Properly shell-quote the --git-builder argument to gbp.
 +
 +  Documentation:
 +  * dgit-user(7): Provide information about how to use sbuild.
 +    Quite ugly due to #868527.  Closes:#868526.
 +  * dgit-user(7): Fixed example rune to use curl (which prints
 +    to stdout, as the rune expects).  [reported by Simon Tatham]
 +
 +  Minor improvements:
 +  * Do not leave many clog-* files in .git/dgit.
 +
 +  Internal changes:
 +  * using-these: New script to help with ad-hoc-testing.
 +  * Refactoring in preparation for push-source [Sean Whitton].
 +
 +  Test suite:
 +  * sbuild-gitish: New test case to check running sbuild from git
 +  * Work around gnupg agent connection races by having our stunt
 +    gpg wrapper simply try running gpg again, once, if it exits 2.
 +    This does not fully suppress the bug but it does significantly reduce
 +    the probability.
 +  * Other tests for new features.
 +  * Various refactoring.
 +
 + -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Mon, 14 Aug 2017 09:31:03 +0100
 +
 +dgit (4.0) experimental; urgency=low
 +
 +  * dgit: --deliberately-not-fast-forward works properly in
 +    split view quilt modes (suppressing the pseudomerge).
 +
 + -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Sun, 12 Feb 2017 22:22:31 +0000
 +
  dgit (3.12) unstable; urgency=high
  
    Important bugfixes to dgit:
diff --combined debian/tests/control
index b2830691d8b3cc1238730e1fa3e790e7f1040d60,76fe9bd5298f8ffebd331fe9e48bc3f422ebcb18..7d2a4867f34559f933331195639ef4af56a19a12
@@@ -1,45 -1,35 +1,45 @@@
  Tests: build-modes-gbp
  Tests-Directory: tests/tests
- Depends: dgit, dgit-infrastructure, devscripts, debhelper (>=8), fakeroot, build-essential, git-buildpackage
+ Depends: dgit, dgit-infrastructure, devscripts, debhelper (>=8), fakeroot, build-essential, chiark-utils-bin, git-buildpackage
  
  Tests: clone-reprepro downstream-gitless
  Tests-Directory: tests/tests
- Depends: dgit, dgit-infrastructure, devscripts, debhelper (>=8), fakeroot, build-essential, reprepro
+ Depends: dgit, dgit-infrastructure, devscripts, debhelper (>=8), fakeroot, build-essential, chiark-utils-bin, reprepro
  
- Depends: dgit, dgit-infrastructure, devscripts, debhelper (>=8), fakeroot, build-essential
 +Tests: dpkgsourceignores-docs
 +Tests-Directory: tests/tests
++Depends: dgit, dgit-infrastructure, devscripts, debhelper (>=8), fakeroot, build-essential, chiark-utils-bin
 +Restrictions: x-dgit-intree-only
 +
  Tests: defdistro-dsd-clone-drs dsd-clone-drs
  Tests-Directory: tests/tests
- Depends: dgit, dgit-infrastructure, devscripts, debhelper (>=8), fakeroot, build-essential
+ Depends: dgit, dgit-infrastructure, devscripts, debhelper (>=8), fakeroot, build-essential, chiark-utils-bin
  Restrictions: x-dgit-intree-only x-dgit-git-only
  
  Tests: gitattributes
  Tests-Directory: tests/tests
- Depends: dgit, dgit-infrastructure, devscripts, debhelper (>=8), fakeroot, build-essential, bsdgames, man-db, git-man
+ Depends: dgit, dgit-infrastructure, devscripts, debhelper (>=8), fakeroot, build-essential, chiark-utils-bin, bsdgames, man-db, git-man
  
  Tests: defdistro-mirror mirror mirror-debnewgit mirror-private
  Tests-Directory: tests/tests
- Depends: dgit, dgit-infrastructure, devscripts, debhelper (>=8), fakeroot, build-essential, rsync
+ Depends: dgit, dgit-infrastructure, devscripts, debhelper (>=8), fakeroot, build-essential, chiark-utils-bin, rsync
  
  Tests: build-modes-sbuild quilt-gbp-build-modes-sbuild
  Tests-Directory: tests/tests
- Depends: dgit, dgit-infrastructure, devscripts, debhelper (>=8), fakeroot, build-essential, sbuild
+ Depends: dgit, dgit-infrastructure, devscripts, debhelper (>=8), fakeroot, build-essential, chiark-utils-bin, sbuild
  Restrictions: x-dgit-schroot-build
  
- Depends: dgit, dgit-infrastructure, devscripts, debhelper (>=8), fakeroot, build-essential, sbuild, man-db
 +Tests: sbuild-gitish
 +Tests-Directory: tests/tests
++Depends: dgit, dgit-infrastructure, devscripts, debhelper (>=8), fakeroot, build-essential, chiark-utils-bin, sbuild, man-db
 +Restrictions: x-dgit-schroot-build
 +
  Tests: spelling
  Tests-Directory: tests/tests
- Depends: dgit, dgit-infrastructure, devscripts, debhelper (>=8), fakeroot, build-essential
+ Depends: dgit, dgit-infrastructure, devscripts, debhelper (>=8), fakeroot, build-essential, chiark-utils-bin
  Restrictions: x-dgit-git-only
  
 -Tests: absurd-gitapply badcommit-rewrite build-modes build-modes-asplit build-modes-gbp-asplit clone-clogsigpipe clone-gitnosuite clone-nogit debpolicy-dbretry debpolicy-newreject debpolicy-quilt-gbp defdistro-rpush defdistro-setup distropatches-reject drs-clone-nogit drs-push-masterupdate drs-push-rejects dsd-clone-nogit dsd-divert fetch-localgitonly fetch-somegit-notlast gbp-orig gitconfig import-dsc import-maintmangle import-native import-nonnative import-tarbomb inarchivecopy mismatches-contents mismatches-dscchanges multisuite newtag-clone-nogit oldnewtagalt oldtag-clone-nogit orig-include-exclude orig-include-exclude-chkquery overwrite-chkclog overwrite-junk overwrite-splitbrains overwrite-version protocol-compat push-buildproductsdir push-newpackage push-nextdgit quilt quilt-gbp quilt-gbp-build-modes quilt-singlepatch quilt-splitbrains quilt-useremail rpush tag-updates test-list-uptodate trustingpolicy-replay unrepresentable version-opt
 +Tests: absurd-gitapply badcommit-rewrite build-modes build-modes-asplit build-modes-gbp-asplit clone-clogsigpipe clone-gitnosuite clone-nogit debpolicy-dbretry debpolicy-newreject debpolicy-quilt-gbp defdistro-rpush defdistro-setup distropatches-reject dpkgsourceignores-correct drs-clone-nogit drs-push-masterupdate drs-push-rejects dsd-clone-nogit dsd-divert fetch-localgitonly fetch-somegit-notlast gbp-orig gitconfig gitworktree import-dsc import-maintmangle import-native import-nonnative import-tarbomb inarchivecopy mismatches-contents mismatches-dscchanges multisuite newtag-clone-nogit oldnewtagalt oldtag-clone-nogit orig-include-exclude orig-include-exclude-chkquery overwrite-chkclog overwrite-junk overwrite-splitbrains overwrite-version protocol-compat push-buildproductsdir push-newpackage push-nextdgit quilt quilt-gbp quilt-gbp-build-modes quilt-singlepatch quilt-splitbrains quilt-useremail rpush tag-updates test-list-uptodate trustingpolicy-replay unrepresentable version-opt
  Tests-Directory: tests/tests
- Depends: dgit, dgit-infrastructure, devscripts, debhelper (>=8), fakeroot, build-essential
+ Depends: dgit, dgit-infrastructure, devscripts, debhelper (>=8), fakeroot, build-essential, chiark-utils-bin
  
diff --combined dgit
index 2e51f76cdc439354a0622fa28313b931f6e88942,afe209e2f16fa61cf84828b689fc9021c6ab8672..31e6bd581b7453d23519d9aa01983e5b21061e4a
--- 1/dgit
--- 2/dgit
+++ b/dgit
@@@ -19,7 -19,7 +19,7 @@@
  
  use strict;
  
 -use Debian::Dgit;
 +use Debian::Dgit qw(:DEFAULT :playground);
  setup_sigwarn();
  
  use IO::Handle;
@@@ -30,6 -30,8 +30,8 @@@ use File::Path
  use File::Temp qw(tempdir);
  use File::Basename;
  use Dpkg::Version;
+ use Dpkg::Compression;
+ use Dpkg::Compression::Process;
  use POSIX;
  use IPC::Open2;
  use Digest::SHA;
@@@ -47,8 -49,6 +49,8 @@@ our $absurdity = undef; ###substituted#
  our @rpushprotovsn_support = qw(4 3 2); # 4 is new tag format
  our $protovsn;
  
 +our $cmd;
 +our $subcommand;
  our $isuite;
  our $idistro;
  our $package;
@@@ -100,8 -100,6 +102,8 @@@ our $git_authline_re = '^([^<>]+) \<(\S
  our $splitbraincache = 'dgit-intern/quilt-cache';
  our $rewritemap = 'dgit-rewrite/map';
  
 +our @dpkg_source_ignores = qw(-i(?:^|/)\.git(?:/|$) -I.git);
 +
  our (@git) = qw(git);
  our (@dget) = qw(dget);
  our (@curl) = (qw(curl --proto-redir), '-all,http,https', qw(-L));
@@@ -113,8 -111,8 +115,8 @@@ 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 (@dpkgbuildpackage) = (qw(dpkg-buildpackage), @dpkg_source_ignores);
 +our (@dpkgsource) = (qw(dpkg-source), @dpkg_source_ignores);
  our (@dpkggenchanges) = qw(dpkg-genchanges);
  our (@mergechanges) = qw(mergechanges -f);
  our (@gbp_build) = ('');
@@@ -253,6 -251,12 +255,6 @@@ sub no_such_package () 
      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;
@@@ -279,10 -283,6 +281,10 @@@ sub gbp_pq 
      return opts_opt_multi_cmd @gbp_pq;
  }
  
 +sub dgit_privdir () {
 +    our $dgit_privdir_made //= ensure_a_playground 'dgit';
 +}
 +
  #---------- remote protocol support, common ----------
  
  # remote push initiator/responder protocol:
@@@ -495,6 -495,12 +497,6 @@@ sub url_get 
  
  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; }
  
@@@ -563,7 -569,7 +565,7 @@@ sub nextarg 
  }
  
  sub pre_help () {
 -    no_local_git_cfg();
 +    not_necessarily_a_tree();
  }
  sub cmd_help () {
      print $helpmsg or die $!;
@@@ -641,17 -647,32 +643,17 @@@ our %defcfg = ('dgit.default.distro' =
  
  our %gitcfgs;
  our @gitcfgsources = qw(cmdline local global system);
 +our $invoked_in_git_tree = 1;
  
  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
 -      
 -      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;
      }
  }
  
@@@ -688,10 -709,9 +690,10 @@@ sub cfg 
        "$us: distro or suite appears not to be (properly) supported";
  }
  
 -sub no_local_git_cfg () {
 +sub not_necessarily_a_tree () {
      # needs to be called from pre_*
      @gitcfgsources = grep { $_ ne 'local' } @gitcfgsources;
 +    $invoked_in_git_tree = 0;
  }
  
  sub access_basedistro__noalias () {
@@@ -986,13 -1006,19 +988,13 @@@ sub commit_getclogp ($) 
      our %commit_getclogp_memo;
      my $memo = $commit_getclogp_memo{$objid};
      return $memo if $memo;
 -    mkpath '.git/dgit';
 -    my $mclog = ".git/dgit/clog-$objid";
 +
 +    my $mclog = dgit_privdir()."clog";
      runcmd shell_cmd "exec >$mclog", @git, qw(cat-file blob),
        "$objid:debian/changelog";
      $commit_getclogp_memo{$objid} = parsechangelog("-l$mclog");
  }
  
 -sub must_getcwd () {
 -    my $d = getcwd();
 -    defined $d or fail "getcwd failed: $!";
 -    return $d;
 -}
 -
  sub parse_dscdata () {
      my $dscfh = new IO::File \$dscdata, '<' or die $!;
      printdebug Dumper($dscdata) if $debuglevel>1;
@@@ -1676,14 -1702,30 +1678,14 @@@ sub create_remote_git_repo () 
  our ($dsc_hash,$lastpush_mergeinput);
  our ($dsc_distro, $dsc_hint_tag, $dsc_hint_url);
  
 -our $ud = '.git/dgit/unpack';
  
 -sub prep_ud (;$) {
 -    my ($d) = @_;
 -    $d //= $ud;
 -    rmtree($d);
 -    mkpath '.git/dgit';
 -    mkdir $d or die $!;
 +sub prep_ud () {
 +    dgit_privdir(); # ensures that $dgit_privdir_made is based on $maindir
 +    fresh_playground 'dgit/unpack';
  }
  
  sub mktree_in_ud_here () {
 -    runcmd qw(git init -q);
 -    runcmd qw(git config gc.auto 0);
 -    foreach my $copy (qw(user.email user.name user.useConfigOnly
 -                         core.sharedRepository
 -                         core.compression core.looseCompression
 -                         core.bigFileThreshold core.fsyncObjectFiles)) {
 -      my $v = $gitcfgs{local}{$copy};
 -      next unless $v;
 -      runcmd qw(git config), $copy, $_ foreach @$v;
 -    }
 -    rmtree('.git/objects');
 -    symlink '../../../../objects','.git/objects' or die $!;
 -    setup_gitattrs(1);
 +    playtree_setup $gitcfgs{local};
  }
  
  sub git_write_tree () {
@@@ -1716,8 -1758,8 +1718,8 @@@ sub remove_stray_gits ($) 
  
  sub mktree_in_ud_from_only_subdir ($;$) {
      my ($what,$raw) = @_;
 -
      # changes into the subdir
 +
      my (@dirs) = <*/.>;
      die "expected one subdir but found @dirs ?" unless @dirs==1;
      $dirs[0] =~ m#^([^/]+)/\.$# or die;
@@@ -2058,13 -2100,13 +2060,13 @@@ sub generate_commits_from_dsc () 
      # See big comment in fetch_from_archive, below.
      # See also README.dsc-import.
      prep_ud();
 -    changedir $ud;
 +    changedir $playground;
  
      my @dfi = dsc_files_info();
      foreach my $fi (@dfi) {
        my $f = $fi->{Filename};
        die "$f ?" if $f =~ m#/|^\.|\.dsc$|\.tmp$#;
 -      my $upper_f = "../../../../$f";
 +      my $upper_f = "$maindir/../$f";
  
        printdebug "considering reusing $f: ";
  
  
        my $path = $ENV{PATH} or die;
  
 +      # we use ../../gbp-pq-output, which (given that we are in
 +      # $playground/PLAYTREE, and $playground is .git/dgit/unpack,
 +      # is .git/dgit.
 +
        foreach my $use_absurd (qw(0 1)) {
            runcmd @git, qw(checkout -q unpa);
            runcmd @git, qw(update-ref -d refs/heads/patch-queue/unpa);
@@@ -2472,8 -2510,8 +2474,8 @@@ EN
            @output = $lastpush_mergeinput;
        }
      }
 -    changedir '../../../..';
 -    rmtree($ud);
 +    changedir $maindir;
 +    rmtree $playground;
      return @output;
  }
  
@@@ -3170,7 -3208,7 +3172,7 @@@ EN
        my $author = clogp_authline $useclogp;
        my $cversion = getfield $useclogp, 'Version';
  
 -      my $mcf = ".git/dgit/mergecommit";
 +      my $mcf = dgit_privdir()."/mergecommit";
        open MC, ">", $mcf or die "$mcf $!";
        print MC <<END or die $!;
  tree $tree
@@@ -3230,6 -3268,7 +3232,6 @@@ EN
      fetch_from_archive_record_1($hash);
  
      if (defined $skew_warning_vsn) {
 -      mkpath '.git/dgit';
        printdebug "SKEW CHECK WANT $skew_warning_vsn\n";
        my $gotclogp = commit_getclogp($hash);
        my $got_vsn = getfield $gotclogp, 'Version';
@@@ -3268,9 -3307,8 +3270,9 @@@ sub setup_mergechangelogs (;$) 
  
      my $driver = 'dpkg-mergechangelogs';
      my $cb = "merge.$driver";
 -    my $attrs = '.git/info/attributes';
 -    ensuredir '.git/info';
 +    confess unless defined $maindir;
 +    my $attrs = "$maindir_gitcommon/info/attributes";
 +    ensuredir "$maindir_gitcommon/info";
  
      open NATTRS, ">", "$attrs.new" or die "$attrs.new $!";
      if (!open ATTRS, "<", $attrs) {
@@@ -3315,16 -3353,15 +3317,16 @@@ sub ensure_setup_existing_tree () 
      set_local_git_config $k, 'true';
  }
  
 -sub open_gitattrs () {
 -    my $gai = new IO::File ".git/info/attributes"
 +sub open_main_gitattrs () {
 +    confess 'internal error no maindir' unless defined $maindir;
 +    my $gai = new IO::File "$maindir_gitcommon/info/attributes"
        or $!==ENOENT
 -      or die "open .git/info/attributes: $!";
 +      or die "open $maindir_gitcommon/info/attributes: $!";
      return $gai;
  }
  
  sub is_gitattrs_setup () {
 -    my $gai = open_gitattrs();
 +    my $gai = open_main_gitattrs();
      return 0 unless $gai;
      while (<$gai>) {
        return 1 if m{^\[attr\]dgit-defuse-attrs\s};
@@@ -3344,15 -3381,15 +3346,15 @@@ sub setup_gitattrs (;$) 
  END
        return;
      }
 -    my $af = ".git/info/attributes";
 -    ensuredir '.git/info';
 +    my $af = "$maindir_gitcommon/info/attributes";
 +    ensuredir "$maindir_gitcommon/info";
      open GAO, "> $af.new" or die $!;
      print GAO <<END or die $!;
  *     dgit-defuse-attrs
 -[attr]dgit-defuse-attrs       -text -eol -crlf -ident -filter
 +[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();
 +    my $gai = open_main_gitattrs();
      if ($gai) {
        while (<$gai>) {
            chomp;
@@@ -3588,7 -3625,6 +3590,7 @@@ sub clone ($) 
      my $multi_fetched = fork_for_multisuite(sub {
          printdebug "multi clone before fetch merge\n";
          changedir $dstdir;
 +      record_maindir();
      });
      if ($multi_fetched) {
          printdebug "multi clone after fetch merge\n";
      mkdir $dstdir or fail "create \`$dstdir': $!";
      changedir $dstdir;
      runcmd @git, qw(init -q);
 +    record_maindir();
      setup_new_tree();
      clone_set_head();
      my $giturl = access_giturl(1);
@@@ -3743,7 -3778,7 +3745,7 @@@ sub maybe_split_brain_save ($$$) 
      my ($headref, $dgitview, $msg) = @_;
      # => message fragment "$saved" describing disposition of $dgitview
      return "commit id $dgitview" unless defined $split_brain_save;
 -    my @cmd = (shell_cmd "cd ../../../..",
 +    my @cmd = (shell_cmd 'cd "$1"; shift', $maindir,
               @git, qw(update-ref -m),
               "dgit --dgit-view-save $msg HEAD=$headref",
               $split_brain_save, $dgitview);
@@@ -3844,7 -3879,8 +3846,7 @@@ sub pseudomerge_make_commit ($$$$ $$) 
        : !length  $overwrite_version ? " --overwrite"
        : " --overwrite=".$overwrite_version;
  
 -    mkpath '.git/dgit';
 -    my $pmf = ".git/dgit/pseudomerge";
 +    my $pmf = dgit_privdir()."/pseudomerge";
      open MC, ">", $pmf or die "$pmf $!";
      print MC <<END or die $!;
  tree $tree
@@@ -3879,7 -3915,6 +3881,7 @@@ sub splitbrain_pseudomerge ($$$$) 
      #
  
      return $dgitview unless defined $archive_hash;
 +    return $dgitview if deliberately_not_fast_forward();
  
      printdebug "splitbrain_pseudomerge...\n";
  
@@@ -4152,7 -4187,7 +4154,7 @@@ EN
      rpush_handle_protovsn_bothends() if $we_are_initiator;
      select_tagformat();
  
 -    my $clogpfn = ".git/dgit/changelog.822.tmp";
 +    my $clogpfn = dgit_privdir()."/changelog.822.tmp";
      runcmd shell_cmd "exec >$clogpfn", qw(dpkg-parsechangelog);
  
      responder_send_file('parsed-changelog', $clogpfn);
      if (madformat_wantfixup($format)) {
        # user might have not used dgit build, so maybe do this now:
        if (quiltmode_splitbrain()) {
 -          changedir $ud;
 +          changedir $playground;
            quilt_make_fake_dsc($upstreamversion);
            my $cachekey;
            ($dgithead, $cachekey) =
                quilt_check_splitbrain_cache($actualhead, $upstreamversion);
            $dgithead or fail
   "--quilt=$quilt_mode but no cached dgit view:
 - perhaps tree changed since dgit build[-source] ?";
 + perhaps HEAD changed since dgit build[-source] ?";
            $split_brain = 1;
            $dgithead = splitbrain_pseudomerge($clogp,
                                               $actualhead, $dgithead,
                                               $archive_hash);
            $maintviewhead = $actualhead;
 -          changedir '../../../..';
 +          changedir $maindir;
            prep_ud(); # so _only_subdir() works, below
        } else {
            commit_quilty_patch();
        }
      }
  
 -    changedir $ud;
 +    changedir $playground;
      progress "checking that $dscfn corresponds to HEAD";
      runcmd qw(dpkg-source -x --),
 -        $dscpath =~ m#^/# ? $dscpath : "../../../$dscpath";
 +        $dscpath =~ m#^/# ? $dscpath : "$maindir/$dscpath";
      my ($tree,$dir) = mktree_in_ud_from_only_subdir("source package");
      check_for_vendor_patches() if madformat($dsc->{format});
 -    changedir '../../../..';
 +    changedir $maindir;
      my @diffcmd = (@git, qw(diff --quiet), $tree, $dgithead);
      debugcmd "+",@diffcmd;
      $!=0; $?=-1;
      my $r = system @diffcmd;
      if ($r) {
        if ($r==256) {
 +          my $referent = $split_brain ? $dgithead : 'HEAD';
            my $diffs = cmdoutput @git, qw(diff --stat), $tree, $dgithead;
 -          fail <<END
 +
 +          my @mode_changes;
 +          my $raw = cmdoutput @git,
 +              qw(diff --no-renames -z -r --raw), $tree, $dgithead;
 +          my $changed;
 +          foreach (split /\0/, $raw) {
 +              if (defined $changed) {
 +                  push @mode_changes, "$changed: $_\n" if $changed;
 +                  $changed = undef;
 +                  next;
 +              } elsif (m/^:0+ 0+ /) {
 +                  $changed = '';
 +              } elsif (m/^:(?:10*)?(\d+) (?:10*)?(\d+) /) {
 +                  $changed = "Mode change from $1 to $2"
 +              } else {
 +                  die "$_ ?";
 +              }
 +          }
 +          if (@mode_changes) {
 +              fail <<END.(join '', @mode_changes).<<END;
 +HEAD specifies a different tree to $dscfn:
 +$diffs
 +END
 +There is a problem with your source tree (see dgit(7) for some hints).
 +To see a full diff, run git diff $tree $referent
 +END
 +          }
 +
 +          fail <<END;
  HEAD specifies a different tree to $dscfn:
  $diffs
  Perhaps you forgot to build.  Or perhaps there is a problem with your
   source tree (see dgit(7) for some hints).  To see a full diff, run
 -   git diff $tree HEAD
 +   git diff $tree $referent
  END
        } else {
            failedcmd @diffcmd;
      }
  
      my @tagwants = push_tagwants($cversion, $dgithead, $maintviewhead,
 -                               ".git/dgit/tag");
 +                               dgit_privdir()."/tag");
      my @tagobjfns;
  
      supplementary_message(<<'END');
@@@ -4422,7 -4428,7 +4424,7 @@@ EN
  }
  
  sub pre_clone () {
 -    no_local_git_cfg();
 +    not_necessarily_a_tree();
  }
  sub cmd_clone {
      parseopts();
      pull();
  }
  
 -sub cmd_push {
 +sub prep_push () {
      parseopts();
 -    badusage "-p is not allowed with dgit push" if defined $package;
 +    build_or_push_prep_early();
 +    pushing();
      check_not_dirty();
 -    my $clogp = parsechangelog();
 -    $package = getfield $clogp, 'Source';
      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';
 -    pushing();
      if ($new_package) {
        local ($package) = $existing_package; # this is a hack
        canonicalise_suite();
      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";
      }
 +}
 +
 +sub cmd_push {
 +    prep_push();
      dopush();
  }
  
@@@ -4636,7 -4641,7 +4638,7 @@@ sub i_method 
  }
  
  sub pre_rpush () {
 -    no_local_git_cfg();
 +    not_necessarily_a_tree();
  }
  sub cmd_rpush {
      my $host = nextarg;
@@@ -4856,7 -4861,7 +4858,7 @@@ sub quiltify_dpkg_commit ($$$;$) 
      my ($patchname,$author,$msg, $xinfo) = @_;
      $xinfo //= '';
  
 -    mkpath '.git/dgit';
 +    mkpath '.git/dgit'; # we are in playtree
      my $descfn = ".git/dgit/quilt-description.tmp";
      open O, '>', $descfn or die "$descfn: $!";
      $msg =~ s/\n+/\n\n/;
  
      my $dgitview = git_rev_parse 'HEAD';
  
 -    changedir '../../../..';
 +    changedir $maindir;
      # When we no longer need to support squeeze, use --create-reflog
      # instead of this:
 -    ensuredir ".git/logs/refs/dgit-intern";
 -    my $makelogfh = new IO::File ".git/logs/refs/$splitbraincache", '>>'
 +    ensuredir "$maindir_gitcommon/logs/refs/dgit-intern";
 +    my $makelogfh = new IO::File "$maindir_gitcommon/logs/refs/$splitbraincache", '>>'
        or die $!;
  
      my $oldcache = git_get_ref "refs/$splitbraincache";
@@@ -5074,7 -5079,7 +5076,7 @@@ EN
      runcmd @git, qw(update-ref -m), $cachekey, "refs/$splitbraincache",
        $dgitview;
  
 -    changedir '.git/dgit/unpack/work';
 +    changedir "$playground/work";
  
      my $saved = maybe_split_brain_save $headref, $dgitview, "converted";
      progress "dgit view: created ($saved)";
@@@ -5373,7 -5378,7 +5375,7 @@@ EN
      my $headref = git_rev_parse('HEAD');
  
      prep_ud();
 -    changedir $ud;
 +    changedir $playground;
  
      my $upstreamversion = upstreamversion $version;
  
  
      die 'bug' if $split_brain && !$need_split_build_invocation;
  
 -    changedir '../../../..';
 +    changedir $maindir;
      runcmd_ordryrun_local
 -        @git, qw(pull --ff-only -q .git/dgit/unpack/work master);
 +        @git, qw(pull --ff-only -q), "$playground/work", qw(master);
  }
  
  sub quilt_fixup_mkwork ($) {
@@@ -5403,7 -5408,7 +5405,7 @@@ sub quilt_fixup_linkorigs ($$) 
      my ($upstreamversion, $fn) = @_;
      # calls $fn->($leafname);
  
 -    foreach my $f (<../../../../*>) { #/){
 +    foreach my $f (<$maindir/../*>) { #/){
        my $b=$f; $b =~ s{.*/}{};
        {
            local ($debuglevel) = $debuglevel-1;
                   debian/control debian/changelog);
      foreach my $maybe (qw(debian/patches debian/source/options
                            debian/tests/control)) {
 -        next unless stat_exists "../../../$maybe";
 +        next unless stat_exists "$maindir/$maybe";
          push @files, $maybe;
      }
  
      my $debtar= srcfn $fakeversion,'.debian.tar.gz';
 -    runcmd qw(env GZIP=-1n tar -zcf), "./$debtar", qw(-C ../../..), @files;
 +    runcmd qw(env GZIP=-1n tar -zcf), "./$debtar", qw(-C), $maindir, @files;
  
      $dscaddfile->($debtar);
      close $fakedsc or die $!;
  sub quilt_check_splitbrain_cache ($$) {
      my ($headref, $upstreamversion) = @_;
      # Called only if we are in (potentially) split brain mode.
 -    # Called in $ud.
 +    # Called in playground.
      # Computes the cache key and looks in the cache.
      # Returns ($dgit_view_commitid, $cachekey) or (undef, $cachekey)
  
      debugcmd "|(probably)",@cmd;
      my $child = open GC, "-|";  defined $child or die $!;
      if (!$child) {
 -      chdir '../../..' or die $!;
 -      if (!stat ".git/logs/refs/$splitbraincache") {
 +      chdir $maindir or die $!;
 +      if (!stat "$maindir_gitcommon/logs/refs/$splitbraincache") {
            $! == ENOENT or die $!;
            printdebug ">(no reflog)\n";
            exit 0;
@@@ -5852,18 -5857,14 +5854,18 @@@ sub cmd_clean () 
      maybe_unapply_patches_again();
  }
  
 -sub build_prep_early () {
 -    our $build_prep_early_done //= 0;
 -    return if $build_prep_early_done++;
 -    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';
 +}
 +
 +sub build_prep_early () {
 +    build_or_push_prep_early();
      notpushing();
      check_not_dirty();
  }
@@@ -6101,17 -6102,15 +6103,17 @@@ sub cmd_gbp_build 
      }
      my @cmd = opts_opt_multi_cmd @gbp_build;
  
 -    push @cmd, (qw(-us -uc --git-no-sign-tags), "--git-builder=@dbp");
 +    push @cmd, (qw(-us -uc --git-no-sign-tags),
 +              "--git-builder=".(shellquote @dbp));
  
      if ($gbp_make_orig) {
 -      ensuredir '.git/dgit';
 -      my $ok = '.git/dgit/origs-gen-ok';
 +      my $priv = dgit_privdir();
 +      my $ok = "$priv/origs-gen-ok";
        unlink $ok or $!==&ENOENT or die $!;
        my @origs_cmd = @cmd;
        push @origs_cmd, qw(--git-cleaner=true);
 -      push @origs_cmd, "--git-prebuild=touch $ok .git/dgit/no-such-dir/ok";
 +      push @origs_cmd, "--git-prebuild=".
 +            "touch ".(shellquote $ok)." ".(shellquote "$priv/no-such-dir/ok");
        push @origs_cmd, @ARGV;
        if (act_local()) {
            debugcmd @origs_cmd;
@@@ -6174,10 -6173,10 +6176,10 @@@ sub build_source 
      } else {
        my @cmd = (@dpkgsource, qw(-b --));
        if ($split_brain) {
 -          changedir $ud;
 +          changedir $playground;
            runcmd_ordryrun_local @cmd, "work";
            my @udfiles = <${package}_*>;
 -          changedir "../../..";
 +          changedir $maindir;
            foreach my $f (@udfiles) {
                printdebug "source copy, found $f\n";
                next unless
                    ($f =~ m/\.debian\.tar(?:\.\w+)$/ &&
                     $f eq srcfn($version, $&));
                printdebug "source copy, found $f - renaming\n";
 -              rename "$ud/$f", "../$f" or $!==ENOENT
 +              rename "$playground/$f", "../$f" or $!==ENOENT
                    or fail "put in place new source file ($f): $!";
            }
        } else {
@@@ -6415,7 -6414,7 +6417,7 @@@ EN
  }
  
  sub pre_archive_api_query () {
 -    no_local_git_cfg();
 +    not_necessarily_a_tree();
  }
  sub cmd_archive_api_query {
      badusage "need only 1 subpath argument" unless @ARGV==1;
@@@ -6434,7 -6433,7 +6436,7 @@@ sub repos_server_url () 
  }    
  
  sub pre_clone_dgit_repos_server () {
 -    no_local_git_cfg();
 +    not_necessarily_a_tree();
  }
  sub cmd_clone_dgit_repos_server {
      badusage "need destination argument" unless @ARGV==1;
  }
  
  sub pre_print_dgit_repos_server_source_url () {
 -    no_local_git_cfg();
 +    not_necessarily_a_tree();
  }
  sub cmd_print_dgit_repos_server_source_url {
      badusage "no arguments allowed to dgit print-dgit-repos-server-source-url"
      print $url, "\n" or die $!;
  }
  
 +sub pre_print_dpkg_source_ignores {
 +    not_necessarily_a_tree();
 +}
 +sub cmd_print_dpkg_source_ignores {
 +    badusage "no arguments allowed to dgit print-dpkg-source-ignores"
 +      if @ARGV;
 +    print "@dpkg_source_ignores\n" or die $!;
 +}
 +
  sub cmd_setup_mergechangelogs {
      badusage "no arguments allowed to dgit setup-mergechangelogs" if @ARGV;
      local $isuite = 'DGIT-SETUP-TREE';
@@@ -6846,13 -6836,12 +6848,13 @@@ if (!@ARGV) 
      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;
  
 +record_maindir if $invoked_in_git_tree;
  git_slurp_config();
  
  my $fn = ${*::}{"cmd_$cmd"};