chiark / gitweb /
badcommit-fixup: Merge from a filtered view of my personal playground
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Fri, 6 Jan 2017 16:09:39 +0000 (16:09 +0000)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Fri, 6 Jan 2017 16:09:39 +0000 (16:09 +0000)
Signed-off-by: Ian Jackson <ijackson@chiark.greenend.org.uk>
152 files changed:
.gitignore [new file with mode: 0644]
DEVELOPER-CERTIFICATE [new file with mode: 0644]
Debian/Dgit.pm [new file with mode: 0644]
Debian/Dgit/Infra.pm [new file with mode: 0644]
Debian/Dgit/Policy/Debian.pm [new file with mode: 0644]
Makefile [new file with mode: 0644]
README.dsc-import [new file with mode: 0644]
absurd/git [new file with mode: 0755]
debian/changelog [new file with mode: 0644]
debian/compat [new file with mode: 0644]
debian/control [new file with mode: 0644]
debian/copyright [new file with mode: 0644]
debian/rules [new file with mode: 0755]
debian/tests/control [new file with mode: 0644]
debian/tests/control.in [new file with mode: 0644]
dgit [new file with mode: 0755]
dgit-maint-gbp.7.pod [new file with mode: 0644]
dgit-maint-merge.7.pod [new file with mode: 0644]
dgit-maint-native.7.pod [new file with mode: 0644]
dgit-nmu-simple.7.pod [new file with mode: 0644]
dgit-sponsorship.7.pod [new file with mode: 0644]
dgit-user.7.pod [new file with mode: 0644]
dgit.1 [new file with mode: 0644]
dgit.7 [new file with mode: 0644]
infra/cgit-regen-config [new file with mode: 0755]
infra/dgit-mirror-rsync [new file with mode: 0755]
infra/dgit-repos-admin-debian [new file with mode: 0755]
infra/dgit-repos-policy-debian [new file with mode: 0755]
infra/dgit-repos-policy-trusting [new file with mode: 0755]
infra/dgit-repos-server [new file with mode: 0755]
infra/dgit-ssh-dispatch [new file with mode: 0755]
infra/drs-cron-wrap [new file with mode: 0755]
infra/get-dm-txt [new file with mode: 0755]
infra/get-suites [new file with mode: 0755]
infra/ssh-wrap [new file with mode: 0755]
local-pod-man [new file with mode: 0755]
tests/Makefile [new file with mode: 0644]
tests/adhoc [new file with mode: 0755]
tests/drs-git-ext [new file with mode: 0755]
tests/dsd-ssh [new file with mode: 0755]
tests/enumerate-tests [new file with mode: 0755]
tests/git-srcs/pari-extra_3-1.git.tar [new file with mode: 0644]
tests/git-template.tar [new file with mode: 0644]
tests/gnupg/dd.gpg [new file with mode: 0644]
tests/gnupg/dm.gpg [new file with mode: 0644]
tests/gnupg/dm.txt [new file with mode: 0644]
tests/gnupg/pubring.gpg [new file with mode: 0644]
tests/gnupg/random_seed [new file with mode: 0644]
tests/gnupg/secring.gpg [new file with mode: 0644]
tests/gnupg/trustdb.gpg [new file with mode: 0644]
tests/lib [new file with mode: 0644]
tests/lib-build-modes [new file with mode: 0644]
tests/lib-core [new file with mode: 0644]
tests/lib-import-chk [new file with mode: 0644]
tests/lib-mirror [new file with mode: 0644]
tests/lib-orig-include-exclude [new file with mode: 0644]
tests/lib-reprepro [new file with mode: 0644]
tests/lib-restricts [new file with mode: 0644]
tests/pkg-srcs/example_1.0-1+absurd.debian.tar.xz [new file with mode: 0644]
tests/pkg-srcs/example_1.0-1+absurd.dsc [new file with mode: 0644]
tests/pkg-srcs/example_1.0-1.100.debian.tar.xz [new file with mode: 0644]
tests/pkg-srcs/example_1.0-1.100.dsc [new file with mode: 0644]
tests/pkg-srcs/example_1.0-1.debian.tar.xz [new file with mode: 0644]
tests/pkg-srcs/example_1.0-1.dsc [new file with mode: 0644]
tests/pkg-srcs/example_1.0.orig-docs.tar.gz [new file with mode: 0644]
tests/pkg-srcs/example_1.0.orig.tar.gz [new file with mode: 0644]
tests/pkg-srcs/pari-extra_3-1.diff.gz [new file with mode: 0644]
tests/pkg-srcs/pari-extra_3-1.dsc [new file with mode: 0644]
tests/pkg-srcs/pari-extra_3-2~dummy1.diff.gz [new file with mode: 0644]
tests/pkg-srcs/pari-extra_3-2~dummy1.dsc [new file with mode: 0644]
tests/pkg-srcs/pari-extra_3.orig.tar.gz [new file with mode: 0644]
tests/pkg-srcs/ruby-rails-3.2_3.2.6-1.debian.tar.gz [new file with mode: 0644]
tests/pkg-srcs/ruby-rails-3.2_3.2.6-1.dsc [new file with mode: 0644]
tests/pkg-srcs/ruby-rails-3.2_3.2.6.orig.tar.gz [new file with mode: 0644]
tests/pkg-srcs/sunxi-tools_1.2-2.~~dgittest.debian.tar.gz [new file with mode: 0644]
tests/pkg-srcs/sunxi-tools_1.2-2.~~dgittest.dsc [new file with mode: 0644]
tests/pkg-srcs/sunxi-tools_1.2.orig.tar.gz [new file with mode: 0644]
tests/run-all [new file with mode: 0755]
tests/setup/examplegit [new file with mode: 0755]
tests/setup/gnupg [new file with mode: 0755]
tests/ssh [new file with mode: 0755]
tests/suites [new file with mode: 0644]
tests/tartree-edit [new file with mode: 0755]
tests/tests/absurd-gitapply [new file with mode: 0755]
tests/tests/build-modes [new file with mode: 0755]
tests/tests/build-modes-asplit [new file with mode: 0755]
tests/tests/build-modes-gbp [new file with mode: 0755]
tests/tests/build-modes-gbp-asplit [new file with mode: 0755]
tests/tests/build-modes-sbuild [new file with mode: 0755]
tests/tests/clone-clogsigpipe [new file with mode: 0755]
tests/tests/clone-gitnosuite [new file with mode: 0755]
tests/tests/clone-nogit [new file with mode: 0755]
tests/tests/clone-reprepro [new file with mode: 0755]
tests/tests/debpolicy-dbretry [new file with mode: 0755]
tests/tests/debpolicy-newreject [new file with mode: 0755]
tests/tests/debpolicy-quilt-gbp [new file with mode: 0755]
tests/tests/distropatches-reject [new file with mode: 0755]
tests/tests/drs-clone-nogit [new file with mode: 0755]
tests/tests/drs-push-masterupdate [new file with mode: 0755]
tests/tests/drs-push-rejects [new file with mode: 0755]
tests/tests/dsd-clone-drs [new file with mode: 0755]
tests/tests/dsd-clone-nogit [new file with mode: 0755]
tests/tests/dsd-divert [new file with mode: 0755]
tests/tests/fetch-localgitonly [new file with mode: 0755]
tests/tests/fetch-somegit-notlast [new file with mode: 0755]
tests/tests/gbp-orig [new file with mode: 0755]
tests/tests/gitconfig [new file with mode: 0755]
tests/tests/import-dsc [new file with mode: 0755]
tests/tests/import-native [new file with mode: 0755]
tests/tests/import-nonnative [new file with mode: 0755]
tests/tests/import-tarbomb [new file with mode: 0755]
tests/tests/inarchivecopy [new file with mode: 0755]
tests/tests/mirror [new file with mode: 0755]
tests/tests/mirror-debnewgit [new file with mode: 0755]
tests/tests/mirror-private [new file with mode: 0755]
tests/tests/mismatches-contents [new file with mode: 0755]
tests/tests/mismatches-dscchanges [new file with mode: 0755]
tests/tests/multisuite [new file with mode: 0755]
tests/tests/newtag-clone-nogit [new file with mode: 0755]
tests/tests/oldnewtagalt [new file with mode: 0755]
tests/tests/oldtag-clone-nogit [new file with mode: 0755]
tests/tests/orig-include-exclude [new file with mode: 0755]
tests/tests/orig-include-exclude-chkquery [new file with mode: 0755]
tests/tests/overwrite-chkclog [new file with mode: 0755]
tests/tests/overwrite-junk [new file with mode: 0755]
tests/tests/overwrite-splitbrains [new file with mode: 0755]
tests/tests/overwrite-version [new file with mode: 0755]
tests/tests/push-buildproductsdir [new file with mode: 0755]
tests/tests/push-newpackage [new file with mode: 0755]
tests/tests/push-nextdgit [new file with mode: 0755]
tests/tests/quilt [new file with mode: 0755]
tests/tests/quilt-gbp [new file with mode: 0755]
tests/tests/quilt-gbp-build-modes [new file with mode: 0755]
tests/tests/quilt-gbp-build-modes-sbuild [new file with mode: 0755]
tests/tests/quilt-singlepatch [new file with mode: 0755]
tests/tests/quilt-splitbrains [new file with mode: 0755]
tests/tests/rpush [new file with mode: 0755]
tests/tests/spelling [new file with mode: 0755]
tests/tests/tag-updates [new file with mode: 0755]
tests/tests/test-list-uptodate [new file with mode: 0755]
tests/tests/trustingpolicy-replay [new file with mode: 0755]
tests/tests/unrepresentable [new file with mode: 0755]
tests/tests/version-opt [new file with mode: 0755]
tests/tstunt/Dpkg/Changelog/Parse.pm [new file with mode: 0644]
tests/tstunt/debuild [new file with mode: 0755]
tests/tstunt/dpkg-parsechangelog [new file with mode: 0755]
tests/tstunt/lintian [new file with mode: 0755]
tests/using-intree [new file with mode: 0755]
tests/worktrees/example_1.0.tar [new file with mode: 0644]
tests/worktrees/pari-extra_3-1.tar [new file with mode: 0644]
tests/worktrees/pari-extra_drs.tar [new file with mode: 0644]
tests/worktrees/ruby-rails-3.2_test.tar [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..13e2c4b
--- /dev/null
@@ -0,0 +1,15 @@
+*~
+tests/tmp
+debian/dgit
+debian/dgit-infrastructure
+debian/files
+debian/*.substvars
+debian/*.log
+debian/debhelper-build-stamp
+dgit-user.7
+dgit-nmu-simple.7
+dgit-maint-native.7
+dgit-maint-merge.7
+dgit-maint-gbp.7
+dgit-sponsorship.7
+substituted
diff --git a/DEVELOPER-CERTIFICATE b/DEVELOPER-CERTIFICATE
new file mode 100644 (file)
index 0000000..912d22e
--- /dev/null
@@ -0,0 +1,38 @@
+Developer Certificate of Origin
+Version 1.1
+
+Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
+1 Letterman Drive
+Suite D4700
+San Francisco, CA, 94129
+
+Everyone is permitted to copy and distribute verbatim copies of this
+license document, but changing it is not allowed.
+
+
+Developer's Certificate of Origin 1.1
+
+By making a contribution to this project, I certify that:
+
+(a) The contribution was created in whole or in part by me and I
+    have the right to submit it under the open source license
+    indicated in the file; or
+
+(b) The contribution is based upon previous work that, to the best
+    of my knowledge, is covered under an appropriate open source
+    license and I have the right under that license to submit that
+    work with modifications, whether created in whole or in part
+    by me, under the same open source license (unless I am
+    permitted to submit under a different license), as indicated
+    in the file; or
+
+(c) The contribution was provided directly to me by some other
+    person who certified (a), (b) or (c) and I have not modified
+    it.
+
+(d) I understand and agree that this project and the contribution
+    are public and that a record of the contribution (including all
+    personal information I submit with it, including my sign-off) is
+    maintained indefinitely and may be redistributed consistent with
+    this project or the open source license(s) involved.
+
diff --git a/Debian/Dgit.pm b/Debian/Dgit.pm
new file mode 100644 (file)
index 0000000..e9921d6
--- /dev/null
@@ -0,0 +1,368 @@
+# -*- perl -*-
+# dgit
+# Debian::Dgit: functions common to dgit and its helpers and servers
+#
+# Copyright (C) 2015-2016  Ian Jackson
+#
+#    This program is free software; you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation; either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+package Debian::Dgit;
+
+use strict;
+use warnings;
+
+use Carp;
+use POSIX;
+use IO::Handle;
+use Config;
+use Digest::SHA;
+use Data::Dumper;
+
+BEGIN {
+    use Exporter   ();
+    our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS);
+
+    $VERSION     = 1.00;
+    @ISA         = qw(Exporter);
+    @EXPORT      = qw(setup_sigwarn
+                     dep14_version_mangle
+                      debiantags debiantag_old debiantag_new
+                     server_branch server_ref
+                      stat_exists link_ltarget
+                     hashfile
+                      fail ensuredir executable_on_path
+                      waitstatusmsg failedcmd_waitstatus
+                     failedcmd_report_cmd failedcmd
+                      cmdoutput cmdoutput_errok
+                      git_rev_parse git_get_ref git_for_each_ref
+                      git_for_each_tag_referring is_fast_fwd
+                      $package_re $component_re $deliberately_re
+                      $branchprefix
+                      initdebug enabledebug enabledebuglevel
+                      printdebug debugcmd
+                      $debugprefix *debuglevel *DEBUG
+                      shellquote printcmd messagequote);
+    # implicitly uses $main::us
+    %EXPORT_TAGS = ( policyflags => [qw(NOFFCHECK FRESHREPO NOCOMMITCHECK)] );
+    @EXPORT_OK   = @{ $EXPORT_TAGS{policyflags} };
+}
+
+our @EXPORT_OK;
+
+our $package_re = '[0-9a-z][-+.0-9a-z]*';
+our $component_re = '[0-9a-zA-Z][-+.0-9a-zA-Z]*';
+our $deliberately_re = "(?:TEST-)?$package_re";
+our $branchprefix = 'dgit';
+
+# policy hook exit status bits
+# see dgit-repos-server head comment for documentation
+# 1 is reserved in case something fails with `exit 1' and to spot
+# dynamic loader, runtime, etc., failures, which report 127 or 255
+sub NOFFCHECK () { return 0x2; }
+sub FRESHREPO () { return 0x4; }
+sub NOCOMMITCHECK () { return 0x8; }
+
+our $debugprefix;
+our $debuglevel = 0;
+
+sub setup_sigwarn () {
+    our $sigwarn_mainprocess = $$;
+    $SIG{__WARN__} = sub { 
+       die $_[0] unless getppid == $sigwarn_mainprocess;
+    };
+}
+
+sub initdebug ($) { 
+    ($debugprefix) = @_;
+    open DEBUG, ">/dev/null" or die $!;
+}
+
+sub enabledebug () {
+    open DEBUG, ">&STDERR" or die $!;
+    DEBUG->autoflush(1);
+    $debuglevel ||= 1;
+}
+    
+sub enabledebuglevel ($) {
+    my ($newlevel) = @_; # may be undef (eg from env var)
+    die if $debuglevel;
+    $newlevel //= 0;
+    $newlevel += 0;
+    return unless $newlevel;
+    $debuglevel = $newlevel;
+    enabledebug();
+}
+    
+sub printdebug {
+    print DEBUG $debugprefix, @_ or die $! if $debuglevel>0;
+}
+
+sub messagequote ($) {
+    local ($_) = @_;
+    s{\\}{\\\\}g;
+    s{\n}{\\n}g;
+    s{\x08}{\\b}g;
+    s{\t}{\\t}g;
+    s{[\000-\037\177]}{ sprintf "\\x%02x", ord $& }ge;
+    $_;
+}
+
+sub shellquote {
+    my @out;
+    local $_;
+    foreach my $a (@_) {
+       $_ = $a;
+       if (!length || m{[^-=_./:0-9a-z]}i) {
+           s{['\\]}{'\\$&'}g;
+           push @out, "'$_'";
+       } else {
+           push @out, $_;
+       }
+    }
+    return join ' ', @out;
+}
+
+sub printcmd {
+    my $fh = shift @_;
+    my $intro = shift @_;
+    print $fh $intro," " or die $!;
+    print $fh shellquote @_ or die $!;
+    print $fh "\n" or die $!;
+}
+
+sub debugcmd {
+    my $extraprefix = shift @_;
+    printcmd(\*DEBUG,$debugprefix.$extraprefix,@_) if $debuglevel>0;
+}
+
+sub dep14_version_mangle ($) {
+    my ($v) = @_;
+    # DEP-14 patch proposed 2016-11-09  "Version Mangling"
+    $v =~ y/~:/_%/;
+    $v =~ s/\.(?=\.|$|lock$)/.#/g;
+    return $v;
+}
+
+sub debiantag_old ($$) { 
+    my ($v,$distro) = @_;
+    return "$distro/". dep14_version_mangle $v;
+}
+
+sub debiantag_new ($$) { 
+    my ($v,$distro) = @_;
+    return "archive/$distro/".dep14_version_mangle $v;
+}
+
+sub debiantags ($$) {
+    my ($version,$distro) = @_;
+    map { $_->($version, $distro) } (\&debiantag_new, \&debiantag_old);
+}
+
+sub server_branch ($) { return "$branchprefix/$_[0]"; }
+sub server_ref ($) { return "refs/".server_branch($_[0]); }
+
+sub stat_exists ($) {
+    my ($f) = @_;
+    return 1 if stat $f;
+    return 0 if $!==&ENOENT;
+    die "stat $f: $!";
+}
+
+sub _us () {
+    $::us // ($0 =~ m#[^/]*$#, $&);
+}
+
+sub fail { 
+    my $s = "@_\n";
+    $s =~ s/\n\n$/\n/;
+    my $prefix = _us().": ";
+    $s =~ s/^/$prefix/gm;
+    die $s;
+}
+
+sub ensuredir ($) {
+    my ($dir) = @_; # does not create parents
+    return if mkdir $dir;
+    return if $! == EEXIST;
+    die "mkdir $dir: $!";
+}
+
+sub executable_on_path ($) {
+    my ($program) = @_;
+    return 1 if $program =~ m{/};
+    my @path = split /:/, ($ENV{PATH} // "/usr/local/bin:/bin:/usr/bin");
+    foreach my $pe (@path) {
+       my $here = "$pe/$program";
+       return $here if stat_exists $here && -x _;
+    }
+    return undef;
+}
+
+our @signames = split / /, $Config{sig_name};
+
+sub waitstatusmsg () {
+    if (!$?) {
+       return "terminated, reporting successful completion";
+    } elsif (!($? & 255)) {
+       return "failed with error exit status ".WEXITSTATUS($?);
+    } elsif (WIFSIGNALED($?)) {
+       my $signum=WTERMSIG($?);
+       return "died due to fatal signal ".
+           ($signames[$signum] // "number $signum").
+           ($? & 128 ? " (core dumped)" : ""); # POSIX(3pm) has no WCOREDUMP
+    } else {
+       return "failed with unknown wait status ".$?;
+    }
+}
+
+sub failedcmd_report_cmd {
+    my $intro = shift @_;
+    $intro //= "failed command";
+    { local ($!); printcmd \*STDERR, _us().": $intro:", @_ or die $!; };
+}
+
+sub failedcmd_waitstatus {
+    if ($? < 0) {
+       return "failed to fork/exec: $!";
+    } elsif ($?) {
+       return "subprocess ".waitstatusmsg();
+    } else {
+       return "subprocess produced invalid output";
+    }
+}
+
+sub failedcmd {
+    # Expects $!,$? as set by close - see below.
+    # To use with system(), set $?=-1 first.
+    #
+    # Actual behaviour of perl operations:
+    #   success              $!==0       $?==0       close of piped open
+    #   program failed       $!==0       $? >0       close of piped open
+    #   syscall failure      $! >0       $?=-1       close of piped open
+    #   failure              $! >0       unchanged   close of something else
+    #   success              trashed     $?==0       system
+    #   program failed       trashed     $? >0       system
+    #   syscall failure      $! >0       unchanged   system
+    failedcmd_report_cmd undef, @_;
+    fail failedcmd_waitstatus();
+}
+
+sub cmdoutput_errok {
+    confess Dumper(\@_)." ?" if grep { !defined } @_;
+    debugcmd "|",@_;
+    open P, "-|", @_ or die "$_[0] $!";
+    my $d;
+    $!=0; $?=0;
+    { local $/ = undef; $d = <P>; }
+    die $! if P->error;
+    if (!close P) { printdebug "=>!$?\n"; return undef; }
+    chomp $d;
+    if ($debuglevel > 0) {
+       $d =~ m/^.*/;
+       my $dd = $&;
+       my $more = (length $' ? '...' : ''); #');
+       $dd =~ s{[^\n -~]|\\}{ sprintf "\\x%02x", ord $& }ge;
+       printdebug "=> \`$dd'",$more,"\n";
+    }
+    return $d;
+}
+
+sub cmdoutput {
+    my $d = cmdoutput_errok @_;
+    defined $d or failedcmd @_;
+    return $d;
+}
+
+sub link_ltarget ($$) {
+    my ($old,$new) = @_;
+    lstat $old or return undef;
+    if (-l _) {
+       $old = cmdoutput qw(realpath  --), $old;
+    }
+    my $r = link $old, $new;
+    $r = symlink $old, $new if !$r && $!==EXDEV;
+    $r or die "(sym)link $old $new: $!";
+}
+
+sub hashfile ($) {
+    my ($fn) = @_;
+    my $h = Digest::SHA->new(256);
+    $h->addfile($fn);
+    return $h->hexdigest();
+}
+
+sub git_rev_parse ($) {
+    return cmdoutput qw(git rev-parse), "$_[0]~0";
+}
+
+sub git_for_each_ref ($$;$) {
+    my ($pattern,$func,$gitdir) = @_;
+    # calls $func->($objid,$objtype,$fullrefname,$reftail);
+    # $reftail is RHS of ref after refs/[^/]+/
+    # breaks if $pattern matches any ref `refs/blah' where blah has no `/'
+    # $pattern may be an array ref to mean multiple patterns
+    $pattern = [ $pattern ] unless ref $pattern;
+    my @cmd = (qw(git for-each-ref), @$pattern);
+    if (defined $gitdir) {
+       @cmd = ('sh','-ec','cd "$1"; shift; exec "$@"','x', $gitdir, @cmd);
+    }
+    open GFER, "-|", @cmd or die $!;
+    debugcmd "|", @cmd;
+    while (<GFER>) {
+       chomp or die "$_ ?";
+       printdebug "|> ", $_, "\n";
+       m#^(\w+)\s+(\w+)\s+(refs/[^/]+/(\S+))$# or die "$_ ?";
+       $func->($1,$2,$3,$4);
+    }
+    $!=0; $?=0; close GFER or die "$pattern $? $!";
+}
+
+sub git_get_ref ($) {
+    # => '' if no such ref
+    my ($refname) = @_;
+    local $_ = $refname;
+    s{^refs/}{[r]efs/} or die "$refname $_ ?";
+    return cmdoutput qw(git for-each-ref --format=%(objectname)), $_;
+}
+
+sub git_for_each_tag_referring ($$) {
+    my ($objreferring, $func) = @_;
+    # calls $func->($tagobjid,$refobjid,$fullrefname,$tagname);
+    printdebug "git_for_each_tag_referring ",
+        ($objreferring // 'UNDEF'),"\n";
+    git_for_each_ref('refs/tags', sub {
+       my ($tagobjid,$objtype,$fullrefname,$tagname) = @_;
+       return unless $objtype eq 'tag';
+       my $refobjid = git_rev_parse $tagobjid;
+       return unless
+           !defined $objreferring # caller wants them all
+           or $tagobjid eq $objreferring
+           or $refobjid eq $objreferring;
+       $func->($tagobjid,$refobjid,$fullrefname,$tagname);
+    });
+}
+
+sub is_fast_fwd ($$) {
+    my ($ancestor,$child) = @_;
+    my @cmd = (qw(git merge-base), $ancestor, $child);
+    my $mb = cmdoutput_errok @cmd;
+    if (defined $mb) {
+       return git_rev_parse($mb) eq git_rev_parse($ancestor);
+    } else {
+       $?==256 or failedcmd @cmd;
+       return 0;
+    }
+}
+
+1;
diff --git a/Debian/Dgit/Infra.pm b/Debian/Dgit/Infra.pm
new file mode 100644 (file)
index 0000000..eff460b
--- /dev/null
@@ -0,0 +1,17 @@
+# -*- perl -*-
+
+package Debian::Dgit::Infra;
+
+use strict;
+use warnings;
+
+# Scripts and programs which are going to `use Debian::Dgit' but which
+# live in dgit-infrastructure (ie are installed with install-infra)
+# should `use Debian::Dgit::Infra' first.  All this module does is
+# adjust @INC so that the script gets the version of the script from
+# the dgit-infrastructure package (which is installed in a different
+# location and may be a different version).
+
+# unshift @INC, q{/usr/share/dgit/infra/perl5}; ###substituted###
+
+1;
diff --git a/Debian/Dgit/Policy/Debian.pm b/Debian/Dgit/Policy/Debian.pm
new file mode 100644 (file)
index 0000000..12f1ee1
--- /dev/null
@@ -0,0 +1,42 @@
+# -*- perl -*-
+
+package Debian::Dgit::Policy::Debian;
+
+use strict;
+use warnings;
+
+use POSIX;
+
+BEGIN {
+    use Exporter   ();
+    our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS);
+
+    $VERSION     = 1.00;
+    @ISA         = qw(Exporter);
+    @EXPORT      = qw(poldb_path poldb_setup $poldbh);
+    %EXPORT_TAGS = ( );
+    @EXPORT_OK   = qw();
+}
+
+our @EXPORT_OK;
+
+our $poldbh;
+
+sub poldb_path ($) {
+    my ($repos) = @_;
+    return "$repos/policy.sqlite3";
+}
+
+sub poldb_setup ($;$) {
+    my ($policydb, $hook) = @_;
+
+    $poldbh ||= DBI->connect("dbi:SQLite:$policydb",'','', {
+       RaiseError=>1, PrintError=>1, AutoCommit=>0
+                          });
+
+    $hook->() if $hook;
+
+    $poldbh->do("PRAGMA foreign_keys = ON");
+}
+
+1;
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..68c1a02
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,106 @@
+# dgit
+# Integration between git and Debian-style archives
+#
+# Copyright (C)2013-2016 Ian Jackson
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+INSTALL=install
+INSTALL_DIR=$(INSTALL) -d
+INSTALL_PROGRAM=$(INSTALL) -m 755
+INSTALL_DATA=$(INSTALL) -m 644
+
+prefix?=/usr/local
+
+bindir=$(prefix)/bin
+mandir=$(prefix)/share/man
+perldir=$(prefix)/share/perl5
+man1dir=$(mandir)/man1
+man7dir=$(mandir)/man7
+infraexamplesdir=$(prefix)/share/doc/dgit-infrastructure/examples
+txtdocdir=$(prefix)/share/doc/dgit
+absurddir=$(prefix)/share/dgit/absurd
+
+PROGRAMS=dgit
+MAN1PAGES=dgit.1
+
+MAN7PAGES=dgit.7                               \
+       dgit-user.7 dgit-nmu-simple.7           \
+       dgit-maint-native.7                     \
+       dgit-maint-merge.7 dgit-maint-gbp.7     \
+       dgit-sponsorship.7
+
+TXTDOCS=README.dsc-import
+PERLMODULES=Debian/Dgit.pm
+ABSURDITIES=git
+
+INFRA_PROGRAMS=dgit-repos-server dgit-ssh-dispatch \
+       dgit-repos-policy-debian dgit-repos-admin-debian \
+       dgit-repos-policy-trusting dgit-mirror-rsync
+INFRA_EXAMPLES=get-dm-txt ssh-wrap drs-cron-wrap get-suites
+INFRA_PERLMODULES= \
+       Debian/Dgit.pm \
+       Debian/Dgit/Infra.pm \
+       Debian/Dgit/Policy/Debian.pm
+
+all:   $(MAN7PAGES) $(addprefix substituted/,$(PROGRAMS))
+
+substituted/%: %
+       mkdir -p substituted
+       perl -pe 's{\bundef\b}{'\''$(absurddir)'\''} if m/###substituted###/' \
+               <$< >$@
+
+install:       installdirs all
+       $(INSTALL_PROGRAM) $(addprefix substituted/,$(PROGRAMS)) \
+               $(DESTDIR)$(bindir)
+       $(INSTALL_PROGRAM) $(addprefix absurd/,$(ABSURDITIES)) \
+               $(DESTDIR)$(absurddir)
+       $(INSTALL_DATA) $(MAN1PAGES) $(DESTDIR)$(man1dir)
+       $(INSTALL_DATA) $(MAN7PAGES) $(DESTDIR)$(man7dir)
+       $(INSTALL_DATA) $(TXTDOCS) $(DESTDIR)$(txtdocdir)
+       set -e; for m in $(PERLMODULES); do \
+               $(INSTALL_DATA) $$m $(DESTDIR)$(perldir)/$${m%/*}; \
+       done
+
+installdirs:
+       $(INSTALL_DIR) $(DESTDIR)$(bindir) \
+               $(DESTDIR)$(man1dir) $(DESTDIR)$(man7dir) \
+               $(DESTDIR)$(txtdocdir) $(DESTDIR)$(absurddir) \
+               $(addprefix $(DESTDIR)$(perldir)/, $(dir $(PERLMODULES)))
+
+install-infra: installdirs-infra
+       $(INSTALL_PROGRAM) $(addprefix infra/, $(INFRA_PROGRAMS)) \
+               $(DESTDIR)$(bindir)
+       $(INSTALL_PROGRAM) $(addprefix infra/, $(INFRA_EXAMPLES)) \
+               $(DESTDIR)$(infraexamplesdir)
+       set -e; for m in $(INFRA_PERLMODULES); do \
+               $(INSTALL_DATA) $$m $(DESTDIR)$(perldir)/$${m%/*}; \
+       done
+
+installdirs-infra:
+       $(INSTALL_DIR) $(DESTDIR)$(bindir) $(DESTDIR)$(infraexamplesdir) \
+               $(addprefix $(DESTDIR)$(perldir)/, $(dir $(INFRA_PERLMODULES)))
+
+check installcheck:
+
+clean distclean mostlyclean maintainer-clean:
+       rm -rf tests/tmp substituted
+       set -e; for m in $(MAN7PAGES); do \
+               test -e $$m.pod && rm -f $$m; \
+       done
+
+%.7: %.7.pod
+       pod2man --section=7 --date="Debian Project" --center="dgit" \
+               --name=$(subst .7,,$@) \
+               $^ $@
diff --git a/README.dsc-import b/README.dsc-import
new file mode 100644 (file)
index 0000000..1ec53b0
--- /dev/null
@@ -0,0 +1,106 @@
+We would like to: represent the input tarballs as a commit each (which
+all get merged together as if by git merge -s subtree), and for quilt
+packages, each patch as a commit.  But w want to avoid (as much as
+possible) reimplementing the package extraction algorithm in
+dpkg-source.
+
+dpkg-source does not currently provide interfaces that look like they
+are intended for what dgit wants to do.  And dgit wants to work with
+old versions of dpkg, so I have implemented the following algorithm
+rather than wait for such interfaces added (even supposing that a sane
+interface could be designed, which is doubtful):
+
+* dgit will untar each input tarball.
+
+  This will be done by scanning the .dsc for things whose names look
+  like (compressed) tarballs, and using the interfaces provided by
+  Dpkg::Compression to get at the tarball.
+
+  Each input tarball unpack will be done separately, and will be
+  followed by git add and git write-tree, to obtain a git tree object
+  corresponding to the tarball contents.
+
+  That tree object will be made into a commit object with no parents.
+  (The package changelog will be searched for the earliest version
+  with the right upstream version component, and the information found
+  there used for the commit object's metadata.)
+
+* For `3.0 (quilt), dgit will run
+    dpkg-source -x --skip-patches
+
+  git plumbing will be used to make the result into a tree and a
+  commit.  The commit will have as parents all the tarballs previously
+  mentioned.  The main orig tarball will be the leftmost parent and
+  the debian tarball the rightmost parent.  The metadata will come
+  from the .dsc and/or the final changelog entry.
+
+  dgit will then dpkg-source --before-build and record the resulting
+  tree, too.
+
+  Then, dgit will switch back to the patches-unapplied version and use
+  `gbp pq import' (in the private working area) to turn the
+  patches-unapplied tree into a patches-applied one.
+
+  Finally dgit will check that the gbp pq generated patches-applied
+  version has the same git tree object as the one generated by
+  dpkg-source --before-build.
+
+* For source formats other than `3.0 (quilt)', dgit will do simply
+    dpkg-source -x.
+
+  Again, it will make that into a tree and a commit.
+
+* For source formats with only single file entry in the .dsc, the
+  (one) tarball is not imported separately (since its tree object
+  would be the same as the extracted object), and the commit of the
+  dpkg-source -x output has no parents.
+
+* As currently, there will be a final no-change-to-the-tree
+  pseudomerge commit which stitches the package into the relevant dgit
+  suite branch.  (By `pseudomerge' we mean something that looks as if
+  it was made with git merge -s ours.)
+
+* As currently, dgit will take steps so that none of the git trees
+  discussed above contain a .pc directory.
+
+
+This has the following properties:
+
+* Each input tarball is represented by a different commit; in usual
+  cases these commits will be the same for every upload of the same
+  upstream version.
+
+* For `3.0 (quilt)' each patch's changes to the upstream files appears
+  as a single git commit (as is the effect of the debian tarball);
+  also, there is a commit object whose tree is just the debian/
+  directory, which might well be the same as certain debian-only git
+  workflow trees.
+
+* For `1.0' non-native, the effect of the diff is represented as a
+  commit.  So eg `git blame' will show synthetic commits corresponding
+  to the correct parts of the input source package.
+
+* It is possible to `git cherry-pick' etc. commits representing `3.0
+  (quilt)' patches.  It is even possible fish out the patch stack as
+  git branch and rebase it elsewhere etc., since the patch stack is
+  represented as a contiguous series of commits which make only the
+  relevant upstream changes.
+
+* Every orig tarball in the source package is decompressed twice, but
+  disk space for only one extra copy of its unpacked contents is
+  needed.  (The converse would be possible in principle but would be
+  very hard to arrange with the current interfaces provided by the
+  various tools.)
+
+* No back doors into the innards of dpkg-source (nor changes to
+  dpkg-dev) are required.
+
+* dgit does grow a dependency on git-buildpackage.
+
+* Knowledge of the source format embedded in dgit is is restricted to
+  some relatively straightforward processing of filenames found in
+  .dsc files.
+
+* dgit now depends on dpkg-source -x --skip-patches followed by
+  dpkg-source --before-build being the same as dpkg-source -x
+  (for `3.0 (quilt)').
diff --git a/absurd/git b/absurd/git
new file mode 100755 (executable)
index 0000000..0f562b5
--- /dev/null
@@ -0,0 +1,115 @@
+#!/bin/sh
+set -e
+
+case "$DGIT_ABSURD_DEBUG" in
+''|0)  exec 3>/dev/null ;;
+1)     exec 3>>../../gbp-pq-output ;;
+*)     exec 3>>../../gbp-pq-output 2>&3 ;;
+esac
+
+log () {
+       echo >&3 "DGIT ABSURD GIT APPLY (DEBUG)  $*"
+       echo >&2 "DGIT ABSURD GIT APPLY (STDERR) $*"
+}
+
+fail () {
+       log "FAILED: $*"
+       exit 127
+}
+
+self=${0%/*}
+npath=${PATH#$self:}
+if test "x$PATH" = "x$npath"; then
+       fail "PATH FILTER FAIL ($0 $self $PATH)"
+fi
+
+bypass=true
+for arg in "$@"; do
+       case "$arg" in
+       apply)  bypass=false; break     ;;
+       -*)                             ;;
+       *)      bypass=true; break      ;;
+       esac
+done
+
+if $bypass; then
+       PATH=$npath
+       echo >&3 "DGIT ABSURD GIT APPLY - BYPASS: $*"
+       exec git "$@"
+fi
+
+log "NO BYPASS: $*"
+
+case "$DGIT_ABSURD_DEBUG" in
+''|0|1) ;;
+*)     set -x ;;
+esac
+
+#exec >/dev/tty 2>&1
+
+index=0
+noo=0
+
+for arg in "$@"; do
+       case "$noo.$arg" in
+       1.--index)
+               index=1
+               continue
+               ;;
+       1.--whitespace=fix)
+               continue
+               ;;
+       ?.-*)
+               fail "UNKNOWN OPTION $arg ($*)"
+               ;;
+       0.apply)
+               ;;
+       1.*)    patch="$arg"
+               ;;
+       *)
+               fail "BAD USAGE $arg ($noo $*)"
+       esac
+       noo=$(( $noo + 1 ))
+done
+
+if [ $noo != 2 ]; then
+       fail "NO PATCH ($*)"
+fi
+
+pwd=`pwd`
+patch=${patch#$pwd/debian/patches/}
+rm -f debian/patches/series
+
+# Work around #848611.
+# We need a stunt filename which the source package must not
+# contain.  A trick is to use the commit hash of HEAD, whose
+# hash value cannot appear in any file in its own tree.
+omgwtf="dgit-omg-wtf-$(git rev-parse HEAD)"
+cat <<END >debian/patches/$omgwtf
+---
+--- a/$omgwtf  2016-10-31 23:28:47.314155919 +0000
++++ b/$omgwtf  2016-12-18 22:40:01.870058270 +0000
+@@ -0,0 +1 @@
++:
+END
+printf "%s\n" "$omgwtf" >debian/patches/series
+printf "%s\n" "$patch" >>debian/patches/series
+
+# Just in case some joker tries to patch .git/something
+mv .git ../.git
+set +e
+dpkg-source --before-build .
+rc=$?
+set -e
+rm -rf .git
+mv ../.git .
+test $rc = 0
+
+rm -f $omgwtf debian/patches/$omgwtf
+
+rm -rf .pc
+git checkout debian/patches/series
+git add -Af .
+
+log "APPLIED $patch"
+#printf 'APPLIED '; date --iso-8601=ns
diff --git a/debian/changelog b/debian/changelog
new file mode 100644 (file)
index 0000000..be2c165
--- /dev/null
@@ -0,0 +1,1100 @@
+dgit (2.16~) unstable; urgency=medium
+
+  * 
+
+ --
+
+dgit (2.15) UNRELEASED; urgency=high
+
+  Infastructure:
+  * Prevent introduction of new commits which lack `committer'
+    information.  Ie, prevent the reception of new commits afflicted by
+    #849041.  Existing commits are tolerated.
+
+  Test suite:
+  * Be much stricter about messages from git-fsck.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Thu, 05 Jan 2017 18:20:23 +0000
+
+dgit (2.14) unstable; urgency=critical
+
+  CRITICAL BUGFIX:
+  * Do not generate bogus commits with --overwrite or import-dsc.
+    Closes:#849041.
+
+  Test suite:
+  * Run a lot of git-fsck.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Wed, 04 Jan 2017 22:52:55 +0000
+
+dgit (2.13) unstable; urgency=high
+
+  Changed behaviour:
+  * quilt fixup: Permit creation of patches which delete files, by psssing
+    --include-removal to dpkg-source, and tolerating it when we do our
+    quilt fixup analysis.  dpkg-source has supported this since at least
+    stretch.  Closes:#848901.
+
+  Error messages:
+  * Improve "cannot represent change" message: print the git old and new
+    modes too.
+
+  Bugfix:
+  * Import: Switch back to unpa branch on patch import iterations.
+    In particular, do not fail utterly if dpkg-source and gbp disagree.
+    Closes:#848843.
+
+  Documentation [Sean Whitton]:
+  * dgit-maint-gbp(7): Remove reference to closed bug.  Closes:#848725.
+  * dgit-sponsorship(7): Update in light of fixed #844129.  Closes:#848789.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Wed, 21 Dec 2016 01:32:41 +0000
+
+dgit (2.12) unstable; urgency=high
+
+  Changed behaviours:
+  * By default, generate a DEP-14 tag as well as a dgit archive/*
+    tag, even in non-split-view quilt modes.  Closes:#844129.
+  * Version tags mangling: Protect dots, as per proposed update to DEP-14.
+
+  Documentation:
+  * dgit-maint-merge(7): Explain how to change to this workflow
+    from an existing git workflow.  [Sean Whitton]  Closes:#847807.
+  * dgit-maint-native(7): Clarify that we mean native source format.
+    [Phil Hands]  Closes:#847987.
+
+  Error messages:
+  * Slightly better message when .dsc not found.  Apropos of #844128.
+  * Give better advice if .dsc/.changes signing fails: if no changes
+    are needed to the package, user may indeed just debsign and dput.
+    Closes:#844131.
+  * Produce better error reporting when absurd git wrapper fails
+    on a patch during .dsc import.  Apropos of #848391.
+
+  Bugfixes:
+  * If we cannot hardlink origs into our extraction area, use symlinks
+    instead.  Closes:#844570.
+  * Suppress some leftover debugging output from import-dsc.
+    Closes:#847658.
+  * Do not fail when cloning a package containing dangling symlinks.
+    Closes:#848512.
+  * Do not fail to import a .dsc containing patches which patch files
+    multiple times, due to #848611.  Closes:#848391.
+  * Do not fail to import a .dsc containing patches to .git/ (!)
+  * infra: dgit-repos-policy-debian which broke due to recent git setting
+    GIT_ALTERNATE_OBJECT_DIRECTORIES in the pre-receive-hook.
+    (fixes test suite regression in stretch).
+
+  Test suite:
+  * Provide and use stunt lintian and debuild, to avoid lintian
+    complaining about our stupid test packages.
+    (fixes test suite regression in stretch).
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Mon, 19 Dec 2016 17:35:18 +0000
+
+dgit (2.11) unstable; urgency=medium
+
+  Documentation:
+  * dgit-user(7): Better explanation of combined suites (comma syntax).
+    Thanks to Sean Whitton for review and suggestions.
+  * dgit(1), dgit(7): Better reference docs for combined suites.
+  * dgit(1): Improve formatting of rpush section.
+
+  Test suite:
+  * Replace make in Test-Depends with build-essential.  Most of the tests
+    do in fact run dpkg-buildpackage which bombs out if build-essential is
+    missing.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Tue, 08 Nov 2016 22:41:29 +0000
+
+dgit (2.10) unstable; urgency=medium
+
+  New features:
+  * Support the Debian *-security suites.
+  * New comma-separated multiple-suite merging facility (readonly),
+    so that users can easily track "jessie, or jessie-security".
+  * dgit-user(7): Suggest `dgit clone P jessie,-security'.
+
+  Bugfixes:
+  * Cope when an orig tarball is a tarbomb.  Ie, if it contains
+    other than one single directory toplevel.  Closes:#843422.
+  * Actually honour the branch name, if we are on dgit branch, to specify
+    the suite, as documented in the manpage.
+  * When cloning a distro which has no git server, correctly leave
+    the user on the local dgit branch, not on `master'.
+  * Fix an unconditional print that was supposed to be a printdebug:
+      origs <blah>.orig.tar.gz f.same=1 #f._differ=-1
+  * Print a slightly better message if .git found in orig tarball(s).
+
+  Test suite:
+  * Test suite: Add fakeroot and make to Test-Depends.  These aren't
+    necessarily pulled in by anything else.  (dpkg-dev Recommends
+    build-essential.  But we don't actually need build-essential.)
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Tue, 08 Nov 2016 01:08:51 +0000
+
+dgit (2.9) unstable; urgency=medium
+
+  New features:
+  * During push, automatically calculate which .origs are required,
+    so user never needs [--ch:]-sa or [--ch:]-sd.  Closes:#829116.
+  * New import-dsc feature.
+  * New option --dgit-view-save= for split view quilt modes.
+    In particular, means that the output of a split view quilt-fixup
+    is left somewhere useful.
+  * dgit clone: Set timestamps in cloned tree to a single unified time.
+    This makes it less likely that the user will trip over any
+    timestamp-dependent FTBFS bugs (eg #842452).
+  * Support dgit --delayed= push (with a warning in the manpage
+    about possible skew).
+  * dgit gbp-build will arrange to let gbp buildpackage generate
+    .orig tarballs if it seems applicable.  Closes:#841094.
+
+  Documentation improvements:
+  * dgit-*(7).  Many new tutorial manpages, several written and many
+    improved by Sean Whitton.
+  * dgit(7): Substantial updates, including documenting split view.
+  * dgit(1): Better cross-references.
+  * dgit(1): Remove obsolete workflow information.
+  * dgit(1): Improved BUGS section.
+  * Fix changelog entry for SIGPIPE to correctly mention
+    Closes:#841090.
+
+  Bugfixes:
+  * Split brain mode: Fix --new.  Closes:#842577.
+  * Properly look for .origs etc. in .., fetching them less often.
+    Closes:#842386.
+  * Reject `dgit pull' in split view quilt modes, to avoid
+    creating unfortunate wreckage on non-dgit-view branches.
+    Closes:#842608.
+  * Cope when cloning suite which doesn't receive uploads,
+    like testing.  Closes:#842621.
+  * Properly fetch all archive dgit view tags, as we intended.
+  * Actually provide a -p (--package=) option (!)
+
+  Test suite fixes:
+  * Test suite: Explicitly configure user.name and user.email, so
+    that tests work when environment doesn't have defaults.
+    Closes:#842279 (I hope).
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Mon, 31 Oct 2016 12:47:18 +0000
+
+dgit (2.8) unstable; urgency=medium
+
+  * When in split build mode for `gbp-build' or `build', run
+    mergechanges as is required.  Closes:#841990.
+  * Test suite: build-mode-*: Check that right .changes comes out
+    (detects #841990).
+  * Defend against debian/patches/series being an unusual object, in case
+    dpkg-source doesn't, in absurd git-apply fallback.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Tue, 25 Oct 2016 17:29:23 +0100
+
+dgit (2.7) unstable; urgency=medium
+
+  Absurd bugfix for serious bug:
+  * Work around `git-apply' problems (eg #841865, #829067) exposed by
+    `gbp pq import' (#841866) by sometimes falling back to an emulation of
+    git-apply in terms of dpkg-source --before-build.  Closes:#841867.
+
+  Minor changes:
+  * dgit(1): Reorder the options, moving more important ones earlier.
+  * dgit(1): Some more info about --deliberately.
+  * Provide various --force-something options.  Please don't use them.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Mon, 24 Oct 2016 02:37:28 +0100
+
+dgit (2.6) unstable; urgency=medium
+
+  Fixes to HTTP handling:
+  * Check for non-2xx HTTP status codes from ftpmaster api server.
+  * Always honour --curl= and --curl:.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Sun, 23 Oct 2016 14:57:22 +0100
+
+dgit (2.5) unstable; urgency=low
+
+  Substantive changes:
+  * Do not crash in split brain quilt modes when the two brains are
+    actually identical.  (Eg --quilt=gbp with no patches.)  Closes:#841770.
+  * Switch to new archive/ tag format by default, even in
+    non-split-brain mode.
+  * Provide --gbp and --dpm as aliases for --quilt=gbp and --quilt=dpm.
+
+  Documentation:
+  * dgit-maint-merge(7): New tutorial manpage from Sean Whitton.
+
+  Test suite:
+  * Introduce setup/gnupg, to help work around gnupg2 bug #841143
+    and improve performance by amortising gnupg migration cost.
+  * Various bugfixes.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Sun, 23 Oct 2016 13:20:23 +0100
+
+dgit (2.4) unstable; urgency=low
+
+  Bugfixes:
+  * split brain cache: Fix a wrong implicit reference to $_.
+    Closes:#841383.
+  * split brain cache: Make sure to write reflog entries for cache updates
+    even if the eventual tree (and therefore commit) is the same.
+    Otherwise, after updating dgit, the cache might have the right answer
+    but not be refreshed even by a build.
+  * dgit gbp-build: No longer invent a --git-debian-branch option.
+    Usually the user is a maintainer using split brain, and we should rely
+    on their own gbp configuration to specify the right check.
+    Closes:#841100.
+
+  Minor docs fix:
+  * dgit(1): Document which --ch: options are a good idea.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Thu, 20 Oct 2016 16:31:54 +0100
+
+dgit (2.3) unstable; urgency=low
+
+  * With --overwrite, do not check all sorts of tags (which may
+    not exist, or might contain wrong things).  Closes:#841101.
+  * When generating pseudomerge in quilt split brain mode due to
+    --overwrite, actually include the version number in the commit
+    message.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Tue, 18 Oct 2016 01:58:05 +0100
+
+dgit (2.2) unstable; urgency=low
+
+  * Fix config relating to Debian to actually make split brain mode
+    work.  Closes:#841085.
+  * Detect SIGPIPE (and SIGCHLD) being blocked or ignored.
+    Closes:#841090.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Mon, 17 Oct 2016 17:31:18 +0100
+
+dgit (2.1) unstable; urgency=low
+
+  * Do not crash due in clone to failure to handle dpkg-parsechangelog
+    SIGPIPE.  Closes:#840989.  Avoids:
+       dgit: failed command: dpkg-parsechangelog --format rfc822 --all
+       dgit: subprocess died due to fatal signal PIPE
+  * git- prefixes: Fix some occurrences of `git-foo' in infrastructure,
+    messages, and test suite.  Filter out .../git-core from PATH in
+    test suite so that we catch future occurrences.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Sun, 16 Oct 2016 19:05:14 +0100
+
+dgit (2.0) unstable; urgency=low
+
+  Incompatible change:
+  * dgit sbuild: does not pass -A to sbuild.  Consequently the default
+    build is now simply sbuild's default.  With older sbuilds it was
+    possible to override dgit's -A by passing another option.  But this
+    has been changed recently and now this default setting is very awkward
+    to change for the dgit user.
+  * dgit gbp-build: Make --quilt=gbp the default.  (See below.)
+  * New tag format (for dgit view) archive/debian/VERSION.
+
+  Major new feature:
+  * --quilt=gbp, --quilt=dpm, --quilt=unpacked: Introduce facility for
+    split view (dgit/mainiainer view), to improve compatibility with some
+    workflow tools.
+
+  New checks and improved behaviours in dgit:
+  * When running dpkg-buildpackage, cope if user specified -g or -G.
+  * dgit sbuild: check that the set of .changes files found is as we
+    expect, before calling mergechanges.  Re:#800060.
+  * dgit sbuild: Rename the used-up .changes files to `.inmulti' to
+    avoid accidental use of the wrong one (by software, or by users).
+  * dgit sbuild: Check that the binary .changes file doesn't contain a
+    .dsc.
+  * Introduce --rm-old-changes to delete previous builds' changes files.
+  * Remove any pre-existing _source.changes file before building source,
+    as a safety check.
+  * No longer tolerate a multitude of .changes files when doing push.
+    Instead, insist on a single one.  Closes:#800110.
+  * dgit sbuild no longer deletes extranious .changes files; instead
+    we rely on --rm-old-changes, or failing that, fail early.
+  * When doing quilt linearisation, treat upstream .gitignores not
+    in the toplevel the same way we treat ones in the toplevel.
+  * When automatically generating quilt patch, honour GIT_COMMITTER_DATE
+    for filename creation (makes filename deterministic in test suite).
+  * New --overwrite option, replaces need to for user to use
+    git merge -s ours.  Closes:#838718.
+  * When generating quilt patches from git commits, make patches that
+    look quite like git-format-patch output (rather than strange things
+    based on an obselete interpretation of DEP-3).
+  * When generating quilt patches from git commits, honour (and strip)
+    any Gbp-Pq headers (that we understand).
+  * Several dgit-generated commits now have slightly better annotations
+    from dgit about what it was doing.
+  * Before committing to push, check that .dsc and .changes correspond.
+    Closes:#800060.
+  * Better error message if non-split-brain patch stack no longer
+    applies (due to new upstream version, or user messing with it).
+    Closes:#833025.
+  * Better error message if HEAD contains changes unrepresentable
+    by `3.0 (quilt)'.  Closes:#834618.
+  * Much better error message when HEAD and .dsc do not match.
+    Closes:#809516.
+
+  Infrastructure:
+  * dgit-repos-policy-debian: Better error handling.
+  * dgit-repos-policy-debian.: fix git-cat-file-handling with multiple
+    taints in db (!).
+  * dgit-infrastructure has, and uses, its own copies of the perl modules.
+    This avoids introducing a versioned dependency between dgit and
+    dgit-infrastructure (and also makes it easier to test cross-version
+    compatibility).
+
+  Documentation:
+  * Document the dgit-distro.DISTRO.quilt-mode config setting.
+  * Clarify the --clean= options' documentation.  Closes:#800054.
+  * Discourage use of the --PROGRAM:OPTION escape hatch.  (Apropos
+    of various bug reports including #800060 and #833025.)
+  * Document the expected form of HEAD for each --quilt= mode.
+
+  Bugfixes:
+  * When cleaning up after failed clone, stat the to-be-cleaned-up
+    directory before running rmtree on it.  Closes:#796773.
+  * Do not call "warn" on failure of cleanup handler in END block
+    (since warn has been made fatal and aborts the cleanup chain).
+  * Print better error message (with `fail' rather than `die') if
+    `dgit clone' cannot create the destination directory.
+  * Properly substitute $changesfile in one of the `You can retry'
+    messages.  Closes:#800078.
+  * Pass --ch:* and -v options to dpkg-buildpackage when building
+    source.  Fixes bad Perl poetry syntax.  Closes:#829121.
+  * When synthesing a commit from a .dsc from the archive, stop
+    internal git reset from printing a confusing message about HEAD.
+  * Turn off git gc in the private working areas.
+  * Do not fail to do some important quilt processing in some
+    --quilt modes.
+  * Fix two calls to chdir without proper error checking.
+  * Fix a couple of bugs in error reporting.
+  * Fix several bugs in .orig detection/recognition.
+  * Tidy up refs/dgit-fetch/ after dgit fetch (if successful).
+  * Fix handling of in-archive copies.
+  * Don't break if user has push.followTags=true.  Closes:#827878.
+  * Arrange for the special dgit remote to be skipped by git fetch --all
+    etc.  And no longer configure a fetch spec, since it won't work
+    anyway.  Closes:#827892.
+  * Allow local git config options to override user-global ones,
+    as is proper.  Closes:#835858.
+  * When generating patch filenames from titles, first transliterate
+    them (lossily) to ascii.  Closes:#834807.
+
+  Test suite:
+  * When sbuild fails, do not crash due to sed not finding the log
+    file.  Instead, simply tolerate the absence of the log file.
+  * Put --no-arch-all in build-modes-sbuild act, not only its real_act.
+    Cosmetic change only.
+  * Set GIT_COMMITTER_DATE and GIT_AUTHOR_DATE and increment them
+    explicitly in drs-push-rejects test.  This avoids date dependencies
+    which can cause that test to fail on fast computers.
+  * Remove some spurios .debs from the example_1.0.tar.
+  * Increase sqlite_busy_timeout in debpolicy-dbretry, because old
+    zealot is very slow and we need to give the other processes time
+    to rollback and release the lock.
+  * Test quilt single-debian-patch.
+  * Provide `tartree-edit gitfetchinfo' etc. to help with comparing
+    different test case git working tree tarballs.
+  * Test dgit-repos-policy-debian with multiple (identical, as it happens)
+    existing taints.
+  * Provide better log output for certain failures.
+  * Many new tests (especially for new functionality).
+  * Add missing debhelper (>=8) to test suite's global Depends.
+  * tstunt arrangements: Fix mishandling of PERLLIB, etc.
+  * tstunt-parsechangelog: Produce Timestamp field (like official one
+    does, now).
+  * Do not fail when git requires --allow-unrelated-histories.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Sun, 16 Oct 2016 12:12:50 +0100
+
+dgit (1.4) unstable; urgency=high
+
+  Bugfixes:
+  * Unbreak --dry-run (`exiting subroutine via next', broken in
+    ac221d67, bug released in 0.22).
+  * When running git-add in commit-quilty-patch, properly escape
+    filenames (which git-add treats as glob patterns).
+  * When running git-add in commit-quilty-patch, use -f and sometimes -A,
+    so as to avoid being broken by any .gitignore, etc.
+  * When quilt linearisation fails, print the right information in
+    the error message.  (This has been broken forever.)
+  * Cope properly with `3.0 (quilt)' with single-debian-patch.
+    Closes:#796016.  (Still does not work with wheezy's dpkg-source, so
+    no test case yet.)
+  * With dgit sbuild, pass our -d before the user's arguments, so that
+    the user can override it.  Closes:#796019.
+
+  New checks and improved behaviours:
+  * Detect and reject git trees containing debian/source/local-options
+    or debian/source/local-patch-header.
+  * In --dry-run mode, _do_ actually run dpkg-source --commit so that we
+    actually do construct the quilt fixup commit; instead, honour
+    --dry-run by avoiding pulling it back to your HEAD.
+  * quilt-fixup checks that the git tree is clean, as for build-prep.
+
+  Documentation:
+  * In dgit(7), discuss binaries and documentation present in upstream but
+    removed by rules clean.
+
+  Test suite:
+  * Run quilt-fixup with -wgf in distropatches-reject,
+    so that we don't need build-depends.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Sat, 22 Aug 2015 15:31:02 +0100
+
+dgit (1.3) unstable; urgency=high
+
+  Important bugfixes:
+  * In option parser test `@ARGV' not `length @ARGV'.  Closes:#795710.
+  * Properly quote package name when constructing regexp in
+    complete_file_from_dsc.  Closes:#795736.  Also, grep the code for
+    likely similar problems elsewhere and improve a (harmless) instance in
+    dgit-repos-server.
+
+  Other improvements:
+  * If a .orig in .. is a symlink, hardlink the link target into our
+    private unpack directory, rather than the link itself (since latter
+    won't work if the symlink is relative).  Closes:#795665.
+  * Test suite: Fix t-restriction-x-dgit-schroot-build in non-adt mode.
+  * Infrastructure: Improve an error message in dgit-repos-policy-debian.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Sun, 16 Aug 2015 17:51:02 +0100
+
+dgit (1.2) unstable; urgency=high
+
+  Improvements:
+  * Honour *.clean-mode configuration setting for --clean= mode.
+  * No longer require option values to be cuddled: support `--opt val' and
+    `-o val'.  Closes:#763332.
+
+  Manpages:
+  * Fix typos.
+  * Document that tags are in DEP-14 format, and that they
+    are used for authenticating pushes.
+  * Correct cross-reference to point to browse.d.d.o.
+  * Move dgit.default.* to main CONFIGURATION section.
+
+  Administrivia:
+  * Add missing close of #793060 to changelog for version 1.1.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Fri, 14 Aug 2015 18:27:20 +0100
+
+dgit (1.1) unstable; urgency=medium
+
+  Bugfixes:
+  * When source package contains things called .git (even files, and even
+    in subdirectories), remove them.  Closes:#793671.
+  * Work around curl -sS -I printing `HTTP/1.0 200 Connection established'
+    before the actual header, so dgit works with https_proxy set (!)
+  * --new is needed for read access to packages in NEW, too.  Document
+    this, and make it work properly.
+  * Work around #793471 (madness with $SIG{__WARN__} and Perl's system
+    builtin): move $SIG{} setting into setup_sigwarn in Dgit.pm, and
+    check getppid.
+  * When invoking git-buildpackage via dgit gbp-build, consider our
+    command line arguments when massaging the dpkg-buildpackage arguments,
+    so that we don't end up giving dpkg-buildpackage contradictory
+    instructions.
+  * Cope with new git-buildpackage which provides gbp, rather than the
+    eponymous command, on PATH.
+
+  Configurability:
+  * Honour dgit-distros.DISTRO.cmd-CMD and .opts-CMD.  Closes:#793427.
+  * Make configuration able to prevent dpkg-mergechangelogs setup.
+  * Provide dgit setup-new-tree (like dpkg-setup-mergechangelogs
+    but only does it if not disabled in config).
+  * Set up git user.email and user.name from distro access config
+    or DEBEMAIL/DEBFULLNAME.  Closes:#793410.
+  * When key to use not specified any other way, use the debian/changelog
+    trailer line.  Closes:#793423.
+  * Honour --git= (mostly).
+
+  Documentation:
+  * Fix some manpage typos.  [ Richard Hartmann ]
+  * Manpage said that --clean=check was -wn but that is --clean=none;
+    correctly document that --clean=check is actually -wc.
+  * Document that up to -DDDD (not just -DD) is meaningfully different.
+  * Document that -cname=value applies only for this run.
+  * Improve manpage comment about defining a new distro.
+  * Document that --quilt=linear is the default for Debian.
+  * Fix a formatting problem in --build-products-dir= doc.
+  * In manpage, do not seem to imply that NMU should be of only one
+    new commit.
+  * Qualify to Debian the manpage comment about how to do NMU.
+  * In discussion on how to start using dgit when already using git, do
+    not imply/assume that existing git history will have identical trees
+    to dgit history.
+  * Remove stray sentence in config section of manpage.
+  * Manpage: Clarify wording of readonly config.
+  * Manpage: Better cross-references for -k and keyid.
+  * dgit(7): No longer say that dgit-repos lives on Alioth.
+
+  Improvements:
+  * Introduce more sophisticated protocol negotiation for rpush.
+  * Do not quote `:' in shellquote.
+  * Print a supplementary message when push fails, giving advice to
+    the user about how to retry.  Closes:#793144.
+  * Slurp in entire git config, for better performance.
+  * Rename `git-build' operation to `gbp-build' to make it clearer what
+    it's for.  Keep the old name as an alias.
+  * Show `dgit sbuild' in usage message.
+  * When we are using dpkg-buildpackage to clean before using it to also
+    do the build, let it do its cleaning thing as part of its run, rather
+    than running it twice.  When we are _not_ supposed to be using
+    dpkg-buildpackage to clean, but we are running it to do the build,
+    pass -nc.  Closes:#793060.
+  * Also suppress spurious runs of the clean target when building using
+    git-buildpackage.
+  * When exec fails, always print the program name in the error message.
+
+  Infrastructure:
+  * Infrastructure: Get mirroring right for fresh repos of existing
+    packages (!)
+
+  Packaging, cleanups, debugging and test suite:
+  * Fix Vcs-Git and Vcs-Browse to refer to chiark.  (The dgit-repos on
+    alioth aren't suitable right now because the master there can
+    currently only be updated with an actual upload, ie dgit push.)
+  * Make warnings fatal in dpkg-repos-admin-debian, dgit-ssh-dispatch
+    (using setup_sigwarn).
+  * tstunt/dpkg-parsechangelog: Make warnings fatal (directly).
+  * tstunt/dpkg-parsechangelog: Do not complain if PERLLIB is empty.
+  * Test suite: Honour DGIT_TEST_DEBUG=''.
+  * With -DDDD, print out all gitcfg references (copious!)
+  * Fix a debug message in the obsolete sshpsql archive access driver.
+  * Test suite: More automatic enumeration of tests.
+  * Test suite: Provide tests which check that all our various build
+    operations run the right targets as expected (ie, that we are massaging
+    the arguments to dpkg-buildpackage, and suppressing our clean target,
+    etc., correctly).
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Mon, 27 Jul 2015 16:34:31 +0100
+
+dgit (1.0) unstable; urgency=medium
+
+  Improvements:
+  * Switch to new production git repositories for reading.
+    (this can no longer divert to alioth).  Public readonly access
+    now works.  Closes:#791447.
+  * Memoise git config lookups (big speedup!)
+  * Provide -wdd aka --clean=dpkg-source-d.  Closes:#792433.
+  * Provide -wc aka --clean=check.
+
+  Manpage updates:
+  * Remove some obsolete caveats from BUGS.
+  * Reorganise and complete the configuration section.
+  * Remove obselete comment about DMs not being able to push.
+    We have, for now, a way to add keys manually.  Closes:#720173.
+
+  Access machinery:
+  * Remove configuration relating to alioth.
+  * Provide for different access mechanisms when pushing.
+  * Provide for configurable git url suffix.
+  * Allow git-url to be '' to force fallback to git-proto etc.
+  * Provide for checking git presence via http[s].
+  * Do some quoting on debug output (needed if the server might not
+    be trustworthy and might send us bad stuff).
+  * Talk to push.dgit.debian.org, rather than the .debian.net alias.
+
+  Infrastructure:
+  * Provide for mirroring git updates to a different server.
+  * Provide cgit-regen-config command for cgi-grnet-01.
+  * Make dgit-ssh-dispatch not spew (harmless) warnings if caller
+    tries for a shell session (ie SSH_ORIGINAL_COMMAND not set).
+
+  Cleanups:
+  * Remove an obsolete comment from the code.
+  * Improve an error message from dgit-repos-policy-debian.
+  * Test suite: Break out t-make-hook-link.
+  * Fix a manpage typo.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Sun, 19 Jul 2015 22:15:53 +0100
+
+dgit (0.30) unstable; urgency=high
+
+  INCOMPATIBLE CHANGES:
+
+  * Client uses new infrastructure:
+    - Check for new dgit git service on dgit-git.debian.net (ie
+      gideon.debian.org), with transition plan based on diversion feature.
+      Closes:#720172.
+    - Old versions of dgit will stop working when the server-side handle is
+      pulled.
+
+  * dgit git trees no longer contain .pc for format `3.0 (quilt)' source
+    packages.  Closes:#764606.
+    - It is deleted whenever we find it.
+    - Older versions of dgit will choke on trees without .pc.
+    - (When doing quilt fixup, we recreate a suitable .pc in a temporary
+      directory so that we can do dpkg-source --comit.)
+
+  * All users are urged to upgrade ASAP.
+
+  Other significant improvements:
+
+  * When generating quilt patches, try to linearise the git history into a
+    series of individual new patches for debian/patches.  Closes:#770710.
+
+  * When receiving a push with dgit-repos-server, update the server's
+    refs/heads/master if we are pushing to what the distro regards as a
+    relevant branch, and the push would ff master.  Closes:#728209.
+
+  * For non-Debian distros, distro version release tags contain distro
+    name a la DEP-14 (rather than hardcoding `debian/').
+
+  * Set up a merge driver for debian/changelog.  Closes:#769291.
+
+  * --clean=git and --clean=none cause dgit to pass -nc to
+    dpkg-buildpackage, suppressing calls to the package's clean target.
+    Also, expand the documentation in this area slightly.  Closes:#768590.
+
+  * Provide --clean=git-ff (aka -wgf), which is useful for dgit itself (!)
+
+  Minor improvements:
+
+  * Reduce some noise output and improve the clarity of some messages.
+  * Be more careful about tag updates during fetch: only update tags
+    referring to uploads to distro we are trying to fetch from.
+  * Change realpath dependency to `coreutils (>= 8.23-1~) | realpath'
+    (Closes:#786955.)
+
+  Bugfixes:
+
+  * Fix handling of rmadison-based and gitless distros (e.g., Ubuntu).
+  * Add missing `gpgv' to test dependencies in debian/tests/control.
+  * Strip `-b <branch>' from contents of Vcs-Git header, when setting up
+    the vcs-git remote.  Closes:#759374.
+  * Do not offer wget as an alternative dependency to curl.  We always
+    unconditionally invoke curl and have no code to use wget.
+    Closes:#760805.
+  * Complain about lack of cuddled values for value-taking single-letter
+    options, rather than thinking the user meat an empty value.
+    Closes:#763332.
+  * Reject (rather than ignoring) further options merged witth -wn, -wg,
+    -wd.
+  * Fix inaccurate error message when archive's git hash is not an
+    ancestor of git repo's git hash.
+  * Detect and bomb out on vendor-specific `3.0 (quilt)' patch series.
+  * Fix the rules clean target to remove test results and output.
+
+  Documentation improvements:
+
+  * Break out dgit(7) from dgit(1).
+  * Provide example workflow for dgit rpush.  Closes:#763334.
+    (Also part of the fix for #768470.)
+  * Document that dgit repos are cloneable with git, in dgit(1)
+    section MODEL.  [Andreas Barth.]  Closes:#768470.
+  * Better documentation for quilt series handling.
+  * Document under `dgit push' that it is best to build with dgit too.
+    Closes:#763333.
+  * Other minor clarifications and improvements.
+
+  Behind-the-scenes work:
+
+  * Use ftpmasterapi archive query method.
+    (Closes:#727702.  Also partly obsoletes #768470.)
+  * New dgit-infrastructure binary package containing dgit-repos-server et
+    al.  Client users probably don't want this stuff.  Also, it provides a
+    convenient way to publish the dependencies.
+  * Many many bugfixes to the server side (dpkg-repos-server et al.).
+  * Add :..; prefix to ssh remote commands, for the benefit of future
+    forced command wrappers.  Implicitly, this defines a new ssh-based
+    command protocol.  Closes:#720174, #720175.
+  * Distro access configuration handling changes (should not be noticeable
+    to most users).
+  * In places, significant restructuring or tidying up.
+  * Turn all perl warnings into errors using $SIG{__WARN__}.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Sun, 05 Jul 2015 01:34:55 +0100
+
+dgit (0.22.1) unstable; urgency=high
+
+  * Use Dpkg::Version::version_compare everywhere, not
+    Dpkg::Version::version_compare_string.  The latter is entirely wrong,
+    meaning that dgit would get many version comparisons wrong.
+    Closes:#768038.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Tue, 04 Nov 2014 12:46:40 +0000
+
+dgit (0.22) unstable; urgency=medium
+
+  Bugfixes:
+  * Clone removes destination directory on error.  Closes:#736153.
+  * Work with wheezy-backports (and keep squeeze-backports working too).
+    Closes:#736524.
+  * Work in read-only no-git-history mode with Ubuntu.  You still have
+    to pass -dubuntu.  Closes:#751781.
+  * Use mirror.ftp-master.debian.org DNS alias rather than coccia.
+    Closes:#752602.
+  * Check hashes of files ourselves rather than running dget to
+    re-retreive the .dsc.
+  * Check SHA-256 of .dsc against hash from archive_query (ie projectb)
+    rather than letting dpkg-source do a signature verification.
+    Closes:#737619.  Closes:#737625.
+  * Treat .dsc as bytes, just like everything else, rather than letting
+    HTTP::Message convert it to a Perl unicode string which the rest of
+    the program mishandles.  Closes:#738536.
+
+  Minor improvements:
+  * Include canonicalised suite name in signed tag message.
+  * Mention cross-version dgit rpush incompatibility in manpage.
+  * Check for rpush protocol version incompatibility and crash early
+    if incompatible.
+  * New script tests/using-intree for running tests on the source tree.
+  * Do not spew diff output to terminal (by default).  Print sensible
+    message instead.  Closes:#736526.
+  * Print better message for lack of configuration settings.
+  * Document that dgit rpush needs gnupg and your public key on the build
+    host.  Closes:#736529.
+  * Fix a manpage reference to `--dget=' where `--dgit=' was intended.
+  * Provide t-archive-process-incoming and t-archive-query subroutines for
+    regression test scripts to use.
+  * Print better message for unknown operations.
+  * Provide `dgit clean'.  Closes:#736527.
+  * When cloning, set up a remote `vcs-git' from the package's Vcs-Git
+    (and put an appropriate caveat in the manpage).  Closes:#740687.
+    Closes:#740721.
+  * Improve error message for .dsc having already been signed (iff
+    using libdpkg-perl 1.17.x).  Closes:#731635.
+  * Improve error message for .dsc parsing failures more generally.
+  * Better reporting of child exit statuses (esp. deaths due to signals).
+  * In rpush, on protocol error talking to build host, check if the
+    subprocess died and report differently if so.  Closes:#736528.
+  * Fixed a manpage typo.
+  * When tests invoke dgit, use --dgit= so that subprocesses use our
+    dgit rather than system one.
+  * Add a test for dgit rpush.
+
+  Major new feature, currently stalled awaiting server infrastructure:
+  * dgit-repos-server: New program for receiving signed-tag-based
+    pushes.  Corresponding support in dgit itself, but not currently
+    used by default for any distro.
+  * Bring forward push of the version tag ref so it happens alongside
+    the push of the suite branch ref.
+  * New git-check and git-create methods "true" which are no-ops.
+  * test-dummy-drs `distro': for testing dgit and dgit-repos-server.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Tue, 19 Aug 2014 11:24:02 +0100
+
+dgit (0.21) unstable; urgency=medium
+
+  Bugfixes relating to unclean trees:
+  * Run a clean (of the specified type) before any build operation; do
+    this with `dpkg-buildpackage -T' clean if necessary, so -wd now works
+    with all the building methods.
+  * Refuse to do quilt fixup (explicitly requested, or as a result of
+    build) if the tree contains ignored files.  Closes:#731632.
+
+  Error message improvements:
+  * Use failedcmd to report errors when ssh psql fails.  Closes:#734281.
+  * failedcmd prints $us, not $_[0] - ie, dgit doesn't pretend,
+    in the error message, to be its child.
+  * Do not report the (irrelevant) $? when madison parsing fails.
+
+  Better workflow flexibility:
+  * Provide --build-products-dir option (and corresponding semantics
+    for -C) to specify where to find the files to upload.  Closes:#731633.
+
+  Support for Debian backports suites:
+  * New quirks infrastructure in configuration and internals,
+    for suites (or perhaps distros) which are a bit like others.
+  * Use correct default archive location.
+  * Compute "-v" option default value correctly.
+  * Closes:#733954.
+
+  Packaging improvement:
+  * Add `Testsuite: autopkgtest' to debian/control.  (This will only have
+    the right effect with recent enought dpkg; it will generate a harmless
+    warning with earlier versions of dpkg.)
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Sun, 19 Jan 2014 02:14:25 +0000
+
+dgit (0.20) unstable; urgency=high
+
+  * Use newest (not oldest) version currently in suite when calculating
+    what value to use for -v<version> by default.  Closes:#732781.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Sat, 21 Dec 2013 19:13:56 +0000
+
+dgit (0.19) unstable; urgency=low
+
+  Testing facilities:
+  * Provide "test-dummy" distro with "dummycat" access method.
+  * Provide a selection of autopkgtest (DEP-8) tests.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Wed, 27 Nov 2013 18:27:17 +0000
+
+dgit (0.18.2) unstable; urgency=high
+
+  Bump archive upload urgency to high.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Sun, 24 Nov 2013 17:42:57 +0000
+dgit (0.18.1) unstable; urgency=low
+
+  Bugfixes:
+  * sshpsql archive query method passes LANG=C.  Closes:#729788.
+  * Subcommand program or argument options containing hyphens work.
+    (Eg, --dpkg-buildpackage:blah was previously incorrectly rejected.)
+
+  Packaging fixes:
+  * Depend on dput.
+  * Depend on curl | wget, as dget needs one of those.  (The dput package,
+    which contains dget, doesn't require them because dput itself works
+    without.)
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Sun, 24 Nov 2013 17:36:03 +0000
+
+dgit (0.18) unstable; urgency=low
+
+  Major new feature:
+  * Remote push (dgit rpush), a la debsign -r.  Closes:#721185.
+
+  Improved behaviours:
+  * dgit build, by default, uses the archive to find out what the correct
+    -v<version> option is to pass to dpkg-genchanges.  Closes:#727200.
+  * Abolish the sshdakls method and replace it with sshpsql: that is, ssh
+    (to coccia, by default) and run sql commands on the ftpmaster
+    database.  This is faster and has fewer bugs but is vulnerable to db
+    schema changes.  Closes:#726955.  Closes:#720170.  Closes:#720176.
+  * When generating git tags, quote the (uncanonicalised) changelog's
+    Distribution field as the suite.
+  * Command execution reports from --dry-run go to stderr.
+
+  Other new features:
+  * Support --gpg=... to provide a replacement command for gpg.
+  * Support --ssh=... and --ssh:... to affect how we run ssh.
+
+  Bugfixes:
+  * When using sbuild, pass the arguments to mergechanges in the right
+    order so that we use the correct Description (the _source one,
+    not the one from sbuild which didn't get e.g. -v<version>).
+  * push actually takes an optional suite, like it says in the synopsis.
+    Closes:#727125.
+  * Fix dgit --damp-run sbuild to actually work.
+  * Fix the "shellquote" internal subroutine.  The bugs in it ought not to
+    have caused any real trouble in previous versions of dgit.
+
+  Documentation and message fixes:
+  * manpage: Clarify comments about orig tarballs.  Closes: #723605.
+  * manpage: Remove comment in BUGS about lack of policy docs
+    for Dgit field, which is specified now.  Closes:#720201.
+  * manpage: Make discussion of --existing-package less scary.  The
+    default archive access method no longer needs it.  Closes:#720171.
+  * Mention "git merge", not "git-merge", in helpful message.
+    Closes:#725632.
+
+  Internal and debugging improvements:
+  * Report chdir actions in debugging output.
+  * Improvements to implementation of --dry-run and --damp-run.
+  * Some code motion and cleanups.
+
+  Note: changelog entries for the following versions, which were uploaded
+  to Debian experimental, have been collapsed into this single entry:
+    0.18~experimental2 0.18~experimental1
+    0.17~experimental7 0.17~experimental6 0.17~experimental5
+    0.17~experimental4 0.17~experimental3 0.17~experimental2
+    0.17~experimental1
+    0.16~experimental3 0.16~experimental2 0.16~experimental1
+  We do describe here all the changes since 0.17.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Sat, 09 Nov 2013 10:12:13 +0000
+
+dgit (0.17) unstable; urgency=high
+
+  * Do not grobble around in .git/refs/; instead, use git-show-ref.
+    This avoids breaking when git makes packed refs.  Closes:728893.
+  * Clarify error message for missing refs/remotes/dgit/dgit/<suite>.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Thu, 07 Nov 2013 00:02:47 +0000
+
+dgit (0.16) unstable; urgency=high
+
+  * Format `(3.0) quilt' fixup does not mind extraneous other files
+    in the build tree (e.g., build products and logs).  Closes: #727053.
+  * Set autoflush on stdout, to get better ordering of debugging
+    etc. output when stdout is redirected.
+  * New --damp-run mode, for more convenient and fuller testing etc.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Tue, 22 Oct 2013 13:06:54 +0100
+
+dgit (0.15) unstable; urgency=low
+
+  * Better handling of packages pushed using dgit and stuck in NEW.
+    (And, use of `--new' is not needed with fetch.)  Closes: #722199.
+  * More comprehensive warnings in many cases of archive skew.
+  * Implement `dgit help' as well as `--help'.  Closes: #721661.
+  * Provide `dgit version' and `--version'.  Closes: #721654.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Thu, 12 Sep 2013 00:14:05 +0100
+
+dgit (0.14) unstable; urgency=low
+
+  * Include package name in tag message.
+  * Create directory .git/dgit when needed during build.  Closes: #721428.
+  * Add Vcs-Git and Vcs-Browser [Richard Hartmann].  Closes: #721404.
+    These fields refer to the development branch, "master", on alioth,
+    not to the dgit suite refs (which are not accessible to git clone).
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Sun, 01 Sep 2013 18:30:44 +0100
+
+dgit (0.13) unstable; urgency=low
+
+  * Reuse already-downloaded .orig files after checking their hashes.
+    Closes: #720526.  (This introduces a dependency on Digest::SHA.)
+  * Do not always pointlessly fetch the .dsc twice.  (That code was
+    erroneously duplicated during editing, apparently.)
+  * Remove DGET_UNPACK from the environment in case the user has set it.
+  * Remove scary warning from Description.
+  * When uploading to Debian, tell dput to upload to "ftp-master".  This
+    avoids problems with derivatives whose dput has a different default.
+    Closes: #720958.
+  * Fix some bugs in dgit fetch --dry-run which made dgit push
+    --dry-run often not work at all.
+  * Update the local tracking branch for the dgit remote, when pushing.
+    Closes: #720956.
+  * Fix references in manpage to old Vcs-Dgit-Master field name.
+  * Reorganise manpage sections to be in a more conventional order.
+  * New manpage section on FILES IN THE SOURCE PACKAGE BUT NOT IN GIT.
+    Closes: #721186.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Thu, 29 Aug 2013 00:27:23 +0100
+
+dgit (0.12) unstable; urgency=low
+
+  * Cope with packages with epoch.  Closes: #720897.
+  * Improve error message for non-fast-forward push.  Closes: #720896.
+  * New --ignore-dirty option to skip noncritical check.  Closes: #720895.
+  * New --no-quilt-fixup option to suppress quilt fixup.  RTFM.
+  * Add Closes line for #720595 to changelog entry for 0.11.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Mon, 26 Aug 2013 16:50:39 +0100
+
+dgit (0.11) unstable; urgency=low
+
+  * Location of dgit-repos is now git.debian.org:/git/dgit-repos/repos.
+    Closes: #720525.  The rename on the server side will break older
+    versions of dgit.
+  * Fix bug which would make quilt patch fixup fail if git status
+    produced "M" lines.
+  * Autogenerated quilt patch fixup patch Description contains several
+    recent git commits, rather than implying that the patch corresponds
+    exactly to the top git commit.
+  * Use "ftp.debian.org" not "http.debian.net" as the default Debian
+    archive.  (http.debian.net tends to defeat certain kinds of cacheing,
+    and can also have more skew.)
+  * dgit build uses dpkg-buildpackage; there is a dgit git-build
+    for using git-buildpackage.  Closes: #720595.
+  * Better error message for use of UNRELEASED suite.  Closes: #720523.
+  * Do not canonicalise suite more than once.  Related to: #720526.
+  * Fix a badly open-coded copy of check_not_dirty.  Closes: #720524.
+  * Fix some bugs in building (eg build-source would fail to do the quilt
+    fixup; the --clean check in build was wrong).
+  * Add missing dependency on realpath.
+  * git-build (git-buildpackage wrapper) does not bother canonicalising
+    the suite if --git-ignore-branch is used.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Sun, 25 Aug 2013 17:00:43 +0100
+
+dgit (0.10) unstable; urgency=low
+
+  * Create .pc/applied-patches - do not empty it (!)
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Sun, 25 Aug 2013 00:51:50 +0100
+
+dgit (0.9) unstable; urgency=low
+
+  * New cleaning arrangements.
+  * More comprehensive workaround for `3.0 (quilt)'.
+  * In push, double-check the .changes against the changelog.
+  * Better error when source package contains .git.  Closes: #720555.
+  * Change our .dsc field name to `Dgit'.  Relevant to #720201.
+  * Fix bug handling our synthetic merges when we see them in
+    the remote suite branch.
+  * `3.0 (quilt)' fixup creates .pc/applied-patches since modern
+    dpkg-source creates it even though old ones didn't always.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Sat, 24 Aug 2013 18:49:02 +0100
+
+dgit (0.8) unstable; urgency=low
+
+  * Fix comparison of archive's .dsc's hash and git branch head
+    to DTRT.
+  * When creating repos in dgit-repos (using the ssh-cmd method),
+    copy _template rather than using mkdir and git init.
+    Closes: #720522.
+  * In push, do git fetch as well as archive fetch, or archive
+    fetch can fail.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Fri, 23 Aug 2013 12:24:09 +0100
+
+dgit (0.7) unstable; urgency=low
+
+  * If dak ls, or rmadison, reports multiple versions, look for them
+    all, and pick the newest .dsc that doesn't give 404.
+  * Manpage formatting fix.
+  * Name the local remote tracking branch remotes/dgit/dgit/<suite>
+    so that we avoid a warning from git about ambiguous branch names.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Thu, 22 Aug 2013 18:29:10 +0100
+
+dgit (0.6) unstable; urgency=low
+
+  * Allow fetching when archive has out-of-date git hash in .dsc.
+    Closes: #720490.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Thu, 22 Aug 2013 16:02:10 +0100
+
+dgit (0.5) unstable; urgency=low
+
+  * Upload to unstable, as this version mostly works.  (All the RC
+    bugs of which I'm aware are now properly represented in the BTS.)
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Thu, 22 Aug 2013 15:38:00 +0100
+
+dgit (0.4~pre2) experimental; urgency=low
+
+  * Mangle debian/<version> tags the way git-buildpackage does
+    (as of git-buildpackage 0.5.5, 3c6bbd0f4992f8da).
+  * Support dgit-distro.<distro>.keyid config option.
+  * Revert change to ssh to alioth CNAME, as the recommended CNAME
+    is to something with no write access to the fs and the new CNAME
+    has not yet been set up.  This reintroduces #720172 :-/.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Thu, 22 Aug 2013 15:31:17 +0100
+
+dgit (0.4~pre1) experimental; urgency=low
+
+  * Use dgit.debian.net vhost on alioth.  Closes:#720172.
+  * Usage message.  Closes:#720085.
+  * Provide "dgit sbuild".
+  * Assorted manpage fixes and improvements.
+  * Fail if a required config item is missing.
+  * Much better error messages.
+  * Better error checking when parsing RFC822-style control data.
+  * Better checking that the supplied .dsc and debian/changes correspond.
+  * Ordering improvement in push: don't add dsc field until git push done.
+  * New --distro option (helps with unknown suites).
+  * Bugfixes.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Thu, 22 Aug 2013 13:36:44 +0100
+
+dgit (0.3) experimental; urgency=low
+
+  * New version which appears to be able to sort of work at least
+    some of the time.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Sat, 17 Aug 2013 09:18:04 +0100
+
+dgit (0.2) experimental; urgency=low
+
+  * New version which might actually work but probably won't.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Fri, 16 Aug 2013 16:52:17 +0100
+
+dgit (0.1) experimental; urgency=low
+
+  * Initial experimental (partial) version.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Thu, 15 Aug 2013 12:09:01 +0100
+
diff --git a/debian/compat b/debian/compat
new file mode 100644 (file)
index 0000000..ec63514
--- /dev/null
@@ -0,0 +1 @@
+9
diff --git a/debian/control b/debian/control
new file mode 100644 (file)
index 0000000..9402fd2
--- /dev/null
@@ -0,0 +1,39 @@
+Source: dgit
+Section: devel
+Priority: optional
+Maintainer: Ian Jackson <ijackson@chiark.greenend.org.uk>
+Standards-Version: 3.9.4.0
+Build-Depends: debhelper (>= 9)
+Testsuite: autopkgtest
+Vcs-Git: git://git.chiark.greenend.org.uk/~ianmdlvl/dgit.git
+Vcs-Browser: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git/dgit.git/
+
+Package: dgit
+Depends: perl, libwww-perl, libdpkg-perl, git-core, devscripts, dpkg-dev,
+         ${misc:Depends}, git-buildpackage, liblist-moreutils-perl,
+         coreutils (>= 8.23-1~) | realpath,
+         libdigest-sha-perl, dput, curl, apt,
+         libjson-perl, ca-certificates,
+         libtext-iconv-perl, libtext-glob-perl
+Recommends: ssh-client, libtext-iconv-perl
+Suggests: sbuild
+Architecture: all
+Description: git interoperability with the Debian archive
+ dgit (with the associated infrastructure) makes it possible to
+ treat the Debian archive as a git repository.
+ .
+ dgit push constructs uploads from git commits
+ .
+ dgit clone and dgit fetch construct git commits from uploads.
+
+Package: dgit-infrastructure
+Depends: ${misc:Depends}, perl, git-core, gpgv, chiark-utils-bin,
+         libjson-perl, libdigest-sha-perl, libdbd-sqlite3-perl, sqlite3,
+         libwww-perl, libdpkg-perl
+Recommends: dgit
+Architecture: all
+Priority: extra
+Description: dgit server backend infrastructure
+ This package contains tools which are useful for setting up a dgit
+ git repository server.  You probably want dgit, the client package,
+ instead of dgit-infrastructure.
diff --git a/debian/copyright b/debian/copyright
new file mode 100644 (file)
index 0000000..a5b8245
--- /dev/null
@@ -0,0 +1,72 @@
+dgit
+Integration between git and Debian-style archives
+
+Copyright (C)2013-2016 Ian Jackson
+Copyright (C)2016 Sean Whitton
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+A copy of the GNU General Public License v3 can be found in
+/usr/share/common-licenses/GPL-3.
+
+
+The tests/ directory contains a complete copy of the source code for
+the pari-extra 3-1 package.  This is a dummy package containing only
+Debian metadata, by Bill Alombert, with a licence statement saying
+it's GPL (implicitly GPLv3 compatible).
+
+
+Contributions are accepted upstram under the same terms; please sign
+off your patches (by writing an approprite Signed-Off-By tag in your
+commit message or patch submission) to indicate your attestation that
+the Developer Certificate of Origin (version 1.1) applies.
+
+
+-8<-
+
+Developer Certificate of Origin
+Version 1.1
+
+Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
+1 Letterman Drive
+Suite D4700
+San Francisco, CA, 94129
+
+Everyone is permitted to copy and distribute verbatim copies of this
+license document, but changing it is not allowed.
+
+
+Developer's Certificate of Origin 1.1
+
+By making a contribution to this project, I certify that:
+
+(a) The contribution was created in whole or in part by me and I
+    have the right to submit it under the open source license
+    indicated in the file; or
+
+(b) The contribution is based upon previous work that, to the best
+    of my knowledge, is covered under an appropriate open source
+    license and I have the right under that license to submit that
+    work with modifications, whether created in whole or in part
+    by me, under the same open source license (unless I am
+    permitted to submit under a different license), as indicated
+    in the file; or
+
+(c) The contribution was provided directly to me by some other
+    person who certified (a), (b) or (c) and I have not modified
+    it.
+
+(d) I understand and agree that this project and the contribution
+    are public and that a record of the contribution (including all
+    personal information I submit with it, including my sign-off) is
+    maintained indefinitely and may be redistributed consistent with
+    this project or the open source license(s) involved.
+
diff --git a/debian/rules b/debian/rules
new file mode 100755 (executable)
index 0000000..9249f88
--- /dev/null
@@ -0,0 +1,59 @@
+#!/usr/bin/make -f
+
+# dgit
+# Integration between git and Debian-style archives
+#
+# Copyright (C)2013-2016 Ian Jackson
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+export prefix=/usr
+
+%:
+       dh $@
+
+override_dh_gencontrol:
+       dh_gencontrol
+       set -e; \
+        cd debian/dgit; \
+        v=$$(perl -ne 'print if s/^version:\s+//i' DEBIAN/control); \
+        perl -i -pe "s/UNRELEASED/$$v/g if m/###substituted###/" usr/bin/dgit
+
+globalperl=/usr/share/perl5
+infraperl=/usr/share/dgit/infra/perl5
+
+override_dh_auto_install:
+       make install prefix=/usr DESTDIR=debian/dgit
+       make install-infra prefix=/usr DESTDIR=debian/dgit-infrastructure \
+               perldir=$(infraperl)
+#      # Most of the Perl modules in dgit-infrastructure live in
+#      # $(infraperl).  The exception is Debian::Dgit::Infra, which
+#      # lives in $(globalperl) and adds $(infraperl) to @INC.
+       set -ex; \
+        base=debian/dgit-infrastructure; \
+        mod=Debian/Dgit/Infra.pm; \
+        src=$${base}$(infraperl)/$${mod}; \
+        dst=$${base}$(globalperl)/$${mod}; \
+        mkdir -p $${dst%/*}; \
+        mv -f $$src $$dst; \
+        perl -i -p -e 'next unless m/###substituted###/;' \
+               -e 'next unless s/^# (?=unshift \@INC,)//;' \
+               -e 'die unless s{q\{\S+\}}{q{$(infraperl)}};' \
+                $$dst
+
+debian/tests/control: tests/enumerate-tests debian/tests/control.in
+       $< gencontrol >$@.new && mv -f $@.new $@
+
+debian/tests/control: tests/lib-core tests/lib-restricts
+debian/tests/control: tests/tests $(wildcard tests/tests/*[^~#])
diff --git a/debian/tests/control b/debian/tests/control
new file mode 100644 (file)
index 0000000..7bd2a13
--- /dev/null
@@ -0,0 +1,31 @@
+Tests: build-modes-gbp
+Tests-Directory: tests/tests
+Depends: dgit, dgit-infrastructure, devscripts, debhelper (>=8), fakeroot, build-essential, git-buildpackage
+
+Tests: clone-reprepro
+Tests-Directory: tests/tests
+Depends: dgit, dgit-infrastructure, devscripts, debhelper (>=8), fakeroot, build-essential, reprepro
+
+Tests: dsd-clone-drs
+Tests-Directory: tests/tests
+Depends: dgit, dgit-infrastructure, devscripts, debhelper (>=8), fakeroot, build-essential
+Restrictions: x-dgit-intree-only x-dgit-git-only
+
+Tests: mirror mirror-debnewgit mirror-private
+Tests-Directory: tests/tests
+Depends: dgit, dgit-infrastructure, devscripts, debhelper (>=8), fakeroot, build-essential, 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
+Restrictions: x-dgit-schroot-build
+
+Tests: spelling
+Tests-Directory: tests/tests
+Depends: dgit, dgit-infrastructure, devscripts, debhelper (>=8), fakeroot, build-essential
+Restrictions: x-dgit-git-only
+
+Tests: absurd-gitapply build-modes build-modes-asplit build-modes-gbp-asplit clone-clogsigpipe clone-gitnosuite clone-nogit debpolicy-dbretry debpolicy-newreject debpolicy-quilt-gbp 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-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 push-buildproductsdir push-newpackage push-nextdgit quilt quilt-gbp quilt-gbp-build-modes quilt-singlepatch quilt-splitbrains 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
+
diff --git a/debian/tests/control.in b/debian/tests/control.in
new file mode 100644 (file)
index 0000000..c032ba8
--- /dev/null
@@ -0,0 +1,2 @@
+Tests-Directory: tests/tests
+Depends: dgit, dgit-infrastructure, devscripts, debhelper (>=8), fakeroot, build-essential
diff --git a/dgit b/dgit
new file mode 100755 (executable)
index 0000000..9d3584f
--- /dev/null
+++ b/dgit
@@ -0,0 +1,6339 @@
+#!/usr/bin/perl -w
+# dgit
+# Integration between git and Debian-style archives
+#
+# Copyright (C)2013-2016 Ian Jackson
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+use strict;
+
+use Debian::Dgit;
+setup_sigwarn();
+
+use IO::Handle;
+use Data::Dumper;
+use LWP::UserAgent;
+use Dpkg::Control::Hash;
+use File::Path;
+use File::Temp qw(tempdir);
+use File::Basename;
+use Dpkg::Version;
+use POSIX;
+use IPC::Open2;
+use Digest::SHA;
+use Digest::MD5;
+use List::Util qw(any);
+use List::MoreUtils qw(pairwise);
+use Text::Glob qw(match_glob);
+use Fcntl qw(:DEFAULT :flock);
+use Carp;
+
+use Debian::Dgit;
+
+our $our_version = 'UNRELEASED'; ###substituted###
+our $absurdity = undef; ###substituted###
+
+our @rpushprotovsn_support = qw(4 3 2); # 4 is new tag format
+our $protovsn;
+
+our $isuite = 'unstable';
+our $idistro;
+our $package;
+our @ropts;
+
+our $sign = 1;
+our $dryrun_level = 0;
+our $changesfile;
+our $buildproductsdir = '..';
+our $new_package = 0;
+our $ignoredirty = 0;
+our $rmonerror = 1;
+our @deliberatelies;
+our %previously;
+our $existing_package = 'dpkg';
+our $cleanmode;
+our $changes_since_version;
+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 $dodep14tag;
+our $dodep14tag_re = 'want|no|always';
+our $split_brain_save;
+our $we_are_responder;
+our $initiator_tempdir;
+our $patches_applied_dirtily = 00;
+our $tagformat_want;
+our $tagformat;
+our $tagformatfn;
+
+our %forceopts = map { $_=>0 }
+    qw(unrepresentable unsupported-source-format
+       dsc-changes-mismatch changes-origs-exactly
+       import-gitapply-absurd
+       import-gitapply-no-absurd
+       import-dsc-with-dgit-field);
+
+our %format_ok = map { $_=>1 } ("1.0","3.0 (native)","3.0 (quilt)");
+
+our $suite_re = '[-+.0-9a-z]+';
+our $cleanmode_re = 'dpkg-source(?:-d)?|git|git-ff|check|none';
+our $orig_f_comp_re = 'orig(?:-[-0-9a-z]+)?';
+our $orig_f_sig_re = '\\.(?:asc|gpg|pgp)';
+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) = qw(git);
+our (@dget) = qw(dget);
+our (@curl) = qw(curl);
+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 (@mergechanges) = qw(mergechanges -f);
+our (@gbp_build) = ('');
+our (@gbp_pq) = ('gbp pq');
+our (@changesopts) = ('');
+
+our %opts_opt_map = ('dget' => \@dget, # accept for compatibility
+                    'curl' => \@curl,
+                    'dput' => \@dput,
+                    'debsign' => \@debsign,
+                     'gpg' => \@gpg,
+                     'sbuild' => \@sbuild,
+                     'ssh' => \@ssh,
+                     'dgit' => \@dgit,
+                     'git' => \@git,
+                     'apt-get' => \@aptget,
+                     'apt-cache' => \@aptcache,
+                     'dpkg-source' => \@dpkgsource,
+                     'dpkg-buildpackage' => \@dpkgbuildpackage,
+                     'dpkg-genchanges' => \@dpkggenchanges,
+                     'gbp-build' => \@gbp_build,
+                     'gbp-pq' => \@gbp_pq,
+                     'ch' => \@changesopts,
+                     'mergechanges' => \@mergechanges);
+
+our %opts_opt_cmdonly = ('gpg' => 1, 'git' => 1);
+our %opts_cfg_insertpos = map {
+    $_,
+    scalar @{ $opts_opt_map{$_} }
+} keys %opts_opt_map;
+
+sub finalise_opts_opts();
+
+our $keyid;
+
+autoflush STDOUT 1;
+
+our $supplementary_message = '';
+our $need_split_build_invocation = 0;
+our $split_brain = 0;
+
+END {
+    local ($@, $?);
+    print STDERR "! $_\n" foreach $supplementary_message =~ m/^.+$/mg;
+}
+
+our $remotename = 'dgit';
+our @ourdscfield = qw(Dgit Vcs-Dgit-Master);
+our $csuite;
+our $instead_distro;
+
+if (!defined $absurdity) {
+    $absurdity = $0;
+    $absurdity =~ s{/[^/]+$}{/absurd} or die;
+}
+
+sub debiantag ($$) {
+    my ($v,$distro) = @_;
+    return $tagformatfn->($v, $distro);
+}
+
+sub debiantag_maintview ($$) { 
+    my ($v,$distro) = @_;
+    return "$distro/".dep14_version_mangle $v;
+}
+
+sub madformat ($) { $_[0] eq '3.0 (quilt)' }
+
+sub lbranch () { return "$branchprefix/$csuite"; }
+my $lbranch_re = '^refs/heads/'.$branchprefix.'/([^/.]+)$';
+sub lref () { return "refs/heads/".lbranch(); }
+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+\://;
+    return $vsn;
+}
+
+sub srcfn ($$) {
+    my ($vsn,$sfx) = @_;
+    return "${package}_".(stripepoch $vsn).$sfx
+}
+
+sub dscfn ($) {
+    my ($vsn) = @_;
+    return srcfn($vsn,".dsc");
+}
+
+sub changespat ($;$) {
+    my ($vsn, $arch) = @_;
+    return "${package}_".(stripepoch $vsn)."_".($arch//'*').".changes";
+}
+
+sub upstreamversion ($) {
+    my ($vsn) = @_;
+    $vsn =~ s/-[^-]+$//;
+    return $vsn;
+}
+
+our $us = 'dgit';
+initdebug('');
+
+our @end;
+END { 
+    local ($?);
+    foreach my $f (@end) {
+       eval { $f->(); };
+       print STDERR "$us: cleanup: $@" if length $@;
+    }
+};
+
+sub badcfg { print STDERR "$us: invalid configuration: @_\n"; exit 12; }
+
+sub forceable_fail ($$) {
+    my ($forceoptsl, $msg) = @_;
+    fail $msg unless grep { $forceopts{$_} } @$forceoptsl;
+    print STDERR "warning: overriding problem due to --force:\n". $msg;
+}
+
+sub forceing ($) {
+    my ($forceoptsl) = @_;
+    my @got = grep { $forceopts{$_} } @$forceoptsl;
+    return 0 unless @got;
+    print STDERR
+ "warning: skipping checks or functionality due to --force-$got[0]\n";
+}
+
+sub no_such_package () {
+    print STDERR "$us: package $package does not exist in suite $isuite\n";
+    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_not_fast_forward () {
+    foreach (qw(not-fast-forward fresh-repo)) {
+       return 1 if deliberately($_) || deliberately("TEST-dgit-only-$_");
+    }
+}
+
+sub quiltmode_splitbrain () {
+    $quilt_mode =~ m/gbp|dpm|unapplied/;
+}
+
+sub opts_opt_multi_cmd {
+    my @cmd;
+    push @cmd, split /\s+/, shift @_;
+    push @cmd, @_;
+    @cmd;
+}
+
+sub gbp_pq {
+    return opts_opt_multi_cmd @gbp_pq;
+}
+
+#---------- remote protocol support, common ----------
+
+# remote push initiator/responder protocol:
+#  $ dgit remote-push-build-host <n-rargs> <rargs>... <push-args>...
+#  where <rargs> is <push-host-dir> <supported-proto-vsn>,... ...
+#  < dgit-remote-push-ready <actual-proto-vsn>
+#
+# occasionally:
+#
+#  > progress NBYTES
+#  [NBYTES message]
+#
+#  > supplementary-message NBYTES          # $protovsn >= 3
+#  [NBYTES message]
+#
+# main sequence:
+#
+#  > file parsed-changelog
+#  [indicates that output of dpkg-parsechangelog follows]
+#  > data-block NBYTES
+#  > [NBYTES bytes of data (no newline)]
+#  [maybe some more blocks]
+#  > data-end
+#
+#  > file dsc
+#  [etc]
+#
+#  > file changes
+#  [etc]
+#
+#  > param head DGIT-VIEW-HEAD
+#  > param csuite SUITE
+#  > param tagformat old|new
+#  > param maint-view MAINT-VIEW-HEAD
+#
+#  > previously REFNAME=OBJNAME       # if --deliberately-not-fast-forward
+#                                     # goes into tag, for replay prevention
+#
+#  > want signed-tag
+#  [indicates that signed tag is wanted]
+#  < data-block NBYTES
+#  < [NBYTES bytes of data (no newline)]
+#  [maybe some more blocks]
+#  < data-end
+#  < files-end
+#
+#  > want signed-dsc-changes
+#  < data-block NBYTES    [transfer of signed dsc]
+#  [etc]
+#  < data-block NBYTES    [transfer of signed changes]
+#  [etc]
+#  < files-end
+#
+#  > complete
+
+our $i_child_pid;
+
+sub i_child_report () {
+    # Sees if our child has died, and reap it if so.  Returns a string
+    # describing how it died if it failed, or undef otherwise.
+    return undef unless $i_child_pid;
+    my $got = waitpid $i_child_pid, WNOHANG;
+    return undef if $got <= 0;
+    die unless $got == $i_child_pid;
+    $i_child_pid = undef;
+    return undef unless $?;
+    return "build host child ".waitstatusmsg();
+}
+
+sub badproto ($$) {
+    my ($fh, $m) = @_;
+    fail "connection lost: $!" if $fh->error;
+    fail "protocol violation; $m not expected";
+}
+
+sub badproto_badread ($$) {
+    my ($fh, $wh) = @_;
+    fail "connection lost: $!" if $!;
+    my $report = i_child_report();
+    fail $report if defined $report;
+    badproto $fh, "eof (reading $wh)";
+}
+
+sub protocol_expect (&$) {
+    my ($match, $fh) = @_;
+    local $_;
+    $_ = <$fh>;
+    defined && chomp or badproto_badread $fh, "protocol message";
+    if (wantarray) {
+       my @r = &$match;
+       return @r if @r;
+    } else {
+       my $r = &$match;
+       return $r if $r;
+    }
+    badproto $fh, "\`$_'";
+}
+
+sub protocol_send_file ($$) {
+    my ($fh, $ourfn) = @_;
+    open PF, "<", $ourfn or die "$ourfn: $!";
+    for (;;) {
+       my $d;
+       my $got = read PF, $d, 65536;
+       die "$ourfn: $!" unless defined $got;
+       last if !$got;
+       print $fh "data-block ".length($d)."\n" or die $!;
+       print $fh $d or die $!;
+    }
+    PF->error and die "$ourfn $!";
+    print $fh "data-end\n" or die $!;
+    close PF;
+}
+
+sub protocol_read_bytes ($$) {
+    my ($fh, $nbytes) = @_;
+    $nbytes =~ m/^[1-9]\d{0,5}$|^0$/ or badproto \*RO, "bad byte count";
+    my $d;
+    my $got = read $fh, $d, $nbytes;
+    $got==$nbytes or badproto_badread $fh, "data block";
+    return $d;
+}
+
+sub protocol_receive_file ($$) {
+    my ($fh, $ourfn) = @_;
+    printdebug "() $ourfn\n";
+    open PF, ">", $ourfn or die "$ourfn: $!";
+    for (;;) {
+       my ($y,$l) = protocol_expect {
+           m/^data-block (.*)$/ ? (1,$1) :
+           m/^data-end$/ ? (0,) :
+           ();
+       } $fh;
+       last unless $y;
+       my $d = protocol_read_bytes $fh, $l;
+       print PF $d or die $!;
+    }
+    close PF or die $!;
+}
+
+#---------- remote protocol support, responder ----------
+
+sub responder_send_command ($) {
+    my ($command) = @_;
+    return unless $we_are_responder;
+    # called even without $we_are_responder
+    printdebug ">> $command\n";
+    print PO $command, "\n" or die $!;
+}    
+
+sub responder_send_file ($$) {
+    my ($keyword, $ourfn) = @_;
+    return unless $we_are_responder;
+    printdebug "]] $keyword $ourfn\n";
+    responder_send_command "file $keyword";
+    protocol_send_file \*PO, $ourfn;
+}
+
+sub responder_receive_files ($@) {
+    my ($keyword, @ourfns) = @_;
+    die unless $we_are_responder;
+    printdebug "[[ $keyword @ourfns\n";
+    responder_send_command "want $keyword";
+    foreach my $fn (@ourfns) {
+       protocol_receive_file \*PI, $fn;
+    }
+    printdebug "[[\$\n";
+    protocol_expect { m/^files-end$/ } \*PI;
+}
+
+#---------- remote protocol support, initiator ----------
+
+sub initiator_expect (&) {
+    my ($match) = @_;
+    protocol_expect { &$match } \*RO;
+}
+
+#---------- end remote code ----------
+
+sub progress {
+    if ($we_are_responder) {
+       my $m = join '', @_;
+       responder_send_command "progress ".length($m) or die $!;
+       print PO $m or die $!;
+    } else {
+       print @_, "\n";
+    }
+}
+
+our $ua;
+
+sub url_get {
+    if (!$ua) {
+       $ua = LWP::UserAgent->new();
+       $ua->env_proxy;
+    }
+    my $what = $_[$#_];
+    progress "downloading $what...";
+    my $r = $ua->get(@_) or die $!;
+    return undef if $r->code == 404;
+    $r->is_success or fail "failed to fetch $what: ".$r->status_line;
+    return $r->decoded_content(charset => 'none');
+}
+
+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) {
+       progress "$us ok: @_";
+    } else {
+       progress "would be ok: @_ (but dry run only)";
+    }
+}
+
+sub dryrun_report {
+    printcmd(\*STDERR,$debugprefix."#",@_);
+}
+
+sub runcmd_ordryrun {
+    if (act_scary()) {
+       runcmd @_;
+    } else {
+       dryrun_report @_;
+    }
+}
+
+sub runcmd_ordryrun_local {
+    if (act_local()) {
+       runcmd @_;
+    } else {
+       dryrun_report @_;
+    }
+}
+
+sub shell_cmd {
+    my ($first_shell, @cmd) = @_;
+    return qw(sh -ec), $first_shell.'; exec "$@"', 'x', @cmd;
+}
+
+our $helpmsg = <<END;
+main usages:
+  dgit [dgit-opts] clone [dgit-opts] package [suite] [./dir|/dir]
+  dgit [dgit-opts] fetch|pull [dgit-opts] [suite]
+  dgit [dgit-opts] build [dpkg-buildpackage-opts]
+  dgit [dgit-opts] sbuild [sbuild-opts]
+  dgit [dgit-opts] push [dgit-opts] [suite]
+  dgit [dgit-opts] rpush build-host:build-dir ...
+important dgit options:
+  -k<keyid>           sign tag and package with <keyid> instead of default
+  --dry-run -n        do not change anything, but go through the motions
+  --damp-run -L       like --dry-run but make local changes, without signing
+  --new -N            allow introducing a new package
+  --debug -D          increase debug level
+  -c<name>=<value>    set git config option (used directly by dgit too)
+END
+
+our $later_warning_msg = <<END;
+Perhaps the upload is stuck in incoming.  Using the version from git.
+END
+
+sub badusage {
+    print STDERR "$us: @_\n", $helpmsg or die $!;
+    exit 8;
+}
+
+sub nextarg {
+    @ARGV or badusage "too few arguments";
+    return scalar shift @ARGV;
+}
+
+sub cmd_help () {
+    print $helpmsg or die $!;
+    exit 0;
+}
+
+our $td = $ENV{DGIT_TEST_DUMMY_DIR} || "DGIT_TEST_DUMMY_DIR-unset";
+
+our %defcfg = ('dgit.default.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.aptget-components' => 'main',
+              'dgit.default.dgit-tag-format' => 'new,old,maint',
+              # 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"
+              # hist means "repo server may have old pushes without new tag"
+              #   ("hist" is implied by "old")
+              'dgit-distro.debian.archive-query' => 'ftpmasterapi:',
+              'dgit-distro.debian.git-check' => 'url',
+              'dgit-distro.debian.git-check-suffix' => '/info/refs',
+              'dgit-distro.debian.new-private-pushers' => 't',
+              'dgit-distro.debian/push.git-url' => '',
+              'dgit-distro.debian/push.git-host' => 'push.dgit.debian.org',
+              'dgit-distro.debian/push.git-user-force' => 'dgit',
+              'dgit-distro.debian/push.git-proto' => 'git+ssh://',
+              'dgit-distro.debian/push.git-path' => '/dgit/debian/repos',
+              'dgit-distro.debian/push.git-create' => 'true',
+              'dgit-distro.debian/push.git-check' => 'ssh-cmd',
+ 'dgit-distro.debian.archive-query-url', 'https://api.ftp-master.debian.org/',
+# 'dgit-distro.debian.archive-query-tls-key',
+#    '/etc/ssl/certs/%HOST%.pem:/etc/dgit/%HOST%.pem',
+# ^ this does not work because curl is broken nowadays
+# Fixing #790093 properly will involve providing providing the key
+# in some pacagke and maybe updating these paths.
+#
+# 'dgit-distro.debian.archive-query-tls-curl-args',
+#   '--ca-path=/etc/ssl/ca-debian',
+# ^ this is a workaround but works (only) on DSA-administered machines
+              'dgit-distro.debian.git-url' => 'https://git.dgit.debian.org',
+              '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.ubuntu.mirror' => 'http://archive.ubuntu.com/ubuntu',
+              'dgit-distro.test-dummy.ssh' => "$td/ssh",
+              'dgit-distro.test-dummy.username' => "alice",
+              'dgit-distro.test-dummy.git-check' => "ssh-cmd",
+              'dgit-distro.test-dummy.git-create' => "ssh-cmd",
+              'dgit-distro.test-dummy.git-url' => "$td/git",
+              'dgit-distro.test-dummy.git-host' => "git",
+              'dgit-distro.test-dummy.git-path' => "$td/git",
+              'dgit-distro.test-dummy.archive-query' => "dummycatapi:",
+              'dgit-distro.test-dummy.archive-query-url' => "file://$td/aq/",
+              'dgit-distro.test-dummy.mirror' => "file://$td/mirror/",
+              'dgit-distro.test-dummy.upload-host' => 'test-dummy',
+               );
+
+our %gitcfgs;
+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
+       
+       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;
+    }
+}
+
+sub git_get_config ($) {
+    my ($c) = @_;
+    foreach my $src (@gitcfgsources) {
+       my $l = $gitcfgs{$src}{$c};
+       printdebug"C $c ".(defined $l ? messagequote "'$l'" : "undef")."\n"
+           if $debuglevel >= 4;
+       $l or next;
+       @$l==1 or badcfg "multiple values for $c".
+           " (in $src git config)" if @$l > 1;
+       return $l->[0];
+    }
+    return undef;
+}
+
+sub cfg {
+    foreach my $c (@_) {
+       return undef if $c =~ /RETURN-UNDEF/;
+       my $v = git_get_config($c);
+       return $v if defined $v;
+       my $dv = $defcfg{$c};
+       return $dv if defined $dv;
+    }
+    badcfg "need value for one of: @_\n".
+       "$us: distro or suite appears not to be (properly) supported";
+}
+
+sub access_basedistro () {
+    if (defined $idistro) {
+       return $idistro;
+    } else {   
+       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_nomdistro () {
+    my $base = access_basedistro();
+    return cfg("dgit-distro.$base.nominal-distro",'RETURN-UNDEF') // $base;
+}
+
+sub access_quirk () {
+    # returns (quirk name, distro to use instead or undef, quirk-specific info)
+    my $basedistro = access_basedistro();
+    my $backports_quirk = cfg("dgit-distro.$basedistro.backports-quirk",
+                             'RETURN-UNDEF');
+    if (defined $backports_quirk) {
+       my $re = $backports_quirk;
+       $re =~ s/[^-0-9a-z_\%*()]/\\$&/ig;
+       $re =~ s/\*/.*/g;
+       $re =~ s/\%/([-0-9a-z_]+)/
+           or $re =~ m/[()]/ or badcfg "backports-quirk needs \% or ( )";
+       if ($isuite =~ m/^$re$/) {
+           return ('backports',"$basedistro-backports",$1);
+       }
+    }
+    return ('none',undef);
+}
+
+our $access_forpush;
+
+sub parse_cfg_bool ($$$) {
+    my ($what,$def,$v) = @_;
+    $v //= $def;
+    return
+       $v =~ m/^[ty1]/ ? 1 :
+       $v =~ m/^[fn0]/ ? 0 :
+       badcfg "$what needs t (true, y, 1) or f (false, n, 0) not \`$v'";
+}      
+
+sub access_forpush_config () {
+    my $d = access_basedistro();
+
+    return 1 if
+       $new_package &&
+       parse_cfg_bool('new-private-pushers', 0,
+                      cfg("dgit-distro.$d.new-private-pushers",
+                          'RETURN-UNDEF'));
+
+    my $v = cfg("dgit-distro.$d.readonly", 'RETURN-UNDEF');
+    $v //= 'a';
+    return
+       $v =~ m/^[ty1]/ ? 0 : # force readonly,    forpush = 0
+       $v =~ m/^[fn0]/ ? 1 : # force nonreadonly, forpush = 1
+       $v =~ m/^[a]/  ? '' : # auto,              forpush = ''
+       badcfg "readonly needs t (true, y, 1) or f (false, n, 0) or a (auto)";
+}
+
+sub access_forpush () {
+    $access_forpush //= access_forpush_config();
+    return $access_forpush;
+}
+
+sub pushing () {
+    die "$access_forpush ?" if ($access_forpush // 1) ne 1;
+    badcfg "pushing but distro is configured readonly"
+       if access_forpush_config() eq '0';
+    $access_forpush = 1;
+    $supplementary_message = <<'END' unless $we_are_responder;
+Push failed, before we got started.
+You can retry the push, after fixing the problem, if you like.
+END
+    finalise_opts_opts();
+}
+
+sub notpushing () {
+    finalise_opts_opts();
+}
+
+sub supplementary_message ($) {
+    my ($msg) = @_;
+    if (!$we_are_responder) {
+       $supplementary_message = $msg;
+       return;
+    } elsif ($protovsn >= 3) {
+       responder_send_command "supplementary-message ".length($msg)
+           or die $!;
+       print PO $msg or die $!;
+    }
+}
+
+sub access_distros () {
+    # Returns list of distros to try, in order
+    #
+    # We want to try:
+    #    0. `instead of' distro name(s) we have been pointed to
+    #    1. the access_quirk distro, if any
+    #    2a. the user's specified distro, or failing that  } basedistro
+    #    2b. the distro calculated from the suite          }
+    my @l = access_basedistro();
+
+    my (undef,$quirkdistro) = access_quirk();
+    unshift @l, $quirkdistro;
+    unshift @l, $instead_distro;
+    @l = grep { defined } @l;
+
+    push @l, access_nomdistro();
+
+    if (access_forpush()) {
+       @l = map { ("$_/push", $_) } @l;
+    }
+    @l;
+}
+
+sub access_cfg_cfgs (@) {
+    my (@keys) = @_;
+    my @cfgs;
+    # The nesting of these loops determines the search order.  We put
+    # the key loop on the outside so that we search all the distros
+    # for each key, before going on to the next key.  That means that
+    # if access_cfg is called with a more specific, and then a less
+    # specific, key, an earlier distro can override the less specific
+    # without necessarily overriding any more specific keys.  (If the
+    # distro wants to override the more specific keys it can simply do
+    # so; whereas if we did the loop the other way around, it would be
+    # impossible to for an earlier distro to override a less specific
+    # key but not the more specific ones without restating the unknown
+    # values of the more specific keys.
+    my @realkeys;
+    my @rundef;
+    # We have to deal with RETURN-UNDEF specially, so that we don't
+    # terminate the search prematurely.
+    foreach (@keys) {
+       if (m/RETURN-UNDEF/) { push @rundef, $_; last; }
+       push @realkeys, $_
+    }
+    foreach my $d (access_distros()) {
+       push @cfgs, map { "dgit-distro.$d.$_" } @realkeys;
+    }
+    push @cfgs, map { "dgit.default.$_" } @realkeys;
+    push @cfgs, @rundef;
+    return @cfgs;
+}
+
+sub access_cfg (@) {
+    my (@keys) = @_;
+    my (@cfgs) = access_cfg_cfgs(@keys);
+    my $value = cfg(@cfgs);
+    return $value;
+}
+
+sub access_cfg_bool ($$) {
+    my ($def, @keys) = @_;
+    parse_cfg_bool($keys[0], $def, access_cfg(@keys, 'RETURN-UNDEF'));
+}
+
+sub string_to_ssh ($) {
+    my ($spec) = @_;
+    if ($spec =~ m/\s/) {
+       return qw(sh -ec), 'exec '.$spec.' "$@"', 'x';
+    } else {
+       return ($spec);
+    }
+}
+
+sub access_cfg_ssh () {
+    my $gitssh = access_cfg('ssh', 'RETURN-UNDEF');
+    if (!defined $gitssh) {
+       return @ssh;
+    } else {
+       return string_to_ssh $gitssh;
+    }
+}
+
+sub access_runeinfo ($) {
+    my ($info) = @_;
+    return ": dgit ".access_basedistro()." $info ;";
+}
+
+sub access_someuserhost ($) {
+    my ($some) = @_;
+    my $user = access_cfg("$some-user-force", 'RETURN-UNDEF');
+    defined($user) && length($user) or
+       $user = access_cfg("$some-user",'username');
+    my $host = access_cfg("$some-host");
+    return length($user) ? "$user\@$host" : $host;
+}
+
+sub access_gituserhost () {
+    return access_someuserhost('git');
+}
+
+sub access_giturl (;$) {
+    my ($optional) = @_;
+    my $url = access_cfg('git-url','RETURN-UNDEF');
+    my $suffix;
+    if (!length $url) {
+       my $proto = access_cfg('git-proto', 'RETURN-UNDEF');
+       return undef unless defined $proto;
+       $url =
+           $proto.
+           access_gituserhost().
+           access_cfg('git-path');
+    } else {
+       $suffix = access_cfg('git-url-suffix','RETURN-UNDEF');
+    }
+    $suffix //= '.git';
+    return "$url/$package$suffix";
+}             
+
+sub parsecontrolfh ($$;$) {
+    my ($fh, $desc, $allowsigned) = @_;
+    our $dpkgcontrolhash_noissigned;
+    my $c;
+    for (;;) {
+       my %opts = ('name' => $desc);
+       $opts{allow_pgp}= $allowsigned || !$dpkgcontrolhash_noissigned;
+       $c = Dpkg::Control::Hash->new(%opts);
+       $c->parse($fh,$desc) or die "parsing of $desc failed";
+       last if $allowsigned;
+       last if $dpkgcontrolhash_noissigned;
+       my $issigned= $c->get_option('is_pgp_signed');
+       if (!defined $issigned) {
+           $dpkgcontrolhash_noissigned= 1;
+           seek $fh, 0,0 or die "seek $desc: $!";
+       } elsif ($issigned) {
+           fail "control file $desc is (already) PGP-signed. ".
+               " Note that dgit push needs to modify the .dsc and then".
+               " do the signature itself";
+       } else {
+           last;
+       }
+    }
+    return $c;
+}
+
+sub parsecontrol {
+    my ($file, $desc, $allowsigned) = @_;
+    my $fh = new IO::Handle;
+    open $fh, '<', $file or die "$file: $!";
+    my $c = parsecontrolfh($fh,$desc,$allowsigned);
+    $fh->error and die $!;
+    close $fh;
+    return $c;
+}
+
+sub getfield ($$) {
+    my ($dctrl,$field) = @_;
+    my $v = $dctrl->{$field};
+    return $v if defined $v;
+    fail "missing field $field in ".$dctrl->get_option('name');
+}
+
+sub parsechangelog {
+    my $c = Dpkg::Control::Hash->new(name => 'parsed changelog');
+    my $p = new IO::Handle;
+    my @cmd = (qw(dpkg-parsechangelog), @_);
+    open $p, '-|', @cmd or die $!;
+    $c->parse($p);
+    $?=0; $!=0; close $p or failedcmd @cmd;
+    return $c;
+}
+
+sub commit_getclogp ($) {
+    # Returns the parsed changelog hashref for a particular commit
+    my ($objid) = @_;
+    our %commit_getclogp_memo;
+    my $memo = $commit_getclogp_memo{$objid};
+    return $memo if $memo;
+    mkpath '.git/dgit';
+    my $mclog = ".git/dgit/clog-$objid";
+    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;
+    $dsc = parsecontrolfh($dscfh,$dscurl,1);
+    printdebug Dumper($dsc) if $debuglevel>1;
+}
+
+our %rmad;
+
+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 $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 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 ($) {
+    my ($subpath) = @_;
+    my @cmd = (@curl, qw(-sS));
+    my $url = access_cfg('archive-query-url');
+    if ($url =~ m#^https://([-.0-9a-z]+)/#) {
+       my $host = $1;
+       my $keys = access_cfg('archive-query-tls-key','RETURN-UNDEF') //'';
+       foreach my $key (split /\:/, $keys) {
+           $key =~ s/\%HOST\%/$host/g;
+           if (!stat $key) {
+               fail "for $url: stat $key: $!" unless $!==ENOENT;
+               next;
+           }
+           fail "config requested specific TLS key but do not know".
+               " how to get curl to use exactly that EE key ($key)";
+#          push @cmd, "--cacert", $key, "--capath", "/dev/enoent";
+#           # Sadly the above line does not work because of changes
+#           # to gnutls.   The real fix for #790093 may involve
+#           # new curl options.
+           last;
+       }
+       # Fixing #790093 properly will involve providing a value
+       # for this on clients.
+       my $kargs = access_cfg('archive-query-tls-curl-ca-args','RETURN-UNDEF');
+       push @cmd, split / /, $kargs if defined $kargs;
+    }
+    push @cmd, $url.$subpath;
+    return @cmd;
+}
+
+sub api_query ($$;$) {
+    use JSON;
+    my ($data, $subpath, $ok404) = @_;
+    badcfg "ftpmasterapi archive query method takes no data part"
+       if length $data;
+    my @cmd = archive_api_query_cmd($subpath);
+    my $url = $cmd[$#cmd];
+    push @cmd, qw(-w %{http_code});
+    my $json = cmdoutput @cmd;
+    unless ($json =~ s/\d+\d+\d$//) {
+       failedcmd_report_cmd undef, @cmd;
+       fail "curl failed to print 3-digit HTTP code";
+    }
+    my $code = $&;
+    return undef if $code eq '404' && $ok404;
+    fail "fetch of $url gave HTTP code $code"
+       unless $url =~ m#^file://# or $code =~ m/^2/;
+    return decode_json($json);
+}
+
+sub canonicalise_suite_ftpmasterapi {
+    my ($proto,$data) = @_;
+    my $suites = api_query($data, 'suites');
+    my @matched;
+    foreach my $entry (@$suites) {
+       next unless grep { 
+           my $v = $entry->{$_};
+           defined $v && $v eq $isuite;
+       } qw(codename name);
+       push @matched, $entry;
+    }
+    fail "unknown suite $isuite" unless @matched;
+    my $cn;
+    eval {
+       @matched==1 or die "multiple matches for suite $isuite\n";
+       $cn = "$matched[0]{codename}";
+       defined $cn or die "suite $isuite info has no codename\n";
+       $cn =~ m/^$suite_re$/ or die "suite $isuite maps to bad codename\n";
+    };
+    die "bad ftpmaster api response: $@\n".Dumper(\@matched)
+       if length $@;
+    return $cn;
+}
+
+sub archive_query_ftpmasterapi {
+    my ($proto,$data) = @_;
+    my $info = api_query($data, "dsc_in_suite/$isuite/$package");
+    my @rows;
+    my $digester = Digest::SHA->new(256);
+    foreach my $entry (@$info) {
+       eval {
+           my $vsn = "$entry->{version}";
+           my ($ok,$msg) = version_check $vsn;
+           die "bad version: $msg\n" unless $ok;
+           my $component = "$entry->{component}";
+           $component =~ m/^$component_re$/ or die "bad component";
+           my $filename = "$entry->{filename}";
+           $filename && $filename !~ m#[^-+:._~0-9a-zA-Z/]|^[/.]|/[/.]#
+               or die "bad filename";
+           my $sha256sum = "$entry->{sha256sum}";
+           $sha256sum =~ m/^[0-9a-f]+$/ or die "bad sha256sum";
+           push @rows, [ $vsn, "/pool/$component/$filename",
+                         $digester, $sha256sum ];
+       };
+       die "bad ftpmaster api response: $@\n".Dumper($entry)
+           if length $@;
+    }
+    @rows = sort { -version_compare($a->[0],$b->[0]) } @rows;
+    return archive_query_prepend_mirror @rows;
+}
+
+sub file_in_archive_ftpmasterapi {
+    my ($proto,$data,$filename) = @_;
+    my $pat = $filename;
+    $pat =~ s/_/\\_/g;
+    $pat = "%/$pat";
+    $pat =~ s#[^-+_.0-9a-z/]# sprintf '%%%02x', ord $& #ge;
+    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 ];
+}
+
+#---------- `dummyapicat' archive query method ----------
+
+sub archive_query_dummycatapi { archive_query_ftpmasterapi @_; }
+sub canonicalise_suite_dummycatapi { canonicalise_suite_ftpmasterapi @_; }
+
+sub file_in_archive_dummycatapi ($$$) {
+    my ($proto,$data,$filename) = @_;
+    my $mirror = access_cfg('mirror');
+    $mirror =~ s#^file://#/# or die "$mirror ?";
+    my @out;
+    my @cmd = (qw(sh -ec), '
+            cd "$1"
+            find -name "$2" -print0 |
+            xargs -0r sha256sum
+        ', qw(x), $mirror, $filename);
+    debugcmd "-|", @cmd;
+    open FIA, "-|", @cmd or die $!;
+    while (<FIA>) {
+       chomp or die;
+       printdebug "| $_\n";
+       m/^(\w+)  (\S+)$/ or die "$_ ?";
+       push @out, { sha256sum => $1, filename => $2 };
+    }
+    close FIA or die failedcmd @cmd;
+    return \@out;
+}
+
+#---------- `madison' archive query method ----------
+
+sub archive_query_madison {
+    return archive_query_prepend_mirror
+       map { [ @$_[0..1] ] } madison_get_parse(@_);
+}
+
+sub madison_get_parse {
+    my ($proto,$data) = @_;
+    die unless $proto eq 'madison';
+    if (!length $data) {
+       $data= access_cfg('madison-distro','RETURN-UNDEF');
+       $data //= access_basedistro();
+    }
+    $rmad{$proto,$data,$package} ||= cmdoutput
+       qw(rmadison -asource),"-s$isuite","-u$data",$package;
+    my $rmad = $rmad{$proto,$data,$package};
+
+    my @out;
+    foreach my $l (split /\n/, $rmad) {
+       $l =~ m{^ \s*( [^ \t|]+ )\s* \|
+                  \s*( [^ \t|]+ )\s* \|
+                  \s*( [^ \t|/]+ )(?:/([^ \t|/]+))? \s* \|
+                  \s*( [^ \t|]+ )\s* }x or die "$rmad ?";
+       $1 eq $package or die "$rmad $package ?";
+       my $vsn = $2;
+       my $newsuite = $3;
+       my $component;
+       if (defined $4) {
+           $component = $4;
+       } else {
+           $component = access_cfg('archive-query-default-component');
+       }
+       $5 eq 'source' or die "$rmad ?";
+       push @out, [$vsn,pool_dsc_subpath($vsn,$component),$newsuite];
+    }
+    return sort { -version_compare($a->[0],$b->[0]); } @out;
+}
+
+sub canonicalise_suite_madison {
+    # madison canonicalises for us
+    my @r = madison_get_parse(@_);
+    @r or fail
+       "unable to canonicalise suite using package $package".
+       " which does not appear to exist in suite $isuite;".
+       " --existing-package may help";
+    return $r[0][2];
+}
+
+sub file_in_archive_madison { return undef; }
+
+#---------- `sshpsql' archive query method ----------
+
+sub sshpsql ($$$) {
+    my ($data,$runeinfo,$sql) = @_;
+    if (!length $data) {
+       $data= access_someuserhost('sshpsql').':'.
+           access_cfg('sshpsql-dbname');
+    }
+    $data =~ m/:/ or badcfg "invalid sshpsql method string \`$data'";
+    my ($userhost,$dbname) = ($`,$'); #';
+    my @rows;
+    my @cmd = (access_cfg_ssh, $userhost,
+              access_runeinfo("ssh-psql $runeinfo").
+              " export LC_MESSAGES=C; export LC_CTYPE=C;".
+              " ".shellquote qw(psql -A), $dbname, qw(-c), $sql);
+    debugcmd "|",@cmd;
+    open P, "-|", @cmd or die $!;
+    while (<P>) {
+       chomp or die;
+       printdebug(">|$_|\n");
+       push @rows, $_;
+    }
+    $!=0; $?=0; close P or failedcmd @cmd;
+    @rows or die;
+    my $nrows = pop @rows;
+    $nrows =~ s/^\((\d+) rows?\)$/$1/ or die "$nrows ?";
+    @rows == $nrows+1 or die "$nrows ".(scalar @rows)." ?";
+    @rows = map { [ split /\|/, $_ ] } @rows;
+    my $ncols = scalar @{ shift @rows };
+    die if grep { scalar @$_ != $ncols } @rows;
+    return @rows;
+}
+
+sub sql_injection_check {
+    foreach (@_) { die "$_ $& ?" if m{[^-+=:_.,/0-9a-zA-Z]}; }
+}
+
+sub archive_query_sshpsql ($$) {
+    my ($proto,$data) = @_;
+    sql_injection_check $isuite, $package;
+    my @rows = sshpsql($data, "archive-query $isuite $package", <<END);
+        SELECT source.version, component.name, files.filename, files.sha256sum
+          FROM source
+          JOIN src_associations ON source.id = src_associations.source
+          JOIN suite ON suite.id = src_associations.suite
+          JOIN dsc_files ON dsc_files.source = source.id
+          JOIN files_archive_map ON files_archive_map.file_id = dsc_files.file
+          JOIN component ON component.id = files_archive_map.component_id
+          JOIN files ON files.id = dsc_files.file
+         WHERE ( suite.suite_name='$isuite' OR suite.codename='$isuite' )
+           AND source.source='$package'
+           AND files.filename LIKE '%.dsc';
+END
+    @rows = sort { -version_compare($a->[0],$b->[0]) } @rows;
+    my $digester = Digest::SHA->new(256);
+    @rows = map {
+       my ($vsn,$component,$filename,$sha256sum) = @$_;
+       [ $vsn, "/pool/$component/$filename",$digester,$sha256sum ];
+    } @rows;
+    return archive_query_prepend_mirror @rows;
+}
+
+sub canonicalise_suite_sshpsql ($$) {
+    my ($proto,$data) = @_;
+    sql_injection_check $isuite;
+    my @rows = sshpsql($data, "canonicalise-suite $isuite", <<END);
+        SELECT suite.codename
+          FROM suite where suite_name='$isuite' or codename='$isuite';
+END
+    @rows = map { $_->[0] } @rows;
+    fail "unknown suite $isuite" unless @rows;
+    die "ambiguous $isuite: @rows ?" if @rows>1;
+    return $rows[0];
+}
+
+sub file_in_archive_sshpsql ($$$) { return undef; }
+
+#---------- `dummycat' archive query method ----------
+
+sub canonicalise_suite_dummycat ($$) {
+    my ($proto,$data) = @_;
+    my $dpath = "$data/suite.$isuite";
+    if (!open C, "<", $dpath) {
+       $!==ENOENT or die "$dpath: $!";
+       printdebug "dummycat canonicalise_suite $isuite $dpath ENOENT\n";
+       return $isuite;
+    }
+    $!=0; $_ = <C>;
+    chomp or die "$dpath: $!";
+    close C;
+    printdebug "dummycat canonicalise_suite $isuite $dpath = $_\n";
+    return $_;
+}
+
+sub archive_query_dummycat ($$) {
+    my ($proto,$data) = @_;
+    canonicalise_suite();
+    my $dpath = "$data/package.$csuite.$package";
+    if (!open C, "<", $dpath) {
+       $!==ENOENT or die "$dpath: $!";
+       printdebug "dummycat query $csuite $package $dpath ENOENT\n";
+       return ();
+    }
+    my @rows;
+    while (<C>) {
+       next if m/^\#/;
+       next unless m/\S/;
+       die unless chomp;
+       printdebug "dummycat query $csuite $package $dpath | $_\n";
+       my @row = split /\s+/, $_;
+       @row==2 or die "$dpath: $_ ?";
+       push @rows, \@row;
+    }
+    C->error and die "$dpath: $!";
+    close C;
+    return archive_query_prepend_mirror
+       sort { -version_compare($a->[0],$b->[0]); } @rows;
+}
+
+sub file_in_archive_dummycat () { return undef; }
+
+#---------- tag format handling ----------
+
+sub access_cfg_tagformats () {
+    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".
+       " to use tag format $tagformat_want->[0] ($tagformat_want->[1])".
+       " - no way to proceed"
+       if $tagformat_want && $tagformat_want->[0] ne $fmt;
+    $tagformat_want = [$fmt, $why, $tagformat_want->[2] // 0];
+}
+
+sub select_tagformat () {
+    # sets $tagformatfn
+    return if $tagformatfn && !$tagformat_want;
+    die 'bug' if $tagformatfn && $tagformat_want;
+    # ... $tagformat_want assigned after previous select_tagformat
+
+    my (@supported) = grep { $_ =~ m/^(?:old|new)$/ } access_cfg_tagformats();
+    printdebug "select_tagformat supported @supported\n";
+
+    $tagformat_want //= [ $supported[0], "distro access configuration", 0 ];
+    printdebug "select_tagformat specified @$tagformat_want\n";
+
+    my ($fmt,$why,$override) = @$tagformat_want;
+
+    fail "target distro supports tag formats @supported".
+       " but have to use $fmt ($why)"
+       unless $override
+           or grep { $_ eq $fmt } @supported;
+
+    $tagformat_want = undef;
+    $tagformat = $fmt;
+    $tagformatfn = ${*::}{"debiantag_$fmt"};
+
+    fail "trying to use unknown tag format \`$fmt' ($why) !"
+       unless $tagformatfn;
+}
+
+#---------- archive query entrypoints and rest of program ----------
+
+sub canonicalise_suite () {
+    return if defined $csuite;
+    fail "cannot operate on $isuite suite" if $isuite eq 'UNRELEASED';
+    $csuite = archive_query('canonicalise_suite');
+    if ($isuite ne $csuite) {
+       progress "canonical suite name for $isuite is $csuite";
+    } else {
+       progress "canonical suite name is $csuite";
+    }
+}
+
+sub get_archive_dsc () {
+    canonicalise_suite();
+    my @vsns = archive_query('archive_query');
+    foreach my $vinfo (@vsns) {
+       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;
+           next;
+       }
+       if ($digester) {
+           $digester->reset();
+           $digester->add($dscdata);
+           my $got = $digester->hexdigest();
+           $got eq $digest or
+               fail "$dscurl has hash $got but".
+                   " archive told us to expect $digest";
+       }
+       parse_dscdata();
+       my $fmt = getfield $dsc, 'Format';
+       $format_ok{$fmt} or forceable_fail [qw(unsupported-source-format)],
+           "unsupported source format $fmt, sorry";
+           
+       $dsc_checked = !!$digester;
+       printdebug "get_archive_dsc: Version ".(getfield $dsc, 'Version')."\n";
+       return;
+    }
+    $dsc = undef;
+    printdebug "get_archive_dsc: nothing in archive, returning undef\n";
+}
+
+sub check_for_git ();
+sub check_for_git () {
+    # returns 0 or 1
+    my $how = access_cfg('git-check');
+    if ($how eq 'ssh-cmd') {
+       my @cmd =
+           (access_cfg_ssh, access_gituserhost(),
+            access_runeinfo("git-check $package").
+            " set -e; cd ".access_cfg('git-path').";".
+            " if test -d $package.git; then echo 1; else echo 0; fi");
+       my $r= cmdoutput @cmd;
+       if (defined $r and $r =~ m/^divert (\w+)$/) {
+           my $divert=$1;
+           my ($usedistro,) = access_distros();
+           # NB that if we are pushing, $usedistro will be $distro/push
+           $instead_distro= cfg("dgit-distro.$usedistro.diverts.$divert");
+           $instead_distro =~ s{^/}{ access_basedistro()."/" }e;
+           progress "diverting to $divert (using config for $instead_distro)";
+           return check_for_git();
+       }
+       failedcmd @cmd unless defined $r and $r =~ m/^[01]$/;
+       return $r+0;
+    } elsif ($how eq 'url') {
+       my $prefix = access_cfg('git-check-url','git-url');
+       my $suffix = access_cfg('git-check-suffix','git-suffix',
+                               'RETURN-UNDEF') // '.git';
+       my $url = "$prefix/$package$suffix";
+       my @cmd = (@curl, qw(-sS -I), $url);
+       my $result = cmdoutput @cmd;
+       $result =~ s/^\S+ 200 .*\n\r?\n//;
+       # curl -sS -I with https_proxy prints
+       # HTTP/1.0 200 Connection established
+       $result =~ m/^\S+ (404|200) /s or
+           fail "unexpected results from git check query - ".
+               Dumper($prefix, $result);
+       my $code = $1;
+       if ($code eq '404') {
+           return 0;
+       } elsif ($code eq '200') {
+           return 1;
+       } else {
+           die;
+       }
+    } elsif ($how eq 'true') {
+       return 1;
+    } elsif ($how eq 'false') {
+       return 0;
+    } else {
+       badcfg "unknown git-check \`$how'";
+    }
+}
+
+sub create_remote_git_repo () {
+    my $how = access_cfg('git-create');
+    if ($how eq 'ssh-cmd') {
+       runcmd_ordryrun
+           (access_cfg_ssh, access_gituserhost(),
+            access_runeinfo("git-create $package").
+            "set -e; cd ".access_cfg('git-path').";".
+            " cp -a _template $package.git");
+    } elsif ($how eq 'true') {
+       # nothing to do
+    } else {
+       badcfg "unknown git-create \`$how'";
+    }
+}
+
+our ($dsc_hash,$lastpush_mergeinput);
+
+our $ud = '.git/dgit/unpack';
+
+sub prep_ud (;$) {
+    my ($d) = @_;
+    $d //= $ud;
+    rmtree($d);
+    mkpath '.git/dgit';
+    mkdir $d or die $!;
+}
+
+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 $!;
+}
+
+sub git_write_tree () {
+    my $tree = cmdoutput @git, qw(write-tree);
+    $tree =~ m/^\w+$/ or die "$tree ?";
+    return $tree;
+}
+
+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 $!;
+    {
+       local $/="\0";
+       while (<GITS>) {
+           chomp or die;
+           print STDERR "$us: warning: removing from $what: ",
+               (messagequote $_), "\n";
+           rmtree $_;
+       }
+    }
+    $!=0; $?=0; close GITS or failedcmd @gitscmd;
+}
+
+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;
+    my $dir = $1;
+    changedir $dir;
+
+    remove_stray_gits($what);
+    mktree_in_ud_here();
+    if (!$raw) {
+       my ($format, $fopts) = get_source_format();
+       if (madformat($format)) {
+           rmtree '.pc';
+       }
+    }
+
+    my $tree=git_add_write_tree();
+    return ($tree,$dir);
+}
+
+our @files_csum_info_fields = 
+    (['Checksums-Sha256','Digest::SHA', 'new(256)', 'sha256sum'],
+     ['Checksums-Sha1',  'Digest::SHA', 'new(1)',   'sha1sum'],
+     ['Files',           'Digest::MD5', 'new()',    'md5sum']);
+
+sub dsc_files_info () {
+    foreach my $csumi (@files_csum_info_fields) {
+       my ($fname, $module, $method) = @$csumi;
+       my $field = $dsc->{$fname};
+       next unless defined $field;
+       eval "use $module; 1;" or die $@;
+       my @out;
+       foreach (split /\n/, $field) {
+           next unless m/\S/;
+           m/^(\w+) (\d+) (\S+)$/ or
+               fail "could not parse .dsc $fname line \`$_'";
+           my $digester = eval "$module"."->$method;" or die $@;
+           push @out, {
+               Hash => $1,
+               Bytes => $2,
+               Filename => $3,
+               Digester => $digester,
+           };
+       }
+       return @out;
+    }
+    fail "missing any supported Checksums-* or Files field in ".
+       $dsc->get_option('name');
+}
+
+sub dsc_files () {
+    map { $_->{Filename} } dsc_files_info();
+}
+
+sub files_compare_inputs (@) {
+    my $inputs = \@_;
+    my %record;
+    my %fchecked;
+
+    my $showinputs = sub {
+       return join "; ", map { $_->get_option('name') } @$inputs;
+    };
+
+    foreach my $in (@$inputs) {
+       my $expected_files;
+       my $in_name = $in->get_option('name');
+
+       printdebug "files_compare_inputs $in_name\n";
+
+       foreach my $csumi (@files_csum_info_fields) {
+           my ($fname) = @$csumi;
+           printdebug "files_compare_inputs $in_name $fname\n";
+
+           my $field = $in->{$fname};
+           next unless defined $field;
+
+           my @files;
+           foreach (split /\n/, $field) {
+               next unless m/\S/;
+
+               my ($info, $f) = m/^(\w+ \d+) (?:\S+ \S+ )?(\S+)$/ or
+                   fail "could not parse $in_name $fname line \`$_'";
+
+               printdebug "files_compare_inputs $in_name $fname $f\n";
+
+               push @files, $f;
+
+               my $re = \ $record{$f}{$fname};
+               if (defined $$re) {
+                   $fchecked{$f}{$in_name} = 1;
+                   $$re eq $info or
+                       fail "hash or size of $f varies in $fname fields".
+                       " (between: ".$showinputs->().")";
+               } else {
+                   $$re = $info;
+               }
+           }
+           @files = sort @files;
+           $expected_files //= \@files;
+           "@$expected_files" eq "@files" or
+               fail "file list in $in_name varies between hash fields!";
+       }
+       $expected_files or
+           fail "$in_name has no files list field(s)";
+    }
+    printdebug "files_compare_inputs ".Dumper(\%fchecked, \%record)
+       if $debuglevel>=2;
+
+    grep { keys %$_ == @$inputs-1 } values %fchecked
+       or fail "no file appears in all file lists".
+       " (looked in: ".$showinputs->().")";
+}
+
+sub is_orig_file_in_dsc ($$) {
+    my ($f, $dsc_files_info) = @_;
+    return 0 if @$dsc_files_info <= 1;
+    # One file means no origs, and the filename doesn't have a "what
+    # part of dsc" component.  (Consider versions ending `.orig'.)
+    return 0 unless $f =~ m/\.$orig_f_tail_re$/o;
+    return 1;
+}
+
+sub is_orig_file_of_vsn ($$) {
+    my ($f, $upstreamvsn) = @_;
+    my $base = srcfn $upstreamvsn, '';
+    return 0 unless $f =~ m/^\Q$base\E\.$orig_f_tail_re$/;
+    return 1;
+}
+
+sub changes_update_origs_from_dsc ($$$$) {
+    my ($dsc, $changes, $upstreamvsn, $changesfile) = @_;
+    my %changes_f;
+    printdebug "checking origs needed ($upstreamvsn)...\n";
+    $_ = getfield $changes, 'Files';
+    m/^\w+ \d+ (\S+ \S+) \S+$/m or
+       fail "cannot find section/priority from .changes Files field";
+    my $placementinfo = $1;
+    my %changed;
+    printdebug "checking origs needed placement '$placementinfo'...\n";
+    foreach my $l (split /\n/, getfield $dsc, 'Files') {
+       $l =~ m/\S+$/ or next;
+       my $file = $&;
+       printdebug "origs $file | $l\n";
+       next unless is_orig_file_of_vsn $file, $upstreamvsn;
+       printdebug "origs $file is_orig\n";
+       my $have = archive_query('file_in_archive', $file);
+       if (!defined $have) {
+           print STDERR <<END;
+archive does not support .orig check; hope you used --ch:--sa/-sd if needed
+END
+           return;
+       }
+       my $found_same = 0;
+       my @found_differ;
+       printdebug "origs $file \$#\$have=$#$have\n";
+       foreach my $h (@$have) {
+           my $same = 0;
+           my @differ;
+           foreach my $csumi (@files_csum_info_fields) {
+               my ($fname, $module, $method, $archivefield) = @$csumi;
+               next unless defined $h->{$archivefield};
+               $_ = $dsc->{$fname};
+               next unless defined;
+               m/^(\w+) .* \Q$file\E$/m or
+                   fail ".dsc $fname missing entry for $file";
+               if ($h->{$archivefield} eq $1) {
+                   $same++;
+               } else {
+                   push @differ,
+ "$archivefield: $h->{$archivefield} (archive) != $1 (local .dsc)";
+               }
+           }
+           die "$file ".Dumper($h)." ?!" if $same && @differ;
+           $found_same++
+               if $same;
+           push @found_differ, "archive $h->{filename}: ".join "; ", @differ
+               if @differ;
+       }
+       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",
+               @found_differ;
+       }
+       # Now we edit the changes file to add or remove it
+       foreach my $csumi (@files_csum_info_fields) {
+           my ($fname, $module, $method, $archivefield) = @$csumi;
+           next unless defined $changes->{$fname};
+           if ($found_same) {
+               # in archive, delete from .changes if it's there
+               $changed{$file} = "removed" if
+                   $changes->{$fname} =~ s/^.* \Q$file\E$(?:)\n//m;
+           } elsif ($changes->{$fname} =~ m/^.* \Q$file\E$(?:)\n/m) {
+               # not in archive, but it's here in the .changes
+           } else {
+               my $dsc_data = getfield $dsc, $fname;
+               $dsc_data =~ m/^(.* \Q$file\E$)\n/m or die "$dsc_data $file ?";
+               my $extra = $1;
+               $extra =~ s/ \d+ /$&$placementinfo /
+                   or die "$fname $extra >$dsc_data< ?"
+                   if $fname eq 'Files';
+               $changes->{$fname} .= "\n". $extra;
+               $changed{$file} = "added";
+           }
+       }
+    }
+    if (%changed) {
+       foreach my $file (keys %changed) {
+           progress sprintf
+               "edited .changes for archive .orig contents: %s %s",
+               $changed{$file}, $file;
+       }
+       my $chtmp = "$changesfile.tmp";
+       $changes->save($chtmp);
+       if (act_local()) {
+           rename $chtmp,$changesfile or die "$changesfile $!";
+       } else {
+           progress "[new .changes left in $changesfile]";
+       }
+    } else {
+       progress "$changesfile already has appropriate .orig(s) (if any)";
+    }
+}
+
+sub make_commit ($) {
+    my ($file) = @_;
+    return cmdoutput @git, qw(hash-object -w -t commit), $file;
+}
+
+sub make_commit_text ($) {
+    my ($text) = @_;
+    my ($out, $in);
+    my @cmd = (@git, qw(hash-object -w -t commit --stdin));
+    debugcmd "|",@cmd;
+    print Dumper($text) if $debuglevel > 1;
+    my $child = open2($out, $in, @cmd) or die $!;
+    my $h;
+    eval {
+       print $in $text or die $!;
+       close $in or die $!;
+       $h = <$out>;
+       $h =~ m/^\w+$/ or die;
+       $h = $&;
+       printdebug "=> $h\n";
+    };
+    close $out;
+    waitpid $child, 0 == $child or die "$child $!";
+    $? and failedcmd @cmd;
+    return $h;
+}
+
+sub clogp_authline ($) {
+    my ($clogp) = @_;
+    my $author = getfield $clogp, 'Maintainer';
+    $author =~ s#,.*##ms;
+    my $date = cmdoutput qw(date), '+%s %z', qw(-d), getfield($clogp,'Date');
+    my $authline = "$author $date";
+    $authline =~ m/$git_authline_re/o or
+       fail "unexpected commit author line format \`$authline'".
+       " (was generated from changelog Maintainer field)";
+    return ($1,$2,$3) if wantarray;
+    return $authline;
+}
+
+sub vendor_patches_distro ($$) {
+    my ($checkdistro, $what) = @_;
+    return unless defined $checkdistro;
+
+    my $series = "debian/patches/\L$checkdistro\E.series";
+    printdebug "checking for vendor-specific $series ($what)\n";
+
+    if (!open SERIES, "<", $series) {
+       die "$series $!" unless $!==ENOENT;
+       return;
+    }
+    while (<SERIES>) {
+       next unless m/\S/;
+       next if m/^\s+\#/;
+
+       print STDERR <<END;
+
+Unfortunately, this source package uses a feature of dpkg-source where
+the same source package unpacks to different source code on different
+distros.  dgit cannot safely operate on such packages on affected
+distros, because the meaning of source packages is not stable.
+
+Please ask the distro/maintainer to remove the distro-specific series
+files and use a different technique (if necessary, uploading actually
+different packages, if different distros are supposed to have
+different code).
+
+END
+       fail "Found active distro-specific series file for".
+           " $checkdistro ($what): $series, cannot continue";
+    }
+    die "$series $!" if SERIES->error;
+    close SERIES;
+}
+
+sub check_for_vendor_patches () {
+    # This dpkg-source feature doesn't seem to be documented anywhere!
+    # But it can be found in the changelog (reformatted):
+
+    #   commit  4fa01b70df1dc4458daee306cfa1f987b69da58c
+    #   Author: Raphael Hertzog <hertzog@debian.org>
+    #   Date: Sun  Oct  3  09:36:48  2010 +0200
+
+    #   dpkg-source: correctly create .pc/.quilt_series with alternate
+    #   series files
+    #   
+    #   If you have debian/patches/ubuntu.series and you were
+    #   unpacking the source package on ubuntu, quilt was still
+    #   directed to debian/patches/series instead of
+    #   debian/patches/ubuntu.series.
+    #   
+    #   debian/changelog                        |    3 +++
+    #   scripts/Dpkg/Source/Package/V3/quilt.pm |    4 +++-
+    #   2 files changed, 6 insertions(+), 1 deletion(-)
+
+    use Dpkg::Vendor;
+    vendor_patches_distro($ENV{DEB_VENDOR}, "DEB_VENDOR");
+    vendor_patches_distro(Dpkg::Vendor::get_current_vendor(),
+                        "Dpkg::Vendor \`current vendor'");
+    vendor_patches_distro(access_basedistro(),
+                         "(base) distro being accessed");
+    vendor_patches_distro(access_nomdistro(),
+                         "(nominal) distro being accessed");
+}
+
+sub generate_commits_from_dsc () {
+    # See big comment in fetch_from_archive, below.
+    # See also README.dsc-import.
+    prep_ud();
+    changedir $ud;
+
+    my @dfi = dsc_files_info();
+    foreach my $fi (@dfi) {
+       my $f = $fi->{Filename};
+       die "$f ?" if $f =~ m#/|^\.|\.dsc$|\.tmp$#;
+
+       printdebug "considering linking $f: ";
+
+       link_ltarget "../../../../$f", $f
+           or ((printdebug "($!) "), 0)
+           or $!==&ENOENT
+           or die "$f $!";
+
+       printdebug "linked.\n";
+
+       complete_file_from_dsc('.', $fi)
+           or next;
+
+       if (is_orig_file_in_dsc($f, \@dfi)) {
+           link $f, "../../../../$f"
+               or $!==&EEXIST
+               or die "$f $!";
+       }
+    }
+
+    # We unpack and record the orig tarballs first, so that we only
+    # need disk space for one private copy of the unpacked source.
+    # But we can't make them into commits until we have the metadata
+    # from the debian/changelog, so we record the tree objects now and
+    # make them into commits later.
+    my @tartrees;
+    my $upstreamv = upstreamversion $dsc->{version};
+    my $orig_f_base = srcfn $upstreamv, '';
+
+    foreach my $fi (@dfi) {
+       # We actually import, and record as a commit, every tarball
+       # (unless there is only one file, in which case there seems
+       # little point.
+
+       my $f = $fi->{Filename};
+       printdebug "import considering $f ";
+       (printdebug "only one dfi\n"), next if @dfi == 1;
+       (printdebug "not tar\n"), next unless $f =~ m/\.tar(\.\w+)?$/;
+       (printdebug "signature\n"), next if $f =~ m/$orig_f_sig_re$/o;
+       my $compr_ext = $1;
+
+       my ($orig_f_part) =
+           $f =~ m/^\Q$orig_f_base\E\.([^._]+)?\.tar(?:\.\w+)?$/;
+
+       printdebug "Y ", (join ' ', map { $_//"(none)" }
+                         $compr_ext, $orig_f_part
+                        ), "\n";
+
+       my $input = new IO::File $f, '<' or die "$f $!";
+       my $compr_pid;
+       my @compr_cmd;
+
+       if (defined $compr_ext) {
+           my $cname =
+               Dpkg::Compression::compression_guess_from_filename $f;
+           fail "Dpkg::Compression cannot handle file $f in source package"
+               if defined $compr_ext && !defined $cname;
+           my $compr_proc =
+               new Dpkg::Compression::Process compression => $cname;
+           my @compr_cmd = $compr_proc->get_uncompress_cmdline();
+           my $compr_fh = new IO::Handle;
+           my $compr_pid = open $compr_fh, "-|" // die $!;
+           if (!$compr_pid) {
+               open STDIN, "<&", $input or die $!;
+               exec @compr_cmd;
+               die "dgit (child): exec $compr_cmd[0]: $!\n";
+           }
+           $input = $compr_fh;
+       }
+
+       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) {
+           chdir "_unpack-tar" or die $!;
+           open STDIN, "<&", $input or die $!;
+           exec @tarcmd;
+           die "dgit (child): exec $tarcmd[0]: $!";
+       }
+       $!=0; (waitpid $tar_pid, 0) == $tar_pid or die $!;
+       !$? or failedcmd @tarcmd;
+
+       close $input or
+           (@compr_cmd ? failedcmd @compr_cmd
+            : die $!);
+       # finally, we have the results in "tarball", but maybe
+       # with the wrong permissions
+
+       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, {
+            Orig => !!$orig_f_part,
+            Sort => (!$orig_f_part         ? 2 :
+                    $orig_f_part =~ m/-/g ? 1 :
+                                            0),
+            F => $f,
+            Tree => $tree,
+        };
+    }
+
+    @tartrees = sort {
+       # put any without "_" first (spec is not clear whether files
+       # are always in the usual order).  Tarballs without "_" are
+       # the main orig or the debian tarball.
+       $a->{Sort} <=> $b->{Sort} or
+       $a->{F}    cmp $b->{F}
+    } @tartrees;
+
+    my $any_orig = grep { $_->{Orig} } @tartrees;
+
+    my $dscfn = "$package.dsc";
+
+    my $treeimporthow = 'package';
+
+    open D, ">", $dscfn or die "$dscfn: $!";
+    print D $dscdata or die "$dscfn: $!";
+    close D or die "$dscfn: $!";
+    my @cmd = qw(dpkg-source);
+    push @cmd, '--no-check' if $dsc_checked;
+    if (madformat $dsc->{format}) {
+       push @cmd, '--skip-patches';
+       $treeimporthow = 'unpatched';
+    }
+    push @cmd, qw(-x --), $dscfn;
+    runcmd @cmd;
+
+    my ($tree,$dir) = mktree_in_ud_from_only_subdir("source package");
+    if (madformat $dsc->{format}) { 
+       check_for_vendor_patches();
+    }
+
+    my $dappliedtree;
+    if (madformat $dsc->{format}) {
+       my @pcmd = qw(dpkg-source --before-build .);
+       runcmd shell_cmd 'exec >/dev/null', @pcmd;
+       rmtree '.pc';
+       $dappliedtree = git_add_write_tree();
+    }
+
+    my @clogcmd = qw(dpkg-parsechangelog --format rfc822 --all);
+    debugcmd "|",@clogcmd;
+    open CLOGS, "-|", @clogcmd or die $!;
+
+    my $clogp;
+    my $r1clogp;
+
+    printdebug "import clog search...\n";
+
+    for (;;) {
+       my $stanzatext = do { local $/=""; <CLOGS>; };
+       printdebug "import clogp ".Dumper($stanzatext) if $debuglevel>1;
+       last if !defined $stanzatext;
+
+       my $desc = "package changelog, entry no.$.";
+       open my $stanzafh, "<", \$stanzatext or die;
+       my $thisstanza = parsecontrolfh $stanzafh, $desc, 1;
+       $clogp //= $thisstanza;
+
+       printdebug "import clog $thisstanza->{version} $desc...\n";
+
+       last if !$any_orig; # we don't need $r1clogp
+
+       # We look for the first (most recent) changelog entry whose
+       # version number is lower than the upstream version of this
+       # package.  Then the last (least recent) previous changelog
+       # entry is treated as the one which introduced this upstream
+       # version and used for the synthetic commits for the upstream
+       # tarballs.
+
+       # One might think that a more sophisticated algorithm would be
+       # necessary.  But: we do not want to scan the whole changelog
+       # file.  Stopping when we see an earlier version, which
+       # necessarily then is an earlier upstream version, is the only
+       # realistic way to do that.  Then, either the earliest
+       # changelog entry we have seen so far is indeed the earliest
+       # upload of this upstream version; or there are only changelog
+       # entries relating to later upstream versions (which is not
+       # possible unless the changelog and .dsc disagree about the
+       # version).  Then it remains to choose between the physically
+       # last entry in the file, and the one with the lowest version
+       # number.  If these are not the same, we guess that the
+       # versions were created in a non-monotic order rather than
+       # that the changelog entries have been misordered.
+
+       printdebug "import clog $thisstanza->{version} vs $upstreamv...\n";
+
+       last if version_compare($thisstanza->{version}, $upstreamv) < 0;
+       $r1clogp = $thisstanza;
+
+       printdebug "import clog $r1clogp->{version} becomes r1\n";
+    }
+    die $! if CLOGS->error;
+    close CLOGS or $?==SIGPIPE or failedcmd @clogcmd;
+
+    $clogp or fail "package changelog has no entries!";
+
+    my $authline = clogp_authline $clogp;
+    my $changes = getfield $clogp, 'Changes';
+    my $cversion = getfield $clogp, 'Version';
+
+    if (@tartrees) {
+       $r1clogp //= $clogp; # maybe there's only one entry;
+       my $r1authline = clogp_authline $r1clogp;
+       # Strictly, r1authline might now be wrong if it's going to be
+       # unused because !$any_orig.  Whatever.
+
+       printdebug "import tartrees authline   $authline\n";
+       printdebug "import tartrees r1authline $r1authline\n";
+
+       foreach my $tt (@tartrees) {
+           printdebug "import tartree $tt->{F} $tt->{Tree}\n";
+
+           $tt->{Commit} = make_commit_text($tt->{Orig} ? <<END_O : <<END_T);
+tree $tt->{Tree}
+author $r1authline
+committer $r1authline
+
+Import $tt->{F}
+
+[dgit import orig $tt->{F}]
+END_O
+tree $tt->{Tree}
+author $authline
+committer $authline
+
+Import $tt->{F}
+
+[dgit import tarball $package $cversion $tt->{F}]
+END_T
+       }
+    }
+
+    printdebug "import main commit\n";
+
+    open C, ">../commit.tmp" or die $!;
+    print C <<END or die $!;
+tree $tree
+END
+    print C <<END or die $! foreach @tartrees;
+parent $_->{Commit}
+END
+    print C <<END or die $!;
+author $authline
+committer $authline
+
+$changes
+
+[dgit import $treeimporthow $package $cversion]
+END
+
+    close C or die $!;
+    my $rawimport_hash = make_commit qw(../commit.tmp);
+
+    if (madformat $dsc->{format}) {
+       printdebug "import apply patches...\n";
+
+       # regularise the state of the working tree so that
+       # the checkout of $rawimport_hash works nicely.
+       my $dappliedcommit = make_commit_text(<<END);
+tree $dappliedtree
+author $authline
+committer $authline
+
+[dgit dummy commit]
+END
+       runcmd @git, qw(checkout -q -b dapplied), $dappliedcommit;
+
+       runcmd @git, qw(checkout -q -b unpa), $rawimport_hash;
+
+       # We need the answers to be reproducible
+       my @authline = clogp_authline($clogp);
+       local $ENV{GIT_COMMITTER_NAME} =  $authline[0];
+       local $ENV{GIT_COMMITTER_EMAIL} = $authline[1];
+       local $ENV{GIT_COMMITTER_DATE} =  $authline[2];
+       local $ENV{GIT_AUTHOR_NAME} =  $authline[0];
+       local $ENV{GIT_AUTHOR_EMAIL} = $authline[1];
+       local $ENV{GIT_AUTHOR_DATE} =  $authline[2];
+
+       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 $@;
+               progress "warning: $@";
+               $path = "$absurdity:$path";
+               progress "$us: trying slow absurd-git-apply...";
+               rename "../../gbp-pq-output","../../gbp-pq-output.0"
+                   or $!==ENOENT
+                   or die $!;
+           }
+           eval {
+               die "forbid absurd git-apply\n" if $use_absurd
+                   && forceing [qw(import-gitapply-no-absurd)];
+               die "only absurd git-apply!\n" if !$use_absurd
+                   && forceing [qw(import-gitapply-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
+                   'exec >/dev/null 2>>../../gbp-pq-output', @showcmd;
+               debugcmd "+",@realcmd;
+               if (system @realcmd) {
+                   die +(shellquote @showcmd).
+                       " failed: ".
+                       failedcmd_waitstatus()."\n";
+               }
+
+               my $gapplied = git_rev_parse('HEAD');
+               my $gappliedtree = cmdoutput @git, qw(rev-parse HEAD:);
+               $gappliedtree eq $dappliedtree or
+                   fail <<END;
+gbp-pq import and dpkg-source disagree!
+ gbp-pq import gave commit $gapplied
+ gbp-pq import gave tree $gappliedtree
+ dpkg-source --before-build gave tree $dappliedtree
+END
+               $rawimport_hash = $gapplied;
+           };
+           last unless $@;
+       }
+       if ($@) {
+           { local $@; eval { runcmd qw(cat ../../gbp-pq-output); }; }
+           die $@;
+       }
+    }
+
+    progress "synthesised git commit from .dsc $cversion";
+
+    my $rawimport_mergeinput = {
+        Commit => $rawimport_hash,
+        Info => "Import of source package",
+    };
+    my @output = ($rawimport_mergeinput);
+
+    if ($lastpush_mergeinput) {
+       my $oldclogp = mergeinfo_getclogp($lastpush_mergeinput);
+       my $oversion = getfield $oldclogp, 'Version';
+       my $vcmp =
+           version_compare($oversion, $cversion);
+       if ($vcmp < 0) {
+           @output = ($rawimport_mergeinput, $lastpush_mergeinput,
+               { Message => <<END, ReverseParents => 1 });
+Record $package ($cversion) in archive suite $csuite
+END
+       } elsif ($vcmp > 0) {
+           print STDERR <<END or die $!;
+
+Version actually in archive:   $cversion (older)
+Last version pushed with dgit: $oversion (newer or same)
+$later_warning_msg
+END
+            @output = $lastpush_mergeinput;
+        } else {
+           # Same version.  Use what's in the server git branch,
+           # discarding our own import.  (This could happen if the
+           # server automatically imports all packages into git.)
+           @output = $lastpush_mergeinput;
+       }
+    }
+    changedir '../../../..';
+    rmtree($ud);
+    return @output;
+}
+
+sub complete_file_from_dsc ($$) {
+    our ($dstdir, $fi) = @_;
+    # Ensures that we have, in $dir, the file $fi, with the correct
+    # contents.  (Downloading it from alongside $dscurl if necessary.)
+
+    my $f = $fi->{Filename};
+    my $tf = "$dstdir/$f";
+    my $downloaded = 0;
+
+    if (stat_exists $tf) {
+       progress "using existing $f";
+    } 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
+       fail "file $f has hash $got but .dsc".
+           " demands hash $fi->{Hash} ".
+           ($downloaded ? "(got wrong file from archive!)"
+            : "(perhaps you should delete this file?)");
+
+    return 1;
+}
+
+sub ensure_we_have_orig () {
+    my @dfi = dsc_files_info();
+    foreach my $fi (@dfi) {
+       my $f = $fi->{Filename};
+       next unless is_orig_file_in_dsc($f, \@dfi);
+       complete_file_from_dsc('..', $fi)
+           or next;
+    }
+}
+
+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, qw(heads/*) if deliberately_not_fast_forward;
+
+    # This is rather miserable:
+    # When git fetch --prune is passed a fetchspec ending with a *,
+    # it does a plausible thing.  If there is no * then:
+    # - it matches subpaths too, even if the supplied refspec
+    #   starts refs, and behaves completely madly if the source
+    #   has refs/refs/something.  (See, for example, Debian #NNNN.)
+    # - if there is no matching remote ref, it bombs out the whole
+    #   fetch.
+    # We want to fetch a fixed ref, and we don't know in advance
+    # if it exists, so this is not suitable.
+    #
+    # Our workaround is to use git ls-remote.  git ls-remote has its
+    # own qairks.  Notably, it has the absurd multi-tail-matching
+    # behaviour: git ls-remote R refs/foo can report refs/foo AND
+    # refs/refs/foo etc.
+    #
+    # Also, we want an idempotent snapshot, but we have to make two
+    # calls to the remote: one to git ls-remote and to git fetch.  The
+    # solution is use git ls-remote to obtain a target state, and
+    # 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";
+
+    my $specre = join '|', map {
+       my $x = $_;
+       $x =~ s/\W/\\$&/g;
+       $x =~ s/\\\*$/.*/;
+       "(?:refs/$x)";
+    } @specs;
+    printdebug "git_fetch_us specre=$specre\n";
+    my $wanted_rref = sub {
+       local ($_) = @_;
+       return m/^(?:$specre)$/o;
+    };
+
+    my $fetch_iteration = 0;
+    FETCH_ITERATION:
+    for (;;) {
+       printdebug "git_fetch_us iteration $fetch_iteration\n";
+        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);
+       debugcmd "|",@lcmd;
+
+       my %wantr;
+       open GITLS, "-|", @lcmd or die $!;
+       while (<GITLS>) {
+           printdebug "=> ", $_;
+           m/^(\w+)\s+(\S+)\n/ or die "ls-remote $_ ?";
+           my ($objid,$rrefname) = ($1,$2);
+           if (!$wanted_rref->($rrefname)) {
+               print STDERR <<END;
+warning: git ls-remote @look reported $rrefname; this is silly, ignoring it.
+END
+               next;
+           }
+           $wantr{$rrefname} = $objid;
+       }
+       $!=0; $?=0;
+       close GITLS or failedcmd @lcmd;
+
+       # OK, now %want is exactly what we want for refs in @specs
+       my @fspecs = map {
+           !m/\*$/ && !exists $wantr{"refs/$_"} ? () :
+           "+refs/$_:".lrfetchrefs."/$_";
+       } @specs;
+
+       printdebug "git_fetch_us 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;
+
+       %lrfetchrefs_f = ();
+       my %objgot;
+
+       git_for_each_ref(lrfetchrefs, sub {
+           my ($objid,$objtype,$lrefname,$reftail) = @_;
+           $lrfetchrefs_f{$lrefname} = $objid;
+           $objgot{$objid} = 1;
+       });
+
+       foreach my $lrefname (sort keys %lrfetchrefs_f) {
+           my $rrefname = 'refs'.substr($lrefname, length lrfetchrefs);
+           if (!exists $wantr{$rrefname}) {
+               if ($wanted_rref->($rrefname)) {
+                   printdebug <<END;
+git-fetch @fspecs created $lrefname which git ls-remote @look didn't list.
+END
+               } else {
+                   print STDERR <<END
+warning: git fetch @fspecs created $lrefname; this is silly, deleting it.
+END
+               }
+               runcmd_ordryrun_local @git, qw(update-ref -d), $lrefname;
+               delete $lrfetchrefs_f{$lrefname};
+               next;
+           }
+       }
+       foreach my $rrefname (sort keys %wantr) {
+           my $lrefname = lrfetchrefs.substr($rrefname, 4);
+           my $got = $lrfetchrefs_f{$lrefname} // '<none>';
+           my $want = $wantr{$rrefname};
+           next if $got eq $want;
+           if (!defined $objgot{$want}) {
+               print STDERR <<END;
+warning: git ls-remote suggests we want $lrefname
+warning:  and it should refer to $want
+warning:  but git fetch didn't fetch that object to any relevant ref.
+warning:  This may be due to a race with someone updating the server.
+warning:  Will try again...
+END
+               next FETCH_ITERATION;
+           }
+           printdebug <<END;
+git-fetch @fspecs made $lrefname=$got but want git ls-remote @look says $want
+END
+           runcmd_ordryrun_local @git, qw(update-ref -m),
+               "dgit fetch git fetch fixup", $lrefname, $want;
+           $lrfetchrefs_f{$lrefname} = $want;
+       }
+       last;
+    }
+    printdebug "git_fetch_us: git fetch --no-insane emulation complete\n",
+       Dumper(\%lrfetchrefs_f);
+
+    my %here;
+    my @tagpats = debiantags('*',access_nomdistro);
+
+    git_for_each_ref([map { "refs/tags/$_" } @tagpats], sub {
+       my ($objid,$objtype,$fullrefname,$reftail) = @_;
+       printdebug "currently $fullrefname=$objid\n";
+       $here{$fullrefname} = $objid;
+    });
+    git_for_each_ref([map { lrfetchrefs."/tags/".$_ } @tagpats], sub {
+       my ($objid,$objtype,$fullrefname,$reftail) = @_;
+       my $lref = "refs".substr($fullrefname, length(lrfetchrefs));
+       printdebug "offered $lref=$objid\n";
+       if (!defined $here{$lref}) {
+           my @upd = (@git, qw(update-ref), $lref, $objid, '');
+           runcmd_ordryrun_local @upd;
+           lrfetchref_used $fullrefname;
+       } elsif ($here{$lref} eq $objid) {
+           lrfetchref_used $fullrefname;
+       } else {
+           print STDERR \
+               "Not updateting $lref from $here{$lref} to $objid.\n";
+       }
+    });
+}
+
+sub mergeinfo_getclogp ($) {
+    # Ensures thit $mi->{Clogp} exists and returns it
+    my ($mi) = @_;
+    $mi->{Clogp} = commit_getclogp($mi->{Commit});
+}
+
+sub mergeinfo_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 fetch_from_archive () {
+    ensure_setup_existing_tree();
+
+    # Ensures that lrref() is what is actually in the archive, one way
+    # or another, according to us - ie this client's
+    # appropritaely-updated archive view.  Also returns the commit id.
+    # If there is nothing in the archive, leaves lrref alone and
+    # returns undef.  git_fetch_us must have already been called.
+    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";
+       }
+    } else {
+       progress "no version available from the archive";
+    }
+
+    # If the archive's .dsc has a Dgit field, there are three
+    # relevant git commitids we need to choose between and/or merge
+    # together:
+    #   1. $dsc_hash: the Dgit field from the archive
+    #   2. $lastpush_hash: the suite branch on the dgit git server
+    #   3. $lastfetch_hash: our local tracking brach for the suite
+    #
+    # These may all be distinct and need not be in any fast forward
+    # relationship:
+    #
+    # If the dsc was pushed to this suite, then the server suite
+    # branch will have been updated; but it might have been pushed to
+    # a different suite and copied by the archive.  Conversely a more
+    # recent version may have been pushed with dgit but not appeared
+    # in the archive (yet).
+    #
+    # $lastfetch_hash may be awkward because archive imports
+    # (particularly, imports of Dgit-less .dscs) are performed only as
+    # needed on individual clients, so different clients may perform a
+    # different subset of them - and these imports are only made
+    # public during push.  So $lastfetch_hash may represent a set of
+    # imports different to a subsequent upload by a different dgit
+    # client.
+    #
+    # Our approach is as follows:
+    #
+    # As between $dsc_hash and $lastpush_hash: if $lastpush_hash is a
+    # descendant of $dsc_hash, then it was pushed by a dgit user who
+    # had based their work on $dsc_hash, so we should prefer it.
+    # Otherwise, $dsc_hash was installed into this suite in the
+    # archive other than by a dgit push, and (necessarily) after the
+    # last dgit push into that suite (since a dgit push would have
+    # been descended from the dgit server git branch); thus, in that
+    # case, we prefer the archive's version (and produce a
+    # pseudo-merge to overwrite the dgit server git branch).
+    #
+    # (If there is no Dgit field in the archive's .dsc then
+    # generate_commit_from_dsc uses the version numbers to decide
+    # whether the suite branch or the archive is newer.  If the suite
+    # branch is newer it ignores the archive's .dsc; otherwise it
+    # generates an import of the .dsc, and produces a pseudo-merge to
+    # overwrite the suite branch with the archive contents.)
+    #
+    # The outcome of that part of the algorithm is the `public view',
+    # and is same for all dgit clients: it does not depend on any
+    # unpublished history in the local tracking branch.
+    #
+    # As between the public view and the local tracking branch: The
+    # local tracking branch is only updated by dgit fetch, and
+    # whenever dgit fetch runs it includes the public view in the
+    # local tracking branch.  Therefore if the public view is not
+    # descended from the local tracking branch, the local tracking
+    # branch must contain history which was imported from the archive
+    # but never pushed; and, its tip is now out of date.  So, we make
+    # a pseudo-merge to overwrite the old imports and stitch the old
+    # history in.
+    #
+    # Finally: we do not necessarily reify the public view (as
+    # described above).  This is so that we do not end up stacking two
+    # pseudo-merges.  So what we actually do is figure out the inputs
+    # to any public view pseudo-merge and put them in @mergeinputs.
+
+    my @mergeinputs;
+    # $mergeinputs[]{Commit}
+    # $mergeinputs[]{Info}
+    # $mergeinputs[0] is the one whose tree we use
+    # @mergeinputs is in the order we use in the actual commit)
+    #
+    # Also:
+    # $mergeinputs[]{Message} is a commit message to use
+    # $mergeinputs[]{ReverseParents} if def specifies that parent
+    #                                list should be in opposite order
+    # Such an entry has no Commit or Info.  It applies only when found
+    # in the last entry.  (This ugliness is to support making
+    # identical imports to previous dgit versions.)
+
+    my $lastpush_hash = git_get_ref(lrfetchref());
+    printdebug "previous reference hash=$lastpush_hash\n";
+    $lastpush_mergeinput = $lastpush_hash && {
+        Commit => $lastpush_hash,
+       Info => "dgit suite branch on dgit git server",
+    };
+
+    my $lastfetch_hash = git_get_ref(lrref());
+    printdebug "fetch_from_archive: lastfetch=$lastfetch_hash\n";
+    my $lastfetch_mergeinput = $lastfetch_hash && {
+       Commit => $lastfetch_hash,
+       Info => "dgit client's archive history view",
+    };
+
+    my $dsc_mergeinput = $dsc_hash && {
+        Commit => $dsc_hash,
+        Info => "Dgit field in .dsc from archive",
+    };
+
+    my $cwd = getcwd();
+    my $del_lrfetchrefs = sub {
+       changedir $cwd;
+       my $gur;
+       printdebug "del_lrfetchrefs...\n";
+       foreach my $fullrefname (sort keys %lrfetchrefs_d) {
+           my $objid = $lrfetchrefs_d{$fullrefname};
+           printdebug "del_lrfetchrefs: $objid $fullrefname\n";
+           if (!$gur) {
+               $gur ||= new IO::Handle;
+               open $gur, "|-", qw(git update-ref --stdin) or die $!;
+           }
+           printf $gur "delete %s %s\n", $fullrefname, $objid;
+       }
+       if ($gur) {
+           close $gur or failedcmd "git update-ref delete lrfetchrefs";
+       }
+    };
+
+    if (defined $dsc_hash) {
+       ensure_we_have_orig();
+       if (!$lastpush_hash || $dsc_hash eq $lastpush_hash) {
+           @mergeinputs = $dsc_mergeinput
+       } elsif (is_fast_fwd($dsc_hash,$lastpush_hash)) {
+           print STDERR <<END or die $!;
+
+Git commit in archive is behind the last version allegedly pushed/uploaded.
+Commit referred to by archive: $dsc_hash
+Last version pushed with dgit: $lastpush_hash
+$later_warning_msg
+END
+           @mergeinputs = ($lastpush_mergeinput);
+       } else {
+           # Archive has .dsc which is not a descendant of the last dgit
+           # push.  This can happen if the archive moves .dscs about.
+           # Just follow its lead.
+           if (is_fast_fwd($lastpush_hash,$dsc_hash)) {
+               progress "archive .dsc names newer git commit";
+               @mergeinputs = ($dsc_mergeinput);
+           } else {
+               progress "archive .dsc names other git commit, fixing up";
+               @mergeinputs = ($dsc_mergeinput, $lastpush_mergeinput);
+           }
+       }
+    } elsif ($dsc) {
+       @mergeinputs = generate_commits_from_dsc();
+       # We have just done an import.  Now, our import algorithm might
+       # have been improved.  But even so we do not want to generate
+       # a new different import of the same package.  So if the
+       # version numbers are the same, just use our existing version.
+       # If the version numbers are different, the archive has changed
+       # (perhaps, rewound).
+       if ($lastfetch_mergeinput &&
+           !version_compare( (mergeinfo_version $lastfetch_mergeinput),
+                             (mergeinfo_version $mergeinputs[0]) )) {
+           @mergeinputs = ($lastfetch_mergeinput);
+       }
+    } elsif ($lastpush_hash) {
+       # only in git, not in the archive yet
+       @mergeinputs = ($lastpush_mergeinput);
+       print STDERR <<END or die $!;
+
+Package not found in the archive, but has allegedly been pushed using dgit.
+$later_warning_msg
+END
+    } else {
+       printdebug "nothing found!\n";
+       if (defined $skew_warning_vsn) {
+           print STDERR <<END or die $!;
+
+Warning: relevant archive skew detected.
+Archive allegedly contains $skew_warning_vsn
+But we were not able to obtain any version from the archive or git.
+
+END
+       }
+       unshift @end, $del_lrfetchrefs;
+       return undef;
+    }
+
+    if ($lastfetch_hash &&
+       !grep {
+           my $h = $_->{Commit};
+           $h and is_fast_fwd($lastfetch_hash, $h);
+           # If true, one of the existing parents of this commit
+           # is a descendant of the $lastfetch_hash, so we'll
+           # be ff from that automatically.
+       } @mergeinputs
+        ) {
+       # Otherwise:
+       push @mergeinputs, $lastfetch_mergeinput;
+    }
+
+    printdebug "fetch mergeinfos:\n";
+    foreach my $mi (@mergeinputs) {
+       if ($mi->{Info}) {
+           printdebug " commit $mi->{Commit} $mi->{Info}\n";
+       } else {
+           printdebug sprintf " ReverseParents=%d Message=%s",
+               $mi->{ReverseParents}, $mi->{Message};
+       }
+    }
+
+    my $compat_info= pop @mergeinputs
+       if $mergeinputs[$#mergeinputs]{Message};
+
+    @mergeinputs = grep { defined $_->{Commit} } @mergeinputs;
+
+    my $hash;
+    if (@mergeinputs > 1) {
+       # here we go, then:
+       my $tree_commit = $mergeinputs[0]{Commit};
+
+       my $tree = cmdoutput @git, qw(cat-file commit), $tree_commit;
+       $tree =~ m/\n\n/;  $tree = $`;
+       $tree =~ m/^tree (\w+)$/m or die "$dsc_hash tree ?";
+       $tree = $1;
+
+       # We use the changelog author of the package in question the
+       # author of this pseudo-merge.  This is (roughly) correct if
+       # this commit is simply representing aa non-dgit upload.
+       # (Roughly because it does not record sponsorship - but we
+       # don't have sponsorship info because that's in the .changes,
+       # which isn't in the archivw.)
+       #
+       # But, it might be that we are representing archive history
+       # updates (including in-archive copies).  These are not really
+       # the responsibility of the person who created the .dsc, but
+       # there is no-one whose name we should better use.  (The
+       # author of the .dsc-named commit is clearly worse.)
+
+       my $useclogp = mergeinfo_getclogp $mergeinputs[0];
+       my $author = clogp_authline $useclogp;
+       my $cversion = getfield $useclogp, 'Version';
+
+       my $mcf = ".git/dgit/mergecommit";
+       open MC, ">", $mcf or die "$mcf $!";
+       print MC <<END or die $!;
+tree $tree
+END
+
+       my @parents = grep { $_->{Commit} } @mergeinputs;
+       @parents = reverse @parents if $compat_info->{ReverseParents};
+       print MC <<END or die $! foreach @parents;
+parent $_->{Commit}
+END
+
+       print MC <<END or die $!;
+author $author
+committer $author
+
+END
+
+       if (defined $compat_info->{Message}) {
+           print MC $compat_info->{Message} or die $!;
+       } else {
+           print MC <<END or die $!;
+Record $package ($cversion) in archive suite $csuite
+
+Record that
+END
+           my $message_add_info = sub {
+               my ($mi) = (@_);
+               my $mversion = mergeinfo_version $mi;
+               printf MC "  %-20s %s\n", $mversion, $mi->{Info}
+                   or die $!;
+           };
+
+           $message_add_info->($mergeinputs[0]);
+           print MC <<END or die $!;
+should be treated as descended from
+END
+           $message_add_info->($_) foreach @mergeinputs[1..$#mergeinputs];
+       }
+
+       close MC or die $!;
+       $hash = make_commit $mcf;
+    } else {
+       $hash = $mergeinputs[0]{Commit};
+    }
+    printdebug "fetch hash=$hash\n";
+
+    my $chkff = sub {
+       my ($lasth, $what) = @_;
+       return unless $lasth;
+       die "$lasth $hash $what ?" unless is_fast_fwd($lasth, $hash);
+    };
+
+    $chkff->($lastpush_hash, 'dgit repo server tip (last push)')
+       if $lastpush_hash;
+    $chkff->($lastfetch_hash, 'local tracking tip (last fetch)');
+
+    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';
+       printdebug "SKEW CHECK GOT $got_vsn\n";
+       if (version_compare($got_vsn, $skew_warning_vsn) < 0) {
+           print STDERR <<END or die $!;
+
+Warning: archive skew detected.  Using the available version:
+Archive allegedly contains    $skew_warning_vsn
+We were able to obtain only   $got_vsn
+
+END
+       }
+    }
+
+    if ($lastfetch_hash ne $hash) {
+       fetch_from_archive_record_2($hash);
+    }
+
+    lrfetchref_used lrfetchref();
+
+    unshift @end, $del_lrfetchrefs;
+    return $hash;
+}
+
+sub set_local_git_config ($$) {
+    my ($k, $v) = @_;
+    runcmd @git, qw(config), $k, $v;
+}
+
+sub setup_mergechangelogs (;$) {
+    my ($always) = @_;
+    return unless $always || access_cfg_bool(1, 'setup-mergechangelogs');
+
+    my $driver = 'dpkg-mergechangelogs';
+    my $cb = "merge.$driver";
+    my $attrs = '.git/info/attributes';
+    ensuredir '.git/info';
+
+    open NATTRS, ">", "$attrs.new" or die "$attrs.new $!";
+    if (!open ATTRS, "<", $attrs) {
+       $!==ENOENT or die "$attrs: $!";
+    } else {
+       while (<ATTRS>) {
+           chomp;
+           next if m{^debian/changelog\s};
+           print NATTRS $_, "\n" or die $!;
+       }
+       ATTRS->error and die $!;
+       close ATTRS;
+    }
+    print NATTRS "debian/changelog merge=$driver\n" or die $!;
+    close NATTRS;
+
+    set_local_git_config "$cb.name", 'debian/changelog merge driver';
+    set_local_git_config "$cb.driver", 'dpkg-mergechangelogs -m %O %A %B %A';
+
+    rename "$attrs.new", "$attrs" or die "$attrs: $!";
+}
+
+sub setup_useremail (;$) {
+    my ($always) = @_;
+    return unless $always || access_cfg_bool(1, 'setup-useremail');
+
+    my $setup = sub {
+       my ($k, $envvar) = @_;
+       my $v = access_cfg("user-$k", 'RETURN-UNDEF') // $ENV{$envvar};
+       return unless defined $v;
+       set_local_git_config "user.$k", $v;
+    };
+
+    $setup->('email', 'DEBEMAIL');
+    $setup->('name', 'DEBFULLNAME');
+}
+
+sub ensure_setup_existing_tree () {
+    my $k = "remote.$remotename.skipdefaultupdate";
+    my $c = git_get_config $k;
+    return if defined $c;
+    set_local_git_config $k, 'true';
+}
+
+sub setup_new_tree () {
+    setup_mergechangelogs();
+    setup_useremail();
+}
+
+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) {
+       $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]) {
+       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 ($) {
+    my ($dstdir) = @_;
+    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);
+       exit 0;
+    }
+    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);
+    clone_set_head();
+    my $giturl = access_giturl(1);
+    if (defined $giturl) {
+       runcmd @git, qw(remote add), 'origin', $giturl;
+    }
+    if ($hasgit) {
+       progress "fetching existing git history";
+       git_fetch_us();
+       runcmd_ordryrun_local @git, qw(fetch origin);
+    } else {
+       progress "starting new git history";
+    }
+    fetch_from_archive() or no_such_package;
+    my $vcsgiturl = $dsc->{'Vcs-Git'};
+    if (length $vcsgiturl) {
+       $vcsgiturl =~ s/\s+-b\s+\S+//g;
+       runcmd @git, qw(remote add vcs-git), $vcsgiturl;
+    }
+    setup_new_tree();
+    clone_finish($dstdir);
+}
+
+sub fetch () {
+    canonicalise_suite();
+    if (check_for_git()) {
+       git_fetch_us();
+    }
+    fetch_from_archive() or no_such_package();
+    printdone "fetched into ".lrref();
+}
+
+sub pull () {
+    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";
+}
+
+sub check_not_dirty () {
+    foreach my $f (qw(local-options local-patch-header)) {
+       if (stat_exists "debian/source/$f") {
+           fail "git tree contains debian/source/$f";
+       }
+    }
+
+    return if $ignoredirty;
+
+    my @cmd = (@git, qw(diff --quiet HEAD));
+    debugcmd "+",@cmd;
+    $!=0; $?=-1; system @cmd;
+    return if !$?;
+    if ($?==256) {
+       fail "working tree is dirty (does not match HEAD)";
+    } else {
+       failedcmd @cmd;
+    }
+}
+
+sub commit_admin ($) {
+    my ($m) = @_;
+    progress "$m";
+    runcmd_ordryrun_local @git, qw(commit -m), $m;
+}
+
+sub commit_quilty_patch () {
+    my $output = cmdoutput @git, qw(status --porcelain);
+    my %adds;
+    foreach my $l (split /\n/, $output) {
+       next unless $l =~ m/\S/;
+       if ($l =~ m{^(?:\?\?| M) (.pc|debian/patches)}) {
+           $adds{$1}++;
+       }
+    }
+    delete $adds{'.pc'}; # if there wasn't one before, don't add it
+    if (!%adds) {
+       progress "nothing quilty to commit, ok.";
+       return;
+    }
+    my @adds = map { s/[][*?\\]/\\$&/g; $_; } sort keys %adds;
+    runcmd_ordryrun_local @git, qw(add -f), @adds;
+    commit_admin <<END
+Commit Debian 3.0 (quilt) metadata
+
+[dgit ($our_version) quilt-fixup]
+END
+}
+
+sub get_source_format () {
+    my %options;
+    if (open F, "debian/source/options") {
+       while (<F>) {
+           next if m/^\s*\#/;
+           next unless m/\S/;
+           s/\s+$//; # ignore missing final newline
+           if (m/\s*\#\s*/) {
+               my ($k, $v) = ($`, $'); #');
+               $v =~ s/^"(.*)"$/$1/;
+               $options{$k} = $v;
+           } else {
+               $options{$_} = 1;
+           }
+       }
+       F->error and die $!;
+       close F;
+    } else {
+       die $! unless $!==&ENOENT;
+    }
+
+    if (!open F, "debian/source/format") {
+       die $! unless $!==&ENOENT;
+       return '';
+    }
+    $_ = <F>;
+    F->error and die $!;
+    chomp;
+    return ($_, \%options);
+}
+
+sub madformat_wantfixup ($) {
+    my ($format) = @_;
+    return 0 unless $format eq '3.0 (quilt)';
+    our $quilt_mode_warned;
+    if ($quilt_mode eq 'nocheck') {
+       progress "Not doing any fixup of \`$format' due to".
+           " ----no-quilt-fixup or --quilt=nocheck"
+           unless $quilt_mode_warned++;
+       return 0;
+    }
+    progress "Format \`$format', need to check/update patch stack"
+       unless $quilt_mode_warned++;
+    return 1;
+}
+
+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 ../../../..",
+              @git, qw(update-ref -m),
+              "dgit --dgit-view-save $msg HEAD=$headref",
+              $split_brain_save, $dgitview);
+    runcmd @cmd;
+    return "and left in $split_brain_save";
+}
+
+# An "infopair" is a tuple [ $thing, $what ]
+# (often $thing is a commit hash; $what is a description)
+
+sub infopair_cond_equal ($$) {
+    my ($x,$y) = @_;
+    $x->[0] eq $y->[0] or fail <<END;
+$x->[1] ($x->[0]) not equal to $y->[1] ($y->[0])
+END
+};
+
+sub infopair_lrf_tag_lookup ($$) {
+    my ($tagnames, $what) = @_;
+    # $tagname may be an array ref
+    my @tagnames = ref $tagnames ? @$tagnames : ($tagnames);
+    printdebug "infopair_lrfetchref_tag_lookup $what @tagnames\n";
+    foreach my $tagname (@tagnames) {
+       my $lrefname = lrfetchrefs."/tags/$tagname";
+       my $tagobj = $lrfetchrefs_f{$lrefname};
+       next unless defined $tagobj;
+       printdebug "infopair_lrfetchref_tag_lookup $tagobj $tagname $what\n";
+       return [ git_rev_parse($tagobj), $what ];
+    }
+    fail @tagnames==1 ? <<END : <<END;
+Wanted tag $what (@tagnames) on dgit server, but not found
+END
+Wanted tag $what (one of: @tagnames) on dgit server, but not found
+END
+}
+
+sub infopair_cond_ff ($$) {
+    my ($anc,$desc) = @_;
+    is_fast_fwd($anc->[0], $desc->[0]) or fail <<END;
+$anc->[1] ($anc->[0]) .. $desc->[1] ($desc->[0]) is not fast forward
+END
+};
+
+sub pseudomerge_version_check ($$) {
+    my ($clogp, $archive_hash) = @_;
+
+    my $arch_clogp = commit_getclogp $archive_hash;
+    my $i_arch_v = [ (getfield $arch_clogp, 'Version'),
+                    'version currently in archive' ];
+    if (defined $overwrite_version) {
+       if (length $overwrite_version) {
+           infopair_cond_equal([ $overwrite_version,
+                                 '--overwrite= version' ],
+                               $i_arch_v);
+       } else {
+           my $v = $i_arch_v->[0];
+           progress "Checking package changelog for archive version $v ...";
+           eval {
+               my @xa = ("-f$v", "-t$v");
+               my $vclogp = parsechangelog @xa;
+               my $cv = [ (getfield $vclogp, 'Version'),
+                          "Version field from dpkg-parsechangelog @xa" ];
+               infopair_cond_equal($i_arch_v, $cv);
+           };
+           if ($@) {
+               $@ =~ s/^dgit: //gm;
+               fail "$@".
+                   "Perhaps debian/changelog does not mention $v ?";
+           }
+       }
+    }
+    
+    printdebug "pseudomerge_version_check i_arch_v @$i_arch_v\n";
+    return $i_arch_v;
+}
+
+sub pseudomerge_make_commit ($$$$ $$) {
+    my ($clogp, $dgitview, $archive_hash, $i_arch_v,
+       $msg_cmd, $msg_msg) = @_;
+    progress "Declaring that HEAD inciudes all changes in $i_arch_v->[0]...";
+
+    my $tree = cmdoutput qw(git rev-parse), "${dgitview}:";
+    my $authline = clogp_authline $clogp;
+
+    chomp $msg_msg;
+    $msg_cmd .=
+       !defined $overwrite_version ? ""
+       : !length  $overwrite_version ? " --overwrite"
+       : " --overwrite=".$overwrite_version;
+
+    mkpath '.git/dgit';
+    my $pmf = ".git/dgit/pseudomerge";
+    open MC, ">", $pmf or die "$pmf $!";
+    print MC <<END or die $!;
+tree $tree
+parent $dgitview
+parent $archive_hash
+author $authline
+committer $authline
+
+$msg_msg
+
+[$msg_cmd]
+END
+    close MC or die $!;
+
+    return make_commit($pmf);
+}
+
+sub splitbrain_pseudomerge ($$$$) {
+    my ($clogp, $maintview, $dgitview, $archive_hash) = @_;
+    # => $merged_dgitview
+    printdebug "splitbrain_pseudomerge...\n";
+    #
+    #     We:      debian/PREVIOUS    HEAD($maintview)
+    # expect:          o ----------------- o
+    #                    \                   \
+    #                     o                   o
+    #                 a/d/PREVIOUS        $dgitview
+    #                $archive_hash              \
+    #  If so,                \                   \
+    #  we do:                 `------------------ o
+    #   this:                                   $dgitview'
+    #
+
+    return $dgitview unless defined $archive_hash;
+
+    printdebug "splitbrain_pseudomerge...\n";
+
+    my $i_arch_v = pseudomerge_version_check($clogp, $archive_hash);
+
+    if (!defined $overwrite_version) {
+       progress "Checking that HEAD inciudes all changes in archive...";
+    }
+
+    return $dgitview if is_fast_fwd $archive_hash, $dgitview;
+
+    if (defined $overwrite_version) {
+    } elsif (!eval {
+       my $t_dep14 = debiantag_maintview $i_arch_v->[0], access_nomdistro;
+       my $i_dep14 = infopair_lrf_tag_lookup($t_dep14, "maintainer view tag");
+       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" ];
+
+       printdebug "splitbrain_pseudomerge i_archive @$i_archive\n";
+
+       infopair_cond_equal($i_dgit, $i_archive);
+       infopair_cond_ff($i_dep14, $i_dgit);
+       infopair_cond_ff($i_dep14, [ $maintview, 'HEAD' ]);
+       1;
+    }) {
+       print STDERR <<END;
+$us: check failed (maybe --overwrite is needed, consult documentation)
+END
+       die "$@";
+    }
+
+    my $r = pseudomerge_make_commit
+       $clogp, $dgitview, $archive_hash, $i_arch_v,
+       "dgit --quilt=$quilt_mode",
+       (defined $overwrite_version ? <<END_OVERWR : <<END_MAKEFF);
+Declare fast forward from $i_arch_v->[0]
+END_OVERWR
+Make fast forward from $i_arch_v->[0]
+END_MAKEFF
+
+    maybe_split_brain_save $maintview, $r, "pseudomerge";
+
+    progress "Made pseudo-merge of $i_arch_v->[0] into dgit view.";
+    return $r;
+}      
+
+sub plain_overwrite_pseudomerge ($$$) {
+    my ($clogp, $head, $archive_hash) = @_;
+
+    printdebug "plain_overwrite_pseudomerge...";
+
+    my $i_arch_v = pseudomerge_version_check($clogp, $archive_hash);
+
+    return $head if is_fast_fwd $archive_hash, $head;
+
+    my $m = "Declare fast forward from $i_arch_v->[0]";
+
+    my $r = pseudomerge_make_commit
+       $clogp, $head, $archive_hash, $i_arch_v,
+       "dgit", $m;
+
+    runcmd @git, qw(update-ref -m), $m, 'HEAD', $r, $head;
+
+    progress "Make pseudo-merge of $i_arch_v->[0] into your HEAD.";
+    return $r;
+}
+
+sub push_parse_changelog ($) {
+    my ($clogpfn) = @_;
+
+    my $clogp = Dpkg::Control::Hash->new();
+    $clogp->load($clogpfn) or die;
+
+    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 $tag = debiantag($cversion, access_nomdistro);
+    runcmd @git, qw(check-ref-format), $tag;
+
+    my $dscfn = dscfn($cversion);
+
+    return ($clogp, $cversion, $dscfn);
+}
+
+sub push_parse_dsc ($$$) {
+    my ($dscfn,$dscfnwhat, $cversion) = @_;
+    $dsc = parsecontrol($dscfn,$dscfnwhat);
+    my $dversion = getfield $dsc, 'Version';
+    my $dscpackage = getfield $dsc, 'Source';
+    ($dscpackage eq $package && $dversion eq $cversion) or
+       fail "$dscfn is for $dscpackage $dversion".
+           " but debian/changelog is for $package $cversion";
+}
+
+sub push_tagwants ($$$$) {
+    my ($cversion, $dgithead, $maintviewhead, $tfbase) = @_;
+    my @tagwants;
+    push @tagwants, {
+        TagFn => \&debiantag,
+       Objid => $dgithead,
+        TfSuffix => '',
+        View => 'dgit',
+    };
+    if (defined $maintviewhead) {
+       push @tagwants, {
+            TagFn => \&debiantag_maintview,
+           Objid => $maintviewhead,
+           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) {
+       $tw->{Tag} = $tw->{TagFn}($cversion, access_nomdistro);
+       $tw->{Tfn} = sub { $tfbase.$tw->{TfSuffix}.$_[0]; };
+    }
+    printdebug 'push_tagwants: ', Dumper(\@_, \@tagwants);
+    return @tagwants;
+}
+
+sub push_mktags ($$ $$ $) {
+    my ($clogp,$dscfn,
+       $changesfile,$changesfilewhat,
+        $tagwants) = @_;
+
+    die unless $tagwants->[0]{View} eq 'dgit';
+
+    $dsc->{$ourdscfield[0]} = $tagwants->[0]{Objid};
+    $dsc->save("$dscfn.tmp") or die $!;
+
+    my $changes = parsecontrol($changesfile,$changesfilewhat);
+    foreach my $field (qw(Source Distribution Version)) {
+       $changes->{$field} eq $clogp->{$field} or
+           fail "changes field $field \`$changes->{$field}'".
+               " does not match changelog \`$clogp->{$field}'";
+    }
+
+    my $cversion = getfield $clogp, 'Version';
+    my $clogsuite = getfield $clogp, 'Distribution';
+
+    # We make the git tag by hand because (a) that makes it easier
+    # to control the "tagger" (b) we can do remote signing
+    my $authline = clogp_authline $clogp;
+    my $delibs = join(" ", "",@deliberatelies);
+    my $declaredistro = access_nomdistro();
+
+    my $mktag = sub {
+       my ($tw) = @_;
+       my $tfn = $tw->{Tfn};
+       my $head = $tw->{Objid};
+       my $tag = $tw->{Tag};
+
+       open TO, '>', $tfn->('.tmp') or die $!;
+       print TO <<END or die $!;
+object $head
+type commit
+tag $tag
+tagger $authline
+
+END
+       if ($tw->{View} eq 'dgit') {
+           print TO <<END or die $!;
+$package release $cversion for $clogsuite ($csuite) [dgit]
+[dgit distro=$declaredistro$delibs]
+END
+           foreach my $ref (sort keys %previously) {
+               print TO <<END or die $!;
+[dgit previously:$ref=$previously{$ref}]
+END
+           }
+       } elsif ($tw->{View} eq 'maint') {
+           print TO <<END or die $!;
+$package release $cversion for $clogsuite ($csuite)
+(maintainer view tag generated by dgit --quilt=$quilt_mode)
+END
+       } else {
+           die Dumper($tw)."?";
+       }
+
+       close TO or die $!;
+
+       my $tagobjfn = $tfn->('.tmp');
+       if ($sign) {
+           if (!defined $keyid) {
+               $keyid = access_cfg('keyid','RETURN-UNDEF');
+           }
+           if (!defined $keyid) {
+               $keyid = getfield $clogp, 'Maintainer';
+           }
+           unlink $tfn->('.tmp.asc') or $!==&ENOENT or die $!;
+           my @sign_cmd = (@gpg, qw(--detach-sign --armor));
+           push @sign_cmd, qw(-u),$keyid if defined $keyid;
+           push @sign_cmd, $tfn->('.tmp');
+           runcmd_ordryrun @sign_cmd;
+           if (act_scary()) {
+               $tagobjfn = $tfn->('.signed.tmp');
+               runcmd shell_cmd "exec >$tagobjfn", qw(cat --),
+                   $tfn->('.tmp'), $tfn->('.tmp.asc');
+           }
+       }
+       return $tagobjfn;
+    };
+
+    my @r = map { $mktag->($_); } @$tagwants;
+    return @r;
+}
+
+sub sign_changes ($) {
+    my ($changesfile) = @_;
+    if ($sign) {
+       my @debsign_cmd = @debsign;
+       push @debsign_cmd, "-k$keyid" if defined $keyid;
+       push @debsign_cmd, "-p$gpg[0]" if $gpg[0] ne 'gpg';
+       push @debsign_cmd, $changesfile;
+       runcmd_ordryrun @debsign_cmd;
+    }
+}
+
+sub dopush () {
+    printdebug "actually entering push\n";
+
+    supplementary_message(<<'END');
+Push failed, while checking state of the archive.
+You can retry the push, after fixing the problem, if you like.
+END
+    if (check_for_git()) {
+       git_fetch_us();
+    }
+    my $archive_hash = fetch_from_archive();
+    if (!$archive_hash) {
+       $new_package or
+           fail "package appears to be new in this suite;".
+               " if this is intentional, use --new";
+    }
+
+    supplementary_message(<<'END');
+Push failed, while preparing your push.
+You can retry the push, after fixing the problem, if you like.
+END
+
+    need_tagformat 'new', "quilt mode $quilt_mode"
+        if quiltmode_splitbrain;
+
+    prep_ud();
+
+    access_giturl(); # check that success is vaguely likely
+    select_tagformat();
+
+    my $clogpfn = ".git/dgit/changelog.822.tmp";
+    runcmd shell_cmd "exec >$clogpfn", qw(dpkg-parsechangelog);
+
+    responder_send_file('parsed-changelog', $clogpfn);
+
+    my ($clogp, $cversion, $dscfn) =
+       push_parse_changelog("$clogpfn");
+
+    my $dscpath = "$buildproductsdir/$dscfn";
+    stat_exists $dscpath or
+       fail "looked for .dsc $dscpath, but $!;".
+           " maybe you forgot to build";
+
+    responder_send_file('dsc', $dscpath);
+
+    push_parse_dsc($dscpath, $dscfn, $cversion);
+
+    my $format = getfield $dsc, 'Format';
+    printdebug "format $format\n";
+
+    my $actualhead = git_rev_parse('HEAD');
+    my $dgithead = $actualhead;
+    my $maintviewhead = undef;
+
+    my $upstreamversion = upstreamversion $clogp->{Version};
+
+    if (madformat_wantfixup($format)) {
+       # user might have not used dgit build, so maybe do this now:
+       if (quiltmode_splitbrain()) {
+           changedir $ud;
+           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] ?";
+           $split_brain = 1;
+           $dgithead = splitbrain_pseudomerge($clogp,
+                                              $actualhead, $dgithead,
+                                              $archive_hash);
+           $maintviewhead = $actualhead;
+           changedir '../../../..';
+           prep_ud(); # so _only_subdir() works, below
+       } else {
+           commit_quilty_patch();
+       }
+    }
+
+    if (defined $overwrite_version && !defined $maintviewhead) {
+       $dgithead = plain_overwrite_pseudomerge($clogp,
+                                               $dgithead,
+                                               $archive_hash);
+    }
+
+    check_not_dirty();
+
+    my $forceflag = '';
+    if ($archive_hash) {
+       if (is_fast_fwd($archive_hash, $dgithead)) {
+           # ok
+       } elsif (deliberately_not_fast_forward) {
+           $forceflag = '+';
+       } else {
+           fail "dgit push: HEAD is not a descendant".
+               " of the archive's version.\n".
+               "To overwrite the archive's contents,".
+               " pass --overwrite[=VERSION].\n".
+               "To rewind history, if permitted by the archive,".
+               " use --deliberately-not-fast-forward.";
+       }
+    }
+
+    changedir $ud;
+    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("source package");
+    check_for_vendor_patches() if madformat($dsc->{format});
+    changedir '../../../..';
+    my @diffcmd = (@git, qw(diff --quiet), $tree, $dgithead);
+    debugcmd "+",@diffcmd;
+    $!=0; $?=-1;
+    my $r = system @diffcmd;
+    if ($r) {
+       if ($r==256) {
+           my $diffs = cmdoutput @git, qw(diff --stat), $tree, $dgithead;
+           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
+END
+       } else {
+           failedcmd @diffcmd;
+       }
+    }
+    if (!$changesfile) {
+       my $pat = changespat $cversion;
+       my @cs = glob "$buildproductsdir/$pat";
+       fail "failed to find unique changes file".
+           " (looked for $pat in $buildproductsdir);".
+           " perhaps you need to use dgit -C"
+           unless @cs==1;
+       ($changesfile) = @cs;
+    } else {
+       $changesfile = "$buildproductsdir/$changesfile";
+    }
+
+    # Check that changes and .dsc agree enough
+    $changesfile =~ m{[^/]*$};
+    my $changes = parsecontrol($changesfile,$&);
+    files_compare_inputs($dsc, $changes)
+       unless forceing [qw(dsc-changes-mismatch)];
+
+    # Perhaps adjust .dsc to contain right set of origs
+    changes_update_origs_from_dsc($dsc, $changes, $upstreamversion,
+                                 $changesfile)
+       unless forceing [qw(changes-origs-exactly)];
+
+    # Checks complete, we're going to try and go ahead:
+
+    responder_send_file('changes',$changesfile);
+    responder_send_command("param head $dgithead");
+    responder_send_command("param csuite $csuite");
+    responder_send_command("param tagformat $tagformat");
+    if (defined $maintviewhead) {
+       die unless ($protovsn//4) >= 4;
+       responder_send_command("param maint-view $maintviewhead");
+    }
+
+    if (deliberately_not_fast_forward) {
+       git_for_each_ref(lrfetchrefs, sub {
+           my ($objid,$objtype,$lrfetchrefname,$reftail) = @_;
+           my $rrefname= substr($lrfetchrefname, length(lrfetchrefs) + 1);
+           responder_send_command("previously $rrefname=$objid");
+           $previously{$rrefname} = $objid;
+       });
+    }
+
+    my @tagwants = push_tagwants($cversion, $dgithead, $maintviewhead,
+                                ".git/dgit/tag");
+    my @tagobjfns;
+
+    supplementary_message(<<'END');
+Push failed, while signing the tag.
+You can retry the push, after fixing the problem, if you like.
+END
+    # If we manage to sign but fail to record it anywhere, it's fine.
+    if ($we_are_responder) {
+       @tagobjfns = map { $_->{Tfn}('.signed-tmp') } @tagwants;
+       responder_receive_files('signed-tag', @tagobjfns);
+    } else {
+       @tagobjfns = push_mktags($clogp,$dscpath,
+                             $changesfile,$changesfile,
+                             \@tagwants);
+    }
+    supplementary_message(<<'END');
+Push failed, *after* signing the tag.
+If you want to try again, you should use a new version number.
+END
+
+    pairwise { $a->{TagObjFn} = $b } @tagwants, @tagobjfns;
+
+    foreach my $tw (@tagwants) {
+       my $tag = $tw->{Tag};
+       my $tagobjfn = $tw->{TagObjFn};
+       my $tag_obj_hash =
+           cmdoutput @git, qw(hash-object -w -t tag), $tagobjfn;
+       runcmd_ordryrun @git, qw(verify-tag), $tag_obj_hash;
+       runcmd_ordryrun_local
+           @git, qw(update-ref), "refs/tags/$tag", $tag_obj_hash;
+    }
+
+    supplementary_message(<<'END');
+Push failed, while updating the remote git repository - see messages above.
+If you want to try again, you should use a new version number.
+END
+    if (!check_for_git()) {
+       create_remote_git_repo();
+    }
+
+    my @pushrefs = $forceflag.$dgithead.":".rrref();
+    foreach my $tw (@tagwants) {
+       push @pushrefs, $forceflag."refs/tags/$tw->{Tag}";
+    }
+
+    runcmd_ordryrun @git,
+       qw(-c push.followTags=false push), access_giturl(), @pushrefs;
+    runcmd_ordryrun @git, qw(update-ref -m), 'dgit push', lrref(), $dgithead;
+
+    supplementary_message(<<'END');
+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";
+       responder_receive_files('signed-dsc-changes',
+                               "$dscpath$dryrunsuffix",
+                               "$changesfile$dryrunsuffix");
+    } else {
+       if (act_local()) {
+           rename "$dscpath.tmp",$dscpath or die "$dscfn $!";
+       } else {
+           progress "[new .dsc left in $dscpath.tmp]";
+       }
+       sign_changes $changesfile;
+    }
+
+    supplementary_message(<<END);
+Push failed, while uploading package(s) to the archive server.
+You can retry the upload of exactly these same files with dput of:
+  $changesfile
+If that .changes file is broken, you will need to use a new version
+number for your next attempt at the upload.
+END
+    my $host = access_cfg('upload-host','RETURN-UNDEF');
+    my @hostarg = defined($host) ? ($host,) : ();
+    runcmd_ordryrun @dput, @hostarg, $changesfile;
+    printdone "pushed and uploaded $cversion";
+
+    supplementary_message('');
+    responder_send_command("complete");
+}
+
+sub cmd_clone {
+    parseopts();
+    notpushing();
+    my $dstdir;
+    badusage "-p is not allowed with clone; specify as argument instead"
+       if defined $package;
+    if (@ARGV==1) {
+       ($package) = @ARGV;
+    } elsif (@ARGV==2 && $ARGV[1] =~ m#^\w#) {
+       ($package,$isuite) = @ARGV;
+    } elsif (@ARGV==2 && $ARGV[1] =~ m#^[./]#) {
+       ($package,$dstdir) = @ARGV;
+    } elsif (@ARGV==3) {
+       ($package,$isuite,$dstdir) = @ARGV;
+    } else {
+       badusage "incorrect arguments to dgit clone";
+    }
+    $dstdir ||= "$package";
+
+    if (stat_exists $dstdir) {
+       fail "$dstdir already exists";
+    }
+
+    my $cwd_remove;
+    if ($rmonerror && !$dryrun_level) {
+       $cwd_remove= getcwd();
+       unshift @end, sub { 
+           return unless defined $cwd_remove;
+           if (!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 { $! == $_ }
+                    (ENOENT, ENOTDIR, EACCES, EPERM, ELOOP)) {
+           } else {
+               print STDERR "check whether to remove $dstdir: $!\n";
+           }
+       };
+    }
+
+    clone($dstdir);
+    $cwd_remove = undef;
+}
+
+sub branchsuite () {
+    my $branch = cmdoutput_errok @git, qw(symbolic-ref HEAD);
+    if ($branch =~ m#$lbranch_re#o) {
+       return $1;
+    } else {
+       return undef;
+    }
+}
+
+sub fetchpullargs () {
+    notpushing();
+    if (!defined $package) {
+       my $sourcep = parsecontrol('debian/control','debian/control');
+       $package = getfield $sourcep, 'Source';
+    }
+    if (@ARGV==0) {
+       $isuite = branchsuite();
+       if (!$isuite) {
+           my $clogp = parsechangelog();
+           $isuite = getfield $clogp, 'Distribution';
+       }
+    } elsif (@ARGV==1) {
+       ($isuite) = @ARGV;
+    } else {
+       badusage "incorrect arguments to dgit fetch or dgit pull";
+    }
+}
+
+sub cmd_fetch {
+    parseopts();
+    fetchpullargs();
+    my $multi_fetched = fork_for_multisuite(sub { });
+    exit 0 if $multi_fetched;
+    fetch();
+}
+
+sub cmd_pull {
+    parseopts();
+    fetchpullargs();
+    if (quiltmode_splitbrain()) {
+       my ($format, $fopts) = get_source_format();
+       madformat($format) and fail <<END
+dgit pull not yet supported in split view mode (--quilt=$quilt_mode)
+END
+    }
+    pull();
+}
+
+sub cmd_push {
+    parseopts();
+    pushing();
+    badusage "-p is not allowed with dgit push" if defined $package;
+    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";
+    }
+    $isuite = getfield $clogp, 'Distribution';
+    if ($new_package) {
+       local ($package) = $existing_package; # this is a hack
+       canonicalise_suite();
+    } else {
+       canonicalise_suite();
+    }
+    if (defined $specsuite &&
+       $specsuite ne $isuite &&
+       $specsuite ne $csuite) {
+           fail "dgit push: changelog specifies $isuite ($csuite)".
+               " but command line specifies $specsuite";
+    }
+    dopush();
+}
+
+#---------- remote commands' implementation ----------
+
+sub cmd_remote_push_build_host {
+    my ($nrargs) = shift @ARGV;
+    my (@rargs) = @ARGV[0..$nrargs-1];
+    @ARGV = @ARGV[$nrargs..$#ARGV];
+    die unless @rargs;
+    my ($dir,$vsnwant) = @rargs;
+    # vsnwant is a comma-separated list; we report which we have
+    # chosen in our ready response (so other end can tell if they
+    # offered several)
+    $debugprefix = ' ';
+    $we_are_responder = 1;
+    $us .= " (build host)";
+
+    pushing();
+
+    open PI, "<&STDIN" or die $!;
+    open STDIN, "/dev/null" or die $!;
+    open PO, ">&STDOUT" or die $!;
+    autoflush PO 1;
+    open STDOUT, ">&STDERR" or die $!;
+    autoflush STDOUT 1;
+
+    $vsnwant //= 1;
+    ($protovsn) = grep {
+       $vsnwant =~ m{^(?:.*,)?$_(?:,.*)?$}
+    } @rpushprotovsn_support;
+
+    fail "build host has dgit rpush protocol versions ".
+       (join ",", @rpushprotovsn_support).
+        " but invocation host has $vsnwant"
+       unless defined $protovsn;
+
+    responder_send_command("dgit-remote-push-ready $protovsn");
+    rpush_handle_protovsn_bothends();
+    changedir $dir;
+    &cmd_push;
+}
+
+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 rpush_handle_protovsn_bothends () {
+    if ($protovsn < 4) {
+       need_tagformat 'old', "rpush negotiated protocol $protovsn";
+    }
+    select_tagformat();
+}
+
+our $i_tmp;
+
+sub i_cleanup {
+    local ($@, $?);
+    my $report = i_child_report();
+    if (defined $report) {
+       printdebug "($report)\n";
+    } elsif ($i_child_pid) {
+       printdebug "(killing build host child $i_child_pid)\n";
+       kill 15, $i_child_pid;
+    }
+    if (defined $i_tmp && !defined $initiator_tempdir) {
+       changedir "/";
+       eval { rmtree $i_tmp; };
+    }
+}
+
+END { i_cleanup(); }
+
+sub i_method {
+    my ($base,$selector,@args) = @_;
+    $selector =~ s/\-/_/g;
+    { no strict qw(refs); &{"${base}_${selector}"}(@args); }
+}
+
+sub cmd_rpush {
+    pushing();
+    my $host = nextarg;
+    my $dir;
+    if ($host =~ m/^((?:[^][]|\[[^][]*\])*)\:/) {
+       $host = $1;
+       $dir = $'; #';
+    } else {
+       $dir = nextarg;
+    }
+    $dir =~ s{^-}{./-};
+    my @rargs = ($dir);
+    push @rargs, join ",", @rpushprotovsn_support;
+    my @rdgit;
+    push @rdgit, @dgit;
+    push @rdgit, @ropts;
+    push @rdgit, qw(remote-push-build-host), (scalar @rargs), @rargs;
+    push @rdgit, @ARGV;
+    my @cmd = (@ssh, $host, shellquote @rdgit);
+    debugcmd "+",@cmd;
+
+    if (defined $initiator_tempdir) {
+       rmtree $initiator_tempdir;
+       mkdir $initiator_tempdir, 0700 or die "$initiator_tempdir: $!";
+       $i_tmp = $initiator_tempdir;
+    } else {
+       $i_tmp = tempdir();
+    }
+    $i_child_pid = open2(\*RO, \*RI, @cmd);
+    changedir $i_tmp;
+    ($protovsn) = initiator_expect { m/^dgit-remote-push-ready (\S+)/ };
+    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+)(?: (.*))?$/;
+           ($1,$2);
+       };
+       i_method "i_resp", $icmd, $iargs;
+    }
+}
+
+sub i_resp_progress ($) {
+    my ($rhs) = @_;
+    my $msg = protocol_read_bytes \*RO, $rhs;
+    progress $msg;
+}
+
+sub i_resp_supplementary_message ($) {
+    my ($rhs) = @_;
+    $supplementary_message = protocol_read_bytes \*RO, $rhs;
+}
+
+sub i_resp_complete {
+    my $pid = $i_child_pid;
+    $i_child_pid = undef; # prevents killing some other process with same pid
+    printdebug "waiting for build host child $pid...\n";
+    my $got = waitpid $pid, 0;
+    die $! unless $got == $pid;
+    die "build host child failed $?" if $?;
+
+    i_cleanup();
+    printdebug "all done\n";
+    exit 0;
+}
+
+sub i_resp_file ($) {
+    my ($keyword) = @_;
+    my $localname = i_method "i_localname", $keyword;
+    my $localpath = "$i_tmp/$localname";
+    stat_exists $localpath and
+       badproto \*RO, "file $keyword ($localpath) twice";
+    protocol_receive_file \*RO, $localpath;
+    i_method "i_file", $keyword;
+}
+
+our %i_param;
+
+sub i_resp_param ($) {
+    $_[0] =~ m/^(\S+) (.*)$/ or badproto \*RO, "bad param spec";
+    $i_param{$1} = $2;
+}
+
+sub i_resp_previously ($) {
+    $_[0] =~ m#^(refs/tags/\S+)=(\w+)$#
+       or badproto \*RO, "bad previously spec";
+    my $r = system qw(git check-ref-format), $1;
+    die "bad previously ref spec ($r)" if $r;
+    $previously{$1} = $2;
+}
+
+our %i_wanted;
+
+sub i_resp_want ($) {
+    my ($keyword) = @_;
+    die "$keyword ?" if $i_wanted{$keyword}++;
+    my @localpaths = i_method "i_want", $keyword;
+    printdebug "[[  $keyword @localpaths\n";
+    foreach my $localpath (@localpaths) {
+       protocol_send_file \*RI, $localpath;
+    }
+    print RI "files-end\n" or die $!;
+}
+
+our ($i_clogp, $i_version, $i_dscfn, $i_changesfn);
+
+sub i_localname_parsed_changelog {
+    return "remote-changelog.822";
+}
+sub i_file_parsed_changelog {
+    ($i_clogp, $i_version, $i_dscfn) =
+       push_parse_changelog "$i_tmp/remote-changelog.822";
+    die if $i_dscfn =~ m#/|^\W#;
+}
+
+sub i_localname_dsc {
+    defined $i_dscfn or badproto \*RO, "dsc (before parsed-changelog)";
+    return $i_dscfn;
+}
+sub i_file_dsc { }
+
+sub i_localname_changes {
+    defined $i_dscfn or badproto \*RO, "dsc (before parsed-changelog)";
+    $i_changesfn = $i_dscfn;
+    $i_changesfn =~ s/\.dsc$/_dgit.changes/ or die;
+    return $i_changesfn;
+}
+sub i_file_changes { }
+
+sub i_want_signed_tag {
+    printdebug Dumper(\%i_param, $i_dscfn);
+    defined $i_param{'head'} && defined $i_dscfn && defined $i_clogp
+       && defined $i_param{'csuite'}
+       or badproto \*RO, "premature desire for signed-tag";
+    my $head = $i_param{'head'};
+    die if $head =~ m/[^0-9a-f]/ || $head !~ m/^../;
+
+    my $maintview = $i_param{'maint-view'};
+    die if defined $maintview && $maintview =~ m/[^0-9a-f]/;
+
+    select_tagformat();
+    if ($protovsn >= 4) {
+       my $p = $i_param{'tagformat'} // '<undef>';
+       $p eq $tagformat
+           or badproto \*RO, "tag format mismatch: $p vs. $tagformat";
+    }
+
+    die unless $i_param{'csuite'} =~ m/^$suite_re$/;
+    $csuite = $&;
+    push_parse_dsc $i_dscfn, 'remote dsc', $i_version;
+
+    my @tagwants = push_tagwants $i_version, $head, $maintview, "tag";
+
+    return
+       push_mktags $i_clogp, $i_dscfn,
+           $i_changesfn, 'remote changes',
+           \@tagwants;
+}
+
+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);
+}
+
+#---------- building etc. ----------
+
+our $version;
+our $sourcechanges;
+our $dscfn;
+
+#----- `3.0 (quilt)' handling -----
+
+our $fakeeditorenv = 'DGIT_FAKE_EDITOR_QUILT';
+
+sub quiltify_dpkg_commit ($$$;$) {
+    my ($patchname,$author,$msg, $xinfo) = @_;
+    $xinfo //= '';
+
+    mkpath '.git/dgit';
+    my $descfn = ".git/dgit/quilt-description.tmp";
+    open O, '>', $descfn or die "$descfn: $!";
+    $msg =~ s/\n+/\n\n/;
+    print O <<END or die $!;
+From: $author
+${xinfo}Subject: $msg
+---
+
+END
+    close O or die $!;
+
+    {
+       local $ENV{'EDITOR'} = cmdoutput qw(realpath --), $0;
+       local $ENV{'VISUAL'} = $ENV{'EDITOR'};
+       local $ENV{$fakeeditorenv} = cmdoutput qw(realpath --), $descfn;
+       runcmd @dpkgsource, qw(--commit --include-removal .), $patchname;
+    }
+}
+
+sub quiltify_trees_differ ($$;$$$) {
+    my ($x,$y,$finegrained,$ignorenamesr,$unrepres) = @_;
+    # returns true iff the two tree objects differ other than in debian/
+    # with $finegrained,
+    # returns bitmask 01 - differ in upstream files except .gitignore
+    #                 02 - differ in .gitignore
+    # if $ignorenamesr is defined, $ingorenamesr->{$fn}
+    #  is set for each modified .gitignore filename $fn
+    # if $unrepres is defined, array ref to which is appeneded
+    #  a list of unrepresentable changes (removals of upstream files
+    #  (as messages)
+    local $/=undef;
+    my @cmd = (@git, qw(diff-tree -z));
+    push @cmd, qw(--name-only) unless $unrepres;
+    push @cmd, qw(-r) if $finegrained || $unrepres;
+    push @cmd, $x, $y;
+    my $diffs= cmdoutput @cmd;
+    my $r = 0;
+    my @lmodes;
+    foreach my $f (split /\0/, $diffs) {
+       if ($unrepres && !@lmodes) {
+           @lmodes = $f =~ m/^\:(\w+) (\w+) \w+ \w+ / or die "$_ ?";
+           next;
+       }
+       my ($oldmode,$newmode) = @lmodes;
+       @lmodes = ();
+
+       next if $f =~ m#^debian(?:/.*)?$#s;
+
+       if ($unrepres) {
+           eval {
+               die "not a plain file\n"
+                   unless $newmode =~ m/^10\d{4}$/ ||
+                          $oldmode =~ m/^10\d{4}$/;
+               if ($oldmode =~ m/[^0]/ &&
+                   $newmode =~ m/[^0]/) {
+                   die "mode changed\n" if $oldmode ne $newmode;
+               } else {
+                   die "non-default mode\n"
+                       unless $newmode =~ m/^100644$/ ||
+                              $oldmode =~ m/^100644$/;
+               }
+           };
+           if ($@) {
+               local $/="\n"; chomp $@;
+               push @$unrepres, [ $f, "$@ ($oldmode->$newmode)" ];
+           }
+       }
+
+       my $isignore = $f =~ m#^(?:.*/)?.gitignore$#s;
+       $r |= $isignore ? 02 : 01;
+       $ignorenamesr->{$f}=1 if $ignorenamesr && $isignore;
+    }
+    printdebug "quiltify_trees_differ $x $y => $r\n";
+    return $r;
+}
+
+sub quiltify_tree_sentinelfiles ($) {
+    # lists the `sentinel' files present in the tree
+    my ($x) = @_;
+    my $r = cmdoutput @git, qw(ls-tree --name-only), $x,
+        qw(-- debian/rules debian/control);
+    $r =~ s/\n/,/g;
+    return $r;
+}
+
+sub quiltify_splitbrain_needed () {
+    if (!$split_brain) {
+       progress "dgit view: changes are required...";
+       runcmd @git, qw(checkout -q -b dgit-view);
+       $split_brain = 1;
+    }
+}
+
+sub quiltify_splitbrain ($$$$$$) {
+    my ($clogp, $unapplied, $headref, $diffbits,
+       $editedignores, $cachekey) = @_;
+    if ($quilt_mode !~ m/gbp|dpm/) {
+       # treat .gitignore just like any other upstream file
+       $diffbits = { %$diffbits };
+       $_ = !!$_ foreach values %$diffbits;
+    }
+    # We would like any commits we generate to be reproducible
+    my @authline = clogp_authline($clogp);
+    local $ENV{GIT_COMMITTER_NAME} =  $authline[0];
+    local $ENV{GIT_COMMITTER_EMAIL} = $authline[1];
+    local $ENV{GIT_COMMITTER_DATE} =  $authline[2];
+    local $ENV{GIT_AUTHOR_NAME} =  $authline[0];
+    local $ENV{GIT_AUTHOR_EMAIL} = $authline[1];
+    local $ENV{GIT_AUTHOR_DATE} =  $authline[2];
+
+    if ($quilt_mode =~ m/gbp|unapplied/ &&
+       ($diffbits->{O2H} & 01)) {
+       my $msg =
+ "--quilt=$quilt_mode specified, implying patches-unapplied git tree\n".
+ " but git tree differs from orig in upstream files.";
+       if (!stat_exists "debian/patches") {
+           $msg .=
+ "\n ... debian/patches is missing; perhaps this is a patch queue branch?";
+       }  
+       fail $msg;
+    }
+    if ($quilt_mode =~ m/dpm/ &&
+       ($diffbits->{H2A} & 01)) {
+       fail <<END;
+--quilt=$quilt_mode specified, implying patches-applied git tree
+ but git tree differs from result of applying debian/patches to upstream
+END
+    }
+    if ($quilt_mode =~ m/gbp|unapplied/ &&
+       ($diffbits->{O2A} & 01)) { # some patches
+       quiltify_splitbrain_needed();
+       progress "dgit view: creating patches-applied version using gbp pq";
+       runcmd shell_cmd 'exec >/dev/null', gbp_pq, qw(import);
+       # gbp pq import creates a fresh branch; push back to dgit-view
+       runcmd @git, qw(update-ref refs/heads/dgit-view HEAD);
+       runcmd @git, qw(checkout -q dgit-view);
+    }
+    if ($quilt_mode =~ m/gbp|dpm/ &&
+       ($diffbits->{O2A} & 02)) {
+       fail <<END
+--quilt=$quilt_mode specified, implying that HEAD is for use with a
+ tool which does not create patches for changes to upstream
+ .gitignores: but, such patches exist in debian/patches.
+END
+    }
+    if (($diffbits->{O2H} & 02) && # user has modified .gitignore
+       !($diffbits->{O2A} & 02)) { # patches do not change .gitignore
+       quiltify_splitbrain_needed();
+       progress "dgit view: creating patch to represent .gitignore changes";
+        ensuredir "debian/patches";
+       my $gipatch = "debian/patches/auto-gitignore";
+       open GIPATCH, ">>", "$gipatch" or die "$gipatch: $!";
+       stat GIPATCH or die "$gipatch: $!";
+       fail "$gipatch already exists; but want to create it".
+           " to record .gitignore changes" if (stat _)[7];
+       print GIPATCH <<END or die "$gipatch: $!";
+Subject: Update .gitignore from Debian packaging branch
+
+The Debian packaging git branch contains these updates to the upstream
+.gitignore file(s).  This patch is autogenerated, to provide these
+updates to users of the official Debian archive view of the package.
+
+[dgit ($our_version) update-gitignore]
+---
+END
+        close GIPATCH or die "$gipatch: $!";
+        runcmd shell_cmd "exec >>$gipatch", @git, qw(diff),
+            $unapplied, $headref, "--", sort keys %$editedignores;
+        open SERIES, "+>>", "debian/patches/series" or die $!;
+        defined seek SERIES, -1, 2 or $!==EINVAL or die $!;
+        my $newline;
+        defined read SERIES, $newline, 1 or die $!;
+       print SERIES "\n" or die $! unless $newline eq "\n";
+       print SERIES "auto-gitignore\n" or die $!;
+       close SERIES or die  $!;
+        runcmd @git, qw(add -- debian/patches/series), $gipatch;
+        commit_admin <<END
+Commit patch to update .gitignore
+
+[dgit ($our_version) update-gitignore-quilt-fixup]
+END
+    }
+
+    my $dgitview = git_rev_parse 'HEAD';
+
+    changedir '../../../..';
+    # 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", '>>'
+      or die $!;
+
+    my $oldcache = git_get_ref "refs/$splitbraincache";
+    if ($oldcache eq $dgitview) {
+       my $tree = cmdoutput qw(git rev-parse), "$dgitview:";
+       # git update-ref doesn't always update, in this case.  *sigh*
+       my $dummy = make_commit_text <<END;
+tree $tree
+parent $dgitview
+author Dgit <dgit\@example.com> 1000000000 +0000
+committer Dgit <dgit\@example.com> 1000000000 +0000
+
+Dummy commit - do not use
+END
+       runcmd @git, qw(update-ref -m), "dgit $our_version - dummy",
+           "refs/$splitbraincache", $dummy;
+    }
+    runcmd @git, qw(update-ref -m), $cachekey, "refs/$splitbraincache",
+       $dgitview;
+
+    changedir '.git/dgit/unpack/work';
+
+    my $saved = maybe_split_brain_save $headref, $dgitview, "converted";
+    progress "dgit view: created ($saved)";
+}
+
+sub quiltify ($$$$) {
+    my ($clogp,$target,$oldtiptree,$failsuggestion) = @_;
+
+    # Quilt patchification algorithm
+    #
+    # We search backwards through the history of the main tree's HEAD
+    # (T) looking for a start commit S whose tree object is identical
+    # to to the patch tip tree (ie the tree corresponding to the
+    # current dpkg-committed patch series).  For these purposes
+    # `identical' disregards anything in debian/ - this wrinkle is
+    # necessary because dpkg-source treates debian/ specially.
+    #
+    # We can only traverse edges where at most one of the ancestors'
+    # trees differs (in changes outside in debian/).  And we cannot
+    # handle edges which change .pc/ or debian/patches.  To avoid
+    # going down a rathole we avoid traversing edges which introduce
+    # debian/rules or debian/control.  And we set a limit on the
+    # number of edges we are willing to look at.
+    #
+    # If we succeed, we walk forwards again.  For each traversed edge
+    # PC (with P parent, C child) (starting with P=S and ending with
+    # C=T) to we do this:
+    #  - git checkout C
+    #  - dpkg-source --commit with a patch name and message derived from C
+    # After traversing PT, we git commit the changes which
+    # should be contained within debian/patches.
+
+    # The search for the path S..T is breadth-first.  We maintain a
+    # todo list containing search nodes.  A search node identifies a
+    # commit, and looks something like this:
+    #  $p = {
+    #      Commit => $git_commit_id,
+    #      Child => $c,                          # or undef if P=T
+    #      Whynot => $reason_edge_PC_unsuitable, # in @nots only
+    #      Nontrivial => true iff $p..$c has relevant changes
+    #  };
+
+    my @todo;
+    my @nots;
+    my $sref_S;
+    my $max_work=100;
+    my %considered; # saves being exponential on some weird graphs
+
+    my $t_sentinels = quiltify_tree_sentinelfiles $target;
+
+    my $not = sub {
+       my ($search,$whynot) = @_;
+       printdebug " search NOT $search->{Commit} $whynot\n";
+       $search->{Whynot} = $whynot;
+       push @nots, $search;
+       no warnings qw(exiting);
+       next;
+    };
+
+    push @todo, {
+       Commit => $target,
+    };
+
+    while (@todo) {
+       my $c = shift @todo;
+       next if $considered{$c->{Commit}}++;
+
+       $not->($c, "maximum search space exceeded") if --$max_work <= 0;
+
+       printdebug "quiltify investigate $c->{Commit}\n";
+
+       # are we done?
+       if (!quiltify_trees_differ $c->{Commit}, $oldtiptree) {
+           printdebug " search finished hooray!\n";
+           $sref_S = $c;
+           last;
+       }
+
+       if ($quilt_mode eq 'nofix') {
+           fail "quilt fixup required but quilt mode is \`nofix'\n".
+               "HEAD commit $c->{Commit} differs from tree implied by ".
+               " debian/patches (tree object $oldtiptree)";
+       }
+       if ($quilt_mode eq 'smash') {
+           printdebug " search quitting smash\n";
+           last;
+       }
+
+       my $c_sentinels = quiltify_tree_sentinelfiles $c->{Commit};
+       $not->($c, "has $c_sentinels not $t_sentinels")
+           if $c_sentinels ne $t_sentinels;
+
+       my $commitdata = cmdoutput @git, qw(cat-file commit), $c->{Commit};
+       $commitdata =~ m/\n\n/;
+       $commitdata =~ $`;
+       my @parents = ($commitdata =~ m/^parent (\w+)$/gm);
+       @parents = map { { Commit => $_, Child => $c } } @parents;
+
+       $not->($c, "root commit") if !@parents;
+
+       foreach my $p (@parents) {
+           $p->{Nontrivial}= quiltify_trees_differ $p->{Commit},$c->{Commit};
+       }
+       my $ndiffers = grep { $_->{Nontrivial} } @parents;
+       $not->($c, "merge ($ndiffers nontrivial parents)") if $ndiffers > 1;
+
+       foreach my $p (@parents) {
+           printdebug "considering C=$c->{Commit} P=$p->{Commit}\n";
+
+           my @cmd= (@git, qw(diff-tree -r --name-only),
+                     $p->{Commit},$c->{Commit}, qw(-- debian/patches .pc));
+           my $patchstackchange = cmdoutput @cmd;
+           if (length $patchstackchange) {
+               $patchstackchange =~ s/\n/,/g;
+               $not->($p, "changed $patchstackchange");
+           }
+
+           printdebug " search queue P=$p->{Commit} ",
+               ($p->{Nontrivial} ? "NT" : "triv"),"\n";
+           push @todo, $p;
+       }
+    }
+
+    if (!$sref_S) {
+       printdebug "quiltify want to smash\n";
+
+       my $abbrev = sub {
+           my $x = $_[0]{Commit};
+           $x =~ s/(.*?[0-9a-z]{8})[0-9a-z]*$/$1/;
+           return $x;
+       };
+       my $reportnot = sub {
+           my ($notp) = @_;
+           my $s = $abbrev->($notp);
+           my $c = $notp->{Child};
+           $s .= "..".$abbrev->($c) if $c;
+           $s .= ": ".$notp->{Whynot};
+           return $s;
+       };
+       if ($quilt_mode eq 'linear') {
+           print STDERR "$us: quilt fixup cannot be linear.  Stopped at:\n";
+           foreach my $notp (@nots) {
+               print STDERR "$us:  ", $reportnot->($notp), "\n";
+           }
+           print STDERR "$us: $_\n" foreach @$failsuggestion;
+           fail "quilt fixup naive history linearisation failed.\n".
+ "Use dpkg-source --commit by hand; or, --quilt=smash for one ugly patch";
+       } elsif ($quilt_mode eq 'smash') {
+       } elsif ($quilt_mode eq 'auto') {
+           progress "quilt fixup cannot be linear, smashing...";
+       } else {
+           die "$quilt_mode ?";
+       }
+
+       my $time = $ENV{'GIT_COMMITTER_DATE'} || time;
+       $time =~ s/\s.*//; # trim timezone from GIT_COMMITTER_DATE
+       my $ncommits = 3;
+       my $msg = cmdoutput @git, qw(log), "-n$ncommits";
+
+       quiltify_dpkg_commit "auto-$version-$target-$time",
+           (getfield $clogp, 'Maintainer'),
+           "Automatically generated patch ($clogp->{Version})\n".
+           "Last (up to) $ncommits git changes, FYI:\n\n". $msg;
+       return;
+    }
+
+    progress "quiltify linearisation planning successful, executing...";
+
+    for (my $p = $sref_S;
+        my $c = $p->{Child};
+        $p = $p->{Child}) {
+       printdebug "quiltify traverse $p->{Commit}..$c->{Commit}\n";
+       next unless $p->{Nontrivial};
+
+       my $cc = $c->{Commit};
+
+       my $commitdata = cmdoutput @git, qw(cat-file commit), $cc;
+       $commitdata =~ m/\n\n/ or die "$c ?";
+       $commitdata = $`;
+       my $msg = $'; #';
+       $commitdata =~ m/^author (.*) \d+ [-+0-9]+$/m or die "$cc ?";
+       my $author = $1;
+
+       my $commitdate = cmdoutput
+           @git, qw(log -n1 --pretty=format:%aD), $cc;
+
+       $msg =~ s/^(.*)\n*/$1\n/ or die "$cc $msg ?";
+
+       my $strip_nls = sub { $msg =~ s/\n+$//; $msg .= "\n"; };
+       $strip_nls->();
+
+       my $title = $1;
+       my $patchname;
+       my $patchdir;
+
+       my $gbp_check_suitable = sub {
+           $_ = shift;
+           my ($what) = @_;
+
+           eval {
+               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 "too long" if length > 200;
+           };
+           return $_ unless $@;
+           print STDERR "quiltifying commit $cc:".
+               " ignoring/dropping Gbp-Pq $what: $@";
+           return undef;
+       };
+
+       if ($msg =~ s/^ (?: gbp(?:-pq)? : \s* name \s+ |
+                          gbp-pq-name: \s* )
+                      (\S+) \s* \n //ixm) {
+           $patchname = $gbp_check_suitable->($1, 'Name');
+       }
+       if ($msg =~ s/^ (?: gbp(?:-pq)? : \s* topic \s+ |
+                          gbp-pq-topic: \s* )
+                      (\S+) \s* \n //ixm) {
+           $patchdir = $gbp_check_suitable->($1, 'Topic');
+       }
+
+       $strip_nls->();
+
+       if (!defined $patchname) {
+           $patchname = $title;
+           $patchname =~ s/[.:]$//;
+            use Text::Iconv;
+           eval {
+               my $converter = new Text::Iconv qw(UTF-8 ASCII//TRANSLIT);
+               my $translitname = $converter->convert($patchname);
+               die unless defined $translitname;
+               $patchname = $translitname;
+           };
+           print STDERR
+               "dgit: patch title transliteration error: $@"
+               if $@;
+           $patchname =~ y/ A-Z/-a-z/;
+           $patchname =~ y/-a-z0-9_.+=~//cd;
+           $patchname =~ s/^\W/x-$&/;
+           $patchname = substr($patchname,0,40);
+       }
+       if (!defined $patchdir) {
+           $patchdir = '';
+       }
+       if (length $patchdir) {
+           $patchname = "$patchdir/$patchname";
+       }
+       if ($patchname =~ m{^(.*)/}) {
+           mkpath "debian/patches/$1";
+       }
+
+       my $index;
+       for ($index='';
+            stat "debian/patches/$patchname$index";
+            $index++) { }
+       $!==ENOENT or die "$patchname$index $!";
+
+       runcmd @git, qw(checkout -q), $cc;
+
+       # We use the tip's changelog so that dpkg-source doesn't
+       # produce complaining messages from dpkg-parsechangelog.  None
+       # of the information dpkg-source gets from the changelog is
+       # actually relevant - it gets put into the original message
+       # which dpkg-source provides our stunt editor, and then
+       # overwritten.
+       runcmd @git, qw(checkout -q), $target, qw(debian/changelog);
+
+       quiltify_dpkg_commit "$patchname$index", $author, $msg,
+           "Date: $commitdate\n".
+           "X-Dgit-Generated: $clogp->{Version} $cc\n";
+
+       runcmd @git, qw(checkout -q), $cc, qw(debian/changelog);
+    }
+
+    runcmd @git, qw(checkout -q master);
+}
+
+sub build_maybe_quilt_fixup () {
+    my ($format,$fopts) = get_source_format;
+    return unless madformat_wantfixup $format;
+    # sigh
+
+    check_for_vendor_patches();
+
+    if (quiltmode_splitbrain) {
+       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
+    }
+
+    my $clogp = parsechangelog();
+    my $headref = git_rev_parse('HEAD');
+
+    prep_ud();
+    changedir $ud;
+
+    my $upstreamversion = upstreamversion $version;
+
+    if ($fopts->{'single-debian-patch'}) {
+       quilt_fixup_singlepatch($clogp, $headref, $upstreamversion);
+    } else {
+       quilt_fixup_multipatch($clogp, $headref, $upstreamversion);
+    }
+
+    die 'bug' if $split_brain && !$need_split_build_invocation;
+
+    changedir '../../../..';
+    runcmd_ordryrun_local
+        @git, qw(pull --ff-only -q .git/dgit/unpack/work master);
+}
+
+sub quilt_fixup_mkwork ($) {
+    my ($headref) = @_;
+
+    mkdir "work" or die $!;
+    changedir "work";
+    mktree_in_ud_here();
+    runcmd @git, qw(reset -q --hard), $headref;
+}
+
+sub quilt_fixup_linkorigs ($$) {
+    my ($upstreamversion, $fn) = @_;
+    # calls $fn->($leafname);
+
+    foreach my $f (<../../../../*>) { #/){
+       my $b=$f; $b =~ s{.*/}{};
+       {
+           local ($debuglevel) = $debuglevel-1;
+           printdebug "QF linkorigs $b, $f ?\n";
+       }
+       next unless is_orig_file_of_vsn $b, $upstreamversion;
+       printdebug "QF linkorigs $b, $f Y\n";
+       link_ltarget $f, $b or die "$b $!";
+        $fn->($b);
+    }
+}
+
+sub quilt_fixup_delete_pc () {
+    runcmd @git, qw(rm -rqf .pc);
+    commit_admin <<END
+Commit removal of .pc (quilt series tracking data)
+
+[dgit ($our_version) upgrade quilt-remove-pc]
+END
+}
+
+sub quilt_fixup_singlepatch ($$$) {
+    my ($clogp, $headref, $upstreamversion) = @_;
+
+    progress "starting quiltify (single-debian-patch)";
+
+    # dpkg-source --commit generates new patches even if
+    # single-debian-patch is in debian/source/options.  In order to
+    # get it to generate debian/patches/debian-changes, it is
+    # necessary to build the source package.
+
+    quilt_fixup_linkorigs($upstreamversion, sub { });
+    quilt_fixup_mkwork($headref);
+
+    rmtree("debian/patches");
+
+    runcmd @dpkgsource, qw(-b .);
+    changedir "..";
+    runcmd @dpkgsource, qw(-x), (srcfn $version, ".dsc");
+    rename srcfn("$upstreamversion", "/debian/patches"), 
+           "work/debian/patches";
+
+    changedir "work";
+    commit_quilty_patch();
+}
+
+sub quilt_make_fake_dsc ($) {
+    my ($upstreamversion) = @_;
+
+    my $fakeversion="$upstreamversion-~~DGITFAKE";
+
+    my $fakedsc=new IO::File 'fake.dsc', '>' or die $!;
+    print $fakedsc <<END or die $!;
+Format: 3.0 (quilt)
+Source: $package
+Version: $fakeversion
+Files:
+END
+
+    my $dscaddfile=sub {
+        my ($b) = @_;
+        
+       my $md = new Digest::MD5;
+
+       my $fh = new IO::File $b, '<' or die "$b $!";
+       stat $fh or die $!;
+       my $size = -s _;
+
+       $md->addfile($fh);
+       print $fakedsc " ".$md->hexdigest." $size $b\n" or die $!;
+    };
+
+    quilt_fixup_linkorigs($upstreamversion, $dscaddfile);
+
+    my @files=qw(debian/source/format debian/rules
+                 debian/control debian/changelog);
+    foreach my $maybe (qw(debian/patches debian/source/options
+                          debian/tests/control)) {
+        next unless stat_exists "../../../$maybe";
+        push @files, $maybe;
+    }
+
+    my $debtar= srcfn $fakeversion,'.debian.tar.gz';
+    runcmd qw(env GZIP=-1n tar -zcf), "./$debtar", qw(-C ../../..), @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.
+    # Computes the cache key and looks in the cache.
+    # Returns ($dgit_view_commitid, $cachekey) or (undef, $cachekey)
+
+    my $splitbrain_cachekey;
+    
+    progress
+ "dgit: split brain (separate dgit view) may be needed (--quilt=$quilt_mode).";
+    # we look in the reflog of dgit-intern/quilt-cache
+    # we look for an entry whose message is the key for the cache lookup
+    my @cachekey = (qw(dgit), $our_version);
+    push @cachekey, $upstreamversion;
+    push @cachekey, $quilt_mode;
+    push @cachekey, $headref;
+
+    push @cachekey, hashfile('fake.dsc');
+
+    my $srcshash = Digest::SHA->new(256);
+    my %sfs = ( %INC, '$0(dgit)' => $0 );
+    foreach my $sfk (sort keys %sfs) {
+       next unless $sfk =~ m/^\$0\b/ || $sfk =~ m{^Debian/Dgit\b};
+       $srcshash->add($sfk,"  ");
+       $srcshash->add(hashfile($sfs{$sfk}));
+       $srcshash->add("\n");
+    }
+    push @cachekey, $srcshash->hexdigest();
+    $splitbrain_cachekey = "@cachekey";
+
+    my @cmd = (@git, qw(log -g), '--pretty=format:%H %gs',
+              $splitbraincache);
+    printdebug "splitbrain cachekey $splitbrain_cachekey\n";
+    debugcmd "|(probably)",@cmd;
+    my $child = open GC, "-|";  defined $child or die $!;
+    if (!$child) {
+       chdir '../../..' or die $!;
+       if (!stat ".git/logs/refs/$splitbraincache") {
+           $! == ENOENT or die $!;
+           printdebug ">(no reflog)\n";
+           exit 0;
+       }
+       exec @cmd; die $!;
+    }
+    while (<GC>) {
+       chomp;
+       printdebug ">| ", $_, "\n" if $debuglevel > 1;
+       next unless m/^(\w+) (\S.*\S)$/ && $2 eq $splitbrain_cachekey;
+           
+       my $cachehit = $1;
+       quilt_fixup_mkwork($headref);
+       my $saved = maybe_split_brain_save $headref, $cachehit, "cache-hit";
+       if ($cachehit ne $headref) {
+           progress "dgit view: found cached ($saved)";
+           runcmd @git, qw(checkout -q -b dgit-view), $cachehit;
+           $split_brain = 1;
+           return ($cachehit, $splitbrain_cachekey);
+       }
+       progress "dgit view: found cached, no changes required";
+       return ($headref, $splitbrain_cachekey);
+    }
+    die $! if GC->error;
+    failedcmd unless close GC;
+
+    printdebug "splitbrain cache miss\n";
+    return (undef, $splitbrain_cachekey);
+}
+
+sub quilt_fixup_multipatch ($$$) {
+    my ($clogp, $headref, $upstreamversion) = @_;
+
+    progress "examining quilt state (multiple patches, $quilt_mode mode)";
+
+    # Our objective is:
+    #  - honour any existing .pc in case it has any strangeness
+    #  - determine the git commit corresponding to the tip of
+    #    the patch stack (if there is one)
+    #  - if there is such a git commit, convert each subsequent
+    #    git commit into a quilt patch with dpkg-source --commit
+    #  - otherwise convert all the differences in the tree into
+    #    a single git commit
+    #
+    # To do this we:
+
+    # Our git tree doesn't necessarily contain .pc.  (Some versions of
+    # dgit would include the .pc in the git tree.)  If there isn't
+    # one, we need to generate one by unpacking the patches that we
+    # have.
+    #
+    # We first look for a .pc in the git tree.  If there is one, we
+    # will use it.  (This is not the normal case.)
+    #
+    # Otherwise need to regenerate .pc so that dpkg-source --commit
+    # can work.  We do this as follows:
+    #     1. Collect all relevant .orig from parent directory
+    #     2. Generate a debian.tar.gz out of
+    #         debian/{patches,rules,source/format,source/options}
+    #     3. Generate a fake .dsc containing just these fields:
+    #          Format Source Version Files
+    #     4. Extract the fake .dsc
+    #        Now the fake .dsc has a .pc directory.
+    # (In fact we do this in every case, because in future we will
+    # want to search for a good base commit for generating patches.)
+    #
+    # Then we can actually do the dpkg-source --commit
+    #     1. Make a new working tree with the same object
+    #        store as our main tree and check out the main
+    #        tree's HEAD.
+    #     2. Copy .pc from the fake's extraction, if necessary
+    #     3. Run dpkg-source --commit
+    #     4. If the result has changes to debian/, then
+    #          - git add them them
+    #          - git add .pc if we had a .pc in-tree
+    #          - git commit
+    #     5. If we had a .pc in-tree, delete it, and git commit
+    #     6. Back in the main tree, fast forward to the new HEAD
+
+    # Another situation we may have to cope with is gbp-style
+    # patches-unapplied trees.
+    #
+    # We would want to detect these, so we know to escape into
+    # quilt_fixup_gbp.  However, this is in general not possible.
+    # Consider a package with a one patch which the dgit user reverts
+    # (with git revert or the moral equivalent).
+    #
+    # That is indistinguishable in contents from a patches-unapplied
+    # tree.  And looking at the history to distinguish them is not
+    # useful because the user might have made a confusing-looking git
+    # history structure (which ought to produce an error if dgit can't
+    # cope, not a silent reintroduction of an unwanted patch).
+    #
+    # So gbp users will have to pass an option.  But we can usually
+    # detect their failure to do so: if the tree is not a clean
+    # patches-applied tree, quilt linearisation fails, but the tree
+    # _is_ a clean patches-unapplied tree, we can suggest that maybe
+    # they want --quilt=unapplied.
+    #
+    # To help detect this, when we are extracting the fake dsc, we
+    # first extract it with --skip-patches, and then apply the patches
+    # afterwards with dpkg-source --before-build.  That lets us save a
+    # tree object corresponding to .origs.
+
+    my $splitbrain_cachekey;
+
+    quilt_make_fake_dsc($upstreamversion);
+
+    if (quiltmode_splitbrain()) {
+       my $cachehit;
+       ($cachehit, $splitbrain_cachekey) =
+           quilt_check_splitbrain_cache($headref, $upstreamversion);
+       return if $cachehit;
+    }
+
+    runcmd qw(sh -ec),
+        'exec dpkg-source --no-check --skip-patches -x fake.dsc >/dev/null';
+
+    my $fakexdir= $package.'-'.(stripepoch $upstreamversion);
+    rename $fakexdir, "fake" or die "$fakexdir $!";
+
+    changedir 'fake';
+
+    remove_stray_gits("source package");
+    mktree_in_ud_here();
+
+    rmtree '.pc';
+
+    my $unapplied=git_add_write_tree();
+    printdebug "fake orig tree object $unapplied\n";
+
+    ensuredir '.pc';
+
+    my @bbcmd = (qw(sh -ec), 'exec dpkg-source --before-build . >/dev/null');
+    $!=0; $?=-1;
+    if (system @bbcmd) {
+       failedcmd @bbcmd if $? < 0;
+       fail <<END;
+failed to apply your git tree's patch stack (from debian/patches/) to
+ the corresponding upstream tarball(s).  Your source tree and .orig
+ are probably too inconsistent.  dgit can only fix up certain kinds of
+ anomaly (depending on the quilt mode).  See --quilt= in dgit(1).
+END
+    }
+
+    changedir '..';
+
+    quilt_fixup_mkwork($headref);
+
+    my $mustdeletepc=0;
+    if (stat_exists ".pc") {
+        -d _ or die;
+       progress "Tree already contains .pc - will use it then delete it.";
+        $mustdeletepc=1;
+    } else {
+        rename '../fake/.pc','.pc' or die $!;
+    }
+
+    changedir '../fake';
+    rmtree '.pc';
+    my $oldtiptree=git_add_write_tree();
+    printdebug "fake o+d/p tree object $unapplied\n";
+    changedir '../work';
+
+
+    # We calculate some guesswork now about what kind of tree this might
+    # be.  This is mostly for error reporting.
+
+    my %editedignores;
+    my @unrepres;
+    my $diffbits = {
+        # H = user's HEAD
+        # O = orig, without patches applied
+        # A = "applied", ie orig with H's debian/patches applied
+        O2H => quiltify_trees_differ($unapplied,$headref,   1,
+                                    \%editedignores, \@unrepres),
+        H2A => quiltify_trees_differ($headref,  $oldtiptree,1),
+        O2A => quiltify_trees_differ($unapplied,$oldtiptree,1),
+    };
+
+    my @dl;
+    foreach my $b (qw(01 02)) {
+        foreach my $v (qw(O2H O2A H2A)) {
+            push @dl, ($diffbits->{$v} & $b) ? '##' : '==';
+        }
+    }
+    printdebug "differences \@dl @dl.\n";
+
+    progress sprintf
+"$us: base trees orig=%.20s o+d/p=%.20s",
+              $unapplied, $oldtiptree;
+    progress sprintf
+"$us: quilt differences: src:  %s orig %s     gitignores:  %s orig %s\n".
+"$us: quilt differences:      HEAD %s o+d/p               HEAD %s o+d/p",
+                             $dl[0], $dl[1],              $dl[3], $dl[4],
+                                 $dl[2],                     $dl[5];
+
+    if (@unrepres) {
+       print STDERR "dgit:  cannot represent change: $_->[1]: $_->[0]\n"
+           foreach @unrepres;
+       forceable_fail [qw(unrepresentable)], <<END;
+HEAD has changes to .orig[s] which are not representable by `3.0 (quilt)'
+END
+    }
+
+    my @failsuggestion;
+    if (!($diffbits->{O2H} & $diffbits->{O2A})) {
+        push @failsuggestion, "This might be a patches-unapplied branch.";
+    }  elsif (!($diffbits->{H2A} & $diffbits->{O2A})) {
+        push @failsuggestion, "This might be a patches-applied branch.";
+    }
+    push @failsuggestion, "Maybe you need to specify one of".
+        " --[quilt=]gbp --[quilt=]dpm --quilt=unapplied ?";
+
+    if (quiltmode_splitbrain()) {
+       quiltify_splitbrain($clogp, $unapplied, $headref,
+                            $diffbits, \%editedignores,
+                           $splitbrain_cachekey);
+       return;
+    }
+
+    progress "starting quiltify (multiple patches, $quilt_mode mode)";
+    quiltify($clogp,$headref,$oldtiptree,\@failsuggestion);
+
+    if (!open P, '>>', ".pc/applied-patches") {
+       $!==&ENOENT or die $!;
+    } else {
+       close P;
+    }
+
+    commit_quilty_patch();
+
+    if ($mustdeletepc) {
+        quilt_fixup_delete_pc();
+    }
+}
+
+sub quilt_fixup_editor () {
+    my $descfn = $ENV{$fakeeditorenv};
+    my $editing = $ARGV[$#ARGV];
+    open I1, '<', $descfn or die "$descfn: $!";
+    open I2, '<', $editing or die "$editing: $!";
+    unlink $editing or die "$editing: $!";
+    open O, '>', $editing or die "$editing: $!";
+    while (<I1>) { print O or die $!; } I1->error and die $!;
+    my $copying = 0;
+    while (<I2>) {
+       $copying ||= m/^\-\-\- /;
+       next unless $copying;
+       print O or die $!;
+    }
+    I2->error and die $!;
+    close O or die $1;
+    exit 0;
+}
+
+sub maybe_apply_patches_dirtily () {
+    return unless $quilt_mode =~ m/gbp|unapplied/;
+    print STDERR <<END or die $!;
+
+dgit: Building, or cleaning with rules target, in patches-unapplied tree.
+dgit: Have to apply the patches - making the tree dirty.
+dgit: (Consider specifying --clean=git and (or) using dgit sbuild.)
+
+END
+    $patches_applied_dirtily = 01;
+    $patches_applied_dirtily |= 02 unless stat_exists '.pc';
+    runcmd qw(dpkg-source --before-build .);
+}
+
+sub maybe_unapply_patches_again () {
+    progress "dgit: Unapplying patches again to tidy up the tree."
+       if $patches_applied_dirtily;
+    runcmd qw(dpkg-source --after-build .)
+       if $patches_applied_dirtily & 01;
+    rmtree '.pc'
+       if $patches_applied_dirtily & 02;
+    $patches_applied_dirtily = 0;
+}
+
+#----- other building -----
+
+our $clean_using_builder;
+# ^ tree is to be cleaned by dpkg-source's builtin idea that it should
+#   clean the tree before building (perhaps invoked indirectly by
+#   whatever we are using to run the build), rather than separately
+#   and explicitly by us.
+
+sub clean_tree () {
+    return if $clean_using_builder;
+    if ($cleanmode eq 'dpkg-source') {
+       maybe_apply_patches_dirtily();
+       runcmd_ordryrun_local @dpkgbuildpackage, qw(-T clean);
+    } elsif ($cleanmode eq 'dpkg-source-d') {
+       maybe_apply_patches_dirtily();
+       runcmd_ordryrun_local @dpkgbuildpackage, qw(-d -T clean);
+    } elsif ($cleanmode eq 'git') {
+       runcmd_ordryrun_local @git, qw(clean -xdf);
+    } elsif ($cleanmode eq 'git-ff') {
+       runcmd_ordryrun_local @git, qw(clean -xdff);
+    } elsif ($cleanmode eq 'check') {
+       my $leftovers = cmdoutput @git, qw(clean -xdn);
+       if (length $leftovers) {
+           print STDERR $leftovers, "\n" or die $!;
+           fail "tree contains uncommitted files and --clean=check specified";
+       }
+    } elsif ($cleanmode eq 'none') {
+    } else {
+       die "$cleanmode ?";
+    }
+}
+
+sub cmd_clean () {
+    badusage "clean takes no additional arguments" if @ARGV;
+    notpushing();
+    clean_tree();
+    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;
+    my $clogp = parsechangelog();
+    $isuite = getfield $clogp, 'Distribution';
+    $package = getfield $clogp, 'Source';
+    $version = getfield $clogp, 'Version';
+    check_not_dirty();
+}
+
+sub build_prep () {
+    build_prep_early();
+    clean_tree();
+    build_maybe_quilt_fixup();
+    if ($rmchanges) {
+       my $pat = changespat $version;
+       foreach my $f (glob "$buildproductsdir/$pat") {
+           if (act_local()) {
+               unlink $f or fail "remove old changes file $f: $!";
+           } else {
+               progress "would remove $f";
+           }
+       }
+    }
+}
+
+sub changesopts_initial () {
+    my @opts =@changesopts[1..$#changesopts];
+}
+
+sub changesopts_version () {
+    if (!defined $changes_since_version) {
+       my @vsns = archive_query('archive_query');
+       my @quirk = access_quirk();
+       if ($quirk[0] eq 'backports') {
+           local $isuite = $quirk[2];
+           local $csuite;
+           canonicalise_suite();
+           push @vsns, archive_query('archive_query');
+       }
+       if (@vsns) {
+           @vsns = map { $_->[0] } @vsns;
+           @vsns = sort { -version_compare($a, $b) } @vsns;
+           $changes_since_version = $vsns[0];
+           progress "changelog will contain changes since $vsns[0]";
+       } else {
+           $changes_since_version = '_';
+           progress "package seems new, not specifying -v<version>";
+       }
+    }
+    if ($changes_since_version ne '_') {
+       return ("-v$changes_since_version");
+    } else {
+       return ();
+    }
+}
+
+sub changesopts () {
+    return (changesopts_initial(), changesopts_version());
+}
+
+sub massage_dbp_args ($;$) {
+    my ($cmd,$xargs) = @_;
+    # We need to:
+    #
+    #  - if we're going to split the source build out so we can
+    #    do strange things to it, massage the arguments to dpkg-buildpackage
+    #    so that the main build doessn't build source (or add an argument
+    #    to stop it building source by default).
+    #
+    #  - add -nc to stop dpkg-source cleaning the source tree,
+    #    unless we're not doing a split build and want dpkg-source
+    #    as cleanmode, in which case we can do nothing
+    #
+    # return values:
+    #    0 - source will NOT need to be built separately by caller
+    #   +1 - source will need to be built separately by caller
+    #   +2 - source will need to be built separately by caller AND
+    #        dpkg-buildpackage should not in fact be run at all!
+    debugcmd '#massaging#', @$cmd if $debuglevel>1;
+#print STDERR "MASS0 ",Dumper($cmd, $xargs, $need_split_build_invocation);
+    if ($cleanmode eq 'dpkg-source' && !$need_split_build_invocation) {
+       $clean_using_builder = 1;
+       return 0;
+    }
+    # -nc has the side effect of specifying -b if nothing else specified
+    # and some combinations of -S, -b, et al, are errors, rather than
+    # later simply overriding earlie.  So we need to:
+    #  - search the command line for these options
+    #  - pick the last one
+    #  - perhaps add our own as a default
+    #  - perhaps adjust it to the corresponding non-source-building version
+    my $dmode = '-F';
+    foreach my $l ($cmd, $xargs) {
+       next unless $l;
+       @$l = grep { !(m/^-[SgGFABb]$/s and $dmode=$_) } @$l;
+    }
+    push @$cmd, '-nc';
+#print STDERR "MASS1 ",Dumper($cmd, $xargs, $dmode);
+    my $r = 0;
+    if ($need_split_build_invocation) {
+       printdebug "massage split $dmode.\n";
+       $r = $dmode =~ m/[S]/     ? +2 :
+            $dmode =~ y/gGF/ABb/ ? +1 :
+            $dmode =~ m/[ABb]/   ?  0 :
+            die "$dmode ?";
+    }
+    printdebug "massage done $r $dmode.\n";
+    push @$cmd, $dmode;
+#print STDERR "MASS2 ",Dumper($cmd, $xargs, $r);
+    return $r;
+}
+
+sub in_parent (&) {
+    my ($fn) = @_;
+    my $wasdir = must_getcwd();
+    changedir "..";
+    $fn->();
+    changedir $wasdir;
+}    
+
+sub postbuild_mergechanges ($) { # must run with CWD=.. (eg in in_parent)
+    my ($msg_if_onlyone) = @_;
+    # If there is only one .changes file, fail with $msg_if_onlyone,
+    # or if that is undef, be a no-op.
+    # Returns the changes file to report to the user.
+    my $pat = changespat $version;
+    my @changesfiles = glob $pat;
+    @changesfiles = sort {
+       ($b =~ m/_source\.changes$/ <=> $a =~ m/_source\.changes$/)
+           or $a cmp $b
+    } @changesfiles;
+    my $result;
+    if (@changesfiles==1) {
+       fail <<END.$msg_if_onlyone if defined $msg_if_onlyone;
+only one changes file from build (@changesfiles)
+END
+       $result = $changesfiles[0];
+    } elsif (@changesfiles==2) {
+       my $binchanges = parsecontrol($changesfiles[1], "binary changes file");
+       foreach my $l (split /\n/, getfield $binchanges, 'Files') {
+           fail "$l found in binaries changes file $binchanges"
+               if $l =~ m/\.dsc$/;
+       }
+       runcmd_ordryrun_local @mergechanges, @changesfiles;
+       my $multichanges = changespat $version,'multi';
+       if (act_local()) {
+           stat_exists $multichanges or fail "$multichanges: $!";
+           foreach my $cf (glob $pat) {
+               next if $cf eq $multichanges;
+               rename "$cf", "$cf.inmulti" or fail "$cf\{,.inmulti}: $!";
+           }
+       }
+       $result = $multichanges;
+    } else {
+       fail "wrong number of different changes files (@changesfiles)";
+    }
+    printdone "build successful, results in $result\n" or die $!;
+}
+
+sub midbuild_checkchanges () {
+    my $pat = changespat $version;
+    return if $rmchanges;
+    my @unwanted = map { s#^\.\./##; $_; } glob "../$pat";
+    @unwanted = grep { $_ ne changespat $version,'source' } @unwanted;
+    fail <<END
+changes files other than source matching $pat already present; building would result in ambiguity about the intended results.
+Suggest you delete @unwanted.
+END
+       if @unwanted;
+}
+
+sub midbuild_checkchanges_vanilla ($) {
+    my ($wantsrc) = @_;
+    midbuild_checkchanges() if $wantsrc == 1;
+}
+
+sub postbuild_mergechanges_vanilla ($) {
+    my ($wantsrc) = @_;
+    if ($wantsrc == 1) {
+       in_parent {
+           postbuild_mergechanges(undef);
+       };
+    } else {
+       printdone "build successful\n";
+    }
+}
+
+sub cmd_build {
+    my @dbp = (@dpkgbuildpackage, qw(-us -uc), changesopts_initial(), @ARGV);
+    my $wantsrc = massage_dbp_args \@dbp;
+    if ($wantsrc > 0) {
+       build_source();
+       midbuild_checkchanges_vanilla $wantsrc;
+    } else {
+       build_prep();
+    }
+    if ($wantsrc < 2) {
+       push @dbp, changesopts_version();
+       maybe_apply_patches_dirtily();
+       runcmd_ordryrun_local @dbp;
+    }
+    maybe_unapply_patches_again();
+    postbuild_mergechanges_vanilla $wantsrc;
+}
+
+sub pre_gbp_build {
+    $quilt_mode //= 'gbp';
+}
+
+sub cmd_gbp_build {
+    build_prep_early();
+
+    # gbp can make .origs out of thin air.  In my tests it does this
+    # even for a 1.0 format package, with no origs present.  So I
+    # guess it keys off just the version number.  We don't know
+    # exactly what .origs ought to exist, but let's assume that we
+    # should run gbp if: the version has an upstream part and the main
+    # orig is absent.
+    my $upstreamversion = upstreamversion $version;
+    my $origfnpat = srcfn $upstreamversion, '.orig.tar.*';
+    my $gbp_make_orig = $version =~ m/-/ && !(() = glob "../$origfnpat");
+
+    if ($gbp_make_orig) {
+       clean_tree();
+       $cleanmode = 'none'; # don't do it again
+       $need_split_build_invocation = 1;
+    }
+
+    my @dbp = @dpkgbuildpackage;
+
+    my $wantsrc = massage_dbp_args \@dbp, \@ARGV;
+
+    if (!length $gbp_build[0]) {
+       if (length executable_on_path('git-buildpackage')) {
+           $gbp_build[0] = qw(git-buildpackage);
+       } else {
+           $gbp_build[0] = 'gbp buildpackage';
+       }
+    }
+    my @cmd = opts_opt_multi_cmd @gbp_build;
+
+    push @cmd, (qw(-us -uc --git-no-sign-tags), "--git-builder=@dbp");
+
+    if ($gbp_make_orig) {
+       ensuredir '.git/dgit';
+       my $ok = '.git/dgit/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, @ARGV;
+       if (act_local()) {
+           debugcmd @origs_cmd;
+           system @origs_cmd;
+           do { local $!; stat_exists $ok; }
+               or failedcmd @origs_cmd;
+       } else {
+           dryrun_report @origs_cmd;
+       }
+    }
+
+    if ($wantsrc > 0) {
+       build_source();
+       midbuild_checkchanges_vanilla $wantsrc;
+    } else {
+       if (!$clean_using_builder) {
+           push @cmd, '--git-cleaner=true';
+       }
+       build_prep();
+    }
+    maybe_unapply_patches_again();
+    if ($wantsrc < 2) {
+       push @cmd, changesopts();
+       runcmd_ordryrun_local @cmd, @ARGV;
+    }
+    postbuild_mergechanges_vanilla $wantsrc;
+}
+sub cmd_git_build { cmd_gbp_build(); } # compatibility with <= 1.0
+
+sub build_source {
+    my $our_cleanmode = $cleanmode;
+    if ($need_split_build_invocation) {
+       # Pretend that clean is being done some other way.  This
+       # forces us not to try to use dpkg-buildpackage to clean and
+       # build source all in one go; and instead we run dpkg-source
+       # (and build_prep() will do the clean since $clean_using_builder
+       # is false).
+       $our_cleanmode = 'ELSEWHERE';
+    }
+    if ($our_cleanmode =~ m/^dpkg-source/) {
+       # dpkg-source invocation (below) will clean, so build_prep shouldn't
+       $clean_using_builder = 1;
+    }
+    build_prep();
+    $sourcechanges = changespat $version,'source';
+    if (act_local()) {
+       unlink "../$sourcechanges" or $!==ENOENT
+           or fail "remove $sourcechanges: $!";
+    }
+    $dscfn = dscfn($version);
+    if ($our_cleanmode eq 'dpkg-source') {
+       maybe_apply_patches_dirtily();
+       runcmd_ordryrun_local @dpkgbuildpackage, qw(-us -uc -S),
+           changesopts();
+    } elsif ($our_cleanmode eq 'dpkg-source-d') {
+       maybe_apply_patches_dirtily();
+       runcmd_ordryrun_local @dpkgbuildpackage, qw(-us -uc -S -d),
+           changesopts();
+    } else {
+       my @cmd = (@dpkgsource, qw(-b --));
+       if ($split_brain) {
+           changedir $ud;
+           runcmd_ordryrun_local @cmd, "work";
+           my @udfiles = <${package}_*>;
+           changedir "../../..";
+           foreach my $f (@udfiles) {
+               printdebug "source copy, found $f\n";
+               next unless
+                   $f eq $dscfn or
+                   ($f =~ m/\.debian\.tar(?:\.\w+)$/ &&
+                    $f eq srcfn($version, $&));
+               printdebug "source copy, found $f - renaming\n";
+               rename "$ud/$f", "../$f" or $!==ENOENT
+                   or fail "put in place new source file ($f): $!";
+           }
+       } else {
+           my $pwd = must_getcwd();
+           my $leafdir = basename $pwd;
+           changedir "..";
+           runcmd_ordryrun_local @cmd, $leafdir;
+           changedir $pwd;
+       }
+       runcmd_ordryrun_local qw(sh -ec),
+           'exec >$1; shift; exec "$@"','x',
+           "../$sourcechanges",
+           @dpkggenchanges, qw(-S), changesopts();
+    }
+}
+
+sub cmd_build_source {
+    badusage "build-source takes no additional arguments" if @ARGV;
+    build_source();
+    maybe_unapply_patches_again();
+    printdone "source built, results in $dscfn and $sourcechanges";
+}
+
+sub cmd_sbuild {
+    build_source();
+    midbuild_checkchanges();
+    in_parent {
+       if (act_local()) {
+           stat_exists $dscfn or fail "$dscfn (in parent directory): $!";
+           stat_exists $sourcechanges
+               or fail "$sourcechanges (in parent directory): $!";
+       }
+       runcmd_ordryrun_local @sbuild, qw(-d), $isuite, @ARGV, $dscfn;
+    };
+    maybe_unapply_patches_again();
+    in_parent {
+       postbuild_mergechanges(<<END);
+perhaps you need to pass -A ?  (sbuild's default is to build only
+arch-specific binaries; dgit 1.4 used to override that.)
+END
+    };
+}    
+
+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();
+    clean_tree();
+    build_maybe_quilt_fixup();
+}
+
+sub cmd_import_dsc {
+    my $needsig = 0;
+
+    while (@ARGV) {
+       last unless $ARGV[0] =~ m/^-/;
+       $_ = shift @ARGV;
+       last if m/^--?$/;
+       if (m/^--require-valid-signature$/) {
+           $needsig = 1;
+       } else {
+           badusage "unknown dgit import-dsc sub-option \`$_'";
+       }
+    }
+
+    badusage "usage: dgit import-dsc .../PATH/TO/.DSC BRANCH" unless @ARGV==2;
+    my ($dscfn, $dstbranch) = @ARGV;
+
+    badusage "dry run makes no sense with import-dsc" unless act_local();
+
+    my $force = $dstbranch =~ s/^\+//   ? +1 :
+               $dstbranch =~ s/^\.\.// ? -1 :
+                                           0;
+    my $info = $force ? " $&" : '';
+    $info = "$dscfn$info";
+
+    my $specbranch = $dstbranch;
+    $dstbranch = "refs/heads/$dstbranch" unless $dstbranch =~ m#^refs/#;
+    $dstbranch = cmdoutput @git, qw(check-ref-format --normalize), $dstbranch;
+
+    my @symcmd = (@git, qw(symbolic-ref -q HEAD));
+    my $chead = cmdoutput_errok @symcmd;
+    defined $chead or $?==256 or failedcmd @symcmd;
+
+    fail "$dstbranch is checked out - will not update it"
+       if defined $chead and $chead eq $dstbranch;
+
+    my $oldhash = git_get_ref $dstbranch;
+
+    open D, "<", $dscfn or fail "open import .dsc ($dscfn): $!";
+    $dscdata = do { local $/ = undef; <D>; };
+    D->error and fail "read $dscfn: $!";
+    close C;
+
+    # we don't normally need this so import it here
+    use Dpkg::Source::Package;
+    my $dp = new Dpkg::Source::Package filename => $dscfn,
+       require_valid_signature => $needsig;
+    {
+       local $SIG{__WARN__} = sub {
+           print STDERR $_[0];
+           return unless $needsig;
+           fail "import-dsc signature check failed";
+       };
+       if (!$dp->is_signed()) {
+           warn "$us: warning: importing unsigned .dsc\n";
+       } else {
+           my $r = $dp->check_signature();
+           die "->check_signature => $r" if $needsig && $r;
+       }
+    }
+
+    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";
+       progress "dgit: import-dsc of .dsc with Dgit field, using git hash";
+       my @cmd = (qw(sh -ec),
+                  "echo $dgit_commit | git cat-file --batch-check");
+       my $objgot = cmdoutput @cmd;
+       if ($objgot =~ m#^\w+ missing\b#) {
+           fail <<END
+.dsc contains Dgit field referring to object $dgit_commit
+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 ($force > 0) {
+               progress "Not fast forward, forced update.";
+           } else {
+               fail "Not fast forward to $dgit_commit";
+           }
+       }
+       @cmd = (@git, qw(update-ref -m), "dgit import-dsc (Dgit): $info",
+               $dstbranch, $dgit_commit);
+       runcmd @cmd;
+       progress "dgit: import-dsc updated git ref $dstbranch";
+       return 0;
+    }
+
+    fail <<END
+Branch $dstbranch already exists
+Specify ..$specbranch for a pseudo-merge, binding in existing history
+Specify  +$specbranch to overwrite, discarding existing history
+END
+       if $oldhash && !$force;
+
+    $package = getfield $dsc, 'Source';
+    my @dfi = dsc_files_info();
+    foreach my $fi (@dfi) {
+       my $f = $fi->{Filename};
+       my $here = "../$f";
+       next if lstat $here;
+       fail "stat $here: $!" unless $! == ENOENT;
+       my $there = $dscfn;
+       if ($dscfn =~ m#^(?:\./+)?\.\./+#) {
+           $there = $';
+       } elsif ($dscfn =~ m#^/#) {
+           $there = $dscfn;
+       } else {
+           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";
+       $there .= "/$f";
+       symlink $there, $here or fail "symlink $there to $here: $!";
+       progress "made symlink $here -> $there";
+#      print STDERR Dumper($fi);
+    }
+    my @mergeinputs = generate_commits_from_dsc();
+    die unless @mergeinputs == 1;
+
+    my $newhash = $mergeinputs[0]{Commit};
+
+    if ($oldhash) {
+       if ($force > 0) {
+           progress "Import, forced update - synthetic orphan git history.";
+       } elsif ($force < 0) {
+           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
+author $authline
+committer $authline
+
+Merge $package ($version) import into $dstbranch
+END
+       } else {
+           die; # caught earlier
+       }
+    }
+
+    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";
+}
+
+sub cmd_archive_api_query {
+    badusage "need only 1 subpath argument" unless @ARGV==1;
+    my ($subpath) = @ARGV;
+    my @cmd = archive_api_query_cmd($subpath);
+    push @cmd, qw(-f);
+    debugcmd ">",@cmd;
+    exec @cmd or fail "exec curl: $!\n";
+}
+
+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);
+    debugcmd ">",@cmd;
+    exec @cmd or fail "exec git clone: $!\n";
+}
+
+sub cmd_setup_mergechangelogs {
+    badusage "no arguments allowed to dgit setup-mergechangelogs" if @ARGV;
+    setup_mergechangelogs(1);
+}
+
+sub cmd_setup_useremail {
+    badusage "no arguments allowed to dgit setup-mergechangelogs" if @ARGV;
+    setup_useremail(1);
+}
+
+sub cmd_setup_new_tree {
+    badusage "no arguments allowed to dgit setup-tree" if @ARGV;
+    setup_new_tree();
+}
+
+#---------- argument parsing and main program ----------
+
+sub cmd_version {
+    print "dgit version $our_version\n" or die $!;
+    exit 0;
+}
+
+our (%valopts_long, %valopts_short);
+our @rvalopts;
+
+sub defvalopt ($$$$) {
+    my ($long,$short,$val_re,$how) = @_;
+    my $oi = { Long => $long, Short => $short, Re => $val_re, How => $how };
+    $valopts_long{$long} = $oi;
+    $valopts_short{$short} = $oi;
+    # $how subref should:
+    #   do whatever assignemnt or thing it likes with $_[0]
+    #   if the option should not be passed on to remote, @rvalopts=()
+    # or $how can be a scalar ref, meaning simply assign the value
+}
+
+defvalopt '--since-version', '-v', '[^_]+|_', \$changes_since_version;
+defvalopt '--distro',        '-d', '.+',      \$idistro;
+defvalopt '',                '-k', '.+',      \$keyid;
+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 {
+    ($changesfile) = (@_);
+    if ($changesfile =~ s#^(.*)/##) {
+       $buildproductsdir = $1;
+    }
+};
+
+defvalopt '--initiator-tempdir','','.*', sub {
+    ($initiator_tempdir) = (@_);
+    $initiator_tempdir =~ m#^/# or
+       badusage "--initiator-tempdir must be used specify an".
+       " absolute, not relative, directory."
+};
+
+sub parseopts () {
+    my $om;
+
+    if (defined $ENV{'DGIT_SSH'}) {
+       @ssh = string_to_ssh $ENV{'DGIT_SSH'};
+    } elsif (defined $ENV{'GIT_SSH'}) {
+       @ssh = ($ENV{'GIT_SSH'});
+    }
+
+    my $oi;
+    my $val;
+    my $valopt = sub {
+       my ($what) = @_;
+       @rvalopts = ($_);
+       if (!defined $val) {
+           badusage "$what needs a value" unless @ARGV;
+           $val = shift @ARGV;
+           push @rvalopts, $val;
+       }
+       badusage "bad value \`$val' for $what" unless
+           $val =~ m/^$oi->{Re}$(?!\n)/s;
+       my $how = $oi->{How};
+       if (ref($how) eq 'SCALAR') {
+           $$how = $val;
+       } else {
+           $how->($val);
+       }
+       push @ropts, @rvalopts;
+    };
+
+    while (@ARGV) {
+       last unless $ARGV[0] =~ m/^-/;
+       $_ = shift @ARGV;
+       last if m/^--?$/;
+       if (m/^--/) {
+           if (m/^--dry-run$/) {
+               push @ropts, $_;
+               $dryrun_level=2;
+           } elsif (m/^--damp-run$/) {
+               push @ropts, $_;
+               $dryrun_level=1;
+           } elsif (m/^--no-sign$/) {
+               push @ropts, $_;
+               $sign=0;
+           } elsif (m/^--help$/) {
+               cmd_help();
+           } elsif (m/^--version$/) {
+               cmd_version();
+           } elsif (m/^--new$/) {
+               push @ropts, $_;
+               $new_package=1;
+           } elsif (m/^--([-0-9a-z]+)=(.+)/s &&
+                    ($om = $opts_opt_map{$1}) &&
+                    length $om->[0]) {
+               push @ropts, $_;
+               $om->[0] = $2;
+           } elsif (m/^--([-0-9a-z]+):(.*)/s &&
+                    !$opts_opt_cmdonly{$1} &&
+                    ($om = $opts_opt_map{$1})) {
+               push @ropts, $_;
+               push @$om, $2;
+           } elsif (m/^--(gbp|dpm)$/s) {
+               push @ropts, "--quilt=$1";
+               $quilt_mode = $1;
+           } elsif (m/^--ignore-dirty$/s) {
+               push @ropts, $_;
+               $ignoredirty = 1;
+           } elsif (m/^--no-quilt-fixup$/s) {
+               push @ropts, $_;
+               $quilt_mode = 'nocheck';
+           } elsif (m/^--no-rm-on-error$/s) {
+               push @ropts, $_;
+               $rmonerror = 0;
+           } elsif (m/^--overwrite$/s) {
+               push @ropts, $_;
+               $overwrite_version = '';
+           } elsif (m/^--overwrite=(.+)$/s) {
+               push @ropts, $_;
+               $overwrite_version = $1;
+           } elsif (m/^--dep14tag$/s) {
+               push @ropts, $_;
+               $dodep14tag= 'want';
+           } elsif (m/^--no-dep14tag$/s) {
+               push @ropts, $_;
+               $dodep14tag= 'no';
+           } elsif (m/^--always-dep14tag$/s) {
+               push @ropts, $_;
+               $dodep14tag= 'always';
+           } elsif (m/^--delayed=(\d+)$/s) {
+               push @ropts, $_;
+               push @dput, $_;
+           } elsif (m/^--dgit-view-save=(.+)$/s) {
+               push @ropts, $_;
+               $split_brain_save = $1;
+               $split_brain_save =~ s#^(?!refs/)#refs/heads/#;
+           } elsif (m/^--(no-)?rm-old-changes$/s) {
+               push @ropts, $_;
+               $rmchanges = !$1;
+           } elsif (m/^--deliberately-($deliberately_re)$/s) {
+               push @ropts, $_;
+               push @deliberatelies, $&;
+           } elsif (m/^--force-(.*)/ && defined $forceopts{$1}) {
+               push @ropts, $&;
+               $forceopts{$1} = 1;
+               $_='';
+           } elsif (m/^--force-/) {
+               print STDERR
+                   "$us: warning: ignoring unknown force option $_\n";
+               $_='';
+           } elsif (m/^--dgit-tag-format=(old|new)$/s) {
+               # undocumented, for testing
+               push @ropts, $_;
+               $tagformat_want = [ $1, 'command line', 1 ];
+               # 1 menas overrides distro configuration
+           } elsif (m/^--always-split-source-build$/s) {
+               # undocumented, for testing
+               push @ropts, $_;
+               $need_split_build_invocation = 1;
+           } elsif (m/^(--[-0-9a-z]+)(=|$)/ && ($oi = $valopts_long{$1})) {
+               $val = $2 ? $' : undef; #';
+               $valopt->($oi->{Long});
+           } else {
+               badusage "unknown long option \`$_'";
+           }
+       } else {
+           while (m/^-./s) {
+               if (s/^-n/-/) {
+                   push @ropts, $&;
+                   $dryrun_level=2;
+               } elsif (s/^-L/-/) {
+                   push @ropts, $&;
+                   $dryrun_level=1;
+               } elsif (s/^-h/-/) {
+                   cmd_help();
+               } elsif (s/^-D/-/) {
+                   push @ropts, $&;
+                   $debuglevel++;
+                   enabledebug();
+               } elsif (s/^-N/-/) {
+                   push @ropts, $&;
+                   $new_package=1;
+               } elsif (m/^-m/) {
+                   push @ropts, $&;
+                   push @changesopts, $_;
+                   $_ = '';
+               } elsif (s/^-wn$//s) {
+                   push @ropts, $&;
+                   $cleanmode = 'none';
+               } elsif (s/^-wg$//s) {
+                   push @ropts, $&;
+                   $cleanmode = 'git';
+               } elsif (s/^-wgf$//s) {
+                   push @ropts, $&;
+                   $cleanmode = 'git-ff';
+               } elsif (s/^-wd$//s) {
+                   push @ropts, $&;
+                   $cleanmode = 'dpkg-source';
+               } elsif (s/^-wdd$//s) {
+                   push @ropts, $&;
+                   $cleanmode = 'dpkg-source-d';
+               } elsif (s/^-wc$//s) {
+                   push @ropts, $&;
+                   $cleanmode = 'check';
+               } elsif (s/^-c([^=]*)\=(.*)$//s) {
+                   push @git, '-c', $&;
+                   $gitcfgs{cmdline}{$1} = [ $2 ];
+               } elsif (s/^-c([^=]+)$//s) {
+                   push @git, '-c', $&;
+                   $gitcfgs{cmdline}{$1} = [ 'true' ];
+               } elsif (m/^-[a-zA-Z]/ && ($oi = $valopts_short{$&})) {
+                   $val = $'; #';
+                   $val = undef unless length $val;
+                   $valopt->($oi->{Short});
+                   $_ = '';
+               } else {
+                   badusage "unknown short option \`$_'";
+               }
+           }
+       }
+    }
+}
+
+sub check_env_sanity () {
+    my $blocked = new POSIX::SigSet;
+    sigprocmask SIG_UNBLOCK, $blocked, $blocked or die $!;
+
+    eval {
+       foreach my $name (qw(PIPE CHLD)) {
+           my $signame = "SIG$name";
+           my $signum = eval "POSIX::$signame" // die;
+           ($SIG{$name} // 'DEFAULT') eq 'DEFAULT' or
+               die "$signame is set to something other than SIG_DFL\n";
+           $blocked->ismember($signum) and
+               die "$signame is blocked\n";
+       }
+    };
+    return unless $@;
+    chomp $@;
+    fail <<END;
+On entry to dgit, $@
+This is a bug produced by something in in your execution environment.
+Giving up.
+END
+}
+
+
+sub finalise_opts_opts () {
+    foreach my $k (keys %opts_opt_map) {
+       my $om = $opts_opt_map{$k};
+
+       my $v = access_cfg("cmd-$k", 'RETURN-UNDEF');
+       if (defined $v) {
+           badcfg "cannot set command for $k"
+               unless length $om->[0];
+           $om->[0] = $v;
+       }
+
+       foreach my $c (access_cfg_cfgs("opts-$k")) {
+           my @vl =
+               map { $_ ? @$_ : () }
+               map { $gitcfgs{$_}{$c} }
+               reverse @gitcfgsources;
+           printdebug "CL $c ", (join " ", map { shellquote } @vl),
+               "\n" if $debuglevel >= 4;
+           next unless @vl;
+           badcfg "cannot configure options for $k"
+               if $opts_opt_cmdonly{$k};
+           my $insertpos = $opts_cfg_insertpos{$k};
+           @$om = ( @$om[0..$insertpos-1],
+                    @vl,
+                    @$om[$insertpos..$#$om] );
+       }
+    }
+}
+
+if ($ENV{$fakeeditorenv}) {
+    git_slurp_config();
+    quilt_fixup_editor();
+}
+
+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"
+    if $dryrun_level == 1;
+if (!@ARGV) {
+    print STDERR $helpmsg or die $!;
+    exit 8;
+}
+my $cmd = shift @ARGV;
+$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;
+}
+
+if (!defined $dodep14tag) {
+    local $access_forpush;
+    $dodep14tag = access_cfg('dep14tag', 'RETURN-UNDEF') // 'want';
+    $dodep14tag =~ m/^($dodep14tag_re)$/ 
+       or badcfg "unknown dep14tag setting \`$dodep14tag'";
+    $dodep14tag = $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;
+}
+
+my $fn = ${*::}{"cmd_$cmd"};
+$fn or badusage "unknown operation $cmd";
+$fn->();
diff --git a/dgit-maint-gbp.7.pod b/dgit-maint-gbp.7.pod
new file mode 100644 (file)
index 0000000..c31dfa5
--- /dev/null
@@ -0,0 +1,126 @@
+=head1 NAME
+
+dgit - tutorial for package maintainers already using git-buildpackage(1)
+
+=head1 INTRODUCTION
+
+This document explains how B<dgit> can be incorporated into a
+git-buildpackage(1) package-maintenance workflow.  This should be read
+jointly with git-buildpackage(1)'s documentation.  Some reasons why
+you might want to incorporate B<dgit> into your existing workflow:
+
+=over 4
+
+=item
+
+Benefit from dgit's safety catches.  In particular, ensure that your
+upload always matches exactly your git HEAD.
+
+=item
+
+Provide a better, more detailed git history to downstream dgit users,
+such as people using dgit to do an NMU (see dgit-nmu-simple(7) and
+dgit-user(7)).
+
+=back
+
+Note that we assume a patches-unapplied repository: the upstream
+source committed to the git repository is unpatched.
+git-buildpackage(1) can work with patched-applied repositories, but is
+normally used with patches-unapplied.
+
+=head1 GIT CONFIGURATION
+
+If you run
+
+=over 4
+
+    % git config dgit.default.quilt-mode gbp
+
+=back
+
+in your repository, you can omit I<--gbp> wherever it occurs below.
+
+Note that this does require that you always work from your gbp master
+branch, never the dgit patches-applied branch.
+
+=head1 BUILDING
+
+You can perform builds like this:
+
+=over 4
+
+    % dgit [--allow-dirty] gbp-build [OPTIONS]
+
+=back
+
+where I<--allow-dirty> is needed for testing uncommitted changes, and
+I<OPTIONS> are any further options to be passed on to
+gbp-buildpackage(1).
+
+When you are ready to build for upload, you will probably want to use
+sbuild(1) or pbuilder(1), or do a source-only upload.  Either
+
+=over 4
+
+    % dgit --rm-old-changes --gbp sbuild
+
+=back
+
+or
+
+=over 4
+
+    % dgit --rm-old-changes gbp-build --git-pbuilder
+
+=back
+
+or
+
+=over 4
+
+    % dgit --rm-old-changes --gbp build-source
+
+=back
+
+We use I<--rm-old-changes> to ensure that there is exactly one changes
+file corresponding to this package, so we can be confident we're
+uploading what we intend (though B<dgit push> will do some safety
+checks).
+
+Note that all of the commands in this section are not required to
+upload with dgit.  You can invoke gbp-buildpackage(1), pbuilder(1) and
+sbuild(1) directly.  However, the defaults for these tools may leave
+you with something that dgit will refuse to upload because it doesn't
+match your git HEAD.  As a general rule, leave all signing and tagging
+to dgit.
+
+=head1 UPLOADING
+
+Don't use I<--git-tag>: B<dgit push> will do this for you.  To upload:
+
+=over 4
+
+    % dgit --gbp push
+
+=back
+
+This will push your git history to the dgit-repos, but you probably
+want to follow it up with a push to alioth.
+
+You will need to pass I<--overwrite> if the previous upload was not
+performed with dgit.
+
+=head1 INCORPORATING NMUS
+
+B<dgit pull> can't yet incorporate NMUs into patches-unapplied gbp
+branches.  You can just apply the NMU diff the traditional way.  The
+next upload will require I<--overwrite>.
+
+=head1 SEE ALSO
+
+dgit(1), dgit(7)
+
+=head1 AUTHOR
+
+This tutorial was written and is maintained by Sean Whitton <spwhitton@spwhitton.name>.
diff --git a/dgit-maint-merge.7.pod b/dgit-maint-merge.7.pod
new file mode 100644 (file)
index 0000000..dc5bfa3
--- /dev/null
@@ -0,0 +1,436 @@
+=head1 NAME
+
+dgit - tutorial for package maintainers, using a workflow centered around git-merge(1)
+
+=head1 INTRODUCTION
+
+This document describes elements of a workflow for maintaining a
+non-native Debian package using B<dgit>.  The workflow makes the
+following opinionated assumptions:
+
+=over 4
+
+=item
+
+Git histories should be the non-linear histories produced by
+git-merge(1), preserving all information about divergent development
+that was later brought together.
+
+=item
+
+Maintaining convenient and powerful git workflows takes priority over
+the usefulness of the raw Debian source package.  The Debian archive
+is thought of as an output format.
+
+For example, we don't spend time curating a series of quilt patches.
+However, the information such a series would contain is readily
+available from B<dgit-repos>.
+
+=item
+
+It is more important to have the Debian package's git history be a
+descendent of upstream's git history than to use exactly the orig.tar
+that upstream makes available for download.
+
+=back
+
+=head1 GIT CONFIGURATION
+
+Add the following to your ~/.gitconfig to teach git-archive(1) how to
+compress orig tarballs:
+
+=over 4
+
+    [tar "tar.xz"]
+       command = xz -c
+    [tar "tar.gz"]
+       command = gzip -c
+
+=back
+
+=head1 INITIAL DEBIANISATION
+
+This section explains how to start using this workflow with a new
+package.  It should be skipped when converting an existing package to
+this workflow.
+
+=head2 When upstream tags releases in git
+
+Suppose that the latest stable upstream release is 1.2.2, and this has
+been tagged '1.2.2' by upstream.
+
+=over 4
+
+    % git clone -oupstream https://some.upstream/foo.git
+    % cd foo
+    % git verify-tag 1.2.2
+    % git reset --hard 1.2.2
+    % git branch --unset-upstream
+
+=back
+
+The final command detachs your master branch from the upstream remote,
+so that git doesn't try to push anything there, or merge unreleased
+upstream commits.  If you want to maintain a copy of your packaging
+branch on B<alioth.debian.org> in addition to B<dgit-repos>, you can
+do something like this:
+
+=over 4
+
+    % git remote add -f origin git.debian.org:/git/collab-maint/foo.git
+    % git push --follow-tags -u origin master
+
+=back
+
+Now go ahead and Debianise your package.  Just make commits on the
+master branch, adding things in the I<debian/> directory.  If you need
+to patch the upstream source, just make commits that change files
+outside of the I<debian/> directory.  It is best to separate commits
+that touch I<debian/> from commits that touch upstream source, so that
+the latter can be cherry-picked by upstream.
+
+Note that there is no need to maintain a separate 'upstream' branch,
+unless you also happen to be involved in upstream development.  We
+work with upstream tags rather than any branches, except when
+forwarding patches (see FORWARDING PATCHES UPSTREAM, below).
+
+Finally, you need an orig tarball.  Generate one with git-archive(1):
+
+=over 4
+
+    % git archive -o ../foo_1.2.2.orig.tar.xz 1.2.2
+
+=back
+
+If you are using the version 1.0 source package format, replace 'xz'
+with 'gz'.
+
+This tarball is ephemeral and easily regenerated, so we don't commit
+it anywhere (e.g. with tools like pristine-tar(1)).
+
+=head3 Verifying upstream's tarball releases
+
+=over 4
+
+It can be a good idea to compare upstream's released tarballs with the
+release tags, at least for the first upload of the package.  If they
+are different, you might need to add some additional steps to your
+I<debian/rules>, such as running autotools.
+
+A convenient way to perform this check is to import the tarball as
+described in the following section, using a different value for
+'upstream-tag', and then use git-diff(1) to compare the imported
+tarball to the release tag.  If they are the same, you can use
+upstream's tarball instead of running git-archive(1).
+
+=back
+
+=head2 When upstream releases only tarballs
+
+We need a virtual upstream branch with virtual release tags.
+gbp-import-orig(1) can manage this for us.  To begin
+
+=over 4
+
+    % mkdir foo
+    % cd foo
+    % git init
+
+=back
+
+Now create I<debian/gbp.conf>:
+
+=over 4
+
+    [DEFAULT]
+    upstream-branch = upstream
+    debian-branch = master
+    upstream-tag = %(version)s
+
+    sign-tags = True
+    pristine-tar = False
+    pristine-tar-commit = False
+
+=back
+
+Then we can import the upstream version:
+
+=over 4
+
+    % git add debian/gbp.conf && git commit -m "create gbp.conf"
+    % gbp import-orig ../foo_1.2.2.orig.tar.xz
+
+=back
+
+You are now ready to proceed as above, making commits to both the
+upstream source and the I<debian/> directory.
+
+If you want to maintain a copy of your repository on
+B<alioth.debian.org>, you should push both the origin and the upstream
+branches:
+
+=over 4
+
+    % git remote add -f origin git.debian.org:/git/collab-maint/foo.git
+    % git push --follow-tags -u origin master upstream
+
+=back
+
+=head1 CONVERTING AN EXISTING PACKAGE
+
+This section explains how to convert an existing Debian package to
+this workflow.  It should be skipped when debianising a new package.
+
+=head2 No existing git history
+
+=over 4
+
+    % dgit clone foo
+    % cd foo
+    % git remote add -f upstream https://some.upstream/foo.git
+
+=back
+
+=head2 Existing git history using another workflow
+
+First, dump any existing patch queue:
+
+=over 4
+
+    % git rm -rf debian/patches
+    % git commit -m "drop existing quilt patch queue"
+
+=back
+
+Then make new upstream tags available:
+
+=over 4
+
+    % git remote add -f upstream https://some.upstream/foo.git
+
+=back
+
+Now you simply need to ensure that your git HEAD is dgit-compatible,
+i.e., it is exactly what you would get if you ran B<dpkg-buildpackage
+-i\.git/ -I.git -S> and then unpacked the resultant source package.
+
+To achieve this, you might need to delete
+I<debian/source/local-options>.  One way to have dgit check your
+progress is to run B<dgit build-source>.
+
+The first dgit push will require I<--overwrite>.
+
+=head1 SOURCE PACKAGE CONFIGURATION
+
+=head2 debian/source/options
+
+We set some source package options such that dgit can transparently
+handle the "dropping" and "refreshing" of changes to the upstream
+source:
+
+=over 4
+
+    single-debian-patch
+    auto-commit
+
+=back
+
+You don't need to create this file if you are using the version 1.0
+source package format.
+
+=head2 Sample text for README.source
+
+It is a good idea to explain how a user can obtain a break down of the
+changes to the upstream source:
+
+=over 4
+
+The Debian packaging of foo is maintained using dgit.  For the sake of
+an efficient workflow, Debian modifications to the upstream source are
+squashed into a single diff, rather than a series of quilt patches.
+To obtain a patch queue for package version 1.2.3-1:
+
+=over 4
+
+    # apt-get install dgit
+    % dgit clone foo
+    % cd foo
+    % git log --oneline 1.2.3..debian/1.2.3-1 -- . ':!debian'
+
+=back
+
+See dgit(1), dgit(7) and dgit-maint-merge(7) for more information.
+
+=back
+
+=head1 BUILDING AND UPLOADING
+
+Use B<dgit build>, B<dgit sbuild>, B<dgit build-source>, and B<dgit
+push> as detailed in dgit(1).  If any command fails, dgit will provide
+a carefully-worded error message explaining what you should do.  If
+it's not clear, file a bug against dgit.  Remember to pass I<--new>
+for the first upload.
+
+As an alternative to B<dgit build> and friends, you can use a tool
+like gitpkg(1).  This works because like dgit, gitpkg(1) enforces that
+HEAD has exactly the contents of the source package.  gitpkg(1) is
+highly configurable, and one dgit user reports using it to produce and
+test multiple source packages, from different branches corresponding
+to each of the current Debian suites.
+
+If you want to skip dgit's checks while iterating on a problem with
+the package build (for example, you don't want to commit your changes
+to git), you can just run dpkg-buildpackage(1) or debuild(1) instead.
+
+=head1 NEW UPSTREAM RELEASES
+
+=head2 When upstream tags releases in git
+
+It's a good idea to preview the merge of the new upstream release.
+First, just check for any new or deleted files that may need
+accounting for in your copyright file:
+
+=over 4
+
+    % git remote update
+    % git diff --stat master..1.2.3 -- . ':!debian'
+
+=back
+
+You can then review the full merge diff:
+
+=over 4
+
+    % git merge-tree `git merge-base master 1.2.3` master 1.2.3 | $PAGER
+
+=back
+
+Once you're satisfied with what will be merged, update your package:
+
+=over 4
+
+    % git archive -o ../foo_1.2.3.orig.tar.xz 1.2.3
+    % git merge 1.2.3
+    % dch -v1.2.3-1 New upstream release.
+    % git add debian/changelog && git commit -m changelog
+
+=back
+
+and you are ready to try a build.
+
+Again, if you are using the version 1.0 source package format, replace
+'xz' with 'gz'.
+
+=head2 When upstream releases only tarballs
+
+You will need the I<debian/gbp.conf> from "When upstream releases only
+tarballs", above.
+
+Then, either
+
+=over 4
+
+    % gbp import-orig ../foo_1.2.2.orig.tar.xz
+
+=back
+
+or if you have a working watch file
+
+=over 4
+
+    % gbp import-orig --uscan
+
+=back
+
+=head1 HANDLING DFSG-NON-FREE MATERIAL
+
+=head2 When upstream tags releases in git
+
+We create a DFSG-clean tag to merge to master:
+
+=over 4
+
+    % git checkout -b pre-dfsg 1.2.3
+    % git rm evil.bin
+    % git commit -m "upstream version 1.2.3 DFSG-cleaned"
+    % git tag -s 1.2.3+dfsg
+    % git checkout master
+    % git branch -D pre-dfsg
+
+=back
+
+Before merging the new 1.2.3+dfsg tag to master, you should first
+determine whether it would be legally dangerous for the non-free
+material to be publicly accessible in the git history on
+B<dgit-repos>.
+
+If it would be dangerous, there is a big problem;
+in this case please consult your archive administrators
+(for Debian this is the dgit administrator dgit-owner@debian.org
+and the ftpmasters ftpmaster@ftp-master.debian.org).
+
+=head2 When upstream releases only tarballs
+
+The easiest way to handle this is to add a B<Files-Excluded> field to
+I<debian/copyright>, and a B<uversionmangle> setting in
+I<debian/watch>.  See uscan(1).  Alternatively, see the I<--filter>
+option detailed in gbp-import-orig(1).
+
+=head1 FORWARDING PATCHES UPSTREAM
+
+The basic steps are:
+
+=over 4
+
+=item 1.
+
+Create a new branch based off upstream's master branch.
+
+=item 2.
+
+git-cherry-pick(1) commits from your master branch onto your new
+branch.
+
+=item 3.
+
+Push the branch somewhere and ask upstream to merge it, or use
+git-format-patch(1) or git-request-pull(1).
+
+=back
+
+For example (and it is only an example):
+
+=over 4
+
+    % # fork foo.git on GitHub
+    % git remote add -f fork git@github.com:spwhitton/foo.git
+    % git checkout -b fix-error upstream/master
+    % git config branch.fix-error.pushRemote fork
+    % git cherry-pick master^2
+    % git push
+    % # submit pull request on GitHub
+
+=back
+
+Note that when you merge an upstream release containing your forwarded
+patches, git and dgit will transparently handle "dropping" the patches
+that have been forwarded, "retaining" the ones that haven't.
+
+=head1 INCORPORATING NMUS
+
+=over 4
+
+    % dgit pull
+
+=back
+
+Alternatively, you can apply the NMU diff to your repository.  The
+next push will then require I<--overwrite>.
+
+=head1 SEE ALSO
+
+dgit(1), dgit(7)
+
+=head1 AUTHOR
+
+This tutorial was written and is maintained by Sean Whitton <spwhitton@spwhitton.name>.  It contains contributions from other dgit contributors too - see the dgit copyright file.
diff --git a/dgit-maint-native.7.pod b/dgit-maint-native.7.pod
new file mode 100644 (file)
index 0000000..03aee59
--- /dev/null
@@ -0,0 +1,116 @@
+=head1 NAME
+
+dgit - tutorial for package maintainers of Debian-native packages
+
+=head1 INTRODUCTION
+
+This document describes elements of a workflow for using B<dgit> to
+maintain a Debian package that uses one of the native source formats
+("1.0" & "3.0 (native)").
+
+=over 4
+
+=item
+
+We expect that your git history is fast-forwarding.
+
+=item
+
+You should be prepared to tolerate a small amount of
+ugliness in your git history
+in the form of merges which stitch
+the dgit-generated archive view
+into your maintainer history.
+
+This is to handle uploads that were not made with dgit,
+such as the uploads you made before switching to this workflow,
+or NMUs.
+
+=back
+
+=head2 Benefits
+
+=over 4
+
+=item
+
+Benefit from dgit's safety catches.  In particular, ensure that your
+upload always matches exactly your git HEAD.
+
+=item
+
+Provide a better,
+more detailed history
+to downstream dgit users.
+
+=item
+
+Incorporate an NMU with one command.
+
+=back
+
+=head1 FIRST PUSH WITH DGIT
+
+You do not need to do anything special to your tree
+to push with dgit.
+
+Simply prepare your git tree in the usual way, and then:
+
+=over 4
+
+    % dgit -wgf sbuild -A -c sid
+    % dgit -wgf --overwrite push
+
+=back
+
+(Do not make any tags yourself: dgit push will do that.)
+
+The --overwrite option tells dgit that you are expecting
+that your git history is not a descendant of the
+history which dgit synthesised from the previous
+non-dgit uploads.
+
+dgit will make a merge commit
+on your branch
+but without making any code changes
+(ie, a pseudo-merge)
+so that your history,
+which will be pushed to the dgit git server,
+is fast forward from the dgit archive view.
+
+=head1 SUBSEQUENT PUSHES
+
+=over 4
+
+    % dgit -wgf push
+
+=back
+
+That's it.
+
+=head1 INCORPORATING AN NMU
+
+=over 4
+
+    % dgit pull
+
+=back
+
+That's it.
+
+Or, if you would prefer to review the changes,
+you can do this:
+
+=over 4
+
+    % dgit fetch
+    % dgit diff HEAD..dgit/dgit/sid
+
+=back
+
+If you do not merge the NMU into your own git history,
+the next push will then require I<--overwrite>.
+
+=head1 SEE ALSO
+
+dgit(1), dgit(7)
diff --git a/dgit-nmu-simple.7.pod b/dgit-nmu-simple.7.pod
new file mode 100644 (file)
index 0000000..3ebc68a
--- /dev/null
@@ -0,0 +1,139 @@
+=head1 NAME
+
+dgit-nmu-simple - tutorial for DDs wanting to NMU with git
+
+=head1 INTRODUCTION AND SCOPE
+
+This tutorial describes how a Debian Developer can do
+a straightforward NMU
+of a package in Debian, using dgit.
+
+This document won't help you decide whether
+an NMU is a good idea or
+whether it be well received.
+The Debian Developers' Reference has some
+(sometimes questionable) guidance on this.
+
+Conversely, you do not need to know anything
+about the usual maintainer's git workflow.
+If appropriate, you can work on many different packages,
+making similar changes,
+without worrying about the individual maintainers' git practices.
+
+This tutorial only covers changes which
+can sensibly be expressed as a
+reasonably small number of linear commits
+(whether to Debian packaging or to upstream files or both).
+
+If you want to do a new upstream version,
+you probably want to do as the maintainer would have done.
+You'll need to find out what the maintainer's
+git practices are
+and 
+consult the appropriate C<dgit-maint-*(7)> workflow tutorial,
+
+=head1 SUMMARY
+
+=over 4
+
+    % dgit clone glibc jessie
+    % cd glibc
+    % git am ~/glibc-security-fix.diff
+    % dch --nmu "Apply upstream's fix for foo bug."
+    % git add debian/changelog && git commit -m"NMU changelog entry"
+    % dpkg-buildpackage -uc -b
+    [ run your tests ]
+    % dch -r && git add debian/changelog && git commit -m"Finalise NMU"
+    % dgit -wgf sbuild -A -c jessie
+    [ final tests on generated .debs ]
+    % dgit -wgf [--delayed=5] push jessie
+    [ enter your gnupg passphrase as prompted ]
+    [ see that push and upload are successful ]
+    [ prepare and email NMU diff (git-diff, git-format-patch) ]
+
+=back
+
+=head1 WHAT KIND OF CHANGES AND COMMITS TO MAKE
+
+When preparing an NMU, the git commits you make on the dgit branch
+should be simple linear series of commmits with good commit messages.
+The commit messages will be published in various ways,
+including perhaps being used as the cover messages for
+genrated quilt patches.
+
+Do not make merge commits.
+Do not try to rebase to drop patches - if you need to revert a
+change which is actually a Debian patch,
+use git-revert.
+
+If you need to modify a Debian patch,
+make a new commit which fixes what needs fixing,
+and explain in the commit message which patch it should be
+squashed with
+(perhaps by use of a commit message in C<git rebase --autosquash -i>
+format).
+
+(Of course if you have specific instructions from the maintainer,
+you can follow those instead.
+But the procedure in this tutorial is legitimate for any maintainer,
+in the sense that it should generate an upload to which the
+maintainer cannot reasonably object.)
+
+=head1 RELEVANT BRANCHES
+
+dgit clone will put you on a branch like C<dgit/sid>.
+There is a pseudo-remote called C<dgit> which also contains a branch
+like C<dgit/sid>, so you do things like
+C<git diff dgit/dgit/sid>
+to see what changes you have made.
+
+=head1 KEEPING YOUR WORKING TREE TIDY
+
+Don't forget to C<git add> any new files you create.
+Otherwise git clean
+(which is requested with the C<-wgf> option in the recipe above)
+will delete them.
+
+Many package builds leave dirty git trees.
+So, commit before building.
+That way you can use C<git reset --hard>.
+
+If you follow this approach
+you don't need to care about the build dirtying the
+tree.
+It also means you don't care about the package clean target,
+which is just as well because many package clean targets are broken.
+
+=head1 OTHER GIT BRANCHES
+
+The dgit git history
+(visible in gitk and git log)
+is not necessarily related to the maintainer's
+or upstream's git history (if any).
+
+If the maintainer has advertised a git repo with
+Vcs-Git
+dgit will set up a remote for it,
+so you can do
+
+=over 4
+
+    % git fetch vcs-git
+
+=back
+
+You can cherry pick changes from there, for example. 
+Note that the maintainer's git history may not be
+suitable for use with dgit.
+For example, it might be a patches-unapplied branch
+or even contain only a debian/ directory.
+
+=head1 UPLOADING TO DELAYED
+
+You can use dgit's I<--delayed> option
+to upload to the DELAYED queue.
+However, you should read the warning about this option in dgit(1).
+
+=head1 SEE ALSO
+
+dgit(1), dgit(7), dgit-maint-*(7)
diff --git a/dgit-sponsorship.7.pod b/dgit-sponsorship.7.pod
new file mode 100644 (file)
index 0000000..3fc59d2
--- /dev/null
@@ -0,0 +1,317 @@
+=head1 NAME
+
+dgit-sponsorship - tutorial for Debian upload sponsorship, using git
+
+=head1 INTRODUCTION AND SCOPE
+
+This tutorial describes how a Debian sponsored contributor
+and
+a sponsoring DD (or DM)
+can collaborate and publish using git.
+
+The sponsor must to be intending to use dgit for the upload.
+(If the sponsor does not use dgit,
+it is not possible to properly publish
+a sponsee's git branch.)
+
+It is best if the sponsee also uses dgit;
+but also covered (later on) is the case where
+the sponsee provides a proposed upload in source package form,
+but the sponsor would like to work in git.
+
+This tutorial does not provide a checklist for the sponsor's review.
+Both contributors are expected to be familiar with Debian
+packaging and Debian's processes, and with git.
+
+=head1 SPONSEE WORKFLOW
+
+This section is addressed to the sponsee:
+
+=head2 General
+
+You should prepare the package as if you were going
+to upload it with C<dgit push> yourself.
+
+For a straightforward NMU, consult L<dgit-nmu-simple(7)>.
+
+If you are the (prospective) maintainer,
+you can adopt any suitable (dgit-compatible)
+git workflow.
+The L<dgit-maint-*(7)> tutorials describe some of the possibilities.
+
+=head2 Upload preparation
+
+You should go through all of the steps 
+a self-uploading maintainer would do,
+including building for ad hoc tests,
+and checking via a formal build (eg using C<dgit sbuild>)
+that the package builds on sid (or the target release).
+
+At the point where you would,
+if you were a DD, 
+do the actual upload
+by running dgit push,
+you hand off to your sponsor.
+
+If you were going to use one of the
+C<--quilt=>
+options to dgit, or
+C<dgit --gbp> or C<dgit --dpm>,
+you must specify that in your handoff email - see below.
+
+=head2 git+origs based handoff
+
+The elements of the handoff consists of:
+
+=over
+
+=item *
+
+The git branch.
+
+=item *
+
+Any .orig tarballs which will be needed,
+or sample git-archive(1)
+or gbp-buildpackage(1)
+command(s) to generate them.
+
+=item *
+
+A sample dgit push command, containing
+any dgit --quilt=, --gbp or --dpm option needed
+
+=item *
+
+Plus of course all the usual information about the state
+of the package,
+any caveats or areas you would like the sponsor to focus their review,
+constraints about upload timing, etc.
+
+=back
+
+If the handoff is done by email,
+the elements above should be a in a single, signed, message.
+This could be an RFS submission
+against the sponsorship-requests pseudo-package.
+
+=head3 git branch
+
+=over 4
+
+The sponsee should push their HEAD as a git branch
+to any suitable git server.
+They can use their own git server;
+alioth is another possibility.
+
+The branch names used by the sponsee on their local machine,
+and on the server, do not matter.
+
+The sponsee should not make a C<debian/>I<version> tag.
+
+Instead, the sponsee should include the
+git commit id of their HEAD
+in their handover email.
+
+=back
+
+=head3 orig tarballs
+
+=over 4
+
+If there are any .origs that are not in the archive already,
+the sponsor will need them as part of the upload.
+
+If the sponsee generated these tarballs with git-archive(1)
+or gbp-buildpackage(1),
+they can simply include a sample invocation of git-archive(1)
+or ensure that a suitable gbp.conf is present
+in the source package
+to generate the tarball.
+
+Otherwise, the simplest approach is to
+commit the orig tarballs
+with pristine-tar(1), e.g.
+
+=over 4
+
+    % pristine-tar commit ../foo_1.2.3.orig.tar.xz upstream/1.2.3
+
+=back
+
+and be sure to push the pristine-tar branch.
+If you are using git-buildpackage(1), just pass
+I<--git-pristine-tar> and I<--git-pristine-tar-commit>.
+
+Alternatively,
+the sponsee can put them on a suitable webserver,
+or attach to the e-mail,
+if they are small.
+
+The sponsee should quote sha256sums of the .origs in their
+handoff email,
+unless they supplied commands to generate them.
+
+=back
+
+=head3 quilt options
+
+=over 4
+
+Some workflows involve git branches which are not natively
+dgit-compatible.
+Normally dgit will convert them as needed, during push.
+
+Supply a sample "dgit push" command
+including any
+C<--gbp> (aka C<--quilt=gbp>),
+C<--dpm> (aka C<--quilt=dpm>),
+or other C<--quilt=> option
+they need to use.
+e.g.
+
+=over 4
+
+    % dgit --gbp push
+
+=back
+
+=back
+
+=head1 SPONSOR WORKFLOW
+
+This part is addressed to the sponsor:
+
+=head2 Receiving and validating the sponsorship request
+
+You should check the signature on the email.
+
+Use C<git fetch> or C<git clone> to obtain the git branch
+prepared by your sponsee,
+and obtain any .origs mentioned by the sponsee
+(to extract .origs committed with pristine-tar,
+you can use origtargz(1),
+or use "gbp clone --pristine-tar".)
+
+Check the git commit ID of the sponsee's branch tip,
+and the sha256sums of the .origs,
+against the handoff email.
+
+Confirm that the sponsee has not made
+a debian/1.2.3-1 tag.
+If they have,
+it is best to ask them to delete it now,
+as it can cause confusion later when dgit push produces its own tag.
+
+Now you can check out the branch tip,
+and do your substantive review.
+
+=head2 Dealing with branches that want --quilt=
+
+If your sponsee mentioned a C<--quilt>
+option, and you don't want to grapple with their preferred tree format,
+you can convert their tree into the standard dgit view:
+
+=over 4
+
+    % dgit -wgf --quilt=foo --dgit-view-save=unquilted quilt-fixup
+    % git checkout unquilted
+
+=back
+
+You should check that what you're looking at is a descendant of
+the sponsee's branch.
+
+=head2 Some hints which may help the review
+
+C<dgit fetch sid> will get you an up-to-date
+C<refs/remotes/dgit/dgit/sid>
+showing what's in the archive already.
+
+C<dgit -wgf --damp-run push>
+will check that dgit can build an appropriate source package.
+
+There is no need to run debdiff.
+dgit will not upload anything that doesn't unpack
+to exactly the git commit you are pushing,
+so you can rely on what you see in C<git diff>.
+
+=head2 Doing the upload
+
+When you have completed your source review,
+and use
+C<dgit -wgf [--quilt=...] sbuild -A -C>
+or similar, to to the build, and then
+C<dgit -wgf [--quilt=...] push>
+to do the upload.
+
+(It is possible to upload from
+the quilt-cache dgit view,
+but this will cause the debian/1.2.3-1 tag to be
+placed on this branch
+rather than the sponsee's working branch.
+Since this might be confusing,
+it is a good idea to switch back to the sponsee's view,
+after reviewing and before pushing.
+If you do want to upload from the quilt-cache dgit view,
+B<do not> pass the --quilt or --gbp or --dpm option again.)
+
+If this was the first upload done with dgit,
+you may need to pass
+C<--overwrite>
+to dgit.
+
+
+=head1 SPONSORING A NON-GIT-USING SPONSEE
+
+This part is addressed to the sponsor:
+
+If your sponsee does not use git,
+you can still do your review with git,
+and use dgit for the upload.
+
+Your sponsee will provide you with a source package:
+that is, a .dsc and the files it refers to.
+Obtain these files, and check signatures as appropriate.
+Then:
+
+=over 4
+
+    % dgit clone PACKAGE
+    % cd PACKAGE
+    % dgit import-dsc /path/to/sponsee's.dsc +sponsee
+    % git checkout sponsee
+
+=back
+
+Or for an entirely new package:
+
+=over 4
+
+    % mkdir PACKAGE
+    % cd PACKAGE
+    % git init
+    % dgit -pPACKAGE import-dsc /path/to/sponsee's.dsc +sponsee
+
+=back
+
+This will leave you looking at the sponsee's package,
+formatted as a dgit branch.
+
+When you have finished your review and your tests,
+you can do the
+dgit sbuild and
+dgit push directly from the "sponsee" branch.
+
+You will need to pass
+C<--overwrite>
+to dgit push for every successive upload.
+This disables a safety catch which would normally spot
+situations where changes are accidentally lost.
+When your sponsee is sending you source packages -
+perhaps multiple source pacakges with the same version number -
+these safety catches are inevitably ineffective.
+
+=head1 SEE ALSO
+
+dgit(1), dgit(7), dgit-nmu-simple(7), dgit-maint-*(7)
diff --git a/dgit-user.7.pod b/dgit-user.7.pod
new file mode 100644 (file)
index 0000000..ad0cca1
--- /dev/null
@@ -0,0 +1,432 @@
+=head1 NAME
+
+dgit-user - making and sharing changes to Debian packages, with git
+
+=head1 INTRODUCTION
+
+dgit lets you fetch the source code to every package on your
+system
+as if your distro used git to maintain all of it.
+
+You can then edit it,
+build updated binary packages (.debs)
+and install and run them.
+You can also share your work with others.
+
+This tutorial gives some recipes and hints for this.
+It assumes you have basic familiarity with git.
+It does not assume any initial familiarity with
+Debian's packaging processes.
+
+If you are a package maintainer within Debian; a DM or DD;
+and/or a sponsee:
+this tutorial is not for you.
+Try L<dgit-nmu-simple(7)>, L<dgit-maint-*(7)>,
+or L<dgit(1)> and L<dgit(7)>.
+
+=head1 SUMMARY
+
+(These runes will be discussed later.)
+
+=over 4
+
+    % dgit clone glibc jessie,-security
+    % cd glibc
+    % wget 'https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=28250;mbox=yes;msg=89' | patch -p1 -u
+    % git commit -a -m 'Fix libc lost output bug'
+    % gbp dch -S --since=dgit/dgit/sid --ignore-branch --commit
+    % sudo apt-get build-dep glibc
+    % dpkg-buildpackage -uc -b
+    % sudo dpkg -i ../libc6_*.deb
+
+=back
+
+Occasionally:
+
+=over 4
+
+    % git clean -xdf
+    % git reset --hard
+
+=back
+
+Later:
+
+=over 4
+
+    % cd glibc
+    % dgit pull jessie,-security
+    % gbp dch -S --since=dgit/dgit/sid --ignore-branch --commit
+    % dpkg-buildpackage -uc -b
+    % sudo dpkg -i ../libc6_*.deb
+
+=back
+
+=head1 FINDING THE RIGHT SOURCE CODE - DGIT CLONE
+
+=over 4
+
+    % dgit clone glibc jessie,-security
+    % cd glibc
+
+=back
+
+dgit clone needs to be told the source package name
+(which might be different to the binary package name,
+which was the name you passed to "apt-get install")
+and the codename or alias of the Debian release
+(this is called the "suite").
+
+=head2 Finding the source package name
+
+For many packages, the source package name is obvious.
+Otherwise, if you know a file that's in the package,
+you can look it up with dpkg:
+
+=over 4
+
+    % dpkg -S /lib/i386-linux-gnu/libc.so.6 
+    libc6:i386: /lib/i386-linux-gnu/libc.so.6
+    % dpkg -s libc6:i386
+    Package: libc6
+    Status: install ok installed
+    ...
+    Source: glibc
+
+=back
+
+(In this example,
+libc6 is a "multi-arch: allowed" package,
+ which means that it exists in several different builds
+ for different architectures.
+That's where C<:i386> comes from.)
+
+=head2 Finding the Debian release (the "suite")
+
+Internally,
+Debian (and derived) distros normally refer to their releases by codenames.
+Debian also has aliases which refer to the current stable release etc.
+So for example, at the time of writing
+Debian C<jessie> (Debian 8) is Debian C<stable>; and
+the current version of Ubuntu is C<yakkety> (Yakkety Yak, 16.10).
+You can specify either
+the codename C<jessie> or the alias C<stable>.
+If you don't say, you get C<sid>,
+which is Debian C<unstable> - the main work-in progress branch.
+
+If you don't know what you're running, try this:
+
+=over 4
+
+    % grep '^deb' /etc/apt/sources.list
+    deb http://the.earth.li/debian/ jessie main non-free contrib
+    ...
+    %
+
+=back
+
+For Debian, you should add C<,-security>
+to the end of the suite name,
+unless you're on unstable or testing.
+Hence, in our example
+C<jessie> becomes C<jessie,-security>.
+(Yes, with a comma.)
+
+=head1 WHAT DGIT CLONE PRODUCES
+
+=head2 What branches are there
+
+dgit clone will give you a new working tree,
+and arrange for you to be on a branch named like
+C<dgit/jessie,-security> (yes, with a comma in the branch name).
+
+For each release (like C<jessie>)
+there is a tracking branch for the contents of the archive, called
+C<remotes/dgit/dgit/jessie>
+(and similarly for other suites).  This can be updated with
+C<dgit fetch jessie>.
+This, the I<remote suite branch>,
+is synthesized by your local copy of dgit.
+It is fast forwarding.
+
+Debian separates out the security updates, into C<*-security>.
+Telling dgit C<jessie,-security> means that it should include 
+any updates available in C<jessie-security>.
+The comma notation is a request to dgit to track jessie,
+or jessie-security if there is an update for the package there.
+
+(You can also dgit fetch in a tree that wasn't made by dgit clone.
+If there's no C<debian/changelog>
+you'll have to supply a C<-p>I<package> option to dgit fetch.)
+
+=head2 What kind of source tree do you get
+
+If the Debian package is based on some upstream release,
+the code layout should be like the upstream version.
+You should find C<git grep> helpful to find where to edit.
+
+The package's Debian metadata and the scripts for building binary
+packages are under C<debian/>.
+C<debian/control>, C<debian/changelog> and C<debian/rules> are the
+starting points.
+The Debian Policy Manual has most of the in-depth
+technical details.
+
+For many Debian packages,
+there will also be some things in C<debian/patches/>.
+It is best to ignore these.
+Insofar as they are relevant
+the changes there will have been applied to the actual files,
+probably by means of actual comments in the git history.
+The contents of debian/patches are ignored
+when building binaries
+from dgitish git branches.
+
+(For Debian afficionados:
+the git trees that come out of dgit are
+"patches-applied packaging branches
+without a .pc directory".)
+
+=head2 What kind of history you get
+
+If you're lucky, the history will be a version of,
+or based on,
+the Debian maintainer's own git history,
+or upstream's git history.
+
+But for many packages the real git history
+does not exist,
+or has not been published in a dgitish form.
+So yuu may find that the history is a rather short
+history invented by dgit.
+
+dgit histories often contain automatically-generated commits,
+including commits which make no changes but just serve
+to make a rebasing branch fast-forward.
+This is particularly true of
+combining branches like
+C<jessie,-security>.
+
+If the package maintainer is using git then
+after dgit clone
+you may find that there is a useful C<vcs-git> remote
+referring to the Debian package maintainer's repository
+for the package.
+You can see what's there with C<git fetch vcs-git>.
+But use what you find there with care:
+Debian maintainers' git repositories often have
+contents which are very confusing and idiosyncratic.
+In particular, you may need to manually apply the patches
+that are in debian/patches before you do anything else!
+
+=head1 BUILDING
+
+=head2 Always commit before building
+
+=over 4
+
+    % wget 'https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=28250;mbox=yes;msg=89' | patch -p1 -u
+    % git commit -a -m 'Fix libc lost output bug'
+
+=back
+
+Debian package builds are often quite messy:
+they may modify files which are also committed to git,
+or leave outputs and teporary files not covered by C<.gitignore>.
+
+Kf you always commit,
+you can use
+
+=over 4
+
+    % git clean -xdf
+    % git reset --hard
+
+=back
+
+to tidy up after a build.
+(If you forgot to commit, don't use those commands;
+instead, you may find that you can use C<git add -p>
+to help commit what you actually wanted to keep.)
+
+These are destructive commands which delete all new files
+(so you B<must> remember to say C<git add>)
+and throw away edits to every file
+(so you B<must> remember to commit).
+
+=head2 Update the changelog (at least once) before building
+
+=over 4
+
+    % gbp dch -S --since=dgit/dgit/sid --ignore-branch --commit
+
+=back
+
+The binaries you build will have a version number which ultimately
+comes from the C<debian/changelog>.
+You want to be able to tell your
+binaries apart from your distro's.
+
+So you should update C<debian/changelog>
+to add a new stanza at the top,
+for your build.
+
+This rune provides an easy way to do this.
+It adds a new changelog
+entry with an uninformative message and a plausible version number
+(containing a bit of your git commit id).
+
+If you want to be more sophisticated,
+the package C<dpkg-dev-el> has a good Emacs mode
+for editing changelogs.
+Alternatively, you could edit the changelog with another text editor,
+or run C<dch> or C<gbp dch> with different options.
+Choosing a good version number is slightly tricky and
+a complete treatment is beyond the scope of this tutorial.
+
+=head2 Actually building
+
+=over 4
+
+    % sudo apt-get build-dep glibc
+    % dpkg-buildpackage -uc -b
+
+=back
+
+apt-get build-dep installs the build dependencies according to the
+official package, not your modified one.  So if you've changed the
+build dependencies you might have to install some of them by hand.
+
+dpkg-buildpackage is the primary tool for building a Debian source
+package.
+C<-uc> means not to pgp-sign the results.
+C<-b> means build all binary packages,
+but not to build a source package.
+
+=head1 INSTALLING
+
+=head2 Debian Jessie or older
+
+=over 4
+
+    % sudo dpkg -i ../libc6_*.deb
+
+=back
+
+You can use C<dpkg -i> to install the
+.debs that came out of your package.
+
+If the dependencies aren't installed,
+you will get an error, which can usually be fixed with
+C<apt-get -f install>.
+
+=head2 Debian Stretch or newer
+
+=over 4
+
+    % sudo apt install ../libc6_*.deb
+
+=back
+
+=head1 Multiarch
+
+If you're working on a library package and your system has multiple
+architectures enabled,
+you may see something like this:
+
+=over 4
+
+    dpkg: error processing package libpcre3-dev:amd64 (--configure):
+     package libpcre3-dev:amd64 2:8.39-3~3.gbp8f25f5 cannot be configured because libpcre3-dev:i386 is at a different version (2:8.39-2)
+
+=back
+
+The multiarch system used by Debian requires each package which is
+present for multiple architectures to be exactly the same across
+all the architectures for which it is installed.
+
+The proper solution
+is to build the package for all the architectures you
+have enabled.
+You'll need a chroot for each of the secondary architectures.
+This iw somewhat tiresome,
+even though Debian has excellent tools for managing chroots.
+C<sbuild-createchroot> from the sbuild package is a
+good starting point.
+
+Otherwise you could deinstall the packages of interest
+for those other architectures
+with something like C<dpkg --remove libpcre3:i386>.
+
+If neither of those are an option,
+your desperate last resort is to try
+using the same version number
+as the official package for your own package.
+(The verseion is controlled by C<debian/changelog> - see above,)
+This is not ideal because it makes it hard to tell what is installed,
+because it will mislead and confuse apt.
+
+With the "same number" approach you may still get errors like
+
+=over 4
+
+trying to overwrite shared '/usr/include/pcreposix.h', which is different from other instances of package libpcre3-dev
+
+=back
+
+but passing C<--force-overwrite> to dpkg will help
+- assuming you know what you're doing.
+
+=head1 SHARING YOUR WORK
+
+The C<dgit/jessie,-security> branch (or whatever) is a normal git branch.
+You can use C<git push> to publish it on any suitable git server.
+
+Anyone who gets that git branch from you
+will be able to build binary packages (.deb)
+just as you did.
+
+If you want to contribute your changes back to Debian,
+you should probably send them as attachments to 
+an email to the
+L<Debian Bug System|https://bugs.debian.org/>
+(either a followup to an existing bug, or a new bug).
+Patches in C<git-format-patch> format are usually very welcome.
+
+=head2 Source packages
+
+The
+git branch is not sufficient to build a source package
+the way Debian does.
+Source packages are somewhat awkward to work with.
+Indeed many plausible git histories or git trees
+cannot be converted into a suitable source package.
+So I recommend you share your git branch instead.
+
+If a git branch is not enough, and
+you need to provide a source package
+but don't care about its format/layout
+(for example because some software you have consumes source packages,
+not git histories)
+you can use this recipe to generate a C<3.0 (native)>
+source package, which is just a tarball
+with accompanying .dsc metadata file:
+
+=over 4
+
+    % echo '3.0 (native)' >debian/source/format
+    % git commit -m 'switch to native source format' debian/source/format
+    % dgit -wgf build-source
+
+=back
+
+If you need to provide a good-looking source package,
+be prepared for a lot more work.
+You will need to read much more, perhaps starting with
+L<dgit-nmu-simple(7)>,
+L<dgit-sponsorship(7)> or
+L<dgit-maint-*(7)>
+
+=head1 SEE ALSO
+
+dgit(1), dgit(7)
diff --git a/dgit.1 b/dgit.1
new file mode 100644 (file)
index 0000000..63a7021
--- /dev/null
+++ b/dgit.1
@@ -0,0 +1,1081 @@
+'\" t
+.TH dgit 1 "" "Debian Project" "dgit"
+.SH NAME
+dgit \- git integration with the Debian archive
+.
+.SH SYNOPSIS
+.B dgit
+[\fIdgit\-opts\fP] \fBclone\fP [\fIdgit\-opts\fP]
+\fIpackage\fP [\fIsuite\fP] [\fB./\fP\fIdir|\fB/\fP\fIdir\fR]
+.br
+.B dgit
+[\fIdgit\-opts\fP] \fBfetch\fP|\fBpull\fP [\fIdgit\-opts\fP]
+[\fIsuite\fP]
+.br
+.B dgit
+[\fIdgit\-opts\fP] \fBbuild\fP|\fBsbuild\fP|\fBbuild-source\fP
+[\fIbuild\-opts\fp]
+.br
+.B dgit
+[\fIdgit\-opts\fP] \fBpush\fP [\fIdgit\-opts\fP]
+[\fIsuite\fP]
+.br
+.B dgit
+[\fIdgit\-opts\fP] \fBrpush\fR \fIbuild-host\fR\fB:\fR\fIbuild-dir\fR
+[\fIpush args...\fR]
+.br
+.B dgit
+[\fIdgit\-opts\fP] \fIaction\fR ...
+.SH DESCRIPTION
+.B dgit
+allows you to treat the Debian archive as if it were a git
+repository.
+
+This is the command line reference.
+Please read the tutorial(s):
+.TS
+lb l.
+dgit-user(7)   for users: editing, building and sharing packages
+dgit-nmu-simple(7)     for DDs: doing a straightforward NMU
+dgit-maint-native(7)   for maintainers of Debian-native packages
+dgit-maint-merge(7)    for maintainers who want a pure git workflow
+dgit-maint-gbp(7)      for maintainers already using git-buildpackage
+dgit-sponsorship(7)    for sponsors and sponsored contributors
+.TE
+.LP
+See \fBdgit(7)\fP for detailed information about the data
+model,
+common problems likely to arise with certain kinds of package,
+etc.
+.SH OPERATIONS
+.TP
+\fBdgit clone\fR \fIpackage\fP [\fIsuite\fP] [\fB./\fP\fIdir|\fB/\fP\fIdir\fR]
+Consults the archive and dgit-repos to construct the git view of
+history for
+.I package
+in
+.I suite
+.RB ( sid
+by default)
+in a new directory (named
+.BI ./ package
+by default);
+also, downloads any necessary orig tarballs.
+
+The suite's git tip is
+left on the local branch
+.BI dgit/ suite
+ready for work, and on the corresponding dgit remote tracking branch.
+The
+.B origin
+remote will be set up to point to the package's dgit-repos tree
+for the distro to which
+.I suite
+belongs.
+
+.I suite
+may be a combination of several underlying suites in the form
+.IR mainsuite \fB,\fR subsuite ...;
+see COMBINED SUITES in dgit(7).
+
+For your convenience, the
+.B vcs-git
+remote will be set up from the package's Vcs-Git field, if there is
+one - but note that in the general case the history found there may be
+different to or even disjoint from dgit's view.
+.TP
+\fBdgit fetch\fR [\fIsuite\fP]
+Consults the archive and git-repos to update the git view of
+history for a specific suite (and downloads any necessary orig
+tarballs), and updates the remote tracking branch
+.BR remotes/dgit/dgit/ \fIsuite\fR.
+If the current branch is
+.BI dgit/ suite
+then dgit fetch defaults to
+.IR suite ;
+otherwise it parses debian/changelog and uses the suite specified
+there.
+suite may be a combined suite, as for clone.
+.TP
+\fBdgit pull\fR [\fIsuite\fP]
+Does dgit fetch, and then merges the new head of the remote tracking
+branch
+.BI remotes/dgit/dgit/ suite
+into the current branch.
+.TP
+\fBdgit build\fR ...
+Runs
+.B dpkg-buildpackage
+with some suitable options.  Options and arguments after build
+will be passed on to dpkg-buildpackage.  It is not necessary to use
+dgit build when using dgit; it is OK to use any approach which ensures
+that the generated source package corresponds to the relevant git
+commit.
+
+Tagging, signing and actually uploading should be left to dgit push.
+.TP
+\fBdgit build-source\fR ...
+Builds the source package, and a changes file for a prospective
+source-only upload, using
+.BR dpkg-source .
+The output is left in
+.IR package \fB_\fR version \fB.dsc\fR
+and
+.IR package \fB_\fR version \fB_source.changes\fR.
+
+Tagging, signing and actually uploading should be left to dgit push.
+.TP
+.B dgit clean
+Cleans the current working tree (according to the --clean= option in
+force).
+.TP
+.B dgit help
+Print a usage summary.
+.TP
+\fBdgit sbuild\fR ...
+Constructs the source package, uses
+.B  sbuild
+to do a binary build, and uses mergechanges to merge the source and
+binary changes files.  Options and arguments after sbuild will be
+passed on to sbuild.
+The output is left in
+.IR package \fB_\fR version \fB_multi.changes\fR.
+
+Tagging, signing and actually uploading should be left to dgit push.
+.TP
+\fBdgit gbp-build\fR ...
+Runs
+.B git-buildpackage
+with some suitable options.  Options and arguments after gbp-build
+will be passed on to git-buildpackage.
+
+By default this uses \-\-quilt=gbp, so HEAD should be a
+git-buildpackage style branch, not a patches-applied branch.
+
+Tagging, signing and actually uploading should be left to dgit push.
+.TP
+\fBdgit push\fR [\fIsuite\fP]
+Does an `upload', pushing the current HEAD to the archive (as a source
+package) and to dgit-repos (as git commits).  The package must already
+have been built ready for upload, with the .dsc and .changes
+left in the parent directory.  It is normally best to do the build
+with dgit too (eg with dgit sbuild): some existing build tools pass
+unhelpful options to dpkg-source et al by default, which can result in
+the built source package not being identical to the git tree.
+
+In more detail: dgit push checks that the current HEAD corresponds to
+the .dsc.  It then pushes the HEAD to the suite's dgit-repos branch,
+adjusts the .changes to include any .origs which the archive lacks
+and exclude .origs which the archive has
+(so -sa and -sd are not needed when building for dgit push),
+makes a signed git tag, edits the .dsc to contain the dgit metadata
+field, runs debsign to sign the upload (.dsc and .changes), pushes the
+signed tag, and finally uses dput to upload the .changes to the
+archive.
+
+dgit push always uses the package, suite and version specified in the
+debian/changelog and the .dsc, which must agree.  If the command line
+specifies a suite then that must match too.
+
+If dgit push fails while uploading, it is fine to simply retry the
+dput on the .changes file at your leisure.
+.TP
+\fBdgit rpush\fR \fIbuild-host\fR\fB:\fR\fIbuild-dir\fR [\fIpush args...\fR]
+Pushes the contents of the specified directory on a remote machine.
+This is like running dgit push on build-host with build-dir as the
+current directory; however, signing operations are done on the
+invoking host.  This allows you to do a push when the system which has
+the source code and the build outputs has no access to the key:
+
+.TS
+l l.
+1.     Clone on build host (dgit clone)
+2.     Edit code on build host (edit, git commit)
+3.     Build package on build host (dgit build)
+4.     Test package on build host or elsewhere (dpkg -i, test)
+5.     Upload by invoking dgit rpush on host with your GPG key.
+.TE
+
+However, the build-host must be able to ssh to the dgit repos.  If
+this is not already the case, you must organise it separately, for
+example by the use of ssh agent forwarding.
+
+The remaining arguments are treated just as dgit push would handle
+them.
+
+build-host and build\-dir can be passed as separate
+arguments; this is assumed to be the case if the first argument
+contains no : (except perhaps one in [ ], to support IPv6 address
+literals).
+
+You will need similar enough versions of dgit on the build-host and
+the invocation host.  The build-host needs gnupg installed, with your
+public key in its keyring (but not your private key, obviously).
+.TP
+.B dgit setup-new-tree
+Configure the current working tree the way that dgit clone would have
+set it up.  Like running
+.B dgit setup-useremail
+and
+.B setup-mergechangelogs
+(but only does each thing if dgit is configured to do it automatically).
+You can use these in any git repository, not just ones used with
+the other dgit operations.
+.TP
+.B dgit setup-useremail
+Set the working tree's user.name and user.email from the
+distro-specific dgit configuration
+.RB ( dgit-distro. \fIdistro\fR .user-name " and " .user-email ),
+or DEBFULLNAME or DEBEMAIL.
+.TP
+.B dgit setup-mergechangelogs
+Configures a git merge helper for the file
+.B debian/changelog
+which uses
+.BR dpkg-mergechangelogs .
+.TP
+.B dgit quilt-fixup
+`3.0 (quilt)' format source packages need changes representing not
+only in-tree but also as patches in debian/patches.  dgit quilt-fixup
+checks whether this has been done; if not, dgit will make appropriate
+patches in debian/patches and also commit the resulting changes to
+git.
+
+This is normally done automatically by dgit build and dgit push.
+
+dgit will try to turn each relevant commit in your git history into a
+new quilt patch.  dgit cannot convert nontrivial merges, or certain
+other kinds of more exotic history.  If dgit can't find a suitable
+linearisation of your history, by default it will fail, but you can
+ask it to generate a single squashed patch instead.
+.TP
+\fBdgit import-dsc\fR [\fIsub-options\fR] \fI../path/to/.dsc\fR [\fB+\fR|\fB..\fR]branch
+Import a Debian-format source package,
+specified by its .dsc,
+into git,
+the way dgit fetch would do.
+
+This does about half the work of dgit fetch:
+it will convert the .dsc into a new, orphan git branch.
+Since dgit has no access to a corresponding source package archive
+or knowledge of the history
+it does not consider whether this version is newer
+than any previous import
+or corresponding git branches;
+and it therefore does not
+make a pseudomerge to bind the import
+into any existing git history.
+
+There is only only sub-option:
+
+.B --require-valid-signature
+causes dgit to insist that the signature on the .dsc is valid
+(using the same criteria as dpkg-source -x).
+Otherwise, dgit tries to verify the signature but
+the outcome is reported only as messages to stderr.
+
+If
+.I branch
+is prefixed with
+.B +
+then if it already exists, it will be simply ovewritten,
+no matter its existing contents.
+If
+.I branch
+is prefixed with
+.B ..
+then if it already exists
+and dgit actually imports the dsc
+(rather than simply reading the git commit out of the Dgit field),
+dgit will make a pseudomerge
+so that the result is necessarily fast forward
+from the existing branch.
+Otherwise, if branch already exists,
+dgit will stop with an error message.
+
+If
+.I branch
+does not start with refs/, refs/heads/ is prepended.
+The specified branch is unconditionally updated.
+
+If the specified .dsc contains a Dgit field,
+dgit will simply make a branch of that commit.
+If you cannot manage to find that commit anywhere,
+consider --force-import-dsc-with-dgit-field.
+.TP
+.B dgit version
+Prints version information and exits.
+.TP
+.BI "dgit clone-dgit-repos-server" " destdir"
+Tries to fetch a copy of the source code for the dgit-repos-server,
+as actually being used on the dgit git server, as a git tree.
+.SH OPTIONS
+.TP
+.BR --dry-run " | " -n
+Go through the motions, fetching all information needed, but do not
+actually update the output(s).  For push, dgit does
+the required checks and leaves the new .dsc in a temporary file,
+but does not sign, tag, push or upload.
+.TP
+.BR --damp-run " | " -L
+Go through many more of the motions: do everything that doesn't
+involve either signing things, or making changes on the public
+servers.
+.TP
+.BI -k keyid
+Use
+.I keyid
+for signing the tag and the upload.  The default comes from the
+distro's
+.B keyid
+config setting (see CONFIGURATION, below), or failing that, the
+uploader trailer line in debian/changelog.
+.TP
+.BR --no-sign
+does not sign tags or uploads (meaningful only with push).
+.TP
+.TP
+.BI -p package
+Specifies that we should process source package
+.I package
+rather than looking in debian/control or debian/changelog.
+Valid with dgit fetch and dgit pull, only.
+.TP
+.BR --clean=git " | " -wg
+Use
+.BR "git clean -xdf"
+to clean the working tree,
+rather than running the package's rules clean target.
+
+This will delete all files which are not tracked by git.
+(Including any files you forgot to git add.)
+
+.BI --clean= ...
+options other than dpkg-source
+are useful when the package's clean target is troublesome, or
+to avoid needing the build-dependencies.
+.TP
+.BR --clean=git-ff " | " -wgf
+Use
+.BR "git clean -xdff"
+to clean the working tree.
+Like
+git clean -xdf
+but it also removes any subdirectories containing different git
+trees (which only unusual packages are likely to create).
+.TP
+.BR --clean=check " | " -wc
+Merely check that the tree is clean (does not contain uncommitted
+files).
+Avoids running rules clean,
+and can avoid needing the build-dependencies.
+.TP
+.BR --clean=none " | " -wn
+Do not clean the tree, nor check that it is clean.
+Avoids running rules clean,
+and can avoid needing the build-dependencies.
+If there are
+files which are not in git, or if the build creates such files, a
+subsequent dgit push will fail.
+.TP
+.BR --clean=dpkg-source " | " -wd
+Use dpkg-buildpackage to do the clean, so that the source package
+is cleaned by dpkg-source running the package's clean target.
+This is the default.
+Requires the package's build dependencies.
+.TP
+.BR --clean=dpkg-source-d " | " -wdd
+Use
+.B dpkg-buildpackage -d
+to do the clean,
+so that the source package
+is cleaned by dpkg-source running the package's clean target.
+The build-dependencies are not checked (due to
+.BR -d ),
+which violates policy, but may work in practice.
+.TP
+.BR -N " | " --new
+The package is or may be new in this suite.  Without this, dgit will
+refuse to push.  It may (for Debian, will) be unable to access the git
+history for any packages which have been newly pushed and have not yet
+been published.
+.TP
+.BR --ignore-dirty
+Do not complain if the working tree does not match your git HEAD.
+This can be useful with build, if you plan to commit later.  (dgit
+push will still ensure that the .dsc you upload and the git tree
+you push are identical, so this option won't make broken pushes.)
+.TP
+.BR --overwrite =\fIprevious-version\fR
+Declare that even though your git branch is not a descendant
+of the version in the archive
+according to the revision history,
+it really does contain
+all the (wanted) changes from that version.
+
+This option is useful if you are the maintainer, and you have
+incorporated NMU changes into your own git workflow in a way that
+doesn't make your branch a fast forward from the NMU.
+
+.I previous-version
+ought to be the version currently in the archive.  If
+.I previous-version
+is not
+specified, dgit will check that the version in the archive is
+mentioned in your debian/changelog.
+(This will avoid losing
+changes unless someone committed to git a finalised changelog
+entry, and then made later changes to that version.)
+
+dgit push --overwrite
+will make a
+pseudo-merge (that is, something that looks like the result
+of git merge -s ours) to stitch the archive's version into your own
+git history, so that your push is a fast forward from the archive.
+
+(In quilt mode
+.BR gbp ", " dpm " or " unpatched ,
+implying a split between the dgit view and the
+maintainer view, the pseudo-merge will appear only in the dgit view.)
+.TP
+.BR --delayed =\fIdays\fR
+Upload to a DELAYED queue.
+
+.B WARNING:
+If the maintainer responds by cancelling
+your upload from the queue,
+and does not make an upload of their own,
+this will not rewind the git branch on the dgit git server.
+Other dgit users will then see your push
+(with a warning message from dgit)
+even though the maintainer wanted to abolish it.
+Such users might unwittingly reintroduce your changes.
+
+If this situation arises,
+someone should make a suitable dgit push
+to update the contents of dgit-repos
+to a version without the controversial changes.
+.TP
+.BR --dgit-view-save= \fIbranch\fR|\fIref\fR
+Specifies that when a split view quilt mode is in operation,
+and dgit calculates
+(or looks up in its cache)
+a dgit view corresponding to your HEAD,
+the dgit view will be left in
+.IR ref .
+The specified ref is unconditionally overwritten,
+so don't specify a branch you want to keep.
+
+This option is effective only with the following operations:
+quilt-fixup; push; all builds.
+And it is only effective with
+--[quilt=]gbp,
+--[quilt=]dpm,
+--quilt=unpatched.
+
+If ref does not start with refs/
+it is taken to to be a branch -
+i.e. refs/heads/ is prepended.
+.TP
+.BI --deliberately- something
+Declare that you are deliberately doing
+.IR something .
+This can be used to override safety catches, including safety catches
+which relate to distro-specific policies.
+The use of --deliberately is declared and published in the signed tags
+generated for you by dgit,
+so that the archive software can give effect to your intent,
+and
+for the benefit humans looking at the history.
+The meanings of
+.IR something s
+understood in the context of Debian are discussed below:
+.TP
+.BR --deliberately-not-fast-forward
+Declare that you are deliberately rewinding history.  When pushing to
+Debian, use this when you are making a renewed upload of an entirely
+new source package whose previous version was not accepted for release
+from NEW because of problems with copyright or redistributibility.
+.TP
+.BR --deliberately-include-questionable-history
+Declare that you are deliberately including, in the git history of
+your current push, history which contains a previously-submitted
+version of this package which was not approved (or has not yet been
+approved) by the ftpmasters.  When pushing to Debian, only use this
+option after verifying that: none of the rejected-from-NEW (or
+never-accepted) versions in the git history of your current push, were
+rejected by ftpmaster for copyright or redistributability reasons.
+.TP
+.BR --deliberately-fresh-repo
+Declare that you are deliberately rewinding history and want to
+throw away the existing repo.  Not relevant when pushing to Debian,
+as the Debian server will do this automatically when necessary.
+.TP
+.BR --quilt=linear
+When fixing up source format `3.0 (quilt)' metadata, insist on
+generating a linear patch stack: one new patch for each relevant
+commit.
+If such a stack cannot be generated, fail.
+This is the default for Debian.
+
+HEAD should be a series of plain commits
+(not touching debian/patches/),
+and pseudomerges,
+with as ancestor a patches-applied branch.
+.TP
+.BR --quilt=auto
+When fixing up source format `3.0 (quilt)' metadata, prefer to
+generate a linear patch stack
+(as with --quilt=auto)
+but if that doesn't seem possible,
+try to generate a single squashed patch for all the changes made in git
+(as with --quilt=smash).
+This is not a good idea for an NMU in Debian.
+.TP
+.BR --quilt=smash
+When fixing up source format `3.0 (quilt)' metadata,
+generate a single additional patch for all the changes made in git.
+This is not a good idea for an NMU in Debian.
+
+(If HEAD has any in-tree patches already, they must apply cleanly.
+This will be the case for any trees produced by dgit fetch or clone;
+if you do not change the upstream version
+nor make changes in debian/patches,
+it will remain true.)
+.TP
+.BR --quilt=nofix
+Check whether source format `3.0 (quilt)' metadata would need fixing
+up, but, if it does, fail.  You must then fix the metadata yourself
+somehow before pushing.  (NB that dpkg-source --commit will not work
+because the dgit git tree does not have a
+.B .pc
+directory.)
+.TP
+.BR --quilt=nocheck " | " --no-quilt-fixup
+Do not check whether up source format `3.0 (quilt)' metadata needs
+fixing up.  If you use this option and the metadata did in fact need
+fixing up, dgit push will fail.
+.TP
+.BR -- [ quilt= ] gbp " | " -- [ quilt= ] dpm " | " --quilt=unapplied
+Tell dgit that you are using a nearly-dgit-compatible git branch,
+aka a
+.BR "maintainer view" ,
+and
+do not want your branch changed by dgit.
+
+.B --gbp
+(short for
+.BR --quilt=gbp )
+is for use with git-buildpackage.
+Your HEAD is expected to be
+a patches-unapplied git branch, except that it might contain changes
+to upstream .gitignore files.  This is the default for dgit gbp-build.
+
+.B --dpm
+(short for
+.BR --quilt=dpm )
+is for use with git-dpm.
+Your HEAD is expected to be
+a patches-applied git branch,
+except that it might contain changes to upstream .gitignore files.
+
+.B --quilt=unapplied
+specifies that your HEAD is a patches-unapplied git branch (and
+that any changes to upstream .gitignore files are represented as
+patches in debian/patches).
+
+With --quilt=gbp|dpm|unapplied,
+dgit push (or precursors like quilt-fixup and build) will automatically
+generate a conversion of your git branch into the right form.
+dgit push will push the
+dgit-compatible form (the
+.BR "dgit view" )
+to the dgit git server.
+The dgit view will be visible to you
+in the dgit remote tracking branches, but your own branch will
+not be modified.
+dgit push will create a tag
+.BI debian/ version
+for the maintainer view, and the dgit tag
+.BI archive/debian/ version
+for the dgit view.
+dgit quilt-fixup will merely do some checks,
+and cache the maintainer view.
+
+.B If you have a branch like this it is essential to specify the appropriate --quilt= option!
+This is because it is not always possible to tell: a patches-unapplied
+git branch of a package with one patch, for example, looks very like
+a patches-applied branch where the user has used git revert to
+undo the patch, expecting to actually revert it.
+However, if you fail to specify the right \-\-quilt option,
+and you aren't too lucky, dgit will notice the problem and stop,
+with a useful hint. 
+.TP
+.BR -d "\fIdistro\fR | " --distro= \fIdistro\fR
+Specifies that the suite to be operated on is part of distro
+.IR distro .
+This overrides the default value found from the git config option
+.BR dgit-suite. \fIsuite\fR .distro .
+The only effect is that other configuration variables (used
+for accessing the archive and dgit-repos) used are
+.BR dgit-distro. \fIdistro\fR .* .
+
+If your suite is part of a distro that dgit already knows about, you
+can use this option to make dgit work even if your dgit doesn't know
+about the suite.  For example, specifying
+.B -ddebian
+will work when the suite is an unknown suite in the Debian archive.
+
+To define a new distro it is necessary to define methods and URLs
+for fetching (and, for dgit push, altering) a variety of information both
+in the archive and in dgit-repos.
+How to set this up is not yet documented.
+.TP
+.BI -C changesfile
+Specifies the .changes file which is to be uploaded.  By default
+dgit push looks for single .changes file in the parent directory whose
+filename suggests it is for the right package and version.
+
+If the specified
+.I changesfile
+pathname contains slashes, the directory part is also used as
+the value for
+.BR --build-products-dir ;
+otherwise, the changes file is expected in that directory (by
+default, in
+.BR .. ).
+.TP
+.B --rm-old-changes
+When doing a build, delete any changes files matching
+.IB package _ version _*.changes
+before starting.  This ensures that
+dgit push (and dgit sbuild) will be able to unambigously
+identify the relevant changes files from the most recent build, even
+if there have been previous builds with different tools or options.
+The default is not to remove, but
+.B \-\-no-rm-old-changes
+can be used to override a previous \-\-rm-old-changes
+or the .rm-old-changes configuration setting.
+.TP
+.BI --build-products-dir= directory
+Specifies where to find the built files to be uploaded.
+By default, dgit looks in the parent directory
+.RB ( .. ).
+.TP
+.BI --no-rm-on-error
+Do not delete the destination directory if clone fails.
+.TP
+.BI --dep14tag
+Generates a DEP-14 tag (eg
+.BR debian/ \fIversion\fR)
+as well as a dgit tag (eg
+.BR archive/debian/ \fIversion\fR)
+where possible.  This is the default.
+.TP
+.BI --no-dep14tag
+Do not generate a DEP-14 tag, except in split quilt view mode.
+(On servers where only the old tag format is supported,
+the dgit tag will have the DEP-14 name.
+This option does not prevent that.)
+.TP
+.BI --dep14tag-always
+Insist on generating a DEP-14 tag
+as well as a dgit tag.
+If the server does not support that, dgit push will fail.
+.TP
+.BI -D
+Prints debugging information to stderr.  Repeating the option produces
+more output (currently, up to -DDDD is meaningfully different).
+.TP
+.BI -c name = value
+Specifies a git configuration option, to be used for this run.
+dgit itself is also controlled by git configuration options.
+.TP
+.RI \fB-v\fR version "|\fB_\fR | " \fB--since-version=\fR version |\fB_\fR
+Specifies the
+.BI -v version
+option to pass to dpkg-genchanges, during builds.  Changes (from
+debian/changelog) since this version will be included in the built
+changes file, and hence in the upload.  If this option is not
+specified, dgit will query the archive and use the latest version
+uploaded to the intended suite.
+
+Specifying
+.B _
+inhibits this, so that no -v option will be passed to dpkg-genchanges
+(and as a result, only the last stanza from debian/changelog will
+be used for the build and upload).
+.TP
+.RI \fB-m\fR maintaineraddress
+Passed to dpkg-genchanges (eventually).
+.TP
+.RI \fB--ch:\fR option
+Specifies a single additional option to pass, eventually, to
+dpkg-genchanges.
+
+Options which are safe to pass include
+.BR -C
+(and also
+.BR "-si -sa -sd"
+although these should never be necessary with Debian since dgit
+automatically calculates whether .origs need to be uploaded.)
+
+For other options the caveat below applies.
+.TP
+.RI \fB--curl:\fR option " | \fB--dput:\fR" option " |..."
+Specifies a single additional option to pass to
+.BR curl ,
+.BR dput ,
+.BR debsign ,
+.BR dpkg-source ,
+.BR dpkg-buildpackage ,
+.BR dpkg-genchanges ,
+.BR sbuild ,
+.BR ssh ,
+.BR dgit ,
+.BR apt-get ,
+.BR apt-cache ,
+.BR gbp-pq ,
+.BR gbp-build ,
+or
+.BR mergechanges .
+Can be repeated as necessary.
+
+Use of this ability should not normally be necessary.
+It is provided for working around bugs,
+or other unusual situations.
+If you use these options,
+you may violate dgit's assumptions
+about the behaviour of its subprograms
+and cause lossage.
+
+For dpkg-buildpackage, dpkg-genchanges, mergechanges and sbuild,
+the option applies only when the program is invoked directly by dgit.
+Usually, for passing options to dpkg-genchanges, you should use
+.BR --ch: \fIoption\fR.
+
+Specifying --git is not effective for some lower-level read-only git
+operations performed by dgit, and also not when git is invoked by
+another program run by dgit.
+
+See notes below regarding ssh and dgit.
+
+NB that --gpg:option is not supported (because debsign does not
+have that facility).
+But see
+.B -k
+and the
+.B keyid
+distro config setting.
+.TP
+.RI \fB--curl=\fR program " | \fB--dput=\fR" program  " |..."
+Specifies alternative programs to use instead of
+.BR curl ,
+.BR dput ,
+.BR debsign ,
+.BR dpkg-source ,
+.BR dpkg-buildpackage ,
+.BR dpkg-genchanges ,
+.BR sbuild ,
+.BR gpg ,
+.BR ssh ,
+.BR dgit ,
+.BR apt-get ,
+.BR apt-cache ,
+.BR git ,
+.BR gbp-pq ,
+.BR gbp-build ,
+or
+.BR mergechanges .
+
+For
+.BR dpkg-buildpackage ,
+.BR dpkg-genchanges ,
+.B mergechanges
+and
+.BR sbuild ,
+this applies only when the program is invoked directly by dgit.
+
+For
+.BR dgit ,
+specifies the command to run on the remote host when dgit
+rpush needs to invoke a remote copy of itself.  (dgit also reinvokes
+itself as the EDITOR for dpkg-source --commit; this is done using
+argv[0], and is not affected by --dgit=).
+
+.BR gbp-build 's
+value
+is used instead of gbp build or git-buildpackage.  (The default is
+the latter unless the former exists on PATH.)
+.BR gbp-pq 's
+value
+is used instead of gbp pq.
+In both cases,
+unusually, the specified value is split on whitespace
+to produce a command and possibly some options and/or arguments.
+
+For
+.BR ssh ,
+the default value is taken from the
+.B DGIT_SSH
+or
+.B GIT_SSH
+environment variables, if set (see below).  And, for ssh, when accessing the
+archive and dgit-repos, this command line setting is overridden by the
+git config variables
+.BI dgit-distro. distro .ssh
+and
+.B .dgit.default.ssh
+(which can in turn be overridden with -c).  Also, when dgit is using
+git to access dgit-repos, only git's idea of what ssh to use (eg,
+.BR GIT_SSH )
+is relevant.
+.TP
+.BI --existing-package= package
+dgit push needs to canonicalise the suite name.  Sometimes, dgit
+lacks a way to ask the archive to do this without knowing the
+name of an existing package.  Without --new we can just use the
+package we are trying to push.  But with --new that will not work, so
+we guess
+.B dpkg
+or use the value of this option.  This option is not needed with the
+default mechanisms for accessing the archive.
+.TP
+.BR -h | --help
+Print a usage summary.
+.TP
+.BI --initiator-tempdir= directory
+dgit rpush uses a temporary directory on the invoking (signing) host.
+This option causes dgit to use
+.I directory
+instead.  Furthermore, the specified directory will be emptied,
+removed and recreated before dgit starts, rather than removed
+after dgit finishes.  The directory specified must be an absolute
+pathname.
+.TP
+.BI --force- something
+Instructs dgit to try to proceed despite detecting
+what it thinks is going to be a fatal problem.
+.B This is probably not going to work.
+These options are provided as an escape hatch,
+in case dgit is confused.
+(They might also be useful for testing error cases.)
+.TP
+.B --import-dsc-with-dgit-field
+Tell dgit import-dsc to treat a .dsc with a Dgit field
+like one without it.
+The result is a fresh import,
+discarding the git history
+that the person who pushed that .dsc was working with.
+.TP
+.B --force-unrepresentable
+Carry on even if
+dgit thinks that your git tree contains changes
+(relative to your .orig tarballs)
+which dpkg-source is not able to represent.
+Your build or push will probably fail later.
+.TP
+.B --force-changes-origs-exactly
+Use the set of .origs specified in your .changes, exactly,
+without regard to what is in the archive already.
+The archive may well reject your upload.
+.TP
+.B --force-unsupported-source-format
+Carry on despite dgit not understanding your source package format.
+dgit will probably mishandle it.
+.TP
+.B --force-dsc-changes-mismatch
+Do not check whether .dsc and .changes match.
+The archive will probably reject your upload.
+.TP
+.BR --force-import-gitapply-absurd " | " --force-import-gitapply-no-absurd
+Force on or off the use of the absurd git-apply emulation
+when running gbp pq import
+when importing a package from a .dsc.
+See Debian bug #841867.
+.SH CONFIGURATION
+dgit can be configured via the git config system.
+You may set keys with git-config (either in system-global or per-tree
+configuration), or provide
+.BI -c key = value
+on the dgit command line.
+.LP
+Settings likely to be useful for an end user include:
+.TP
+.BR dgit-suite. \fIsuite\fR .distro " \fIdistro\fR"
+Specifies the distro for a suite.  dgit keys off the suite name (which
+appears in changelogs etc.), and uses that to determine the distro
+which is involved.  The config used is thereafter that for the distro.
+
+.I suite
+may be a glob pattern.
+.TP
+.BI dgit.default.distro " distro"
+The default distro for an unknown suite.
+.TP
+.BR dgit.default. *
+for each
+.BR dgit-distro. \fIdistro\fR . *,
+the default value used if there is no distro-specific setting.
+.TP
+.BR dgit-distro. \fIdistro\fR .clean-mode
+One of the values for the command line --clean= option; used if
+--clean is not specified.
+.TP
+.BR dgit-distro. \fIdistro\fR .quilt-mode
+One of the values for the command line --quilt= option; used if
+--quilt is not specified.
+.TP
+.BR dgit-distro. \fIdistro\fR .rm-old-changes
+Boolean, used if neither \-\-rm-old-changes nor \-\-no-rm-old-changes
+is specified.  The default is not to remove.
+.TP
+.BR dgit-distro. \fIdistro\fR .readonly " " auto | a " | " true | t | y | 1 " | " false | f | n | 0
+Whether you have push access to the distro.
+For Debian, it is OK to use auto, which uses readonly mode if you are
+not pushing right now;
+but, setting this to false will avoid relying on the mirror of the dgit
+git repository server.
+.TP
+.BI dgit-distro. distro .keyid
+See also
+.BR -k .
+.TP
+.BI dgit-distro. distro .mirror " url"
+.TP
+.BI dgit-distro. distro .username
+Not relevant for Debian.
+.TP
+.BI dgit-distro. distro .upload-host
+Might be useful if you have an intermediate queue server.
+.TP
+.BI dgit-distro. distro .user-name " " dgit-distro. distro .user-email
+Values to configure for user.name and user.email in new git trees.  If
+not specified, the DEBFULLNAME and DEBEMAIL environment variables are
+used, respectively.  Only used if .setup-usermail is not disabled.
+.TP
+.BI dgit-distro. distro .setup-useremail
+Whether to set user.name and user.email in new git trees.
+True by default.  Ignored for dgit setup-setup-useremail, which does it anyway.
+.TP
+.BI dgit-distro. distro .setup-mergechangelogs
+Whether to setup a merge driver which uses dpkg-mergechangelogs for
+debian/changelog.  True by default.  Ignored for dgit
+setup-mergechangelogs, which does it anyway.
+.TP
+.BI dgit-distro. distro .cmd- cmd
+Program to use instead of
+.IR cmd .
+Works like
+.BR -- \fIcmd\fR = "... ."
+.TP
+.BI dgit-distro. distro .opts- cmd
+Extra options to pass to
+.IR cmd .
+Works like
+.BR -- \fIcmd\fR : "... ."
+To pass several options, configure multiple values in git config
+(with git config --add).  The options for
+.BI dgit.default.opts- cmd
+.BI dgit-distro. distro /push.opts- cmd
+and are all used, followed by options from dgit's command line.
+.SH ACCESS CONFIGURATION
+There are many other settings which specify how a particular distro's
+services (archive and git) are provided.  These should not normally be
+adjusted, but are documented for the benefit of distros who wish to
+adopt dgit.
+.TP
+.BR dgit-distro. \fIdistro\fR /push. *
+If set, overrides corresponding non \fB/push\fR config when
+.BR readonly=false ,
+or when pushing and
+.BR readonly=auto .
+.TP
+.BI dgit-distro. distro .git-url
+.TP
+.BR dgit-distro. \fIdistro\fR .git-url [ -suffix ]
+.TP
+.BI dgit-distro. distro .git-proto
+.TP
+.BI dgit-distro. distro .git-path
+.TP
+.BR dgit-distro. \fIdistro\fR .git-check " " true | false | url | ssh-cmd
+.TP
+.BI dgit-distro. distro .git-check-suffix
+.TP
+.BR dgit-distro. \fIdistro\fR .diverts.divert " " new-distro | / \fIdistro-suffix\fR
+.TP
+.BI dgit-distro. distro .git-create " " ssh-cmd | true
+.TP
+.BR dgit-distro. \fIdistro\fR .archive-query " " ftpmasterapi: " | " madison: "\fIdistro\fR | " dummycat: "\fI/path\fR  | " sshpsql: \fIuser\fR @ \fIhost\fR : \fIdbname\fR
+.TP
+.BR dgit-distro. \fIdistro\fR .archive-query- ( url | tls-key | curl-ca-args )
+.TP
+.BI dgit-distro. distro .madison-distro
+.TP
+.BI dgit-distro. distro .archive-query-default-component
+.TP
+.BI dgit-distro. distro .dgit-tag-format
+.TP
+.BR dgit-distro. \fIdistro\fR .dep14tag " " want | no | always
+.TP
+.BI dgit-distro. distro .ssh
+.TP
+.BI dgit-distro. distro .sshpsql-dbname
+.TP
+.BR dgit-distro. \fIdistro\fR . ( git | sshpsql ) - ( user | host | user-force )
+.TP
+.BI dgit-distro. distro .backports-quirk
+.SH ENVIRONMENT VARIABLES
+.TP
+.BR DGIT_SSH ", " GIT_SSH
+specify an alternative default program (and perhaps arguments) to use
+instead of ssh.  DGIT_SSH is consulted first and may contain arguments;
+if it contains any whitespace will be passed to the shell.  GIT_SSH
+specifies just the program; no arguments can be specified, so dgit
+interprets it the same way as git does.
+See
+also the --ssh= and --ssh: options.
+.TP
+.BR DEBEMAIL ", " DEBFULLNAME
+Default git user.email and user.name for new trees.  See
+.BR "dgit setup-new-tree" .
+.TP
+.BR gpg ", " dpkg- "..., " debsign ", " git ", " curl ", " dput ", " LWP::UserAgent
+and other subprograms and modules used by dgit are affected by various
+environment variables.  Consult the documentaton for those programs
+for details.
+.SH BUGS
+There should be
+a `dgit rebase-prep' command or some such to turn a
+fast-forwarding branch containing pseudo-merges
+back into a rebasing patch stack.
+It might have to leave a note
+for a future dgit push.
+
+If the dgit push fails halfway through,
+it is not necessarily restartable and
+idempotent.
+It would be good to check that the proposed signing key is
+available before starting work.
+
+dgit's build functions, and dgit push, may make changes to
+your current HEAD.  Sadly this is necessary for packages in the `3.0
+(quilt)' source format.  This is ultimately due to what I consider
+design problems in quilt and dpkg-source.
+
+--dry-run does not always work properly, as not doing some of the git
+fetches may result in subsequent actions being different.  Doing a
+non-dry-run dgit fetch first will help.
+--damp-run is likely to work much better.
+.SH SEE ALSO
+\fBdgit\fP(7),
+\fBdgit-*\fP(7),
+\fBcurl\fP(1),
+\fBdput\fP(1),
+\fBdebsign\fP(1),
+\fBgit-config\fP(1),
+\fBgit-buildpackage\fP(1),
+\fBdpkg-buildpackage\fP(1),
+.br
+https://browse.dgit.debian.org/
diff --git a/dgit.7 b/dgit.7
new file mode 100644 (file)
index 0000000..f7e50e4
--- /dev/null
+++ b/dgit.7
@@ -0,0 +1,385 @@
+.TH dgit 7 "" "Debian Project" "dgit"
+.SH NAME
+dgit \- principles of operation
+.SH SUMMARY
+.B dgit
+treats the Debian archive as a version control system, and
+bidirectionally gateways between the archive and git.  The git view of
+the package can contain the usual upstream git history, and will be
+augmented by commits representing uploads done by other developers not
+using dgit.  This git history is stored in a canonical location known
+as
+.B dgit-repos
+which lives on a dedicated git server.
+
+git branches suitable for use with dgit
+can be edited directly in git,
+and used directly for building binary packages.
+They can be shared using all conventional means for sharing git
+branches.
+It is not necessary to use dgit to work with dgitish git branches.
+However, dgit is (usually) needed in order to convert to or from
+Debian-format source packages.
+.SH SEE ALSO
+.TP
+\fBdgit\fP(1)
+Reference manual and documentation catalogue.
+.TP
+\fBdgit-*\fB(7)
+Tutorials and workflow guides.  See dgit(1) for a list.
+.SH MODEL
+You may use any suitable git workflow with dgit, provided you
+satisfy dgit's requirements:
+
+dgit maintains a pseudo-remote called
+.BR dgit ,
+with one branch per suite.  This remote cannot be used with
+plain git.
+
+The
+.B dgit-repos
+repository for each package contains one ref per suite named
+\fBrefs/dgit/\fR\fIsuite\fR.  These should be pushed to only by
+dgit.  They are fast forwarding.  Each push on this branch
+corresponds to an upload (or attempted upload).
+
+However, it is perfectly fine to have other branches in dgit-repos;
+normally the dgit-repos repo for the package will be accessible via
+the remote name `origin'.
+
+dgit push will also make signed tags called
+.BI archive/debian/ version
+(with version encoded a la DEP-14)
+and push them to dgit-repos.  These are used at the
+server to authenticate pushes.
+
+Uploads made by dgit contain an additional field
+.B Dgit
+in the source package .dsc.  (This is added by dgit push.)
+This specifies a commit (an ancestor of the dgit/suite
+branch) whose tree is identical to the unpacked source upload.
+
+Uploads not made by dgit are represented in git by commits which are
+synthesised by dgit.  The tree of each such commit corresponds to the
+unpacked source; there is a
+commit with the contents,
+and a
+pseudo-merge from last known upload - that is, from the contents of
+the dgit/suite branch.
+Depending on the source package format,
+the contents commit may have a more complex structure,
+but ultimately it will be a convergence of stubby branches
+from origin commits representing the components of the source package.
+
+dgit expects trees that it works with to have a
+.B dgit
+(pseudo) remote.  This refers to the dgit-created git view of
+the corresponding archive.
+
+The dgit archive tracking view is synthesised locally,
+on demand,
+by each copy of dgit.
+The tracking view is always a descendant of the
+dgit-repos suite branch (if one exists),
+but may be ahead of it if uploads have been done without dgit.
+The archive tracking view is always fast forwarding within
+each suite.
+
+dgit push can operate on any commit which is a descendant of
+the suite tracking branch.
+
+dgit does not make a systematic record of
+its imports of orig tarball(s).
+So it does not work by finding git tags or branches
+referring to orig tarball(s).
+The
+orig tarballs are downloaded (by dgit clone) into the parent
+directory, as with a traditional (non-gitish) dpkg-source workflow.
+You need to retain these tarballs in the parent directory for dgit
+build and dgit push.
+(They are not needed for purely-git-based workflows.)
+
+dgit repositories could be cloned with standard (git) methods.
+However,
+the dgit repositories do not contain uploads not made with dgit.
+And
+for sourceful builds / uploads the orig
+tarball(s) will need to be present in the parent directory.
+
+To a user looking at the archive, changes pushed
+in a simple NMU
+using dgit look like
+reasonable
+changes made in an NMU: in a `3.0 (quilt)' package the delta from the
+previous upload is recorded in new patch(es) constructed by dpkg-source.
+.SH COMBINED SUITES
+dgit can synthesize a combined view of several underlying suites.
+This is requested by specifying, for
+.I suite,
+a comma-separated list:
+.IP
+.IR mainsuite \fB,\fR subsuite ...
+.LP
+This facility is available with dgit clone, fetch and pull, only.
+
+dgit will fetch the same package from each specified underlying suite,
+separately (as if with dgit fetch).
+dgit will then generate a pseudomerge commit
+on the tracking branch
+.BI remotes/dgit/dgit/ suite
+which has the tip of each of the underlying suites
+as an ancestor,
+and which contains the same as the suite which
+has the highest version of the package.
+
+The package must exist in mainsuite,
+but need not exist in the subsuites.
+
+If a specified subsuite starts with
+.B -
+then mainsuite is prepended.
+
+So, for example,
+.B stable,-security
+means to look for the package in stable, and stable-security,
+taking whichever is newer.
+If stable is currently jessie,
+dgit clone would leave you on the branch
+.BR dgit/jessie,-security .
+
+Combined suites are not supported by the dgit build operations.
+This is because those options are intended for building for
+uploading source packages,
+and look in the changelog to find the relevant suite.
+It does not make sense to name a dgit-synthesised combined suite
+in a changelog,
+or to try to upload to it.
+
+When using this facility, it is important to always specify the
+same suites in the same order:
+dgit will not be make a coherent fast-forwarding history
+view otherwise.
+
+The history generated by this feature is not normally suitable
+for merging back into upstreams,
+as it necessarily contains unattractive pseudomerges.
+.SH LIMITATIONS
+Because the synthesis
+of the suite tracking branches
+is done locally based only on the current archive state,
+it will not necessarily see every upload
+not done with dgit.
+Also, different versions of dgit
+(or the software it calls)
+might import the same .dscs differently
+(although we try to minimise this).
+As a consequence, the dgit tracking views of the same
+suite, made by different instances of dgit, may vary.
+They will have the same contents, but may have different history.
+
+There is no uniform linkage between the tracking branches for
+different suites.
+The Debian infrastructure
+does not do any automatic import of uploads made without dgit.
+It would be possible for a distro's infrastructure to do this;
+in that case,
+different dgit client instances
+would see exactly the same history.
+
+There has been no bulk import of historical uploads into
+Debian's dgit infrastructure.
+To do this it would be necessary to decide whether to
+import existing vcs history
+(which might not be faithful to dgit's invariants)
+or previous non-Dgit uploads
+(which would not provide a very rich history).
+.SH READ-ONLY DISTROS
+Distros which do not maintain a set of dgit history git repositories
+can still be used in a read-only mode with dgit.  Currently Ubuntu
+is configured this way.
+.SH PACKAGE SOURCE FORMATS
+If you are not the maintainer, you do not need to worry about the
+source format of the package.  You can just make changes as you like
+in git.  If the package is a `3.0 (quilt)' package, the patch stack
+will usually not be represented in the git history.
+.SH FORMAT 3.0 (QUILT)
+For a format `3.0 (quilt)' source package, dgit may have to make a
+commit on your current branch to contain metadata used by quilt and
+dpkg-source.
+
+This is because `3.0 (quilt)' source format represents the patch stack
+as files in debian/patches/ actually inside the source tree.  This
+means that, taking the whole tree (as seen by git or ls) (i)
+dpkg-source cannot represent certain trees, and (ii) packing up a tree
+in `3.0 (quilt)' and then unpacking it does not always yield the same
+tree.
+
+dgit will automatically work around this for you when building and
+pushing.  The only thing you need to know is that dgit build, sbuild,
+etc., may make new commits on your HEAD.  If you're not a quilt user
+this commit won't contain any changes to files you care about.
+
+You can explicitly request that dgit do just this fixup, by running
+dgit quilt-fixup.
+
+If you are a quilt user you need to know that dgit's git trees are
+`patches applied packaging branches' and do not contain the .pc
+directory (which is used by quilt to record which patches are
+applied).  If you want to manipulate the patch stack you probably want
+to be looking at tools like git-dpm.
+.SH SPLIT VIEW QUILT MODE
+When working with git branches intended
+for use with the `3.0 (quilt)' source format
+dgit can automatically convert a suitable
+maintainer-provided git branch
+(in one of a variety of formats)
+into a dgit branch.
+
+When a split view mode is engaged
+dgit build commands and
+dgit push
+will, on each invocation,
+convert the user's HEAD into the dgit view,
+so that it can be built and/or uploaded.
+
+dgit push in split view mode will push the dgit view to the dgit
+git server.
+The dgit view is always a descendant of the maintainer view.
+dgit push will also make a maintainer view tag
+according to DEP-14
+and push that to the dgit git server.
+
+Split view mode must be enabled explicitly
+(by the use of the applicable command line options,
+subcommands, or configuration).
+This is because it is not possible to reliably tell
+(for example)
+whether a git tree for a dpkg-source `3.0 (quilt)' package
+is a patches-applied or patches-unapplied tree.
+
+Split view conversions are cached in the ref
+dgit-intern/quilt-cache.
+This should not be manipulated directly.
+.SH FILES IN THE SOURCE PACKAGE BUT NOT IN GIT - AUTOTOOLS ETC.
+This section is mainly of interest to maintainers who want to use dgit
+with their existing git history for the Debian package.
+
+Some developers like to have an extra-clean git tree which lacks files
+which are normally found in source tarballs and therefore in Debian
+source packages.  For example, it is conventional to ship ./configure
+in the source tarball, but some people prefer not to have it present
+in the git view of their project.
+
+dgit requires that the source package unpacks to exactly the same
+files as are in the git commit on which dgit push operates.  So if you
+just try to dgit push directly from one of these extra-clean git
+branches, it will fail.
+
+As the maintainer you therefore have the following options:
+.TP
+\(bu
+Persuade upstream that the source code in their git history and the
+source they ship as tarballs should be identical.  Of course simply
+removing the files from the tarball may make the tarball hard for
+people to use.
+.IP
+One answer is to commit the (maybe autogenerated)
+files, perhaps with some simple automation to deal with conflicts and
+spurious changes.  This has the advantage that someone who clones
+the git repository finds the program just as easy to build as someone
+who uses the tarball.
+.TP
+\(bu
+Have separate git branches which do contain the extra files, and after
+regenerating the extra files (whenever you would have to anyway),
+commit the result onto those branches.
+.TP
+\(bu
+Provide source packages which lack the files you don't want
+in git, and arrange for your package build to create them as needed.
+This may mean not using upstream source tarballs and makes the Debian
+source package less useful for people without Debian build
+infrastructure.
+.LP
+Of course it may also be that the differences are due to build system
+bugs, which cause unintended files to end up in the source package.
+dgit will notice this and complain.  You may have to fix these bugs
+before you can unify your existing git history with dgit's.
+.LP
+.SH FILES IN THE SOURCE PACKAGE BUT NOT IN GIT - DOCS, BINARIES ETC.
+Some upstream tarballs contain build artifacts which upstream expects
+some users not to want to rebuild (or indeed to find hard to rebuild),
+but which in Debian we always rebuild.
+.LP
+Examples sometimes include crossbuild firmware binaries and
+documentation.
+To avoid problems when building updated source
+packages
+(in particular, to avoid trying to represent as changes in
+the source package uninteresting or perhaps unrepresentable changes
+to such files)
+many maintainers arrange for the package clean target
+to delete these files.
+.LP
+dpkg-source does not
+(with any of the commonly used source formats)
+represent deletion of files (outside debian/) present in upstream.
+Thus deleting such files in a dpkg-source working tree does not
+actually result in them being deleted from the source package.
+Thus
+deleting the files in rules clean sweeps this problem under the rug.
+.LP
+However, git does always properly record file deletion.
+Since dgit's
+principle is that the dgit git tree is the same of dpkg-source -x,
+that means that a dgit-compatible git tree always contains these
+files.
+.LP
+For the non-maintainer,
+this can be observed in the following suboptimal occurrences:
+.TP
+\(bu
+The package clean target often deletes these files, making the git
+tree dirty trying to build the source package, etc.
+This can be fixed
+by using
+.BR "dgit -wg" " aka " "--clean=git" ,
+so that the package clean target is never run.
+.TP
+\(bu
+The package build modifies these files, so that builds make the git
+tree dirty.
+This can be worked around by using `git reset --hard'
+after each build
+(or at least before each commit or push).
+.LP
+From the maintainer's point of view,
+the main consequence is that to make a dgit-compatible git branch
+it is necessary to commit these files to git.
+The maintainer has a few additional options for mitigation:
+for example,
+it may be possible for the rules file to arrange to do the
+build in a temporary area, which avoids updating the troublesome
+files;
+they can then be left in the git tree without seeing trouble.
+.SH PROBLEMS WITH PACKAGE CLEAN TARGETS ETC.
+A related problem is other unexpected behaviour by a package's
+.B clean
+target.
+If a package's rules
+modify files which are distributed in the package,
+or simply forget to remove certain files,
+dgit will complain that the tree is dirty.
+.LP
+Again, the solution is to use
+.BR "dgit -wg" " aka " "--clean=git" ,
+which instructs dgit to use git clean instead of the package's
+build target,
+along with perhaps
+.B git reset --hard
+before each build.
+.LP
+This is 100% reliable, but has the downside
+that if you forget to git add or to commit, and then use
+.BR "dgit -wg" " or " "git reset --hard" ,
+your changes may be lost.
diff --git a/infra/cgit-regen-config b/infra/cgit-regen-config
new file mode 100755 (executable)
index 0000000..36228a1
--- /dev/null
@@ -0,0 +1,26 @@
+#!/bin/sh
+set -e
+
+root=/srv/dgit.debian.org
+
+repos=$root/unpriv/repos
+outfile=$root/etc/projects.cgit
+lockfile=$outfile.lock
+template=$root/config/cgit-template
+
+flock $lockfile -c '
+       outfile='"$outfile"'
+       repos='"$repos"'
+       exec >"$outfile.tmp"
+       for ff in "$repos"/[0-9a-z]*.git; do
+               f=${ff##*/}
+               p=${f%.git}
+               cat <<END
+repo.url=$f
+repo.path=$repos/$f
+END
+               sed "s/%PACKAGE%/$p/g" <'"$template"'
+               echo
+       done
+       mv -f "$outfile.tmp" "$outfile"
+'
diff --git a/infra/dgit-mirror-rsync b/infra/dgit-mirror-rsync
new file mode 100755 (executable)
index 0000000..0d29ffb
--- /dev/null
@@ -0,0 +1,171 @@
+#!/bin/bash
+#
+# Mirror script for use as a dgit-repos-server mirror hook
+#
+# In addition to updated-hook (invoked by dgit-repos-server),
+# this script also supports the following ACTIONs:
+#   MIRROR-HOOK-SCRIPT ... setup [...]            create queue dir etc.
+#   MIRROR-HOOK-SCRIPT ... backlog [...]          do all packages which need it
+#   MIRROR-HOOK-SCRIPT ... all [...]              do all packages
+#   MIRROR-HOOK-SCRIPT ... mirror PACKAGE [...]   do just that, longer timeout
+#
+# DISTRO-DIR must contain a file `mirror-settings' which is a bash
+# script fragment assigning the following variables:
+#   remoterepos                for rsync, in form user@host:/dir
+# and optionally
+#   hooktimeout                default 30 [sec]
+#   rsynctimeout       default 900 [sec]
+#   rsyncssh           default 'ssh -o batchmode=yes'
+#   rsync              array, default (rsync -rltH --safe-links --delete)
+#   repos              default DISTRO-DIR/repos
+# (optional settings are all set before mirror-settings is included,
+# so you can modify them with += or some such)
+
+set -e
+set -o pipefail
+shopt -s nullglob
+
+case "$DGIT_DRS_DEBUG" in
+''|0|1)                ;;
+*)             set -x  ;;
+esac
+
+fail () {
+       echo >&2 "dgit-mirror-rsync: $*"; exit 127
+}
+
+if [ $# -lt 2 ]; then fail "too few arguments"; fi
+
+self=$0
+
+case "$self" in
+/*)                            ;;
+*/*)   self="$PWD/$self"       ;;
+*)                             ;;
+esac
+
+distrodir=$1;  shift
+action=$1;     shift
+package=$1
+
+repos=$distrodir/repos
+
+rsync=(rsync -rltH --safe-links --delete)
+hooktimeout=30
+rsynctimeout=900
+rsyncssh='ssh -o batchmode=yes'
+
+. $distrodir/mirror-settings
+
+# contents of $queue
+# $queue/$package.n    - mirror needed
+# $queue/$package.a    - being attempted, or attempt failed
+# $queue/$package.lock - lock (with-lock-ex)
+# $queue/$package.err  - stderr from failed (or current) run
+# $queue/$package.log  - stderr from last successful run
+
+cd $repos
+queue=_mirror-queue
+
+case "$remoterepos" in
+*:/*|/*)       ;;
+'')            fail "remoterepos config not set" ;;
+*)             fail "remoterepos config does not match *:/* or /*" ;;
+esac
+
+actually () {
+       "${rsync[@]}" \
+               --timeout=$rsynctimeout                         \
+               -e "$rsyncssh"                                  \
+               "$repos/$package.git"/.                         \
+               "$remoterepos/$package.git"
+}
+
+reinvoke () {
+       newaction="$1"; shift
+
+       exec                                                    \
+       "$@"                                                    \
+       "$self" "$distrodir" "reinvoke$newaction" "$package"
+}
+
+check-package-mirrorable () {
+       local repo=$repos/$package.git
+       local mode=$(stat -c%a "$repo")
+       case $mode in
+       *5)     return  0       ;;
+       *0)     return  1       ;;
+       *)      echo >&2 "unexpected mode $mode for $repo"; return 1    ;;
+       esac
+}
+
+lock-and-process () {
+       check-package-mirrorable || return 0
+       reinvoke -locked with-lock-ex -w "$queue/$package.lock"
+}
+
+attempt () {
+       exec 3>&2 >"$queue/$package.err" 2>&1
+       if actually; then
+               rm -f "$queue/$package.a"
+               exec 2>&3 2>&1
+               mv -f "$queue/$package.err" "$queue/$package.log"
+               if ! [ -s "$queue/$package.log" ]; then
+                       rm "$queue/$package.log"
+               fi
+               rm "$queue/$package.lock"
+       else
+               cat >&3 "$queue/$package.err"
+               exit 127
+       fi
+}
+
+lock-and-process-baseof-f () {
+       package=${f##*/}
+       package=${package%.*}
+       lock-and-process
+}
+
+case "$action" in
+
+updated-hook)
+       check-package-mirrorable || exit 0
+       touch "$queue/$package.n"
+       reinvoke -timed timeout --foreground $hooktimeout
+       ;;
+
+reinvoke-timed)
+       (lock-and-process) >/dev/null 2>&1
+       ;;
+
+mirror)
+       lock-and-process
+       ;;
+
+reinvoke-locked)
+       touch "$queue/$package.a"
+       rm -f "$queue/$package.n"
+       attempt
+       ;;
+
+backlog)
+       for f in $queue/*.[na]; do
+               (lock-and-process-baseof-f ||:)
+       done
+       ;;
+
+all)
+       for f in [a-z0-9]*.git; do
+               (lock-and-process-baseof-f)
+       done
+       ;;
+
+setup)
+       test -d "$queue" || mkdir "$queue"
+       ;;
+
+*)
+       fail "bad action $action"
+       ;;
+
+esac
diff --git a/infra/dgit-repos-admin-debian b/infra/dgit-repos-admin-debian
new file mode 100755 (executable)
index 0000000..6d1e4d0
--- /dev/null
@@ -0,0 +1,220 @@
+#!/usr/bin/perl -w
+# dgit repos policy admin script for Debian
+#
+# Copyright (C) 2015-2016  Ian Jackson
+#
+#    This program is free software; you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation; either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+use strict;
+
+use Debian::Dgit::Infra; # must precede Debian::Dgit; - can change @INC!
+use Debian::Dgit;
+setup_sigwarn();
+
+our $usage = <<'END';
+usage:
+  dgit-repos-admin-debian [<options>] operation...
+options:
+  --git-dir /path/to/git/repo/or/working/tree
+  --repos /path/to/dgit/repos/directory    } alternatives
+  --db /path/to/dgit/repos/policy.sqlite3  }
+  (at least one of above required; if only one, cwd is used for other)
+operations:
+  create-db
+  list-taints
+  taint [--global|<package>] <gitobjid> '<comment>'
+  untaint [--global|<package>] <gitobjid>
+END
+
+use POSIX;
+use DBI;
+
+use Debian::Dgit::Policy::Debian;
+
+sub badusage ($) { die "bad usage: $_[0]\n$usage"; }
+
+use Getopt::Long qw(:config posix_default gnu_compat bundling);
+
+our ($git_dir,$repos_dir,$db_path);
+
+GetOptions("git-dir=s" => \$git_dir,
+          "repos=s" => \$repos_dir,
+          "db=s" => \$db_path)
+    or die $usage;
+
+$db_path //= poldb_path($repos_dir) if defined $repos_dir;
+$db_path // $repos_dir ||
+    die <<'END'.$usage;
+Must supply --git-dir and/or --repos (or --db instead of --repos).
+If only one of --git-dir and --repos is supplied, other is taken to
+be current working directory.
+END
+# /
+
+$git_dir //= '.';
+$repos_dir //= '.';
+
+our $p;
+our $gitobjid;
+
+sub get_package_objid () {
+    $p = shift @ARGV;  $p // badusage "operation needs package or --global";
+    if ($p eq '--global') {
+       $p = '';
+    } else {
+       $p =~ m/^$package_re$/ or badusage 'package name or --global needed';
+    }
+    $gitobjid = shift @ARGV;
+    $gitobjid // badusage "operation needs git object id";
+    $gitobjid =~ m/\W/ && badusage "invalid git object id";
+}
+
+sub sort_out_git_dir () {
+    foreach my $sfx ('/.git', '') {
+       my $path = "$git_dir/$sfx";
+       if (stat_exists "$path/objects") {
+           $ENV{GIT_DIR} = $git_dir = $path;
+           return;
+       }
+    }
+    die "git directory $git_dir doesn't seem valid\n";
+}
+
+sub show_taints ($$@) {
+    my ($m, $cond, @condargs) = @_;
+    my $q = $poldbh->prepare
+       ("SELECT package,gitobjid,gitobjtype,time,comment, ".
+        " (gitobjdata IS NOT NULL) hasdata".
+        " FROM taints WHERE $cond".
+        " ORDER BY package, gitobjid, time");
+    $q->execute(@condargs);
+    print "$m:\n" or die $!;
+    my $count = 0;
+    while (my $row = $q->fetchrow_hashref) {
+       my $t = strftime "%Y-%m-%dT%H:%M:%S", gmtime $row->{time};
+       my $objinfo = $row->{gitobjtype}. ($row->{hasdata} ? '+' : ' ');
+       my $comment = $row->{comment};
+       $comment =~ s/\\/\\\\/g; $comment =~ s/\n/\\n/g;
+       printf(" %s %-30s %s %7s %s\n",
+              $t, $row->{package}, $row->{gitobjid},
+              $objinfo, $row->{comment})
+           or die $!;
+       $count++;
+    }
+    return $count;
+}
+
+sub cmd_list_taints ($) {
+    badusage "no args/options" if @ARGV;
+    my $count = show_taints("all taints","1");
+    printf "%d taints listed\n", $count or die $!;
+}
+
+sub cmd_create_db ($) {
+    badusage "no args/options" if @ARGV;
+
+    $poldbh->do(<<END);
+       CREATE TABLE IF NOT EXISTS taints (
+           taint_id   INTEGER NOT NULL PRIMARY KEY ASC AUTOINCREMENT,
+           package    TEXT    NOT NULL,
+           gitobjid   TEXT    NOT NULL,
+           comment    TEXT    NOT NULL,
+           time       INTEGER,
+           gitobjtype TEXT,
+           gitobjdata TEXT
+           )
+END
+    $poldbh->do(<<END);
+       CREATE INDEX IF NOT EXISTS taints_by_gitobjid
+           ON taints (gitobjid, package)
+END
+    # any one of of the listed deliberatelies will override its taint
+    # the field `deliberately' contains `--deliberately-blah-blah',
+    # not just `blah blah'.
+    $poldbh->do(<<END);
+       CREATE TABLE IF NOT EXISTS taintoverrides (
+           taint_id  INTEGER NOT NULL
+                     REFERENCES taints (taint_id)
+                         ON UPDATE RESTRICT
+                         ON DELETE CASCADE
+                     DEFERRABLE INITIALLY DEFERRED,
+           deliberately TEXT NOT NULL,
+           PRIMARY KEY (taint_id, deliberately)
+       )
+END
+
+    $poldbh->commit;
+}
+
+sub show_taints_bypackage ($) {
+    my ($m) = @_;
+    show_taints($m, "package = ?", $p);
+}
+
+sub show_taints_bygitobjid ($) {
+    my ($m) = @_;
+    show_taints($m, "gitobjid = ?", $gitobjid);
+}
+
+sub show_relevant_taints ($) {
+    my ($what) = @_;
+    show_taints_bypackage($p ? "$what taints for package $p"
+                         : "$what global taints");
+    show_taints_bygitobjid("$what taints for object $gitobjid");
+}
+
+sub cmd_taint () {
+    get_package_objid();
+    my $comment = shift @ARGV;
+    $comment // badusage "operation needs comment";
+    @ARGV && badusage "too many arguments to taint";
+
+    sort_out_git_dir();
+    $!=0; $?=0; my $objtype = `git cat-file -t $gitobjid`;
+    chomp $objtype or die "$? $!";
+
+    $poldbh->do("INSERT INTO taints".
+               " (package, gitobjid, gitobjtype, time, comment)".
+               " VALUES (?,?,?,?,?)", {},
+               $p, $gitobjid, $objtype, time, $comment);
+    $poldbh->commit;
+    print "taint added\n" or die $!;
+    show_relevant_taints("resulting");
+}
+
+sub cmd_untaint () {
+    get_package_objid();
+    @ARGV && badusage "too many arguments to untaint";
+
+    show_relevant_taints("existing");
+    my $affected =
+       $poldbh->do("DELETE FROM taints".
+                   " WHERE package = ? AND gitobjid = ?",
+                   {}, $p, $gitobjid);
+    $poldbh->commit;
+    printf "%d taints removed\n", $affected or die $!;
+    exit $affected ? 0 : 1;
+}
+
+
+my $cmd = shift @ARGV;
+$cmd // badusage "need operation";
+
+$cmd =~ y/-/_/;
+my $fn = ${*::}{"cmd_$cmd"};
+$fn or badusage "unknown operation $cmd";
+
+poldb_setup($db_path);
+
+$fn->();
diff --git a/infra/dgit-repos-policy-debian b/infra/dgit-repos-policy-debian
new file mode 100755 (executable)
index 0000000..cff5d06
--- /dev/null
@@ -0,0 +1,538 @@
+#!/usr/bin/perl -w
+# dgit repos policy hook script for Debian
+#
+# Copyright (C) 2015-2016  Ian Jackson
+#
+#    This program is free software; you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation; either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+use strict;
+
+use Debian::Dgit::Infra; # must precede Debian::Dgit; - can change @INC!
+use Debian::Dgit qw(:DEFAULT :policyflags);
+setup_sigwarn();
+
+use POSIX;
+use JSON;
+use File::Temp qw(tempfile);
+use DBI;
+use IPC::Open2;
+use Data::Dumper;
+
+use Debian::Dgit::Policy::Debian;
+
+initdebug('%');
+enabledebuglevel $ENV{'DGIT_DRS_DEBUG'};
+
+END { $? = 127; } # deliberate exit uses _exit
+
+our $distro = shift @ARGV // die "need DISTRO";
+our $repos = shift @ARGV // die "need DGIT-REPOS-DIR";
+our $dgitlive = shift @ARGV // die "need DGIT-LIVE-DIR";
+our $distrodir = shift @ARGV // die "need DISTRO-DIR";
+our $action = shift @ARGV // die "need ACTION";
+
+our $publicmode = 02775;
+our $new_upload_propagation_slop = 3600*4 + 100;# fixme config;
+
+our $poldbh;
+our $pkg;
+our $pkgdir;
+our ($pkg_exists,$pkg_secret);
+
+our $stderr;
+
+our ($version,$suite,$tagname);
+our %deliberately;
+
+# We assume that it is not possible for NEW to have a version older
+# than sid.
+
+# Whenever pushing, we check for
+#   source-package-local tainted history
+#   global tainted history
+#   can be overridden by --deliberately except for an admin prohib taint
+# 
+# ALL of the following apply only if history is secret:
+# 
+# if NEW has no version, or a version which is not in our history[1]
+#   (always)
+#   check all suites
+#   if any suite's version is in our history[1], publish our history
+#   otherwise discard our history,
+#     tainting --deliberately-include-questionable-history
+# 
+# if NEW has a version which is in our history[1]
+#   (on push only)
+#   require explicit specification of one of
+#     --deliberately-include-questionable-history
+#     --deliberately-not-fast-forward
+#       (latter will taint old NEW version --d-i-q-h)
+#   (otherwise)
+#   leave it be
+# 
+# [1] looking for the relevant git tag for the version number and not
+#    caring what that tag refers to.
+#
+#    When we are doing a push to a fresh repo, any version will do: in
+#    this case, this is the first dgit upload of an existing package,
+#    and we trust that the uploader hasn't included in their git
+#    history any previous non-dgit uploads.
+#
+# A wrinkle: if we approved a push recently, we treat NEW as having
+# a version which is in our history.  This is because the package may
+# still be being uploaded.  (We record this using the timestamp of the
+# package's git repo directory.)
+
+# We aim for the following invariants and properties:
+#
+# - .dsc of published dgit package will have corresponding publicly
+#   visible dgit-repo (soon)
+#
+# - when a new package is rejected we help maintainer avoid
+#   accidentally including bad objects in published dgit history
+#
+# - .dsc of NEW dgit package has corresponding dgit-repo but not
+#   publicly readable
+
+sub apiquery ($) {
+    my ($subpath) = @_;
+    local $/=undef;
+    my $dgit = "$dgitlive/dgit";
+    $dgit = "dgit" if !stat_exists $dgit;
+    my $cmd = "$dgit -d$distro \$DGIT_TEST_OPTS";
+    $cmd .= " -".("D" x $debuglevel) if $debuglevel;
+    $cmd .= " archive-api-query $subpath";
+    printdebug "apiquery $cmd\n";
+    $!=0; $?=0; my $json = `$cmd`;
+    defined $json && !$? or die "$subpath $! $?";
+    my $r = decode_json $json;
+    my $d = new Data::Dumper([$r], [qw(r)]);
+    printdebug "apiquery $subpath | ", $d->Dump() if $debuglevel>=2;
+    return $r;
+}
+
+sub vsn_in_our_history ($) {
+    my ($vsn) = @_;
+
+    # Eventually, when we withdraw support for old-format (DEP-14
+    # namespace) tags, we will need to change this to only look
+    # for debiantag_new.  See the commit
+    #   "Tag change: Update dgit-repos-policy-debian"
+    # (reverting which is a good start for that change).
+
+    my @tagrefs = map { "refs/tags/".$_ } debiantags $vsn, $distro;
+    printdebug " checking history  vsn=$vsn tagrefs=@tagrefs\n";
+    open F, "-|", qw(git for-each-ref), @tagrefs;
+    $_ = <F>;
+    close F;
+    return 1 if defined && m/\S/;
+    die "$pkg tagrefs @tagrefs $? $!" if $?;
+    return 0;
+}
+
+sub specific_suite_has_suitable_vsn ($$) {
+    my ($suite, $vsn_check) = @_; # tests $vsn_check->($version)
+    my $in_suite = apiquery "dsc_in_suite/$suite/$pkg";
+    foreach my $entry (@$in_suite) {
+       my $vsn = $entry->{version};
+       die "$pkg ?" unless defined $vsn;
+       printdebug " checking history found suite=$suite vsn=$vsn\n";
+       return 1 if $vsn_check->($vsn);
+    }
+    return 0;
+}
+
+sub new_has_vsn_in_our_history () {
+    return specific_suite_has_suitable_vsn('new', \&vsn_in_our_history);
+}
+
+sub good_suite_has_suitable_vsn ($) {
+    my ($vsn_check) = @_; # as for specific_suite_has_specific_vsn
+    my $suites = apiquery "suites";
+    foreach my $suitei (@$suites) {
+       my $suite = $suitei->{name};
+       die unless defined $suite;
+       next if $suite =~ m/\bnew$/;
+       return 1 if specific_suite_has_suitable_vsn($suite, $vsn_check);
+    }
+    return 0;
+}
+
+sub statpackage () {
+    $pkgdir = "$repos/$pkg.git";
+    if (!stat_exists $pkgdir) {
+       printdebug "statpackage $pkg => ENOENT\n";
+       $pkg_exists = 0;
+    } else {
+       $pkg_exists = 1;
+       $pkg_secret = !!(~(stat _)[2] & 05);
+       printdebug "statpackage $pkg => exists, secret=$pkg_secret.\n";
+    }
+}
+
+sub getpackage () {
+    die unless @ARGV >= 1;
+    $pkg = shift @ARGV;
+    die unless $pkg =~ m/^$package_re$/;
+
+    statpackage();
+}
+
+sub add_taint ($$) {
+    my ($refobj, $reason) = @_;
+
+    printdebug "TAINTING $refobj\n",
+        (map { "\%| $_" } split "\n", $reason),
+        "\n";
+
+    my $tf = new File::Temp or die $!;
+    print $tf "$refobj^0\n" or die $!;
+    flush $tf or die $!;
+    seek $tf,0,0 or die $!;
+
+    my $gcfpid = open GCF, "-|";
+    defined $gcfpid or die $!;
+    if (!$gcfpid) {
+       open STDIN, "<&", $tf or die $!;
+       exec 'git', 'cat-file', '--batch';
+       die $!;
+    }
+
+    close $tf or die $!;
+    $_ = <GCF>;
+    defined $_ or die;
+    m/^(\w+) (\w+) (\d+)\n/ or die "$_ ?";
+    my $gitobjid = $1;
+    my $gitobjtype = $2;
+    my $bytes = $3;
+
+    my $gitobjdata;
+    if ($gitobjtype eq 'commit' or $gitobjtype eq 'tag') {
+       $!=0; read GCF, $gitobjdata, $bytes == $bytes
+           or die "$gitobjid $bytes $!";
+    }
+    close GCF;
+
+    $poldbh->do("INSERT INTO taints".
+               " (package, gitobjid, gitobjtype, gitobjdata, time, comment)".
+               " VALUES (?,?,?,?,?,?)", {},
+               $pkg, $gitobjid, $gitobjtype, $gitobjdata, time, $reason);
+
+    my $taint_id = $poldbh->last_insert_id(undef,undef,"taints","taint_id");
+    die unless defined $taint_id;
+
+    $poldbh->do("INSERT INTO taintoverrides".
+               " (taint_id, deliberately)".
+               " VALUES (?, '--deliberately-include-questionable-history')", 
+               {}, $taint_id);
+}
+
+sub add_taint_by_tag ($$) {
+    my ($tagname,$refobjid) = @_;
+    add_taint($refobjid,
+             "tag $tagname referred to this object in git tree but all".
+             " previously pushed versions were found to have been".
+             " removed from NEW (ie, rejected) (or never arrived)");
+}
+
+sub check_package () {
+    return 0 unless $pkg_exists;
+    return 0 unless $pkg_secret;
+
+    printdebug "check_package\n";
+
+    chdir $pkgdir or die "$pkgdir $!";
+
+    stat '.' or die "$pkgdir $!";
+    my $mtime = ((stat _)[9]);
+    my $age = time -  $mtime;
+    printdebug "check_package age=$age\n";
+
+    if (good_suite_has_suitable_vsn(\&vsn_in_our_history)) {
+       chmod $publicmode, "." or die $!;
+       $pkg_secret = 0;
+       return 0;
+    }
+
+    return 0 if $age < $new_upload_propagation_slop;
+
+    return 0 if new_has_vsn_in_our_history();
+
+    printdebug "check_package secret, deleted, tainting\n";
+
+    git_for_each_ref('refs/tags', sub {
+       my ($objid,$objtype,$fullrefname,$tagname) = @_;
+       add_taint_by_tag($tagname,$objid);
+    });
+
+    return FRESHREPO;
+}
+
+sub action_check_package () {
+    getpackage();
+    return check_package();
+}
+
+sub getpushinfo () {
+    die unless @ARGV >= 4;
+    $version = shift @ARGV;
+    $suite = shift @ARGV;
+    $tagname = shift @ARGV;
+    my $delibs = shift @ARGV;
+    foreach my $delib (split /\,/, $delibs) {
+       $deliberately{$delib} = 1;
+    }
+}
+
+sub deliberately ($) { return $deliberately{"--deliberately-$_[0]"}; }
+
+sub action_push () {
+    getpackage();
+    getpushinfo();
+
+    check_package(); # might make package public, or might add taints
+
+    return 0 unless $pkg_exists;
+    return 0 unless $pkg_secret;
+
+    # we suppose that NEW has a version which is already in our
+    # history, as otherwise the repo would have been blown away
+
+    if (deliberately('not-fast-forward')) {
+       add_taint(server_ref($suite),
+                 "rewound suite $suite; --deliberately-not-fast-forward".
+                 " specified in signed tag $tagname for upload of".
+                 " version $version");
+       return NOFFCHECK|FRESHREPO;
+    }
+    if (deliberately('include-questionable-history')) {
+       return 0;
+    }
+    die "\nPackage is in NEW and has not been accepted or rejected yet;".
+       " use a --deliberately option to specify whether you are".
+       " keeping or discarding the previously pushed history. ".
+       " Please RTFM dgit(1).\n\n";
+}
+
+sub action_push_confirm () {
+    getpackage();
+    getpushinfo();
+    die unless @ARGV >= 1;
+    my $freshrepo = shift @ARGV;
+
+    my $initq = $poldbh->prepare(<<END);
+        SELECT taint_id, gitobjid FROM taints t
+            WHERE (package = ? OR package = '')
+END
+    $initq->execute($pkg);
+
+    my @objscatcmd = qw(git);
+    push @objscatcmd, qw(--git-dir), $freshrepo if length $freshrepo;
+    push @objscatcmd, qw(cat-file --batch);
+    debugcmd '|',@objscatcmd if $debuglevel>=2;
+
+    my @taintids;
+    my $chkinput = tempfile();
+    while (my $taint = $initq->fetchrow_hashref()) {
+       push @taintids, $taint->{taint_id};
+       print $chkinput $taint->{gitobjid}, "\n" or die $!;
+       printdebug '|> ', $taint->{gitobjid}, "\n" if $debuglevel>=2;
+    }
+    flush $chkinput or die $!;
+    seek $chkinput,0,0 or die $!;
+
+    my $checkpid = open CHKOUT, "-|" // die $!;
+    if (!$checkpid) {
+       open STDIN, "<&", $chkinput or die $!;
+       delete $ENV{GIT_ALTERNATE_OBJECT_DIRECTORIES};
+       # ^ recent versions of git set this in the environment of
+       # receive hooks.  This can cause us to see things which
+       # the user is trying to abolish.
+       exec @objscatcmd or die $!;
+    }
+
+    my ($taintinfoq,$overridesanyq,$untaintq,$overridesq);
+
+    my $overridesstmt = <<END;
+        SELECT deliberately FROM taintoverrides WHERE (
+            1=0
+END
+    my @overridesv = sort keys %deliberately;
+    $overridesstmt .= <<END foreach @overridesv;
+            OR deliberately = ?
+END
+    $overridesstmt .= <<END;
+       ) AND taint_id = ?
+        ORDER BY deliberately ASC
+END
+
+    my $mustreject=0;
+
+    while (my $taintid = shift @taintids) {
+       $!=0; $_ = <CHKOUT>;
+       die "($taintid @objscatcmd) $!" unless defined $_;
+       printdebug "|< ", $_ if $debuglevel>=2;
+
+       next if m/^\w+ missing$/;
+       die "($taintid @objscatcmd) $_ ?" unless m/^(\w+) (\w+) (\d+)\s/;
+       my ($objid,$objtype,$nbytes) = ($1,$2,$3);
+
+       my $drop;
+       (read CHKOUT, $drop, $nbytes) == $nbytes
+           or die "($taintid @objscatcmd) $!";
+
+       $!=0; $_ = <CHKOUT>;
+       die "($taintid @objscatcmd) $!" unless defined $_;
+       die "($taintid @objscatcmd) $_ ?" if m/\S/;
+
+       $taintinfoq ||= $poldbh->prepare(<<END);
+            SELECT package, time, comment FROM taints WHERE taint_id =  ?
+END
+        $taintinfoq->execute($taintid);
+
+       my $ti = $taintinfoq->fetchrow_hashref();
+       die "($taintid)" unless $ti;
+
+       my $timeshow = defined $ti->{time}
+           ? " at time ".strftime("%Y-%m-%d %H:%M:%S Z", gmtime $ti->{time})
+           : "";
+       my $pkgshow = length $ti->{package}
+           ? "package $ti->{package}"
+           : "any package";
+
+       $stderr .= <<END;
+
+History contains tainted $objtype $objid
+Taint recorded$timeshow for $pkgshow
+Reason: $ti->{comment}
+END
+
+        printdebug "SQL overrides: @overridesv $taintid /\n$overridesstmt\n";
+
+        $overridesq ||= $poldbh->prepare($overridesstmt);
+       $overridesq->execute(@overridesv, $taintid);
+       my ($ovwhy) = $overridesq->fetchrow_array();
+       if (!defined $ovwhy) {
+           $overridesanyq ||= $poldbh->prepare(<<END);
+               SELECT 1 FROM taintoverrides WHERE taint_id = ? LIMIT 1
+END
+           $overridesanyq->execute($taintid);
+           my ($ovany) = $overridesanyq->fetchrow_array();
+           $stderr .= $ovany ? <<END : <<END;
+Could be forced using --deliberately.  Consult documentation.
+END
+Uncorrectable error.  If confused, consult administrator.
+END
+            $mustreject = 1;
+       } else {
+           $stderr .= <<END;
+Forcing due to --deliberately-$ovwhy
+END
+            $untaintq ||= $poldbh->prepare(<<END);
+                DELETE FROM taints WHERE taint_id = ?
+END
+            $untaintq->execute($taintid);
+        }
+    }
+    close CHKOUT;
+
+    if ($mustreject) {
+       $stderr .= <<END;
+
+Rejecting push due to questionable history.
+END
+        return 1;
+    }
+
+    if (length $freshrepo) {
+       if (!good_suite_has_suitable_vsn(sub { 1; })) {
+           stat $freshrepo or die "$freshrepo $!";
+           my $oldmode = ((stat _)[2]);
+           my $oldwrites = $oldmode & 0222;
+           # remove r and x bits which have corresponding w bits clear
+           my $newmode = $oldmode &
+               (~0555 | ($oldwrites << 1) | ($oldwrites >> 1));
+           printdebug sprintf "chmod %#o (was %#o) %s\n",
+               $newmode, $oldmode, $freshrepo;
+           chmod $newmode, $freshrepo or die $!;
+           utime undef, undef, $freshrepo or die $!;
+       }
+    }
+
+    return 0;
+}
+
+sub action_check_list () {
+    opendir L, "$repos" or die "$repos $!";
+    while (defined (my $dent = readdir L)) {
+       next unless $dent =~ m/^($package_re)\.git$/;
+       $pkg = $1;
+       statpackage();
+       next unless $pkg_exists;
+       next unless $pkg_secret;
+       print "$pkg\n" or die $!;
+    }
+    closedir L or die $!;
+    close STDOUT or die $!;
+    return 0;
+}
+
+$action =~ y/-/_/;
+my $fn = ${*::}{"action_$action"};
+if (!$fn) {
+    printdebug "dgit-repos-policy-debian: unknown action $action\n";
+    exit 0;
+}
+
+my $sleepy=0;
+my $rcode;
+
+my $db_busy_exception= 'Debian::Dgit::Policy::Debian::DB_BUSY';
+
+my @orgargv = @ARGV;
+
+for (;;) {
+    @ARGV = @orgargv;
+    eval {
+       poldb_setup(poldb_path($repos), sub {
+           $poldbh->{HandleError} = sub {
+               return 0 unless $poldbh->err == 5; # SQLITE_BUSY, not in .pm :-(
+               die bless { }, $db_busy_exception;
+           };
+
+           eval ($ENV{'DGIT_RPD_TEST_DBLOOP_HOOK'}//'');
+           die $@ if length $@;
+           # used by tests/tests/debpolicy-dbretry
+        });
+
+       $stderr = '';
+
+       $rcode = $fn->();
+       die unless defined $rcode;
+
+       $poldbh->commit;
+    };
+    last unless length $@;
+    die $@ unless ref $@ eq $db_busy_exception;
+
+    die if $sleepy >= 20;
+    $sleepy++;
+    print STDERR "[policy database busy, retrying (${sleepy}s)]\n";
+
+    eval { $poldbh->rollback; };
+}
+
+print STDERR $stderr or die $!;
+flush STDERR or die $!;
+_exit $rcode;
diff --git a/infra/dgit-repos-policy-trusting b/infra/dgit-repos-policy-trusting
new file mode 100755 (executable)
index 0000000..b551d50
--- /dev/null
@@ -0,0 +1,58 @@
+#!/bin/bash
+#
+# This is a genuine policy, not just one for testing.
+#
+# It allows anyone authorised to push to also, on demand:
+#   - wipe the repo and replace it with a new one
+#     (with --deliberately-fresh-repo)
+#   - do non-fast-forward pushes
+#     (with --deliberately-not-fast-forward)
+
+set -e
+
+case "$DGIT_DRS_DEBUG" in
+''|0)  exec 3>/dev/null        ;;
+1)     exec 3>&2               ;;
+*)     exec 3>&2;      set -x  ;;
+esac
+
+distro=$1      ; shift
+reposdir=$1    ; shift
+livedir=$1     ; shift
+distrodir=$1   ; shift
+action=$1      ; shift
+
+echo >&3 "dgit-repos-policy-trusting: action=$action"
+
+case "$action" in
+push|push-confirm)     ;;
+*) exit 0              ;;
+esac
+
+package=$1     ; shift
+version=$1     ; shift
+suite=$1       ; shift
+tagname=$1     ; shift
+delibs=$1      ; shift
+
+bitmask=0
+
+policyflags () {
+       perl -e '
+               use Debian::Dgit::Infra;
+               use Debian::Dgit qw(:policyflags); print '$1',"\n"
+       '
+}
+
+set -e
+
+case "$action//,$delibs," in
+push//*,--deliberately-fresh-repo,*)
+       bitmask=$(( bitmask | `policyflags 'NOFFCHECK|FRESHREPO'` ))
+       ;;
+push//*,--deliberately-not-fast-forward,*)
+       bitmask=$(( bitmask | `policyflags 'NOFFCHECK'` ))
+       ;;
+esac
+
+exit $bitmask
diff --git a/infra/dgit-repos-server b/infra/dgit-repos-server
new file mode 100755 (executable)
index 0000000..eb4b377
--- /dev/null
@@ -0,0 +1,1177 @@
+#!/usr/bin/perl -w
+# dgit-repos-server
+#
+# git protocol proxy to check dgit pushes etc.
+#
+# Copyright (C) 2014-2016  Ian Jackson
+#
+#    This program is free software; you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation; either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# usages:
+#   dgit-repos-server DISTRO DISTRO-DIR AUTH-SPEC [<settings>] --ssh
+#   dgit-repos-server DISTRO DISTRO-DIR AUTH-SPEC [<settings>] --cron
+# settings
+#   --repos=GIT-REPOS-DIR      default DISTRO-DIR/repos/
+#   --suites=SUITES-FILE       default DISTRO-DIR/suites
+#   --suites-master=SUITES-FILE default DISTRO-DIR/suites-master
+#   --policy-hook=POLICY-HOOK  default DISTRO-DIR/policy-hook
+#   --mirror-hook=MIRROR-HOOK  default DISTRO-DIR/mirror-hook
+#   --dgit-live=DGIT-LIVE-DIR  default DISTRO-DIR/dgit-live
+# (DISTRO-DIR is not used other than as default and to pass to policy
+# and mirror hooks)
+# internal usage:
+#  .../dgit-repos-server --pre-receive-hook PACKAGE
+#
+# Invoked as the ssh restricted command
+#
+# Works like git-receive-pack
+#
+# SUITES-FILE is the name of a file which lists the permissible suites
+# one per line (#-comments and blank lines ignored).  For --suites-master
+# it is a list of the suite(s) which should, when pushed to, update
+# `master' on the server (if fast forward).
+#
+# AUTH-SPEC is a :-separated list of
+#   KEYRING.GPG,AUTH-SPEC
+# where AUTH-SPEC is one of
+#   a
+#   mDM.TXT
+# (With --cron AUTH-SPEC is not used and may be the empty string.)
+
+use strict;
+
+use Debian::Dgit::Infra; # must precede Debian::Dgit; - can change @INC!
+use Debian::Dgit qw(:DEFAULT :policyflags);
+setup_sigwarn();
+
+# DGIT-REPOS-DIR contains:
+# git tree (or other object)      lock (in acquisition order, outer first)
+#
+#  _tmp/PACKAGE_prospective       ! } SAME.lock, held during receive-pack
+#
+#  _tmp/PACKAGE_incoming$$        ! } SAME.lock, held during receive-pack
+#  _tmp/PACKAGE_incoming$$_fresh  ! }
+#
+#  PACKAGE.git                      } PACKAGE.git.lock
+#  PACKAGE_garbage                  }   (also covers executions of
+#  PACKAGE_garbage-old              }    policy hook script for PACKAGE)
+#  PACKAGE_garbage-tmp              }
+#  policy*                          } (for policy hook script, covered by
+#                                   }  lock only when invoked for a package)
+#
+# leaf locks, held during brief operaton only:
+#
+#  _empty                           } SAME.lock
+#  _empty.new                       }
+#
+#  _template                        } SAME.lock
+#
+# locks marked ! may be held during client data transfer
+
+# What we do on push is this:
+#  - extract the destination repo name
+#  - make a hardlink clone of the destination repo
+#  - provide the destination with a stunt pre-receive hook
+#  - run actual git-receive-pack with that new destination
+#   as a result of this the stunt pre-receive hook runs; it does this:
+#    + understand what refs we are allegedly updating and
+#      check some correspondences:
+#        * we are updating only refs/tags/[archive/]DISTRO/* and refs/dgit/*
+#        * and only one of each
+#        * and the tag does not already exist
+#      and
+#        * recover the suite name from the destination refs/dgit/ ref
+#    + disassemble the signed tag into its various fields and signature
+#      including:
+#        * parsing the first line of the tag message to recover
+#          the package name, version and suite
+#        * checking that the package name corresponds to the dest repo name
+#        * checking that the suite name is as recovered above
+#    + verify the signature on the signed tag
+#      and if necessary check that the keyid and package are listed in dm.txt
+#    + check various correspondences:
+#        * the signed tag must refer to a commit
+#        * the signed tag commit must be the refs/dgit value
+#        * the name in the signed tag must correspond to its ref name
+#        * the tag name must be [archive/]debian/<version> (massaged as needed)
+#        * the suite is one of those permitted
+#        * the signed tag has a suitable name
+#        * run the "push" policy hook
+#        * replay prevention for --deliberately-not-fast-forward
+#        * check the commit is a fast forward
+#        * handle a request from the policy hook for a fresh repo
+#    + push the signed tag and new dgit branch to the actual repo
+#
+# If the destination repo does not already exist, we need to make
+# sure that we create it reasonably atomically, and also that
+# we don't every have a destination repo containing no refs at all
+# (because such a thing causes git-fetch-pack to barf).  So then we
+# do as above, except:
+#  - before starting, we take out our own lock for the destination repo
+#  - we create a prospective new destination repo by making a copy
+#    of _template
+#  - we use the prospective new destination repo instead of the
+#    actual new destination repo (since the latter doesn't exist)
+#  - after git-receive-pack exits, we
+#    + check that the prospective repo contains a tag and head
+#    + rename the prospective destination repo into place
+#
+# Cleanup strategy:
+#  - We are crash-only
+#  - Temporary working trees and their locks are cleaned up
+#    opportunistically by a program which tries to take each lock and
+#    if successful deletes both the tree and the lockfile
+#  - Prospective working trees and their locks are cleaned up by
+#    a program which tries to take each lock and if successful
+#    deletes any prospective working tree and the lock (but not
+#    of course any actual tree)
+#  - It is forbidden to _remove_ the lockfile without removing
+#    the corresponding temporary tree, as the lockfile is also
+#    a stampfile whose presence indicates that there may be
+#    cleanup to do
+#
+# Policy hook scripts are invoked like this:
+#   POLICY-HOOK-SCRIPT DISTRO DGIT-REPOS-DIR DGIT-LIVE-DIR DISTRO-DIR ACTION...
+# ie.
+#   POLICY-HOOK-SCRIPT ... check-list [...]
+#   POLICY-HOOK-SCRIPT ... check-package PACKAGE [...]
+#   POLICY-HOOK-SCRIPT ... push PACKAGE \
+#         VERSION SUITE TAGNAME DELIBERATELIES [...]
+#   POLICY-HOOK-SCRIPT ... push-confirm PACKAGE \
+#         VERSION SUITE TAGNAME DELIBERATELIES FRESH-REPO|'' [...]
+#
+# DELIBERATELIES is like this: --deliberately-foo,--deliberately-bar,...
+#
+# Exit status of policy hook is a bitmask.
+# Bit weight constants are defined in Dgit.pm.
+#    NOFFCHECK   (2)
+#         suppress dgit-repos-server's fast-forward check ("push" only)
+#    FRESHREPO   (4)
+#         blow away repo right away (ie, as if before push or fetch)
+#         ("check-package" and "push" only)
+#    NOCOMMITCHECK   (8)
+#         suppress dgit-repos-server's check that commits do
+#         not lack "committer" info (eg as produced by #849041)
+# any unexpected bits mean failure, and then known set bits are ignored
+# if no unexpected bits set, operation continues (subject to meaning
+# of any expected bits set).  So, eg, exit 0 means "continue normally"
+# and would be appropriate for an unknown action.
+#
+# cwd for push and push-confirm is a temporary repo where the incoming
+# objects have been received; TAGNAME is the version-based tag.
+#
+# FRESH-REPO is '' iff the repo for this package already existed, or
+# the pathname of the newly-created repo which will be renamed into
+# place if everything goes well.  (NB that this is generally not the
+# same repo as the cwd, because the objects are first received into a
+# temporary repo so they can be examined.)  In this case FRESH-REPO
+# contains exactly the objects and refs that will appear in the
+# destination if push-confirm approves.
+# 
+# if push requested FRESHREPO, push-confirm happens in the old working
+# repo and FRESH-REPO is guaranteed not to be ''.
+#
+# policy hook for a particular package will be invoked only once at
+# a time - (see comments about DGIT-REPOS-DIR, above)
+#
+# check-list and check-package are invoked via the --cron option.
+# First, without any locking, check-list is called.  It should produce
+# a list of package names (one per line).  Then check-package will be
+# invoked for each named package, in each case after taking an
+# appropriate lock.
+#
+# If policy hook wants to run dgit (or something else in the dgit
+# package), it should use DGIT-LIVE-DIR/dgit (etc.), or if that is
+# ENOENT, use the installed version.
+#
+# Mirror hook scripts are invoked like this:
+#   MIRROR-HOOK-SCRIPT DISTRO-DIR ACTION...
+# and currently there is only one action invoked by dgit-repos-server:
+#   MIRROR-HOOK-SCRIPT DISTRO-DIR updated-hook PACKAGE [...]
+#
+# Exit status of the mirror hook is advisory only.  The mirror hook
+# runs too late to do anything useful about a problem, so the only
+# effect of a mirror hook exiting nonzero is a warning message to
+# stderr (which the pushing user should end up seeing).
+#
+# If the mirror hook does not exist, it is silently skipped.
+
+use POSIX;
+use Fcntl qw(:flock);
+use File::Path qw(rmtree);
+use File::Temp qw(tempfile);
+
+initdebug('');
+
+our $func;
+our $dgitrepos;
+our $package;
+our $distro;
+our $suitesfile;
+our $suitesformasterfile;
+our $policyhook;
+our $mirrorhook;
+our $dgitlive;
+our $distrodir;
+our $destrepo;
+our $workrepo;
+our $keyrings;
+our @lockfhs;
+
+our @deliberatelies;
+our %previously;
+our $policy;
+our @policy_args;
+
+#----- utilities -----
+
+sub realdestrepo () { "$dgitrepos/$package.git"; }
+
+sub acquirelock ($$) {
+    my ($lock, $must) = @_;
+    my $fh;
+    printdebug sprintf "locking %s %d\n", $lock, $must;
+    for (;;) {
+       close $fh if $fh;
+       $fh = new IO::File $lock, ">" or die "open $lock: $!";
+       my $ok = flock $fh, $must ? LOCK_EX : (LOCK_EX|LOCK_NB);
+       if (!$ok) {
+           die "flock $lock: $!" if $must;
+           printdebug " locking $lock failed\n";
+           return undef;
+       }
+       next unless stat_exists $lock;
+       my $want = (stat _)[1];
+       stat $fh or die $!;
+       my $got = (stat _)[1];
+       last if $got == $want;
+    }
+    return $fh;
+}
+
+sub acquirermtree ($$) {
+    my ($tree, $must) = @_;
+    my $fh = acquirelock("$tree.lock", $must);
+    if ($fh) {
+       push @lockfhs, $fh;
+       rmtree $tree;
+    }
+    return $fh;
+}
+
+sub locksometree ($) {
+    my ($tree) = @_;
+    acquirelock("$tree.lock", 1);
+}
+
+sub lockrealtree () {
+    locksometree(realdestrepo);
+}
+
+sub mkrepotmp () { ensuredir "$dgitrepos/_tmp" };
+
+sub removedtagsfile () { "$dgitrepos/_removed-tags/$package"; }
+
+sub recorderror ($) {
+    my ($why) = @_;
+    my $w = $ENV{'DGIT_DRS_WORK'}; # we are in stunthook
+    if (defined $w) {
+       chomp $why;
+       open ERR, ">", "$w/drs-error" or die $!;
+       print ERR $why, "\n" or die $!;
+       close ERR or die $!;
+       return 1;
+    }
+    return 0;
+}
+
+sub reject ($) {
+    my ($why) = @_;
+    recorderror "reject: $why";
+    die "\ndgit-repos-server: reject: $why\n\n";
+}
+
+sub runcmd {
+    debugcmd '+',@_;
+    $!=0; $?=0;
+    my $r = system @_;
+    die (shellquote @_)." $? $!" if $r;
+}
+
+sub policyhook {
+    my ($policyallowbits, @polargs) = @_;
+    # => ($exitstatuspolicybitmap);
+    die if $policyallowbits & ~0x3e;
+    my @cmd = ($policyhook,$distro,$dgitrepos,$dgitlive,$distrodir,@polargs);
+    debugcmd '+M',@cmd;
+    my $r = system @cmd;
+    die "system: $!" if $r < 0;
+    die "dgit-repos-server: policy hook failed (or rejected) ($?)\n"
+       if $r & ~($policyallowbits << 8);
+    printdebug sprintf "hook => %#x\n", $r;
+    return $r >> 8;
+}
+
+sub mkemptyrepo ($$) {
+    my ($dir,$sharedperm) = @_;
+    runcmd qw(git init --bare --quiet), "--shared=$sharedperm", $dir;
+}
+
+sub mkrepo_fromtemplate ($) {
+    my ($dir) = @_;
+    my $template = "$dgitrepos/_template";
+    my $templatelock = locksometree($template);
+    printdebug "copy template $template -> $dir\n";
+    my $r = system qw(cp -a --), $template, $dir;
+    !$r or die "create new repo $dir failed: $r $!";
+    close $templatelock;
+}
+
+sub movetogarbage () {
+    # realdestrepo must have been locked
+
+    my $real = realdestrepo;
+    return unless stat_exists $real;
+
+    my $garbagerepo = "$dgitrepos/${package}_garbage";
+    # We arrange to always keep at least one old tree, for recovery
+    # from mistakes.  This is either $garbage or $garbage-old.
+    if (stat_exists "$garbagerepo") {
+       printdebug "movetogarbage: rmtree $garbagerepo-tmp\n";
+       rmtree "$garbagerepo-tmp";
+       if (rename "$garbagerepo-old", "$garbagerepo-tmp") {
+           printdebug "movetogarbage: $garbagerepo-old -> -tmp, rmtree\n";
+           rmtree "$garbagerepo-tmp";
+       } else {
+           die "$garbagerepo $!" unless $!==ENOENT;
+           printdebug "movetogarbage: $garbagerepo-old -> -tmp\n";
+       }
+       printdebug "movetogarbage: $garbagerepo -> -old\n";
+       rename "$garbagerepo", "$garbagerepo-old" or die "$garbagerepo $!";
+    }
+
+    ensuredir "$dgitrepos/_removed-tags";
+    open PREVIOUS, ">>", removedtagsfile or die removedtagsfile." $!";
+    git_for_each_ref([ map { 'refs/tags/'.$_ } debiantags('*',$distro) ],
+                    sub {
+       my ($objid,$objtype,$fullrefname,$reftail) = @_;
+       print PREVIOUS "\n$objid $reftail .\n" or die $!;
+    }, $real);
+    close PREVIOUS or die $!;
+
+    printdebug "movetogarbage: $real -> $garbagerepo\n";
+    rename $real, $garbagerepo
+       or $! == ENOENT
+       or die "$garbagerepo $!";
+}
+
+sub policy_checkpackage () {
+    my $lfh = lockrealtree();
+
+    $policy = policyhook(FRESHREPO,'check-package',$package);
+    if ($policy & FRESHREPO) {
+       movetogarbage();
+    }
+
+    close $lfh;
+}
+
+#----- git-receive-pack -----
+
+sub fixmissing__git_receive_pack () {
+    mkrepotmp();
+    $destrepo = "$dgitrepos/_tmp/${package}_prospective";
+    acquirermtree($destrepo, 1);
+    mkrepo_fromtemplate($destrepo);
+}
+
+sub makeworkingclone () {
+    mkrepotmp();
+    $workrepo = "$dgitrepos/_tmp/${package}_incoming$$";
+    acquirermtree($workrepo, 1);
+    my $lfh = lockrealtree();
+    runcmd qw(git clone -l -q --mirror), $destrepo, $workrepo;
+    close $lfh;
+    rmtree "${workrepo}_fresh";
+}
+
+sub setupstunthook () {
+    my $prerecv = "$workrepo/hooks/pre-receive";
+    my $fh = new IO::File $prerecv, O_WRONLY|O_CREAT|O_TRUNC, 0777
+       or die "$prerecv: $!";
+    print $fh <<END or die "$prerecv: $!";
+#!/bin/sh
+set -e
+exec $0 --pre-receive-hook $package
+END
+    close $fh or die "$prerecv: $!";
+    $ENV{'DGIT_DRS_WORK'}= $workrepo;
+    $ENV{'DGIT_DRS_DEST'}= $destrepo;
+    printdebug " stunt hook set up $prerecv\n";
+}
+
+sub dealwithfreshrepo () {
+    my $freshrepo = "${workrepo}_fresh";
+    return unless stat_exists $freshrepo;
+    $destrepo = $freshrepo;
+}
+
+sub mirrorhook {
+    my @cmd = ($mirrorhook,$distrodir,@_);
+    debugcmd '+',@cmd;
+    return unless stat_exists $mirrorhook;
+    my $r = system @cmd;
+    if ($r) {
+       printf STDERR <<END,
+dgit-repos-server: warning: mirror hook failed: %s
+dgit-repos-server: push complete but may not fully visible.
+END
+            ($r < 0 ? "exec: $!" :
+            $r == (124 << 8) ? "exited status 124 (timeout?)" :
+            !($r & ~0xff00) ? "exited ".($? >> 8) :
+            "wait status $?");
+    }
+}
+
+sub maybeinstallprospective () {
+    return if $destrepo eq realdestrepo;
+
+    if (open REJ, "<", "$workrepo/drs-error") {
+       local $/ = undef;
+       my $msg = <REJ>;
+       REJ->error and die $!;
+       print STDERR $msg;
+       exit 1;
+    } else {
+       $!==&ENOENT or die $!;
+    }
+
+    printdebug " show-ref ($destrepo) ...\n";
+
+    my $child = open SR, "-|";
+    defined $child or die $!;
+    if (!$child) {
+       chdir $destrepo or die $!;
+       exec qw(git show-ref);
+       die $!;
+    }
+    my %got = qw(newtag 0 omtag 0 head 0);
+    while (<SR>) {
+       chomp or die;
+       printdebug " show-refs| $_\n";
+       s/^\S*[1-9a-f]\S* (\S+)$/$1/ or die;
+       next if m{^refs/heads/master$};
+       my $wh =
+           m{^refs/tags/archive/} ? 'newtag' :
+           m{^refs/tags/} ? 'omtag' :
+           m{^refs/dgit/} ? 'head' :
+           die;
+       use Data::Dumper;
+       die if $got{$wh}++;
+    }
+    $!=0; $?=0; close SR or $?==256 or die "$? $!";
+
+    printdebug "installprospective ?\n";
+    die Dumper(\%got)." -- missing refs in new repo"
+       unless $got{head} && grep { m/tag$/ && $got{$_} } keys %got;
+
+    lockrealtree();
+
+    if ($destrepo eq "${workrepo}_fresh") {
+       movetogarbage;
+    }
+
+    printdebug "install $destrepo => ".realdestrepo."\n";
+    rename $destrepo, realdestrepo or die $!;
+    remove realdestrepo.".lock" or die $!;
+}
+
+sub main__git_receive_pack () {
+    makeworkingclone();
+    setupstunthook();
+    runcmd qw(git receive-pack), $workrepo;
+    dealwithfreshrepo();
+    maybeinstallprospective();
+    mirrorhook('updated-hook', $package);
+}
+
+#----- stunt post-receive hook -----
+
+our ($tagname, $tagval, $suite, $oldcommit, $commit);
+our ($version, %tagh);
+our ($maint_tagname, $maint_tagval);
+
+our ($tagexists_error);
+
+sub readupdates () {
+    printdebug " updates ...\n";
+    my %tags;
+    while (<STDIN>) {
+       chomp or die;
+       printdebug " upd.| $_\n";
+       m/^(\S+) (\S+) (\S+)$/ or die "$_ ?";
+       my ($old, $sha1, $refname) = ($1, $2, $3);
+       if ($refname =~ m{^refs/tags/(?=(?:archive/)?$distro/)}) {
+           my $tn = $'; #';
+           $tags{$tn} = $sha1;
+           $tagexists_error= "tag $tn already exists -".
+               " not replacing previously-pushed version"
+               if $old =~ m/[^0]/;
+       } elsif ($refname =~ m{^refs/dgit/}) {
+           reject "pushing multiple heads!" if defined $suite;
+           $suite = $'; #';
+           $oldcommit = $old;
+           $commit = $sha1;
+       } else {
+           reject "pushing unexpected ref!";
+       }
+    }
+    STDIN->error and die $!;
+
+    reject "push is missing tag ref update" unless %tags;
+    my @newtags = grep { m#^archive/# } keys %tags;
+    my @omtags = grep { !m#^archive/# } keys %tags;
+    reject "pushing too many similar tags" if @newtags>1 || @omtags>1;
+    if (@newtags) {
+       ($tagname) = @newtags;
+       ($maint_tagname) = @omtags;
+    } else {
+       ($tagname) = @omtags or die;
+    }
+    $tagval = $tags{$tagname};
+    $maint_tagval = $tags{$maint_tagname // ''};
+
+    reject "push is missing head ref update" unless defined $suite;
+    printdebug " updates ok.\n";
+}
+
+sub parsetag () {
+    printdebug " parsetag...\n";
+    open PT, ">dgit-tmp/plaintext" or die $!;
+    open DS, ">dgit-tmp/plaintext.asc" or die $!;
+    open T, "-|", qw(git cat-file tag), $tagval or die $!;
+    for (;;) {
+       $!=0; $_=<T>; defined or die $!;
+       print PT or die $!;
+       if (m/^(\S+) (.*)/) {
+           push @{ $tagh{$1} }, $2;
+       } elsif (!m/\S/) {
+           last;
+       } else {
+           die;
+       }
+    }
+    $!=0; $_=<T>; defined or die $!;
+    m/^($package_re) release (\S+) for \S+ \((\S+)\) \[dgit\]$/ or
+       reject "tag message not in expected format";
+
+    die unless $1 eq $package;
+    $version = $2;
+    die "$3 != $suite " unless $3 eq $suite;
+
+    my $copyl = $_;
+    for (;;) {
+       print PT $copyl or die $!;
+       $!=0; $_=<T>; defined or die "missing signature? $!";
+       $copyl = $_;
+       if (m/^\[dgit ([^"].*)\]$/) { # [dgit "something"] is for future
+           $_ = $1." ";
+           while (length) {
+               if (s/^distro\=(\S+) //) {
+                   die "$1 != $distro" unless $1 eq $distro;
+               } elsif (s/^(--deliberately-$deliberately_re) //) {
+                   push @deliberatelies, $1;
+               } elsif (s/^previously:(\S+)=(\w+) //) {
+                   die "previously $1 twice" if defined $previously{$1};
+                   $previously{$1} = $2;
+               } elsif (s/^[-+.=0-9a-z]\S* //) {
+               } else {
+                   die "unknown dgit info in tag ($_)";
+               }
+           }
+           next;
+       }
+       last if m/^-----BEGIN PGP/;
+    }
+    $_ = $copyl;
+    for (;;) {
+       print DS or die $!;
+       $!=0; $_=<T>;
+       last if !defined;
+    }
+    T->error and die $!;
+    close PT or die $!;
+    close DS or die $!;
+    printdebug " parsetag ok.\n";
+}
+
+sub checksig_keyring ($) {
+    my ($keyringfile) = @_;
+    # returns primary-keyid if signed by a key in this keyring
+    # or undef if not
+    # or dies on other errors
+
+    my $ok = undef;
+
+    printdebug " checksig keyring $keyringfile...\n";
+
+    our @cmd = (qw(gpgv --status-fd=1 --keyring),
+                  $keyringfile,
+                  qw(dgit-tmp/plaintext.asc dgit-tmp/plaintext));
+    debugcmd '|',@cmd;
+
+    open P, "-|", @cmd
+       or die $!;
+
+    while (<P>) {
+       next unless s/^\[GNUPG:\] //;
+       chomp or die;
+       printdebug " checksig| $_\n";
+       my @l = split / /, $_;
+       if ($l[0] eq 'NO_PUBKEY') {
+           last;
+       } elsif ($l[0] eq 'VALIDSIG') {
+           my $sigtype = $l[9];
+           $sigtype eq '00' or reject "signature is not of type 00!";
+           $ok = $l[10];
+           die unless defined $ok;
+           last;
+       }
+    }
+    close P;
+
+    printdebug sprintf " checksig ok=%d\n", !!$ok;
+
+    return $ok;
+}
+
+sub dm_txt_check ($$) {
+    my ($keyid, $dmtxtfn) = @_;
+    printdebug " dm_txt_check $keyid $dmtxtfn\n";
+    open DT, '<', $dmtxtfn or die "$dmtxtfn $!";
+    while (<DT>) {
+       m/^fingerprint:\s+\Q$keyid\E$/oi
+           ..0 or next;
+       if (s/^allow:/ /i..0) {
+       } else {
+           m/^./
+               or reject "key $keyid missing Allow section in permissions!";
+           next;
+       }
+       # in right stanza...
+       s/^[ \t]+//
+           or reject "package $package not allowed for key $keyid";
+       # in allow field...
+       s/\([^()]+\)//;
+       s/\,//;
+       chomp or die;
+       printdebug " dm_txt_check allow| $_\n";
+       foreach my $p (split /\s+/) {
+           if ($p eq $package) {
+               # yay!
+               printdebug " dm_txt_check ok\n";
+               return;
+           }
+       }
+    }
+    DT->error and die $!;
+    close DT or die $!;
+    reject "key $keyid not in permissions list although in keyring!";
+}
+
+sub verifytag () {
+    foreach my $kas (split /:/, $keyrings) {
+       printdebug "verifytag $kas...\n";
+       $kas =~ s/^([^,]+),// or die;
+       my $keyid = checksig_keyring $1;
+       if (defined $keyid) {
+           if ($kas =~ m/^a$/) {
+               printdebug "verifytag a ok\n";
+               return; # yay
+           } elsif ($kas =~ m/^m([^,]+)$/) {
+               dm_txt_check($keyid, $1);
+               printdebug "verifytag m ok\n";
+               return;
+           } else {
+               die;
+           }
+       }   
+    }
+    reject "key not found in keyrings";
+}
+
+sub suite_is_in ($) {
+    my ($sf) = @_;
+    printdebug "suite_is_in ($sf)\n";
+    if (!open SUITES, "<", $sf) {
+       $!==ENOENT or die $!;
+       return 0;
+    }
+    while (<SUITES>) {
+       chomp;
+       next unless m/\S/;
+       next if m/^\#/;
+       s/\s+$//;
+       return 1 if $_ eq $suite;
+    }
+    die $! if SUITES->error;
+    return 0;
+}
+
+sub checksuite () {
+    printdebug "checksuite ($suitesfile)\n";
+    return if suite_is_in $suitesfile;
+    reject "unknown suite";
+}
+
+sub checktagnoreplay () {
+    # We need to prevent a replay attack using an earlier signed tag.
+    # We also want to archive in the history the object ids of
+    # anything we remove, even if we get rid of the actual objects.
+    #
+    # So, we check that the signed tag mentions the name and tag
+    # object id of:
+    #
+    # (a) In the case of FRESHREPO: all tags and refs/heads/* in
+    #     the repo.  That is, effectively, all the things we are
+    #     deleting.
+    #
+    #     This prevents any tag implying a FRESHREPO push
+    #     being replayed into a different state of the repo.
+    #
+    #     There is still the folowing risk: If a non-ff push is of a
+    #     head which is an ancestor of a previous ff-only push, the
+    #     previous push can be replayed.
+    #
+    #     So we keep a separate list, as a file in the repo, of all
+    #     the tag object ids we have ever seen and removed.  Any such
+    #     tag object id will be rejected even for ff-only pushes.
+    #
+    # (b) In the case of just NOFFCHECK: all tags referring to the
+    #     current head for the suite (there must be at least one).
+    #
+    #     This prevents any tag implying a NOFFCHECK push being
+    #     replayed to rewind from a different head.
+    #
+    #     The possibility of an earlier ff-only push being replayed is
+    #     eliminated as follows: the tag from such a push would still
+    #     be in our repo, and therefore the replayed push would be
+    #     rejected because the set of refs being updated would be
+    #     wrong.
+
+    if (!open PREVIOUS, "<", removedtagsfile) {
+       die removedtagsfile." $!" unless $!==ENOENT;
+    } else {
+       # Protocol for updating this file is to append to it, not
+       # write-new-and-rename.  So all updates are prefixed with \n
+       # and suffixed with " .\n" so that partial writes can be
+       # ignored.
+       while (<PREVIOUS>) {
+           next unless m/^(\w+) (.*) \.\n/;
+           next unless $1 eq $tagval;
+           reject "Replay of previously-rewound upload ($tagval $2)";
+       }
+       die removedtagsfile." $!" if PREVIOUS->error;
+       close PREVIOUS;
+    }
+
+    return unless $policy & (FRESHREPO|NOFFCHECK);
+
+    my $garbagerepo = "$dgitrepos/${package}_garbage";
+    lockrealtree();
+
+    my $nchecked = 0;
+    my @problems;
+
+    my $check_ref_previously= sub {
+       my ($objid,$objtype,$fullrefname,$reftail) = @_;
+       my $supkey = $fullrefname;
+       $supkey =~ s{^refs/}{} or die "$supkey $objid ?";
+       my $supobjid = $previously{$supkey};
+       if (!defined $supobjid) {
+           printdebug "checktagnoreply - missing\n";
+           push @problems, "does not declare previously $supkey";
+       } elsif ($supobjid ne $objid) {
+           push @problems, "declared previously $supkey=$supobjid".
+               " but actually previously $supkey=$objid";
+       } else {
+           $nchecked++;
+       }
+    };
+
+    if ($policy & FRESHREPO) {
+       foreach my $kind (qw(tags heads)) {
+           git_for_each_ref("refs/$kind", $check_ref_previously);
+       }
+    } else {
+       my $branch= server_branch($suite);
+       my $branchhead= git_get_ref(server_ref($suite));
+       if (!length $branchhead) {
+           # No such branch - NOFFCHECK was unnecessary.  Oh well.
+           printdebug "checktagnoreplay - not FRESHREPO, new branch, ok\n";
+       } else {
+           printdebug "checktagnoreplay - not FRESHREPO,".
+               " checking for overwriting refs/$branch=$branchhead\n";
+           git_for_each_tag_referring($branchhead, sub {
+               my ($tagobjid,$refobjid,$fullrefname,$tagname) = @_;
+               $check_ref_previously->($tagobjid,undef,$fullrefname,undef);
+            });
+           printdebug "checktagnoreplay - not FRESHREPO, nchecked=$nchecked";
+           push @problems, "does not declare previously any tag".
+               " referring to branch head $branch=$branchhead"
+               unless $nchecked;
+       }
+    }
+
+    if (@problems) {
+       reject "replay attack prevention check failed:".
+           " signed tag for $version: ".
+           join("; ", @problems).
+           "\n";
+    }
+    printdebug "checktagnoreplay - all ok ($tagval)\n"
+}
+
+sub tagh1 ($) {
+    my ($tag) = @_;
+    my $vals = $tagh{$tag};
+    reject "missing header $tag in signed tag object" unless $vals;
+    reject "multiple headers $tag in signed tag object" unless @$vals == 1;
+    return $vals->[0];
+}
+
+sub checks () {
+    printdebug "checks\n";
+
+    tagh1('type') eq 'commit' or reject "tag refers to wrong kind of object";
+    tagh1('object') eq $commit or reject "tag refers to wrong commit";
+    tagh1('tag') eq $tagname or reject "tag name in tag is wrong";
+
+    my @expecttagnames = debiantags($version, $distro);
+    printdebug "expected tag @expecttagnames\n";
+    grep { $tagname eq $_ } @expecttagnames or die;
+
+    foreach my $othertag (grep { $_ ne $tagname } @expecttagnames) {
+       reject "tag $othertag (pushed with differing dgit version)".
+           " already exists -".
+           " not replacing previously-pushed version"
+           if git_get_ref "refs/tags/".$othertag;
+    }
+
+    lockrealtree();
+
+    @policy_args = ($package,$version,$suite,$tagname,
+                   join(",",@deliberatelies));
+    $policy = policyhook(NOFFCHECK|FRESHREPO, 'push', @policy_args);
+
+    if (defined $tagexists_error) {
+       if ($policy & FRESHREPO) {
+           printdebug "ignoring tagexists_error: $tagexists_error\n";
+       } else {
+           reject $tagexists_error;
+       }
+    }
+
+    checktagnoreplay();
+    checksuite();
+
+    # check that our ref is being fast-forwarded
+    printdebug "oldcommit $oldcommit\n";
+    if (!($policy & NOFFCHECK) && $oldcommit =~ m/[^0]/) {
+       $?=0; $!=0; my $mb = `git merge-base $commit $oldcommit`;
+       chomp $mb;
+       $mb eq $oldcommit or reject "not fast forward on dgit branch";
+    }
+
+    # defend against commits generated by #849041
+    if (!($policy & NOCOMMITCHECK)) {
+       my @checks = qw(%an %ae %at
+                       %cn %ce %ct);
+       my @chk = qw(git log -z);
+       push @chk, '--pretty=tformat:%H%n'.
+           (join "", map { $_, '%n' } @checks);
+       push @chk, "^$oldcommit" if $oldcommit =~ m/[^0]/;
+       push @chk, $commit;;
+       printdebug " ~NOCOMMITCHECK @chk\n";
+       open CHK, "-|", @chk or die $!;
+       local $/ = "\0";
+       while (<CHK>) {
+           next unless m/^$/m;
+           m/^\w+(?=\n)/ or die;
+           reject "corrupted object $& (missing metadata)";
+       }
+       $!=0; $?=0; close CHK or $?==256 or die "$? $!";
+    }
+
+    if ($policy & FRESHREPO) {
+       # It's a bit late to be discovering this here, isn't it ?
+       #
+       # What we do is: Generate a fresh destination repo right now,
+       # and arrange to treat it from now on as if it were a
+       # prospective repo.
+       #
+       # The presence of this fresh destination repo is detected by
+       # the parent, which responds by making a fresh master repo
+       # from the template.  (If the repo didn't already exist then
+       # $destrepo was _prospective, and we change it here.  This is
+       # OK because the parent's check for _fresh persuades it not to
+       # use _prospective.)
+       #
+       $destrepo = "${workrepo}_fresh"; # workrepo lock covers
+       mkrepo_fromtemplate $destrepo;
+    }
+}
+
+sub onwardpush () {
+    my @cmdbase = (qw(git send-pack), $destrepo);
+    push @cmdbase, qw(--force) if $policy & NOFFCHECK;
+
+    my @cmd = @cmdbase;
+    push @cmd, "$commit:refs/dgit/$suite",
+              "$tagval:refs/tags/$tagname";
+    push @cmd, "$maint_tagval:refs/tags/$maint_tagname"
+       if defined $maint_tagname;
+    debugcmd '+',@cmd;
+    $!=0;
+    my $r = system @cmd;
+    !$r or die "onward push to $destrepo failed: $r $!";
+
+    if (suite_is_in $suitesformasterfile) {
+       @cmd = @cmdbase;
+       push @cmd, "$commit:refs/heads/master";
+       debugcmd '+', @cmd;
+       $!=0; my $r = system @cmd;
+       # tolerate errors (might be not ff)
+       !($r & ~0xff00) or die
+           "onward push to $destrepo#master failed: $r $!";
+    }
+}
+
+sub finalisepush () {
+    if ($destrepo eq realdestrepo) {
+       policyhook(0, 'push-confirm', @policy_args, '');
+       onwardpush();
+    } else {
+       # We are to receive the push into a new repo (perhaps
+       # because the policy push hook asked us to with FRESHREPO, or
+       # perhaps because the repo didn't exist before).
+       #
+       # We want to provide the policy push-confirm hook with a repo
+       # which looks like the one which is going to be installed.
+       # The working repo is no good because it might contain
+       # previous history.
+       #
+       # So we push the objects into the prospective new repo right
+       # away.  If the hook declines, we decline, and the prospective
+       # repo is never installed.
+       onwardpush();
+       policyhook(0, 'push-confirm', @policy_args, $destrepo);
+    }
+}
+
+sub stunthook () {
+    printdebug "stunthook in $workrepo\n";
+    chdir $workrepo or die "chdir $workrepo: $!";
+    mkdir "dgit-tmp" or $!==EEXIST or die $!;
+    readupdates();
+    parsetag();
+    verifytag();
+    checks();
+    finalisepush();
+    printdebug "stunthook done.\n";
+}
+
+#----- git-upload-pack -----
+
+sub fixmissing__git_upload_pack () {
+    $destrepo = "$dgitrepos/_empty";
+    my $lfh = locksometree($destrepo);
+    return if stat_exists $destrepo;
+    rmtree "$destrepo.new";
+    mkemptyrepo "$destrepo.new", "0644";
+    rename "$destrepo.new", $destrepo or die $!;
+    unlink "$destrepo.lock" or die $!;
+    close $lfh;
+}
+
+sub main__git_upload_pack () {
+    my $lfh = locksometree($destrepo);
+    printdebug "git-upload-pack in $destrepo\n";
+    chdir $destrepo or die "$destrepo: $!";
+    close $lfh;
+    runcmd qw(git upload-pack), ".";
+}
+
+#----- arg parsing and main program -----
+
+sub argval () {
+    die unless @ARGV;
+    my $v = shift @ARGV;
+    die if $v =~ m/^-/;
+    return $v;
+}
+
+our %indistrodir = (
+    # keys are used for DGIT_DRS_XXX too
+    'repos' => \$dgitrepos,
+    'suites' => \$suitesfile,
+    'suites-master' => \$suitesformasterfile,
+    'policy-hook' => \$policyhook,
+    'mirror-hook' => \$mirrorhook,
+    'dgit-live' => \$dgitlive,
+    );
+
+our @hookenvs = qw(distro suitesfile suitesformasterfile policyhook
+                   mirrorhook dgitlive keyrings dgitrepos distrodir);
+
+# workrepo and destrepo handled ad-hoc
+
+sub mode_ssh () {
+    die if @ARGV;
+
+    my $cmd = $ENV{'SSH_ORIGINAL_COMMAND'};
+    $cmd =~ m{
+       ^
+       (?: \S* / )?
+       ( [-0-9a-z]+ )
+       \s+
+       '? (?: \S* / )?
+       ($package_re) \.git
+       '?$
+    }ox 
+    or reject "command string not understood";
+    my $method = $1;
+    $package = $2;
+
+    my $funcn = $method;
+    $funcn =~ y/-/_/;
+    my $mainfunc = $main::{"main__$funcn"};
+
+    reject "unknown method" unless $mainfunc;
+
+    policy_checkpackage();
+
+    if (stat_exists realdestrepo) {
+       $destrepo = realdestrepo;
+    } else {
+       printdebug " fixmissing $funcn\n";
+       my $fixfunc = $main::{"fixmissing__$funcn"};
+       &$fixfunc;
+    }
+
+    printdebug " running main $funcn\n";
+    &$mainfunc;
+}
+
+sub mode_cron () {
+    die if @ARGV;
+
+    my $listfh = tempfile();
+    open STDOUT, ">&", $listfh or die $!;
+    policyhook(0,'check-list');
+    open STDOUT, ">&STDERR" or die $!;
+
+    seek $listfh, 0, 0 or die $!;
+    while (<$listfh>) {
+       chomp or die;
+       next if m/^\s*\#/;
+       next unless m/\S/;
+       die unless m/^($package_re)$/;
+       
+       $package = $1;
+       policy_checkpackage();
+    }
+    die $! if $listfh->error;
+}    
+
+sub parseargsdispatch () {
+    die unless @ARGV;
+
+    delete $ENV{'GIT_DIR'}; # if not run via ssh, our parent git process
+    delete $ENV{'GIT_PREFIX'}; # sets these and they mess things up
+
+    if ($ENV{'DGIT_DRS_DEBUG'}) {
+       enabledebug();
+    }
+
+    if ($ARGV[0] eq '--pre-receive-hook') {
+       if ($debuglevel) {
+           $debugprefix.="=";
+           printdebug "in stunthook ".(shellquote @ARGV)."\n";
+           foreach my $k (sort keys %ENV) {
+               printdebug "$k=$ENV{$k}\n" if $k =~  m/^DGIT/;
+           }
+       }
+       shift @ARGV;
+       @ARGV == 1 or die;
+       $package = shift @ARGV;
+       ${ $main::{$_} } = $ENV{"DGIT_DRS_\U$_"} foreach @hookenvs;
+       defined($workrepo = $ENV{'DGIT_DRS_WORK'}) or die;
+       defined($destrepo = $ENV{'DGIT_DRS_DEST'}) or die;
+       open STDOUT, ">&STDERR" or die $!;
+       eval {
+           stunthook();
+       };
+       if ($@) {
+           recorderror "$@" or die;
+           die $@;
+       }
+       exit 0;
+    }
+
+    $distro    = argval();
+    $distrodir = argval();
+    $keyrings  = argval();
+
+    foreach my $dk (keys %indistrodir) {
+       ${ $indistrodir{$dk} } = "$distrodir/$dk";
+    }
+
+    while (@ARGV && $ARGV[0] =~ m/^--([-0-9a-z]+)=/ && $indistrodir{$1}) {
+       ${ $indistrodir{$1} } = $'; #';
+       shift @ARGV;
+    }
+
+    $ENV{"DGIT_DRS_\U$_"} = ${ $main::{$_} } foreach @hookenvs;
+
+    die unless @ARGV==1;
+
+    my $mode = shift @ARGV;
+    die unless $mode =~ m/^--(\w+)$/;
+    my $fn = ${*::}{"mode_$1"};
+    die unless $fn;
+    $fn->();
+}
+
+sub unlockall () {
+    while (my $fh = pop @lockfhs) { close $fh; }
+}
+
+sub cleanup () {
+    unlockall();
+    if (!chdir "$dgitrepos/_tmp") {
+       $!==ENOENT or die $!;
+       return;
+    }
+    foreach my $lf (<*.lock>) {
+       my $tree = $lf;
+       $tree =~ s/\.lock$//;
+       next unless acquirermtree($tree, 0);
+       remove $lf or warn $!;
+       unlockall();
+    }
+}
+
+parseargsdispatch();
+cleanup();
diff --git a/infra/dgit-ssh-dispatch b/infra/dgit-ssh-dispatch
new file mode 100755 (executable)
index 0000000..c5861d2
--- /dev/null
@@ -0,0 +1,181 @@
+#!/usr/bin/perl -w
+# wrapper to dispatch git ssh service requests
+#
+# Copyright (C) 2015-2016  Ian Jackson
+#
+#    This program is free software; you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation; either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+use strict;
+
+use Debian::Dgit::Infra; # must precede Debian::Dgit; - can change @INC!
+use Debian::Dgit;
+setup_sigwarn();
+
+use POSIX;
+
+open DEBUG, '>/dev/null' or die $!;
+if (@ARGV && $ARGV[0] eq '-D') {
+    shift @ARGV;
+    open DEBUG, '>&STDERR' or die $!;
+}
+
+die unless @ARGV>=1 && @ARGV<=2 && $ARGV[0] !~ m/^-/;
+our ($dispatchdir,$authrune) = @ARGV;
+
+$authrune //= join ':',
+    '@/keyrings/debian-keyring.gpg,a',
+    '@/keyrings/debian-maintainers.gpg,m@/dm.txt';
+
+our $lre = $package_re;
+our $qre = '["'."']?";
+
+# $dispatchdir/distro=DISTRO should contain
+#    dgit-live          a clone of dgit (only if not using installed vsns)
+#    diverts
+#    repos/             }  by virtue of
+#    suites             }    dgit-repos-server's defaults relating to
+#    policy-hook        }    dispatch-dir
+# plus files required by the authrune (by default, keyrings/ and dm.txt)
+#
+# diverts should be list of
+#  <pat> [<divert-to>]
+# where <pat> is a package name pattern which may contain * or literals.
+# <divert-to> is for `git config dgit-distro.DISTRO.diverts.<divert-to>'
+
+our ($distro,$pkg, $d);
+our ($dgitlive,$repos,$suites,$diverts,$policyhook,$repo);
+
+sub checkdivert ($) {
+    my ($df) = @_;
+    if (!open DIV, '<', $df) {
+       $!==ENOENT or die $!;
+       return undef;
+    } else {
+       while (<DIV>) {
+           s/^\s+//; s/\s+$//;
+           next unless m/\S/;
+           next if m/^\#/;
+           my $divert;
+           if (s/\s+(\S+)$//) { $divert=$1; }
+           s/[^-+._0-9a-zA-Z*]/\\$&/g;
+           s/\*/.*/g;
+           printf DEBUG 'DISPATCH DIVERT ^%s$ %s'."\n",
+               $_, ($divert // '(undef)');
+           if ($pkg =~ m/^$_$/) { return $divert; }
+       }
+       DIV->error and die $!;
+       close DIV;
+       return undef;
+    }
+}
+       
+sub finish () {
+    close STDOUT or die $!;
+    exit 0;
+}
+
+sub prl ($) {
+    print @_, "\n" or die $!;
+}
+       
+sub selectpackage ($$;$) {
+    my $divertfn;
+    ($distro,$pkg, $divertfn) = @_; # $distro,$pkg must have sane syntax
+
+    $d = "$dispatchdir/distro=$distro";
+
+    if (!stat $d) {
+       die $! unless $!==ENOENT;
+       die "unknown distro ($distro)\n";
+    }
+
+    $dgitlive=    "$d/dgit-live";
+    $repos=       "$d/repos";
+    $suites=      "$d/suites";
+    $policyhook=  "$d/policy-hook";
+
+    $authrune =~ s/\@/$d/g;
+
+    my $divert = checkdivert("$d/diverts");
+    if (defined $divert) {
+       $divertfn //= sub {
+           die "diverted to $divert incompletely or too late!\n";
+       };
+       $divertfn->($divert);
+       die;
+    }
+
+    $repo = "$repos/$pkg.git";
+
+    print DEBUG "DISPATCH DISTRO $distro PKG $pkg\n";
+}
+
+sub hasrepo () {
+    if (stat $repo) {
+       -d _ or die;
+       return 1;
+    } else {
+       $!==ENOENT or die $!;
+       return 0;
+    }
+}
+
+sub serve_up ($) {
+    my ($repo) = @_;
+    exec qw(git upload-pack --strict --timeout=1000), $repo;
+    die "exec git: $!";
+}
+
+sub dispatch () {
+    local ($_) = $ENV{'SSH_ORIGINAL_COMMAND'} // '';
+
+    if (m#^: dgit ($lre) git-check ($lre) ;#) {
+       selectpackage $1,$2, sub { prl "divert @_"; finish; };
+       prl hasrepo;
+       finish;
+    } elsif (
+       m#^${qre}git-([-a-z]+) ${qre}/dgit/($lre)/repos/($lre)\.git${qre}$#
+       ) {
+       my $cmd=$1;
+       selectpackage $2,$3;
+       if ($cmd eq 'receive-pack') {
+           $ENV{'PERLLIB'} //= '';
+           $ENV{'PERLLIB'} =~ s#^(?=.)#:#;
+           $ENV{'PERLLIB'} =~ s#^# $ENV{DGIT_TEST_INTREE} // $dgitlive #e;
+           my $s = "$dgitlive/infra/dgit-repos-server";
+           $s = "dgit-repos-server" if !stat_exists $s;
+           exec $s, $distro, $d, $authrune, qw(--ssh);
+           die "exec $s: $!";
+       } elsif ($cmd eq 'upload-pack') {
+           $repo='$repos/_empty' unless hasrepo;
+           serve_up $repo;
+       } else {
+           die "unsupported git operation $cmd ($_)";
+       }
+    } elsif (
+ m#^${qre}git-upload-pack ${qre}/dgit/($lre)/(?:repos/)?_dgit-repos-server\.git${qre}$#
+       ) {
+       my $distro= $1;
+       # if running installed packages, source code should come
+       # some other way
+       serve_up("$dispatchdir/distro=$1/dgit-live/.git");
+    } elsif (m#^${qre}git-upload-pack\s#) {
+       die "unknown repo to serve ($_).  use dgit, or for server source ".
+           "git clone here:/dgit/DISTRO/repos/_dgit-repos-server.git";
+    } else {
+       die "unsupported operation ($_)";
+    }
+}
+
+dispatch;
diff --git a/infra/drs-cron-wrap b/infra/drs-cron-wrap
new file mode 100755 (executable)
index 0000000..52e819b
--- /dev/null
@@ -0,0 +1,14 @@
+#!/bin/sh
+set -e
+umask 002
+
+distro=$1; shift
+
+srvdir=/srv/dgit.debian.org
+dispatchdir=$srvdir/dispatch-dir
+dgitlive=$srvdir/dgit-live
+
+distrodir=$dispatchdir/distro=$distro
+
+PERLLIB="$dgitlive${PERLLIB+:}${PERLLIB}" \
+exec $dgitlive/infra/dgit-repos-server $distro $distrodir '' --cron
diff --git a/infra/get-dm-txt b/infra/get-dm-txt
new file mode 100755 (executable)
index 0000000..0b9ab10
--- /dev/null
@@ -0,0 +1,21 @@
+#!/bin/sh
+set -e
+
+cd ${DGIT_INFRA_GETDMTXT_DATADIR-/srv/dgit.debian.org/data}
+${DGIT_INFRA_GETDMTXT_UMASK-umask 002}
+
+file=dm.txt
+server=ftp-master.debian.org
+path=$file
+
+certargs=$(git config dgit-distro.debian.archive-query-tls-curl-ca-args \
+       || (echo >&2 "git config failed"; exit 1))
+
+with-lock-ex -f $file.lock sh -c "
+       if ! curl $certargs \
+               >$file.new https://$server/$path 2>$file.stderr; then
+               cat $file.stderr >&2
+               exit 127
+       fi
+       mv -f $file.new $file
+"
diff --git a/infra/get-suites b/infra/get-suites
new file mode 100755 (executable)
index 0000000..c5a4c56
--- /dev/null
@@ -0,0 +1,26 @@
+#!/bin/bash
+set -e
+set -o pipefail
+
+srvdir=/srv/dgit.debian.org
+dgitlive=${DGIT_TEST_INTREE-$srvdir/dgit-live}
+output=${DGIT_GETSUITES_OUTPUT-$srvdir/data/suites}
+
+export PERLLIB="$dgitlive${PERLLIB+:}${PERLLIB}"
+
+$dgitlive/dgit archive-api-query /suites | perl -we '
+       use strict;
+       use JSON;
+       undef $/;
+       my $json = <STDIN>;
+       die $! if STDIN->error;
+       my $items = decode_json $json;
+       foreach my $item (@$items) {
+               next unless ($item->{archive}//"") eq "ftp-master";
+               next unless ($item->{codename});
+               print $item->{codename}, "\n" or die $!;
+       }
+       flush STDOUT or die $!;
+' >$output.new
+
+mv -f $output.new $output
diff --git a/infra/ssh-wrap b/infra/ssh-wrap
new file mode 100755 (executable)
index 0000000..deccc38
--- /dev/null
@@ -0,0 +1,10 @@
+#!/bin/sh
+set -e
+umask 002
+
+srvdir=/srv/dgit.debian.org
+dispatchdir=$srvdir/dispatch-dir
+dgitlive=$srvdir/dgit-live
+
+PERLLIB="$dgitlive${PERLLIB+:}${PERLLIB}" \
+exec $dgitlive/infra/dgit-ssh-dispatch $dispatchdir
diff --git a/local-pod-man b/local-pod-man
new file mode 100755 (executable)
index 0000000..3c3e0ea
--- /dev/null
@@ -0,0 +1,13 @@
+#!/bin/bash
+set -e
+
+case "$#.$1" in
+1.[^-]*) ;;
+*) echo >&2 'usage: ./local-pod-man dgit-something[.7[.pod]]'; exit 16;;
+esac
+base="$1"
+base="${base%.pod}"
+base="${base%.7}"
+
+make "$base.7"
+man -l "$base.7"
diff --git a/tests/Makefile b/tests/Makefile
new file mode 100644 (file)
index 0000000..5bd3eee
--- /dev/null
@@ -0,0 +1,14 @@
+# usage: tests/using-intree make -f tests/Makefile
+# (optionally setting TESTSCRIPTS='tests/tests/foo tests/tests/bar')
+
+TESTSCRIPTS ?= $(shell tests/enumerate-tests)
+TESTNAMES := $(notdir $(TESTSCRIPTS))
+
+all: $(foreach t,$(TESTNAMES),tests/tmp/$t.ok)
+       @echo "ALL PASSED"
+
+tests/tmp:
+       mkdir -p $@
+
+tests/tmp/%.ok: tests/tmp
+       tests/tests/$* >tests/tmp/$*.log 2>&1
diff --git a/tests/adhoc b/tests/adhoc
new file mode 100755 (executable)
index 0000000..b45251f
--- /dev/null
@@ -0,0 +1,51 @@
+#!/bin/bash
+#
+# usage:
+#  after tests/tests/some-test has been run (and maybe failed)
+#   cd tests/tmp/some-test/blah
+#   ../../adhoc ../../../dgit some options
+# or
+#  after tests/tests/some-test has been run (and maybe failed)
+#   cd tests/tmp/some-test/blah
+#   ../../adhoc some rune run by some piece of infrastructure
+#
+# effects:
+#   directly sets the env variables which arrange to use
+#   programs etc. from the working tree
+
+ourname=adhoc
+
+case $0 in
+*/$ourname)
+       : ${DGIT_TEST_INTREE:=$(realpath "${0%/$ourname}/..")}
+       ;;
+*)
+       echo >&2 "$ourname must be invoked as .../$ourname not $0"
+       exit 127
+       ;;
+esac
+
+export DGIT_TEST_INTREE
+
+. $DGIT_TEST_INTREE/tests/lib-core
+
+t-set-intree
+
+pwd=$(realpath "$(pwd)")
+basis=$DGIT_TEST_INTREE/tests/tmp
+case "$pwd" in
+"$basis" | "$basis"/*)
+       testname=${pwd/"$basis/"/}
+       testname=${testname%%/*}
+       tmp="$basis/$testname"
+       ;;
+*)
+       fail "$ourname pwd must be inside some test (in $basis), not $pwd"
+       ;;
+esac
+
+export ADTTMP=$tmp
+
+t-set-using-tmp
+
+exec "$@"
diff --git a/tests/drs-git-ext b/tests/drs-git-ext
new file mode 100755 (executable)
index 0000000..ad27c9b
--- /dev/null
@@ -0,0 +1,15 @@
+#!/bin/sh
+set -e
+tmp=$DGIT_TEST_TMP
+
+: ${DGIT_DRS_DEBUG:=1}
+export DGIT_DRS_DEBUG
+echo >&2 '(((((((((((((((((((((((((((((((((((((((('
+set -x
+export SSH_ORIGINAL_COMMAND="$*"
+${DGIT_REPOS_SERVER_TEST-dgit-repos-server} \
+       test-dummy $tmp/distro=test-dummy \
+       $tmp/dd.gpg,a:$tmp/dm.gpg,m$tmp/dm.txt \
+       --repos=$tmp/git --suites=$tmp/suites \
+       --ssh
+: '))))))))))))))))))))))))))))))))))))))))'
diff --git a/tests/dsd-ssh b/tests/dsd-ssh
new file mode 100755 (executable)
index 0000000..d5df5aa
--- /dev/null
@@ -0,0 +1,18 @@
+#!/bin/sh
+set -e
+
+echo >&2 '(((((((((((((((((((((((((((((((((((((((('
+set -x
+
+tmp=$DGIT_TEST_TMP
+cd /
+userhost="$1"; shift
+export SSH_ORIGINAL_COMMAND="$*"
+
+# undoes PERLLIB so that we rely on dgit-ssh-dispatch setting it
+# we have to compensate with -I so that dgit-ssh-dispatch finds Dgit.pm
+unset PERLLIB
+${DGIT_TEST_INTREE+perl -I}$DGIT_TEST_INTREE \
+${DGIT_SSH_DISPATCH_TEST-dgit-ssh-dispatch} -D $tmp
+
+: '))))))))))))))))))))))))))))))))))))))))'
diff --git a/tests/enumerate-tests b/tests/enumerate-tests
new file mode 100755 (executable)
index 0000000..2c00f97
--- /dev/null
@@ -0,0 +1,109 @@
+#!/bin/bash
+
+set -e
+
+. tests/lib-core
+. tests/lib-restricts
+
+mode=$1
+
+test-begin- () {
+       whynots=''
+}
+
+restriction- () {
+       set +e
+       whynot=$(t-restriction-$r)
+       rc=$?
+       whynot="${whynot//
+/ / }"
+       set -e
+       case "$rc.$whynot" in
+       0.)     ;;
+       1.?*)   whynots="$whynots${whynots:+; }$whynot" ;;
+       *)      fail "restriction $r for $t gave $rc $whynot !"
+       esac
+}
+
+dependencies- () {
+       :
+}
+
+test-done- () {
+       case "$whynots" in
+       '')     echo $t ;;
+       ?*)     echo >&2 "SKIP $t $whynots" ;;
+       esac
+}
+
+finish- () {
+       :
+}
+
+test-begin-gencontrol () {
+       restrictions=''
+       dependencies=''
+}
+
+restriction-gencontrol () {
+       restrictions+=" $r"
+}
+
+dependencies-gencontrol () {
+       dependencies+=", $deps"
+}
+
+test-done-gencontrol () {
+       stanza=$(
+               add_Depends="$dependencies" \
+               perl <debian/tests/control.in -wpe '
+                       if (/^(\w+):/) {
+                               my $h = $1;
+                               s{$}{ $ENV{"add_$h"} // "" }e;
+                       }
+               ' 
+               case "$restrictions" in
+               ?*) echo "Restrictions:$restrictions" ;;
+               esac
+               )
+       key=$(printf "%s" "$stanza" | sha256sum)
+       key=${key%% *}
+       eval "
+               stanza_$key=\"\$stanza\"
+               tests_$key+=\" \${t#tests/tests/}\"
+       "
+       keys=" ${keys/ $key /}"
+       keys+=" $key "
+}
+
+finish-gencontrol () {
+       for key in $keys; do
+               eval "
+                       stanza=\$stanza_$key
+                       tests=\$tests_$key
+               "
+               printf "Tests:%s\n%s\n\n" "$tests" "$stanza"
+       done
+}
+
+seddery () {
+       local seddery=$1
+       sed <$t -n '
+               20q;
+               /^: t-enumerate-tests-end$/q;
+               '"$seddery"'
+       '
+}
+
+for t in $(run-parts --list tests/tests); do
+       test-begin-$mode
+       for r in $(seddery 's/^t-restrict //p'); do
+               restriction-$mode
+       done
+       for deps in $(seddery 's/^t-dependencies //p'); do
+               dependencies-$mode
+       done
+       test-done-$mode
+done
+
+finish-$mode
diff --git a/tests/git-srcs/pari-extra_3-1.git.tar b/tests/git-srcs/pari-extra_3-1.git.tar
new file mode 100644 (file)
index 0000000..1673c72
Binary files /dev/null and b/tests/git-srcs/pari-extra_3-1.git.tar differ
diff --git a/tests/git-template.tar b/tests/git-template.tar
new file mode 100644 (file)
index 0000000..030bb8a
Binary files /dev/null and b/tests/git-template.tar differ
diff --git a/tests/gnupg/dd.gpg b/tests/gnupg/dd.gpg
new file mode 100644 (file)
index 0000000..bc16981
Binary files /dev/null and b/tests/gnupg/dd.gpg differ
diff --git a/tests/gnupg/dm.gpg b/tests/gnupg/dm.gpg
new file mode 100644 (file)
index 0000000..d58af9d
Binary files /dev/null and b/tests/gnupg/dm.gpg differ
diff --git a/tests/gnupg/dm.txt b/tests/gnupg/dm.txt
new file mode 100644 (file)
index 0000000..da257fe
--- /dev/null
@@ -0,0 +1,1800 @@
+Fingerprint: A7830CCABA4AFF02E50213FE8F32B4422F52107F
+Uid: Adrian Knoth <adi@drcomp.erfurt.thur.de>
+Allow: a2jmidid (A62D2CFBD50B9B5BF360D54B159EB5C4EFC8774C),
+ ardour (A62D2CFBD50B9B5BF360D54B159EB5C4EFC8774C),
+ ardour3 (218EE0362033C87B6C135FA4A3BABAE2408DD6CF),
+ calf (A62D2CFBD50B9B5BF360D54B159EB5C4EFC8774C),
+ jack-audio-connection-kit (A62D2CFBD50B9B5BF360D54B159EB5C4EFC8774C),
+ jackd-defaults (A62D2CFBD50B9B5BF360D54B159EB5C4EFC8774C),
+ jackd2 (A62D2CFBD50B9B5BF360D54B159EB5C4EFC8774C),
+ kmidimon (A62D2CFBD50B9B5BF360D54B159EB5C4EFC8774C),
+ libdrumstick (A62D2CFBD50B9B5BF360D54B159EB5C4EFC8774C),
+ libffado (A62D2CFBD50B9B5BF360D54B159EB5C4EFC8774C),
+ midisport-firmware (A62D2CFBD50B9B5BF360D54B159EB5C4EFC8774C),
+ qjackctl (A62D2CFBD50B9B5BF360D54B159EB5C4EFC8774C),
+ zita-at1 (04160004A8276E40BB9890FBE8A48AE5311D765A)
+
+Fingerprint: 834C97925D15D2A573C2478777013C58233007A7
+Uid: Ahmed Toulan <thelinuxer@ubuntu.com>
+Allow: gdigi (9F73032EEAC9F7AD951F280ECB668E29A3FD0DF7)
+
+Fingerprint: 487FCD050895105F43C206089B2E6B82752DB03B
+Uid: Alberto Luaces Fernández <aluaces@udc.es>
+Allow: openscenegraph (2A8E80505C486298430DD0937F7606A445DCA80E)
+
+Fingerprint: 2875F6B1C2D27A4F0C8AF60B2A714497E37363AE
+Uid: Aleksey Kravchenko <rhash.admin@gmail.com>
+Allow: rhash (75FFFC9F717B526296A20609BDD933B785FEC17F)
+
+Fingerprint: 3224C4469D7DF8F3D6F41A02BBC756DDBE595F6B
+Uid: Alexander Chernyakhovsky <achernya@mit.edu>
+Allow: byobu (51892A7D16D049BB046BDC7797325DD8F9FDD506),
+ config-package-dev (51892A7D16D049BB046BDC7797325DD8F9FDD506),
+ hesiod (198F5EAE4F00F3D1E9A7BC50B1CA92E8A7D86B95)
+
+Fingerprint: 085C34EAC00FC1E6615982C13C13A8C2D9012EC1
+Uid: Alexander Golovko <alexandro@ankalagon.ru>
+Allow: bacula (C331BA3F75FB723B5873785B06EAA066E397832F),
+ bacula-doc (C331BA3F75FB723B5873785B06EAA066E397832F)
+
+Fingerprint: B1A51EB2779DD01743CC19BA1CF792111B5228B0
+Uid: Alexandre Mestiashvili <alex@biotec.tu-dresden.de>
+Allow: bowtie2 (84C1977A89E7EFED3E5CA62E2FD8BEDAC020EED1),
+ cufflinks (84C1977A89E7EFED3E5CA62E2FD8BEDAC020EED1),
+ libdancer-session-cookie-perl (84C1977A89E7EFED3E5CA62E2FD8BEDAC020EED1),
+ libpam-abl (84C1977A89E7EFED3E5CA62E2FD8BEDAC020EED1),
+ pycorrfit (6D2EB3CAF6BCD06EEF42247F60305B31C09FD35A),
+ seq-gen (84C1977A89E7EFED3E5CA62E2FD8BEDAC020EED1),
+ tophat (84C1977A89E7EFED3E5CA62E2FD8BEDAC020EED1),
+ transtermhp (84C1977A89E7EFED3E5CA62E2FD8BEDAC020EED1)
+
+Fingerprint: 800A1489C2F186F57C3D41C734705168FE9F0236
+Uid: Alexandre Raymond <alexandre.j.raymond@gmail.com>
+Allow: password-gorilla (710F265D7B816542F23CE054D4B7DDF7B16CCA95),
+ restartd (710F265D7B816542F23CE054D4B7DDF7B16CCA95)
+
+Fingerprint: 5B76E76B4AAD389A76F9BCF99688FFC1C78102DF
+Uid: Allison Randal <allison@lohutok.net>
+Allow: parrot (A4F455C3414B10563FCC9244AFA51BD6CDE573CB)
+
+Fingerprint: 7FEB617A87114E1BDE8C89C92713E679084651AF
+Uid: Andrea Colangelo <warp10@ubuntu.com>
+Allow: fortune-mod (8F049AD82C92066C7352D28A7B585B30807C2A87),
+ key-mon (66B4DFB68CB24EBBD8650BC4F4B4B0CC797EBFAB),
+ tennix (DDC53E5130FD08223F98945649086AD3EBE2F31F)
+
+Fingerprint: 0BCA751BEEB81FBC7661BEA5C412AF7E994376FD
+Uid: Andreas Hildebrandt <anhi@bioinf.uni-sb.de>
+Allow: ball (84C1977A89E7EFED3E5CA62E2FD8BEDAC020EED1)
+
+Fingerprint: 1662F3FE0EF53DE5E6CE0031B446EEA8329A945A
+Uid: Andreas Noteng <andreas@noteng.no>
+Allow: checkinstall (218EE0362033C87B6C135FA4A3BABAE2408DD6CF),
+ transgui (374FF2AD0A12935FD0B0C84F1B132E01CEC6AD46)
+
+Fingerprint: DB306E4B10FFD98EF4DB55D7194B631AB2DA2888
+Uid: Andreas Rönnquist <gusnan@gusnan.se>
+Allow: devilspie2 (B3131A451DBFDF7CA05B4197054BBB9F7D806442),
+ sciteproj (B3131A451DBFDF7CA05B4197054BBB9F7D806442)
+
+Fingerprint: 34CA12A3C6F8B15672C2D0D7D286CE0C0C62B791
+Uid: Andrew Ruthven <andrew@etc.gen.nz>
+Allow: mythtv-status (8C470B2A0B31568E110D432516281F2E007C98D1)
+
+Fingerprint: 3E02FD6656295952110BAB99F51B18C720248224
+Uid: Apollon Oikonomopoulos <apoikos@dmesg.gr>
+Allow: beanstalkc (A9592C521CB904077D6598009D0B5E5B1EEC8F0E),
+ beanstalkd (A9592C521CB904077D6598009D0B5E5B1EEC8F0E),
+ ganeti (4C951CEC98B44B68A286FF458489B14D8807529B),
+ ioping (A9592C521CB904077D6598009D0B5E5B1EEC8F0E),
+ python-hiredis (A9592C521CB904077D6598009D0B5E5B1EEC8F0E),
+ redsocks (A9592C521CB904077D6598009D0B5E5B1EEC8F0E),
+ ruby-hiredis (A9592C521CB904077D6598009D0B5E5B1EEC8F0E),
+ xmobar (A9592C521CB904077D6598009D0B5E5B1EEC8F0E)
+
+Fingerprint: C1FABEE40A628709CCFB7D2C964D005C0CA7686C
+Uid: Artur R. Czechowski <arturcz@hell.pl>
+Allow: imms (BBBD45EA818AB86FF67E7285D3E17383CFA7FF06),
+ lwatch (BBBD45EA818AB86FF67E7285D3E17383CFA7FF06),
+ rrdcollect (BBBD45EA818AB86FF67E7285D3E17383CFA7FF06)
+
+Fingerprint: AF031CB8DFFB7DC5E1EEEB04A7C9FF063F3D2E03
+Uid: Axel Wagner <axel@mathphys.fsk.uni-heidelberg.de>
+Allow: shellex (424E14D703E7C6D43D9D6F364E7160ED4AC8EE1D)
+
+Fingerprint: D6E01EC516A5DFCEF71956D3775079E5B850BC93
+Uid: Bernhard Schmidt <berni@birkenwald.de>
+Allow: torrus (A3C282024F979BAD6ED484D18196A5446BBA3C84)
+
+Fingerprint: EC9F905D866DBE46A896C827BE0C924203F4552D
+Uid: ChangZhuo Chen <czchen@gmail.com>
+Allow: ibus-chewing (374FF2AD0A12935FD0B0C84F1B132E01CEC6AD46),
+ libchewing (374FF2AD0A12935FD0B0C84F1B132E01CEC6AD46)
+
+Fingerprint: 1DE86AB01897A330D973D77C50DD5A29FB099999
+Uid: Chris Boot <bootc@bootc.net>
+Allow: ppp (1F2232EEE56FD048EAEFE47F1467F0D8E1EE3FB1),
+ ulogd2 (0A55B7C51223394286EC74C35394479DD3524C51)
+
+Fingerprint: 7D1ACFFAD9E0806C9C4CD3925C13D6DB93052E03
+Uid: Christian Hofstaedtler <christian@hofstaedtler.name>
+Allow: boot-info-script (556509901902AF06ADC6E01C04AAE5B397F1AAAC),
+ bundler (556509901902AF06ADC6E01C04AAE5B397F1AAAC),
+ cciss-vol-status (556509901902AF06ADC6E01C04AAE5B397F1AAAC),
+ gist (556509901902AF06ADC6E01C04AAE5B397F1AAAC),
+ grml-debootstrap (556509901902AF06ADC6E01C04AAE5B397F1AAAC),
+ grml-rescueboot (556509901902AF06ADC6E01C04AAE5B397F1AAAC),
+ grml2usb (556509901902AF06ADC6E01C04AAE5B397F1AAAC),
+ pbundler (556509901902AF06ADC6E01C04AAE5B397F1AAAC),
+ ruby-mechanize (556509901902AF06ADC6E01C04AAE5B397F1AAAC),
+ salt (556509901902AF06ADC6E01C04AAE5B397F1AAAC)
+
+Fingerprint: 08F084DA146C873C361AAFA8E76004C5CEF0C94C
+Uid: Christian Kastner <christian@kvr.at>
+Allow: diveintopython3 (CDB5A1243ACDB63009AD07212D4EB3A6015475F5)
+
+Fingerprint: 08CD6D1255FA3A875C1FB096398D1112D3A4BDE1
+Uid: Christian M. Amsüss <christian@amsuess.com>
+Allow: openscad (9A57344C54D3C8610A5F22FCBAA569D0CBF12A6A)
+
+Fingerprint: 3688337C0D3E372594ECE4018D52CDE95117E119
+Uid: Christian Welzel <gawain@camlann.de>
+Allow: typo3-src (B8BF54137B09D35CF026FE9D091AB856069AAA1C)
+
+Fingerprint: 7062DAA4F001B9C6616700CF68C287DFC6A80226
+Uid: Colin King <colin.king@ubuntu.com>
+Allow: eventstat (73EE922658C2E07340EA9613E7F710555409E422),
+ powerstat (73EE922658C2E07340EA9613E7F710555409E422),
+ thermald (73EE922658C2E07340EA9613E7F710555409E422)
+
+Fingerprint: 709F54E4ECF3195623326AE3F82E5CC04B2B2B9E
+Uid: Daniel Baumann <daniel>
+Allow: clzip (65A12DF4FE31AD6BAC4D76AE3355F4D63B5821CC),
+ criu (A4AD7A700EE5F70F31B16FA32127371B9BB23062),
+ dosfstools (A4AD7A700EE5F70F31B16FA32127371B9BB23062),
+ foxtrotgps (A4AD7A700EE5F70F31B16FA32127371B9BB23062),
+ fuse (A4AD7A700EE5F70F31B16FA32127371B9BB23062),
+ gfxboot (A4AD7A700EE5F70F31B16FA32127371B9BB23062),
+ gfxboot-examples (A4AD7A700EE5F70F31B16FA32127371B9BB23062),
+ gfxboot-themes (A4AD7A700EE5F70F31B16FA32127371B9BB23062),
+ git-stuff (A4AD7A700EE5F70F31B16FA32127371B9BB23062),
+ gnu-efi (A4AD7A700EE5F70F31B16FA32127371B9BB23062),
+ irker (A4AD7A700EE5F70F31B16FA32127371B9BB23062),
+ libcgroup (A4AD7A700EE5F70F31B16FA32127371B9BB23062),
+ live-boot (A4AD7A700EE5F70F31B16FA32127371B9BB23062),
+ live-build (A4AD7A700EE5F70F31B16FA32127371B9BB23062),
+ live-config (A4AD7A700EE5F70F31B16FA32127371B9BB23062),
+ live-debconfig (A4AD7A700EE5F70F31B16FA32127371B9BB23062),
+ live-images (A4AD7A700EE5F70F31B16FA32127371B9BB23062),
+ live-manual (A4AD7A700EE5F70F31B16FA32127371B9BB23062),
+ live-tools (A4AD7A700EE5F70F31B16FA32127371B9BB23062),
+ lunzip (65A12DF4FE31AD6BAC4D76AE3355F4D63B5821CC),
+ lxc (A4AD7A700EE5F70F31B16FA32127371B9BB23062),
+ lzd (E4F0EDDF374F2C50D4735EC097833DC998EF9A49),
+ lzip (A4AD7A700EE5F70F31B16FA32127371B9BB23062),
+ lziprecover (65A12DF4FE31AD6BAC4D76AE3355F4D63B5821CC),
+ lzlib (A4AD7A700EE5F70F31B16FA32127371B9BB23062),
+ ntfs-3g (A4AD7A700EE5F70F31B16FA32127371B9BB23062),
+ pdlzip (65A12DF4FE31AD6BAC4D76AE3355F4D63B5821CC),
+ plymouth (A4AD7A700EE5F70F31B16FA32127371B9BB23062),
+ plzip (65A12DF4FE31AD6BAC4D76AE3355F4D63B5821CC),
+ python-irc (A4AD7A700EE5F70F31B16FA32127371B9BB23062),
+ syslinux (A4AD7A700EE5F70F31B16FA32127371B9BB23062),
+ tftp-hpa (A4AD7A700EE5F70F31B16FA32127371B9BB23062),
+ vsftpd (A4AD7A700EE5F70F31B16FA32127371B9BB23062),
+ zutils (65A12DF4FE31AD6BAC4D76AE3355F4D63B5821CC)
+
+Fingerprint: 160853D573C1690115FA4B78B012AD8CF19F599F
+Uid: Darren Salt <linux@youmustbejoking.demon.co.uk>
+Allow: glbsp (93005DC27E876C37ED7BCA9A98083544945348A4),
+ gxine (93005DC27E876C37ED7BCA9A98083544945348A4),
+ mercurial-buildpackage (93005DC27E876C37ED7BCA9A98083544945348A4),
+ playmidi (93005DC27E876C37ED7BCA9A98083544945348A4),
+ pngmeta (93005DC27E876C37ED7BCA9A98083544945348A4),
+ rfkill (93005DC27E876C37ED7BCA9A98083544945348A4),
+ xine-lib (93005DC27E876C37ED7BCA9A98083544945348A4),
+ xine-lib-1.2 (93005DC27E876C37ED7BCA9A98083544945348A4),
+ xine-ui (93005DC27E876C37ED7BCA9A98083544945348A4)
+
+Fingerprint: AE0DBF5A92A5ADE49481BA6F8A3171EF366150CE
+Uid: David Steele <dsteele@gmail.com>
+Allow: gnome-gmail (467348C85F3A5A9B2B0D169EBF1E9D1F76D52AC4)
+
+Fingerprint: 126064058E7A80FE7D0672298F342172F407DB73
+Uid: Devid Antonio Filoni <d.filoni@ubuntu.com>
+Allow: gexiv2 (DDC53E5130FD08223F98945649086AD3EBE2F31F),
+ granite (DDC53E5130FD08223F98945649086AD3EBE2F31F),
+ ircp-tray (DDC53E5130FD08223F98945649086AD3EBE2F31F),
+ libraw (DDC53E5130FD08223F98945649086AD3EBE2F31F),
+ msn-pecan (DDC53E5130FD08223F98945649086AD3EBE2F31F),
+ shotwell (DDC53E5130FD08223F98945649086AD3EBE2F31F),
+ sqlheavy (DDC53E5130FD08223F98945649086AD3EBE2F31F),
+ sushi (DDC53E5130FD08223F98945649086AD3EBE2F31F)
+
+Fingerprint: 1AFC09386700D1A8F05C65ADE5AB5F161CDD0D98
+Uid: Diane Trout <diane@ghic.org>
+Allow: ktp-accounts-kcm (12DDFA84AC23B2BBF04B313CAB645F406286A7D0),
+ ktp-approver (12DDFA84AC23B2BBF04B313CAB645F406286A7D0),
+ ktp-auth-handler (12DDFA84AC23B2BBF04B313CAB645F406286A7D0),
+ ktp-call-ui (12DDFA84AC23B2BBF04B313CAB645F406286A7D0),
+ ktp-contact-list (12DDFA84AC23B2BBF04B313CAB645F406286A7D0),
+ ktp-contact-runner (12DDFA84AC23B2BBF04B313CAB645F406286A7D0),
+ ktp-desktop-applets (12DDFA84AC23B2BBF04B313CAB645F406286A7D0),
+ ktp-filetransfer-handler (12DDFA84AC23B2BBF04B313CAB645F406286A7D0),
+ ktp-kded-integration-module (12DDFA84AC23B2BBF04B313CAB645F406286A7D0),
+ ktp-send-file (12DDFA84AC23B2BBF04B313CAB645F406286A7D0),
+ ktp-text-ui (12DDFA84AC23B2BBF04B313CAB645F406286A7D0),
+ meta-kde-telepathy (12DDFA84AC23B2BBF04B313CAB645F406286A7D0)
+
+Fingerprint: 6EE50664D18E4E0CB8B43DF700E89439F228292B
+Uid: Dima Kogan <dima@secretsauce.net>
+Allow: feedgnuplot (BBBD45EA818AB86FF67E7285D3E17383CFA7FF06),
+ libdogleg (BBBD45EA818AB86FF67E7285D3E17383CFA7FF06),
+ liblbfgs (BBBD45EA818AB86FF67E7285D3E17383CFA7FF06),
+ tcpflow (BBBD45EA818AB86FF67E7285D3E17383CFA7FF06),
+ vlfeat (BBBD45EA818AB86FF67E7285D3E17383CFA7FF06),
+ xcscope-el (E50AFD55ADD27AAB97163A8B21D20589974B3E96)
+
+Fingerprint: 2626C74FA4EA17DF304B1EF2A03F52085D424895
+Uid: Dimitrios Eftaxiopoulos <eftaxi12@otenet.gr>
+Allow: freefem++ (20691DFCC2C98C47952984EE00018C22381A7594),
+ mathgl (20691DFCC2C98C47952984EE00018C22381A7594)
+
+Fingerprint: 3A82860837A0CD32470F91E62AC1E075F5ED1B25
+Uid: The Roman People <spqr-pop@debian.org>
+Allow: not-a-package (BCD22CD83243B79D3DFAC33EA3DBCBC039B13D8A),
+ pari-extra (BCD22CD83243B79D3DFAC33EA3DBCBC039B13D8A)
+
+Fingerprint: F24299FF1BBC9018B906A4CB6026936D2F1C8AE0
+Uid: Dmitry Shachnev <mitya57@gmail.com>
+Allow: mathjax (6EB223D7D71E67A53C93A7DA3B56E2BBD53FDCB1),
+ mathjax-docs (6EB223D7D71E67A53C93A7DA3B56E2BBD53FDCB1),
+ pymarkups (6EB223D7D71E67A53C93A7DA3B56E2BBD53FDCB1),
+ pyqt5 (7523647B95E5047547EC2BBA1DA8DA33DDCD686A),
+ python-gdata (6EB223D7D71E67A53C93A7DA3B56E2BBD53FDCB1),
+ python-markdown (ECA1E3F28E112432D485DD95EB36171A6FF9435F),
+ python-qt4 (7523647B95E5047547EC2BBA1DA8DA33DDCD686A),
+ python-secretstorage (6EB223D7D71E67A53C93A7DA3B56E2BBD53FDCB1),
+ retext (6EB223D7D71E67A53C93A7DA3B56E2BBD53FDCB1),
+ sip4 (7523647B95E5047547EC2BBA1DA8DA33DDCD686A),
+ sphinx (6EB223D7D71E67A53C93A7DA3B56E2BBD53FDCB1),
+ woff-tools (6EB223D7D71E67A53C93A7DA3B56E2BBD53FDCB1)
+
+Fingerprint: 71B8F031E648BCD95EAC18FA47712171F2ED62FB
+Uid: Elmar Heeb <elmar@heebs.ch>
+Allow: autocutsel (F067EA2726B9C3FC1486202EC09E1D8995930EDE)
+
+Fingerprint: 64CF7C59E56B38F04CA6F861C9F1CBF56351F719
+Uid: Elías Alejandro Año Mendoza <ealmdz@gmail.com>
+Allow: fdclone (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ gpick (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ uget (04160004A8276E40BB9890FBE8A48AE5311D765A)
+
+Fingerprint: 5201B8B5039F3F03E628F16B826355E260F7BCB2
+Uid: Emile Joubert <emile@rabbitmq.com>
+Allow: rabbitmq-server (325A3DD1F8D5E51C5D1D83725EB9E72A228A3AE4)
+
+Fingerprint: 1D68927B7F2964590BB000F0AE0C84BB0A2368F0
+Uid: Emilien Klein <emilien+debian@klein.st>
+Allow: nautilus-image-manipulator (E1D8579682144687E416948C859FEF67258E26B1)
+
+Fingerprint: B8CE4DE21080DCF903E16C40F513C419E4B9D0AC
+Uid: Emmanuel Bourg <ebourg@apache.org>
+Allow: ant (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ ant-contrib (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ ant1.7 (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ antlr (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ antlr-maven-plugin (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ antlr3 (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ apache-log4j1.2 (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ apache-pom (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ asm (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ asm2 (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ asm3 (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ aspectj-maven-plugin (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ avalon-framework (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ axis (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ batik (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ bcel (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ bouncycastle (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ build-helper-maven-plugin (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ cglib (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ checkstyle (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ commons-beanutils (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ commons-configuration (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ commons-csv (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ commons-daemon (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ commons-exec (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ commons-httpclient (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ commons-io (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ commons-javaflow (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ commons-jci (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ commons-math (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ commons-math3 (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ commons-parent (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ commons-pool (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ commons-vfs (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ derby (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ dom4j (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ doxia-maven-plugin (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ ehcache (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ excalibur-logger (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ excalibur-logkit (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ exec-maven-plugin (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ felix-bundlerepository (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ felix-framework (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ felix-gogo-command (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ felix-gogo-runtime (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ felix-gogo-shell (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ felix-main (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ felix-osgi-obr (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ felix-shell (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ felix-shell-tui (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ felix-utils (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ fop (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ guava-libraries (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ hessian (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ hsqldb (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ hsqldb1.8.0 (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ httpcomponents-client (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ httpcomponents-core (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ ivy (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ jakarta-jmeter (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ jakarta-log4j (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ jakarta-taglibs-standard (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ jarjar-maven-plugin (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ jasperreports (E50AFD55ADD27AAB97163A8B21D20589974B3E96),
+ jasperreports3.7 (E50AFD55ADD27AAB97163A8B21D20589974B3E96),
+ java3d (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ javacc (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ javacc-maven-plugin (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ jcifs (E50AFD55ADD27AAB97163A8B21D20589974B3E96),
+ jenkins-dom4j (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ jmock (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ joda-convert (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ jsch (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ jsoup (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ junit (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ junit4 (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ jzlib (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ libapache-poi-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ libasm4-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ libcommons-attributes-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ libcommons-cli-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ libcommons-codec-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ libcommons-collections-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ libcommons-collections3-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ libcommons-compress-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ libcommons-dbcp-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ libcommons-digester-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ libcommons-discovery-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ libcommons-el-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ libcommons-fileupload-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ libcommons-jexl-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ libcommons-jexl2-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ libcommons-jxpath-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ libcommons-lang-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ libcommons-lang3-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ libcommons-launcher-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ libcommons-logging-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ libcommons-modeler-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ libcommons-net-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ libcommons-net1-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ libcommons-net2-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ libcommons-openpgp-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ libcommons-validator-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ libfreemarker-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ libhamcrest-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ libhibernate3-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ libitext-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ libitext5-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ libj2ssh-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ libjgroups-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ libjoda-time-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ libjtype-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ libmiglayout-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ libmx4j-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ liboro-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ libproxool-java (E50AFD55ADD27AAB97163A8B21D20589974B3E96),
+ librelaxng-datatype-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ libswingx-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ libswingx1-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ libws-commons-util (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ libws-commons-util-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ libxalan2-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ libxerces2-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ libxml-security-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ libxmpcore-java (E50AFD55ADD27AAB97163A8B21D20589974B3E96),
+ libxstream-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ lucene-solr (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ lucene2 (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ maven (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ maven-antrun-extended-plugin (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ maven-antrun-plugin (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ maven-assembly-plugin (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ maven-bundle-plugin (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ maven-clean-plugin (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ maven-compiler-plugin (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ maven-dependency-plugin (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ maven-ear-plugin (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ maven-ejb-plugin (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ maven-hpi-plugin (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ maven-install-plugin (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ maven-invoker-plugin (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ maven-jar-plugin (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ maven-javadoc-plugin (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ maven-plugin-tools (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ maven-project-info-reports-plugin (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ maven-resources-plugin (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ maven-shade-plugin (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ maven-site-plugin (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ maven-source-plugin (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ maven-stapler-plugin (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ maven-war-plugin (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ maven2 (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ maven2-core (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ mina (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ mina2 (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ munge-maven-plugin (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ mysql-connector-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ ognl (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ opencsv (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ plexus-maven-plugin (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ properties-maven-plugin (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ sitemesh (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ substance (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ surefire (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ tagsoup (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ uima-addons (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ uima-as (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ uimaj (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ vecmath (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ velocity (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ velocity-tools (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ xml-maven-plugin (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ xmlbeans-maven-plugin (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ xmlunit (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ xz-java (DEE63CAACE54B9B27DDCD34A88E5D733DD899610)
+
+Fingerprint: F6258DF2BFF9A73F5B1E15C306456DD791E95791
+Uid: Emmanuel Kasper Kasprzyk <emmanuel@libera.cc>
+Allow: mame (73ED4244FD43588620AC2644258494BA917A225E),
+ mess (73ED4244FD43588620AC2644258494BA917A225E)
+
+Fingerprint: FC3D302E02E469C30AA89067F7D931D4B882EBD7
+Uid: Eric Cooper <ecc@cmu.edu>
+Allow: approx (58EB0999C64E897EE894B8037853DA4D49881AD3),
+ ocaml-sha (02054829E12D0F2A8E648E62745C4766D4CACDFF)
+
+Fingerprint: 73571E85C19F4281D8C97AA86CA41A7743B8D6C8
+Uid: Erik de Castro Lopo <erikd@mega-nerd.com>
+Allow: libsamplerate (B2F9C5E313771B0DC8B0F6B503C0023E05410E97),
+ libsndfile (B2F9C5E313771B0DC8B0F6B503C0023E05410E97),
+ sndfile-tools (B2F9C5E313771B0DC8B0F6B503C0023E05410E97)
+
+Fingerprint: 11DD7D0BBA65B6AE507DD59200AB067AE47B79A4
+Uid: Eugen Dedu <Eugen.Dedu@pu-pm.univ-fcomte.fr>
+Allow: ekiga (75FFFC9F717B526296A20609BDD933B785FEC17F),
+ opal (75FFFC9F717B526296A20609BDD933B785FEC17F),
+ ptlib (75FFFC9F717B526296A20609BDD933B785FEC17F)
+
+Fingerprint: 4D60D518D9ADC00459A27B1CD7D3402E0106630C
+Uid: Eugene Zhukov <jevgeni.zh@gmail.com>
+Allow: epubcheck (693367FFAECD8EAACD1F063B0171E1828AE09345),
+ xml-maven-plugin (693367FFAECD8EAACD1F063B0171E1828AE09345)
+
+Fingerprint: ECA5F5232FE1F8E3B1303ACB5E326303C98B5D5D
+Uid: Federico Ceratto <federico.ceratto@gmail.com>
+Allow: cmd2 (2BABC6254E66E7B8450AC3E1E6AA90171392B174),
+ debtags (66B4DFB68CB24EBBD8650BC4F4B4B0CC797EBFAB),
+ easyzone (2BABC6254E66E7B8450AC3E1E6AA90171392B174),
+ freshen (2BABC6254E66E7B8450AC3E1E6AA90171392B174),
+ gitmagic (2BABC6254E66E7B8450AC3E1E6AA90171392B174),
+ pudb (2BABC6254E66E7B8450AC3E1E6AA90171392B174),
+ pymongo (2BABC6254E66E7B8450AC3E1E6AA90171392B174),
+ python-bottle (2BABC6254E66E7B8450AC3E1E6AA90171392B174),
+ python-enum (2BABC6254E66E7B8450AC3E1E6AA90171392B174),
+ python-nmap (2BABC6254E66E7B8450AC3E1E6AA90171392B174),
+ python-xattr (2BABC6254E66E7B8450AC3E1E6AA90171392B174),
+ runsnakerun (2BABC6254E66E7B8450AC3E1E6AA90171392B174),
+ squaremap (2BABC6254E66E7B8450AC3E1E6AA90171392B174),
+ tgmochikit (2BABC6254E66E7B8450AC3E1E6AA90171392B174),
+ turbojson (2BABC6254E66E7B8450AC3E1E6AA90171392B174),
+ turbokid (2BABC6254E66E7B8450AC3E1E6AA90171392B174),
+ uncertainties (2BABC6254E66E7B8450AC3E1E6AA90171392B174)
+
+Fingerprint: CAF85751553EAC43538D56487F3805C7D82FB9C8
+Uid: Felix Zielcke <fzielcke@z-51.de>
+Allow: grub (AC0A4FF12611B6FCCF01C111393587D97D86500B),
+ grub-installer (AC0A4FF12611B6FCCF01C111393587D97D86500B),
+ grub2 (AC0A4FF12611B6FCCF01C111393587D97D86500B),
+ libaal (AC0A4FF12611B6FCCF01C111393587D97D86500B),
+ reiser4progs (AC0A4FF12611B6FCCF01C111393587D97D86500B),
+ reiserfsprogs (AC0A4FF12611B6FCCF01C111393587D97D86500B)
+
+Fingerprint: 0FB1803BC91D1B601E2A2C22702A26DB4CCB3FFC
+Uid: Floris Bruynooghe <flub@devork.be>
+Allow: omniorb-dfsg (4B74F71B1C2272344249BF53CF62D79438E68E0E),
+ python-omniorb (4B74F71B1C2272344249BF53CF62D79438E68E0E)
+
+Fingerprint: 25F3B67F038187D3ADF60A3329E36B9A01ED3AC7
+Uid: Frank Habermann <lordlamer@lordlamer.de>
+Allow: ckeditor (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ fckeditor (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ knowledgeroot (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ prototypejs (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ scriptaculous (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tinymce (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ zendframework (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E)
+
+Fingerprint: B5BCBDDE7CA813D4F6A3D135A7771D09B55C9C2B
+Uid: Gabriele Giacone <1o5g4r8o@gmail.com>
+Allow: critterding (E50AFD55ADD27AAB97163A8B21D20589974B3E96),
+ gnash (E50AFD55ADD27AAB97163A8B21D20589974B3E96),
+ jedit (E50AFD55ADD27AAB97163A8B21D20589974B3E96),
+ jxplorer (E50AFD55ADD27AAB97163A8B21D20589974B3E96),
+ pidgin-skype (DDC53E5130FD08223F98945649086AD3EBE2F31F),
+ sunflow (E50AFD55ADD27AAB97163A8B21D20589974B3E96),
+ sweethome3d (65A12DF4FE31AD6BAC4D76AE3355F4D63B5821CC),
+ sweethome3d-furniture (E50AFD55ADD27AAB97163A8B21D20589974B3E96),
+ sweethome3d-furniture-editor (E50AFD55ADD27AAB97163A8B21D20589974B3E96),
+ sweethome3d-furniture-nonfree (E50AFD55ADD27AAB97163A8B21D20589974B3E96),
+ sweethome3d-textures-editor (E50AFD55ADD27AAB97163A8B21D20589974B3E96),
+ ubiquity-extension (A62D2CFBD50B9B5BF360D54B159EB5C4EFC8774C)
+
+Fingerprint: 2985B56E5FBC9D9C21E9597B8B6EF2528F075E1D
+Uid: Gianfranco Costamagna <costamagnagianfranco@yahoo.it>
+Allow: boinc (84C1977A89E7EFED3E5CA62E2FD8BEDAC020EED1),
+ boinc-app-milkyway (84C1977A89E7EFED3E5CA62E2FD8BEDAC020EED1),
+ boinc-app-seti (84C1977A89E7EFED3E5CA62E2FD8BEDAC020EED1),
+ docbook2x (B5E7DDCA01B39BEE841B1E199B46F1FB088F6B8C),
+ hedgewars (1B23D4F88EC0D9020555E438AB8C00CFF8E26537),
+ qviaggiatreno (E5D870FFBB267D90D52C56B5347A7D93176015ED)
+
+Fingerprint: 05D0169C26E41593418129DF199A64FADFB500FF
+Uid: Gregor Jasny <gjasny@googlemail.com>
+Allow: c-ares (3BDC04824EA81277AE46EA72F98825AC26B47B9F),
+ v4l-utils (3BDC04824EA81277AE46EA72F98825AC26B47B9F)
+
+Fingerprint: 519B6955BF534A43E3D60827554297EDF9CCA585
+Uid: Guo Yixuan <culu.gyx@gmail.com>
+Allow: boinc (84C1977A89E7EFED3E5CA62E2FD8BEDAC020EED1),
+ boinc-app-seti (84C1977A89E7EFED3E5CA62E2FD8BEDAC020EED1),
+ gcc-4.4-doc-non-dfsg (84C1977A89E7EFED3E5CA62E2FD8BEDAC020EED1),
+ gcc-4.6-doc (84C1977A89E7EFED3E5CA62E2FD8BEDAC020EED1),
+ gcc-4.7-doc (84C1977A89E7EFED3E5CA62E2FD8BEDAC020EED1),
+ gcc-4.8-doc (84C1977A89E7EFED3E5CA62E2FD8BEDAC020EED1),
+ gcc-doc-defaults (84C1977A89E7EFED3E5CA62E2FD8BEDAC020EED1),
+ pod2pdf (36E2EDDEC21FEC8F77B87436D362B62A54B99890)
+
+Fingerprint: 4656B626E481EE4E7EC572DA46CC5C63F318A56A
+Uid: Helge Kreutzmann <browser@helgefjell.de>
+Allow: asclock (F849E2025D1C194DE62BC6C829BE5D2268FD549F),
+ goobox (F849E2025D1C194DE62BC6C829BE5D2268FD549F),
+ linuxinfo (F849E2025D1C194DE62BC6C829BE5D2268FD549F)
+
+Fingerprint: DF2494F9FA1BDEFC1A718FFCD350BB0228F60193
+Uid: Hsin-Yi Chen <ossug.hychen@gmail.com>
+Allow: python-ucltip (DA06F3E341E999EC18C376DDA108FBC534A26946),
+ python-vsgui (DA06F3E341E999EC18C376DDA108FBC534A26946)
+
+Fingerprint: 7405E745574809734800156DB65019C47F7A36F8
+Uid: IOhannes m zmölnig <zmoelnig@umlaeute.mur.at>
+Allow: assimp (5E61C8780F86295CE17D86779F0FE587374BBE81),
+ gem (5E61C8780F86295CE17D86779F0FE587374BBE81),
+ pd-iemambi (5E61C8780F86295CE17D86779F0FE587374BBE81),
+ pd-iemmatrix (5E61C8780F86295CE17D86779F0FE587374BBE81),
+ pd-iemnet (5E61C8780F86295CE17D86779F0FE587374BBE81),
+ pd-osc (5E61C8780F86295CE17D86779F0FE587374BBE81),
+ pd-readanysf (5E61C8780F86295CE17D86779F0FE587374BBE81),
+ pd-zexy (5E61C8780F86295CE17D86779F0FE587374BBE81),
+ pdp (5E61C8780F86295CE17D86779F0FE587374BBE81),
+ puredata (5E61C8780F86295CE17D86779F0FE587374BBE81),
+ v4l2loopback (5E629EE5232197357B84CF4332247FBB40AD1FA6)
+
+Fingerprint: 9D5CEE01334F46CE2FEF6DC6EC63699779074FA8
+Uid: Ian Campbell <ijc@hellion.org.uk>
+Allow: flash-kernel (B60EBF2984453C70D74CF478FF914AF0C2B35520),
+ qcontrol (F849E2025D1C194DE62BC6C829BE5D2268FD549F),
+ xserver-xorg-video-ivtvdev (33B18B87928138D4E7AE88FF7867D53C747935DD)
+
+Fingerprint: 068B8122734EF48428A2E477BC2CB1A2686FF87F
+Uid: Ignace Mouzannar <mouzannar@gmail.com>
+Allow: lshell (F8921D3A7404C86E11352215C7197699B29B232A)
+
+Fingerprint: B11E2D9D2861F45188B200A158B29F8CF74EC6C8
+Uid: Iulian Udrea <iulian@linux.com>
+Allow: haskell-adjunctions (3D0EFB95E7B5237F16E82258E352D5C51C5041D4),
+ haskell-algebra (3D0EFB95E7B5237F16E82258E352D5C51C5041D4),
+ haskell-bifunctors (3D0EFB95E7B5237F16E82258E352D5C51C5041D4),
+ haskell-categories (3D0EFB95E7B5237F16E82258E352D5C51C5041D4),
+ haskell-comonad (3D0EFB95E7B5237F16E82258E352D5C51C5041D4),
+ haskell-comonad-transformers (3D0EFB95E7B5237F16E82258E352D5C51C5041D4),
+ haskell-comonads-fd (3D0EFB95E7B5237F16E82258E352D5C51C5041D4),
+ haskell-contravariant (3D0EFB95E7B5237F16E82258E352D5C51C5041D4),
+ haskell-data-lens (3D0EFB95E7B5237F16E82258E352D5C51C5041D4),
+ haskell-distributive (3D0EFB95E7B5237F16E82258E352D5C51C5041D4),
+ haskell-free (3D0EFB95E7B5237F16E82258E352D5C51C5041D4),
+ haskell-keys (3D0EFB95E7B5237F16E82258E352D5C51C5041D4),
+ haskell-maths (4E469519ED677734268FBD958F7BF8FC4A11C97A),
+ haskell-representable-functors (3D0EFB95E7B5237F16E82258E352D5C51C5041D4),
+ haskell-representable-tries (3D0EFB95E7B5237F16E82258E352D5C51C5041D4),
+ haskell-semigroupoids (3D0EFB95E7B5237F16E82258E352D5C51C5041D4),
+ haskell-void (3D0EFB95E7B5237F16E82258E352D5C51C5041D4)
+
+Fingerprint: 7BF5F6AC36431F5D40DC137A4CF2B218F54DAE3D
+Uid: Jakob Haufe <sur5r@sur5r.net>
+Allow: blogofile (424E14D703E7C6D43D9D6F364E7160ED4AC8EE1D),
+ glabels (424E14D703E7C6D43D9D6F364E7160ED4AC8EE1D),
+ minitube (424E14D703E7C6D43D9D6F364E7160ED4AC8EE1D)
+
+Fingerprint: 80887BA0E28203ADC125E0049D50E144E6357327
+Uid: James Hunt <james.hunt@ubuntu.com>
+Allow: procenv (D764F6CC2AB59A38B1147D73887B60618B3C16AE),
+ utfout (D764F6CC2AB59A38B1147D73887B60618B3C16AE)
+
+Fingerprint: B65D085B94117B813160B659ED34CEABE27BAABC
+Uid: Jameson Graef Rollins <jrollins@finestructure.net>
+Allow: assword (0EE5BE979282D80B9F7540F1CCD2ED94D21739E9)
+
+Fingerprint: 0896F05999C906DBB3BCD04BF1E30FE50CA6B4AA
+Uid: Jan Beyer <jan@beathovn.de>
+Allow: bibus (84C1977A89E7EFED3E5CA62E2FD8BEDAC020EED1),
+ gwyddion (84C1977A89E7EFED3E5CA62E2FD8BEDAC020EED1)
+
+Fingerprint: 4617E5FBC56DACB67C8CDE643A4CB2701A89CC23
+Uid: Jan-Pascal van Best <janpascal@vanbest.org>
+Allow: spotweb (D53A815A3CB7659AF882E3958EEDCC1BAA1F32FF)
+
+Fingerprint: 4A543513D2DF6351A8178EA35B019455E2B84FA5
+Uid: Jaromír MikeÅ¡ <mira.mikes@seznam.cz>
+Allow: aliki (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ amb-plugins (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ ambdec (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ ardour3 (218EE0362033C87B6C135FA4A3BABAE2408DD6CF),
+ brp-pacu (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ caps (218EE0362033C87B6C135FA4A3BABAE2408DD6CF),
+ clthreads (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ clxclient (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ composite (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ dataquay (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ drc (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ drumkv1 (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ ebumeter (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ eq10q (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ faustworks (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ fil-plugins (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ garmindev (5701F1D0D18B87E61AA31F3DC073D2287FFB9E9B),
+ gmidimonitor (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ gtklick (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ gwc (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ gxtuner (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ invada-studio-plugins (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ invada-studio-plugins-lv2 (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ ir.lv2 (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ jaaa (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ jack-capture (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ jack-midi-clock (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ jalv (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ japa (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ jconvolver (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ jkmeter (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ jmeters (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ jnoise (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ jnoisemeter (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ klick (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ ladspa-sdk (218EE0362033C87B6C135FA4A3BABAE2408DD6CF),
+ libinstpatch (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ libltc (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ lv2-c++-tools (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ lv2core (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ lv2dynparam1 (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ lv2fil (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ mcp-plugins (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ meterbridge (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ midisnoop (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ nekobee (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ paulstretch (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ petri-foo (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ phat (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ plotmm (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ pyliblo (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ qlandkartegt (5701F1D0D18B87E61AA31F3DC073D2287FFB9E9B),
+ qmidiarp (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ qmidiroute (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ rev-plugins (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ rubberband (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ samplv1 (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ showq (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ sineshaper (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ slv2 (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ sonic-visualiser (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ sooperlooper (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ specimen (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ swami (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ synthv1 (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ tap-plugins (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ tap-plugins-doc (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ vamp-plugin-sdk (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ vco-plugins (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ wah-plugins (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ x42-plugins (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ xjadeo (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ yoshimi (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ zita-ajbridge (218EE0362033C87B6C135FA4A3BABAE2408DD6CF),
+ zita-alsa-pcmi (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ zita-at1 (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ zita-convolver (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ zita-lrx (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ zita-mu1 (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ zita-resampler (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ zita-rev1 (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ zynaddsubfx (04160004A8276E40BB9890FBE8A48AE5311D765A)
+
+Fingerprint: E8CDDDFCE25A166FAF617CB647D23A6E56164AC2
+Uid: Jean-Michel Vourgère <jmv_deb@nirgal.com>
+Allow: mdbtools (1EBA24A7DAAE662C6FF34B37C7F521C122B282CA)
+
+Fingerprint: 0D80EABB04476E542C9408E10ADB299C1F137C9F
+Uid: Jeroen Dekkers <jeroen@dekkers.ch>
+Allow: sbjson (F1F3A87ED983DFAD791ADAD83DAF54A21EEF5276),
+ sogo (F1F3A87ED983DFAD791ADAD83DAF54A21EEF5276),
+ sope (F1F3A87ED983DFAD791ADAD83DAF54A21EEF5276)
+
+Fingerprint: 5E787F8DB58F02AF5C63C9DD4A2254641FE1B08B
+Uid: Joachim Wiedorn <ad_debian@joonet.de>
+Allow: backup2l (73471499CC60ED9EEE805946C5BD6C8F2295D502),
+ duply (B2FF1D95CE8F7A22DF4CF09BA73E0055558FB8DD),
+ fox1.6 (820F6308F2B08DA24D3EC20E750807B5551BE447),
+ lilo (B3131A451DBFDF7CA05B4197054BBB9F7D806442),
+ squidguard (D5C2F9BFCA128BBA22A77218872F702C4D6E25A8),
+ xfe (9FED5C6CE206B70A585770CA965522B9D49AE731)
+
+Fingerprint: 1D75E212B34CF4BFA9E0D0D8DE6DE039C1CFC265
+Uid: Joao Eriberto Mota Filho <eriberto@eriberto.pro.br>
+Allow: album (6A41FE6D5D7B2911D0E47FE80CE33C910F9CB28F),
+ album-data (6A41FE6D5D7B2911D0E47FE80CE33C910F9CB28F),
+ bittwist (6A41FE6D5D7B2911D0E47FE80CE33C910F9CB28F),
+ chaosreader (6A41FE6D5D7B2911D0E47FE80CE33C910F9CB28F),
+ core-network (6A41FE6D5D7B2911D0E47FE80CE33C910F9CB28F),
+ diskscan (B2DEE66036C40829FCD0F10CFC0DB1BBCD460BDE),
+ ext4magic (6A41FE6D5D7B2911D0E47FE80CE33C910F9CB28F),
+ f3 (6A41FE6D5D7B2911D0E47FE80CE33C910F9CB28F),
+ gconjugue (6A41FE6D5D7B2911D0E47FE80CE33C910F9CB28F),
+ hapm (6A41FE6D5D7B2911D0E47FE80CE33C910F9CB28F),
+ hlbr (6A41FE6D5D7B2911D0E47FE80CE33C910F9CB28F),
+ hlbrw (6A41FE6D5D7B2911D0E47FE80CE33C910F9CB28F),
+ iceweasel-linky (5818BF0C98A32B8382BFD3B4564126F229F19BD1),
+ jp2a (6A41FE6D5D7B2911D0E47FE80CE33C910F9CB28F),
+ libpcapnav (6A41FE6D5D7B2911D0E47FE80CE33C910F9CB28F),
+ lime-forensics (B2DEE66036C40829FCD0F10CFC0DB1BBCD460BDE),
+ linky (5818BF0C98A32B8382BFD3B4564126F229F19BD1),
+ mac-robber (6A41FE6D5D7B2911D0E47FE80CE33C910F9CB28F),
+ netmate (B2DEE66036C40829FCD0F10CFC0DB1BBCD460BDE),
+ pacman4console (6A41FE6D5D7B2911D0E47FE80CE33C910F9CB28F),
+ pcapfix (6A41FE6D5D7B2911D0E47FE80CE33C910F9CB28F),
+ pdfcrack (B2DEE66036C40829FCD0F10CFC0DB1BBCD460BDE),
+ phpwebcounter (6A41FE6D5D7B2911D0E47FE80CE33C910F9CB28F),
+ phpwebcounter-extra (6A41FE6D5D7B2911D0E47FE80CE33C910F9CB28F),
+ sentinella (6A41FE6D5D7B2911D0E47FE80CE33C910F9CB28F),
+ vokoscreen (B2DEE66036C40829FCD0F10CFC0DB1BBCD460BDE),
+ volatility (B2DEE66036C40829FCD0F10CFC0DB1BBCD460BDE),
+ volatility-profiles (B2DEE66036C40829FCD0F10CFC0DB1BBCD460BDE),
+ yara (B2DEE66036C40829FCD0F10CFC0DB1BBCD460BDE)
+
+Fingerprint: 102E2FE7D5141DBD12B260FCB09E40B0F2AE6AB9
+Uid: Joe Healy <joehealy@gmail.com>
+Allow: salt (2CCB26BC5C49BC221F20794255C9882D999BBCC4)
+
+Fingerprint: B3F8B98212122A2015555F1E8B1344DE9F807F14
+Uid: Johannes Ring <johannr@simula.no>
+Allow: doconce (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ dolfin (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ fenics (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ ferari (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ ffc (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ fiat (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ instant (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ preprocess (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ ptex2tex (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ scitools (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ syfi (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ ufc (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ ufl (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ viper (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ vmtk (DEE63CAACE54B9B27DDCD34A88E5D733DD899610)
+
+Fingerprint: CACE80AE01512F9AE8AB80D61C01F443C9C93C5A
+Uid: John Stamp <jstamp@mehercule.net>
+Allow: kcometen4 (7A33ECAA188B96F27C917288B3464F896AA15948),
+ lastfm (8F049AD82C92066C7352D28A7B585B30807C2A87),
+ liblastfm (8F049AD82C92066C7352D28A7B585B30807C2A87)
+
+Fingerprint: CFC5B232C0D082CAE6B3A166F04CEFF6016CFFD0
+Uid: Jonas Genannt <jonas@brachium-system.net>
+Allow: gitalist (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ graphite-carbon (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ graphite-web (F607B30B062EA443D33C70E0396DA361FE5F1D7F),
+ libalgorithm-dependency-perl (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ libapp-cache-perl (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ libaudio-mixer-perl (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ libcatalyst-plugin-unicode-encoding-perl (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ libcatalyst-view-component-subinclude-perl (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ libcrypt-hcesha-perl (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ libdatetime-format-duration-perl (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ libdbd-ldap-perl (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ libfile-flat-perl (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ libgit-pure-perl (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ libjavascript-rpc-perl (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ libmoosex-types-iso8601-perl (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ libparams-util-perl (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ libpasswd-unix-perl (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ libpod-tests-perl (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ libprefork-perl (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ libtemplate-plugin-cycle-perl (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ libtemplate-plugin-utf8decode-perl (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ libtest-classapi-perl (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ libtest-inline-perl (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ libtest-utf8-perl (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ mcollective (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ python-ceres (F607B30B062EA443D33C70E0396DA361FE5F1D7F),
+ python-whisper (F607B30B062EA443D33C70E0396DA361FE5F1D7F),
+ ruby-gelf (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ ruby-stomp (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E)
+
+Fingerprint: 6150F716E9FEE0D115B073EB156EEC0737AD3296
+Uid: Jonathan McCrohan <jmccrohan@gmail.com>
+Allow: dtv-scan-tables (26C0976C594BE3C7A5A392DDF7180D26AEDAA642),
+ figlet (3BE16591C78C2AA431DDAEEF640602273516D372),
+ lcd4linux (93005DC27E876C37ED7BCA9A98083544945348A4),
+ nyancat (CDB5A1243ACDB63009AD07212D4EB3A6015475F5),
+ transmission-remote-cli (8AE6CDFF6535192FB5B659212262D36F7ADF9466),
+ wavemon (843E5FA61E6063389876D2E5EE4AFD69EC65108F)
+
+Fingerprint: 8B77BA48391B3CE51C22953132CC4AAC028756FF
+Uid: Julian Taylor <jtaylor.debian@googlemail.com>
+Allow: fftw3 (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ ipython (1D2FA89858DAAF6217862DF7AEF6F1A2A7457645),
+ keepass2 (3D0EFB95E7B5237F16E82258E352D5C51C5041D4),
+ libmatheval (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ node-marked (03C4E7ABB880F524306E48156611C05EDD39F374),
+ pycxx (7523647B95E5047547EC2BBA1DA8DA33DDCD686A),
+ python-scipy (0C19C882237D25D43B8A41BE70373CF1290DB9CE),
+ pyzmq (1D2FA89858DAAF6217862DF7AEF6F1A2A7457645)
+
+Fingerprint: C11CA58C0175C240D58C30C8D27DDE1140A2F113
+Uid: KURASHIKI Satoru <lurdan@gmail.com>
+Allow: bzr-email (58E1222F9696C885A3CD104C5D328D082AAAB140),
+ e2wm (678AC67A3C5A16058122D62171A802D0BCD1BC92),
+ emacs-calfw (5E629EE5232197357B84CF4332247FBB40AD1FA6),
+ emacs-window-layout (5E629EE5232197357B84CF4332247FBB40AD1FA6),
+ goldencheetah (031C623D94EF1F3EF17B1C84E5EFAB90080EA63C),
+ howm (5E629EE5232197357B84CF4332247FBB40AD1FA6),
+ hyperestraier (5E629EE5232197357B84CF4332247FBB40AD1FA6),
+ mha4mysql-manager (5E629EE5232197357B84CF4332247FBB40AD1FA6),
+ mha4mysql-node (5E629EE5232197357B84CF4332247FBB40AD1FA6),
+ mysqltuner (5E629EE5232197357B84CF4332247FBB40AD1FA6),
+ qdbm (5E629EE5232197357B84CF4332247FBB40AD1FA6),
+ ruby-serverspec (0B29D88E42E6B765B8D8EA507839619DD439668E),
+ ruby-specinfra (0B29D88E42E6B765B8D8EA507839619DD439668E),
+ yaskkserv (031C623D94EF1F3EF17B1C84E5EFAB90080EA63C),
+ yatex (0B29D88E42E6B765B8D8EA507839619DD439668E)
+
+Fingerprint: B1A47069121F6642BB3D7F3E20B7283AFE254C69
+Uid: Keith Winstein <keithw@mit.edu>
+Allow: mosh (FBE01342FCEFD379D3DCE61764959FE9838DF19C)
+
+Fingerprint: 770B5CDBFB4B868B61434845C617869F1478504E
+Uid: Keng-Yu Lin <kengyu@lexical.tw>
+Allow: codecgraph (DA06F3E341E999EC18C376DDA108FBC534A26946),
+ ibus-array (DA06F3E341E999EC18C376DDA108FBC534A26946),
+ urfkill (DA06F3E341E999EC18C376DDA108FBC534A26946)
+
+Fingerprint: DF603D3A3C151B2CDF1952F418DD4D72F2CBCA06
+Uid: Kiwamu Okabe <kiwamu@masterq.net>
+Allow: carettah (5E629EE5232197357B84CF4332247FBB40AD1FA6),
+ ghc-mod (5E629EE5232197357B84CF4332247FBB40AD1FA6),
+ haskell-hcwiid (5E629EE5232197357B84CF4332247FBB40AD1FA6),
+ howm (5E629EE5232197357B84CF4332247FBB40AD1FA6),
+ jhc (0B29D88E42E6B765B8D8EA507839619DD439668E),
+ uim (0B29D88E42E6B765B8D8EA507839619DD439668E)
+
+Fingerprint: 281C6E4D93EFF746CAA9C2E8E40215299C840E81
+Uid: Koichi Akabe <vbkaisetsu@gmail.com>
+Allow: bzr-search (5E629EE5232197357B84CF4332247FBB40AD1FA6),
+ bzr-stats (5E629EE5232197357B84CF4332247FBB40AD1FA6),
+ bzr-upload (5E629EE5232197357B84CF4332247FBB40AD1FA6),
+ glogic (5E629EE5232197357B84CF4332247FBB40AD1FA6),
+ hts-voice-nitech-jp-atr503-m001 (5E629EE5232197357B84CF4332247FBB40AD1FA6),
+ htsengine (5E629EE5232197357B84CF4332247FBB40AD1FA6),
+ ngraph-gtk (5E629EE5232197357B84CF4332247FBB40AD1FA6),
+ open-jtalk (5E629EE5232197357B84CF4332247FBB40AD1FA6),
+ py3cairo (5E629EE5232197357B84CF4332247FBB40AD1FA6),
+ python-twitter (5E629EE5232197357B84CF4332247FBB40AD1FA6),
+ qr-tools (58E1222F9696C885A3CD104C5D328D082AAAB140),
+ xflr5 (58E1222F9696C885A3CD104C5D328D082AAAB140)
+
+Fingerprint: 0F65B88F1EB8928A3DF9D76C28466F1A54C2A185
+Uid: Laurent Léonard <laurent@open-minds.org>
+Allow: gtk-vnc (FBDF66F84CAC5E588EC477E49FCF2CCD3F3E6426),
+ kio-ftps (FBDF66F84CAC5E588EC477E49FCF2CCD3F3E6426),
+ ocaml-libvirt (FBDF66F84CAC5E588EC477E49FCF2CCD3F3E6426),
+ virt-manager (FBDF66F84CAC5E588EC477E49FCF2CCD3F3E6426),
+ virt-top (FBDF66F84CAC5E588EC477E49FCF2CCD3F3E6426),
+ virt-viewer (FBDF66F84CAC5E588EC477E49FCF2CCD3F3E6426),
+ virt-what (FBDF66F84CAC5E588EC477E49FCF2CCD3F3E6426),
+ virtinst (FBDF66F84CAC5E588EC477E49FCF2CCD3F3E6426)
+
+Fingerprint: FA9A6C62AEA901A663C7A0160A4B6370D2318442
+Uid: Lifeng Sun <lifongsun@gmail.com>
+Allow: cernlib (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ clhep (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ fastjet (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ fflas-ffpack (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ geant321 (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ givaro (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ hepmc (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ herwig++ (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ lhapdf (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ looptools (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ mclibs (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ ntl (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ paw (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ pythia8 (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ rivet (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ root-system (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ siscone (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ thepeg (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ yaml-cpp (DEE63CAACE54B9B27DDCD34A88E5D733DD899610)
+
+Fingerprint: FBFC21C78686E0451268694738548517DAD3D5EE
+Uid: Luis Uribe <acme@eviled.org>
+Allow: phpunit-story (E4F0EDDF374F2C50D4735EC097833DC998EF9A49)
+
+Fingerprint: B97BD6A80CAC4981091AE547FE558C72A67013C3
+Uid: Maarten Lankhorst <maarten.lankhorst@canonical.com>
+Allow: glw (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ libdrm (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ libfontenc (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ libfs (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ libglu (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ libice (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ libpciaccess (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ libsm (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ libx11 (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ libxau (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ libxaw (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ libxcomposite (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ libxcursor (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ libxdamage (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ libxdmcp (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ libxext (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ libxfixes (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ libxfont (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ libxi (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ libxinerama (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ libxkbcommon (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ libxkbfile (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ libxmu (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ libxp (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ libxpm (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ libxrandr (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ libxrender (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ libxres (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ libxss (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ libxt (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ libxtst (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ libxv (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ libxvmc (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ libxxf86dga (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ libxxf86vm (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ mesa (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ mesa-demos (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ pixman (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ wayland (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ weston (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ x11-xserver-utils (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ x11proto-bigreqs (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ x11proto-composite (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ x11proto-core (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ x11proto-damage (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ x11proto-dmx (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ x11proto-dri2 (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ x11proto-fixes (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ x11proto-fonts (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ x11proto-gl (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ x11proto-input (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ x11proto-kb (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ x11proto-print (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ x11proto-randr (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ x11proto-record (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ x11proto-render (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ x11proto-resource (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ x11proto-scrnsaver (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ x11proto-video (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ x11proto-xcmisc (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ x11proto-xext (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ x11proto-xf86bigfont (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ x11proto-xf86dga (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ x11proto-xf86dri (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ x11proto-xf86vidmode (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ x11proto-xinerama (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ xft (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ xorg (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ xorg-server (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ xserver-xorg-input-acecad (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ xserver-xorg-input-aiptek (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ xserver-xorg-input-elographics (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ xserver-xorg-input-evdev (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ xserver-xorg-input-joystick (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ xserver-xorg-input-keyboard (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ xserver-xorg-input-mouse (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ xserver-xorg-input-mutouch (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ xserver-xorg-input-synaptics (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ xserver-xorg-input-vmmouse (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ xserver-xorg-input-void (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ xserver-xorg-video-ati (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ xserver-xorg-video-cirrus (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ xserver-xorg-video-dummy (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ xserver-xorg-video-fbdev (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ xserver-xorg-video-intel (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ xserver-xorg-video-mach64 (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ xserver-xorg-video-mga (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ xserver-xorg-video-modesetting (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ xserver-xorg-video-neomagic (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ xserver-xorg-video-nouveau (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ xserver-xorg-video-openchrome (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ xserver-xorg-video-qxl (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ xserver-xorg-video-r128 (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ xserver-xorg-video-s3 (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ xserver-xorg-video-savage (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ xserver-xorg-video-siliconmotion (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ xserver-xorg-video-sis (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ xserver-xorg-video-sisusb (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ xserver-xorg-video-tdfx (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ xserver-xorg-video-trident (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ xserver-xorg-video-vesa (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ xserver-xorg-video-vmware (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ xtrans (7B27A3F1A6E18CD9588B4AE8310180050905E40C)
+
+Fingerprint: C98D9133762ACACB50A0E8B13A8336743AF72612
+Uid: Maia Kozheva <sikon@ubuntu.com>
+Allow: smplayer (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ smplayer-themes (04160004A8276E40BB9890FBE8A48AE5311D765A)
+
+Fingerprint: 861486250177633EE01392AD26CAA901117A251E
+Uid: Marcin Juszkiewicz <marcin@juszkiewicz.com.pl>
+Allow: vboot-utils (6A41FE6D5D7B2911D0E47FE80CE33C910F9CB28F)
+
+Fingerprint: 549D24B3A7CD941FAEE117F162955F6B9B1F5883
+Uid: Markus Frosch <markus@lazyfrosch.de>
+Allow: icinga-web (CC992DDDD39E75B0B0AAB25CD35BBC99BC7D020A),
+ nagvis (6E3966C1E1D15DB973D05B491E45F8CA9DE23B16)
+
+Fingerprint: ACF3D088EF32EDEF6A1A835FD9AD14B9513B51E4
+Uid: Markus Koschany <apo@gambaru.de>
+Allow: angrydd (62FF8A7584E029569546000674263B37F5B5F913),
+ berusky2 (62FF8A7584E029569546000674263B37F5B5F913),
+ berusky2-data (62FF8A7584E029569546000674263B37F5B5F913),
+ box2d (D53A815A3CB7659AF882E3958EEDCC1BAA1F32FF),
+ bullet (62FF8A7584E029569546000674263B37F5B5F913),
+ byzanz (62FF8A7584E029569546000674263B37F5B5F913),
+ easymock (E50AFD55ADD27AAB97163A8B21D20589974B3E96),
+ foobillardplus (62FF8A7584E029569546000674263B37F5B5F913),
+ freeorion (62FF8A7584E029569546000674263B37F5B5F913),
+ gamazons (55043B43EFEB282F587CF5816598789058A23DE9),
+ gtkatlantic (62FF8A7584E029569546000674263B37F5B5F913),
+ iftop (50BC7CF939D20C272A6B065652B6BBD953968D1B),
+ libgetopt-java (E50AFD55ADD27AAB97163A8B21D20589974B3E96),
+ libjide-oss-java (E50AFD55ADD27AAB97163A8B21D20589974B3E96),
+ lincity-ng (62FF8A7584E029569546000674263B37F5B5F913),
+ marsshooter (62FF8A7584E029569546000674263B37F5B5F913),
+ mediathekview (FBDF66F84CAC5E588EC477E49FCF2CCD3F3E6426),
+ mockito (E50AFD55ADD27AAB97163A8B21D20589974B3E96),
+ opencity (D53A815A3CB7659AF882E3958EEDCC1BAA1F32FF),
+ osmo (62FF8A7584E029569546000674263B37F5B5F913),
+ performous (D53A815A3CB7659AF882E3958EEDCC1BAA1F32FF),
+ pyblosxom (62FF8A7584E029569546000674263B37F5B5F913),
+ xarchiver (62FF8A7584E029569546000674263B37F5B5F913)
+
+Fingerprint: ED6762360784E331E25303D6025AFE95AC9DF31B
+Uid: Markus Wanner <markus@bluegap.ch>
+Allow: asio (BBBD45EA818AB86FF67E7285D3E17383CFA7FF06),
+ flightgear (9A57344C54D3C8610A5F22FCBAA569D0CBF12A6A),
+ flightgear-data (9A57344C54D3C8610A5F22FCBAA569D0CBF12A6A),
+ monotone (23499FA0AE0DE2BA9E18560DC7D930259DFFAAD4),
+ postgis (FED969C79E6721F57D9552706864730DF095E5E4),
+ simgear (9A57344C54D3C8610A5F22FCBAA569D0CBF12A6A)
+
+Fingerprint: AC297E5C46B9D0B61C717681D6D09BE48405BBF6
+Uid: Mathias Behrle <mathiasb@mbsolutions.selfip.biz>
+Allow: hgnested (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ openoffice-python (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ python-sql (6D2EB3CAF6BCD06EEF42247F60305B31C09FD35A),
+ pywebdav (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ relatorio (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ suds (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-client (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-meta (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-account (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-account-be (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-account-de-skr03 (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-account-invoice (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-account-invoice-history (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-account-invoice-line-standalone (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-account-product (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-account-statement (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-analytic-account (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-analytic-invoice (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-analytic-purchase (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-analytic-sale (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-calendar (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-calendar-classification (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-calendar-scheduling (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-calendar-todo (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-company (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-company-work-time (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-country (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-currency (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-dashboard (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-google-maps (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-ldap-authentication (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-ldap-connection (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-party (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-party-siret (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-party-vcarddav (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-product (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-product-cost-fifo (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-product-cost-history (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-product-price-list (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-project (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-project-plan (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-project-revenue (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-purchase (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-purchase-invoice-line-standalone (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-sale (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-sale-opportunity (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-sale-price-list (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-stock (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-stock-forecast (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-stock-inventory-location (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-stock-location-sequence (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-stock-lot (6D2EB3CAF6BCD06EEF42247F60305B31C09FD35A),
+ tryton-modules-stock-product-location (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-stock-supply (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-stock-supply-day (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-modules-timesheet (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-neso (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-proteus (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ tryton-server (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ vatnumber (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E)
+
+Fingerprint: 20BB3CCECFE50A6139A57E9A1BB375334D750376
+Uid: Mats Erik Andersson <mats.andersson@gisladisker.se>
+Allow: netsed (7C0717F9FA2B2B9D788B141BA6DC24D9DA2493D1)
+
+Fingerprint: 95FAA648DE7D0AECC8DDCB984E9018E0A329126B
+Uid: Matteo Cypriani <mcy@lm7.fr>
+Allow: astyle (00C3E184E8AF12711856DFD280D0A42FF2C850CA),
+ pyfeed (5F35F67BAFA8B6D581CA08EBD003852FBD52529E),
+ salutatoi (5F35F67BAFA8B6D581CA08EBD003852FBD52529E),
+ urwid-satext (5F35F67BAFA8B6D581CA08EBD003852FBD52529E),
+ xmlelements (5F35F67BAFA8B6D581CA08EBD003852FBD52529E)
+
+Fingerprint: 4E8E810A6B445FDE68DAD0258062398983B2CF7A
+Uid: Matteo F. Vescovi <mfv.debian@gmail.com>
+Allow: babl (73ED4244FD43588620AC2644258494BA917A225E),
+ blender (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ entangle (6D2EB3CAF6BCD06EEF42247F60305B31C09FD35A),
+ gegl (73ED4244FD43588620AC2644258494BA917A225E),
+ glew (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ gtkpod (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ libebml (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ libmatroska (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ mp4v2 (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ opencolorio (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ openimageio (2BABC6254E66E7B8450AC3E1E6AA90171392B174),
+ totalopenstation (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ yafaray (04160004A8276E40BB9890FBE8A48AE5311D765A),
+ yafaray-exporter (04160004A8276E40BB9890FBE8A48AE5311D765A)
+
+Fingerprint: 7F6A9F44282018E218DE24AACF49D0E68A2FAFBC
+Uid: Matthijs Kooijman <matthijs@stdin.nl>
+Allow: catcodec (73ED4244FD43588620AC2644258494BA917A225E),
+ grfcodec (73ED4244FD43588620AC2644258494BA917A225E),
+ nforenum (73ED4244FD43588620AC2644258494BA917A225E),
+ nml (73ED4244FD43588620AC2644258494BA917A225E),
+ openttd (73ED4244FD43588620AC2644258494BA917A225E),
+ openttd-opengfx (73ED4244FD43588620AC2644258494BA917A225E),
+ openttd-openmsx (73ED4244FD43588620AC2644258494BA917A225E),
+ openttd-opensfx (73ED4244FD43588620AC2644258494BA917A225E)
+
+Fingerprint: D8812F4065320B8DCA3CEF18694CADEF51C7B5B6
+Uid: Michael Fladischer <FladischerMichael@fladi.at>
+Allow: cairosvg (A9592C521CB904077D6598009D0B5E5B1EEC8F0E),
+ django-countries (CDB5A1243ACDB63009AD07212D4EB3A6015475F5),
+ django-floppyforms (A9592C521CB904077D6598009D0B5E5B1EEC8F0E),
+ django-markupfield (CDB5A1243ACDB63009AD07212D4EB3A6015475F5),
+ django-nose (CDB5A1243ACDB63009AD07212D4EB3A6015475F5),
+ django-picklefield (CDB5A1243ACDB63009AD07212D4EB3A6015475F5),
+ django-reversion (F78CBA07817BB149A11D339069F2FC516EA71993),
+ sorl-thumbnail (A9592C521CB904077D6598009D0B5E5B1EEC8F0E)
+
+Fingerprint: DD66EBB6F93FAA322F0CB74E49F47A3A48F81543
+Uid: Michael Ziegler <diese-addy@funzt-halt.net>
+Allow: mumble-django (8A306ED1C122B4D524D05137A780421C68021CE4),
+ python-django-extdirect (DDC53E5130FD08223F98945649086AD3EBE2F31F),
+ python-django-piston (DDC53E5130FD08223F98945649086AD3EBE2F31F),
+ python-django-rosetta (DDC53E5130FD08223F98945649086AD3EBE2F31F)
+
+Fingerprint: D003326F93F33345EF9ADC4BD61360421C9B1AE5
+Uid: Michele Cane <michele.cane@gmail.com>
+Allow: lyz (C331BA3F75FB723B5873785B06EAA066E397832F),
+ zotero-standalone-build (C331BA3F75FB723B5873785B06EAA066E397832F)
+
+Fingerprint: 4CB7FE1E280ECC90F29A597E6E608B637D8967E9
+Uid: Miguel Landaeta <miguel@miguel.cc>
+Allow: codenarc (E50AFD55ADD27AAB97163A8B21D20589974B3E96),
+ eclipselink (E50AFD55ADD27AAB97163A8B21D20589974B3E96),
+ euca2ools (E50AFD55ADD27AAB97163A8B21D20589974B3E96),
+ gant (E50AFD55ADD27AAB97163A8B21D20589974B3E96),
+ gmetrics (E50AFD55ADD27AAB97163A8B21D20589974B3E96),
+ groovy (E50AFD55ADD27AAB97163A8B21D20589974B3E96),
+ hawtjni (E50AFD55ADD27AAB97163A8B21D20589974B3E96),
+ jansi (E50AFD55ADD27AAB97163A8B21D20589974B3E96),
+ jansi-native (E50AFD55ADD27AAB97163A8B21D20589974B3E96),
+ libhibernate-jbosscache-java (E50AFD55ADD27AAB97163A8B21D20589974B3E96),
+ libhibernate3-java (E50AFD55ADD27AAB97163A8B21D20589974B3E96),
+ libjsr166y-java (B3131A451DBFDF7CA05B4197054BBB9F7D806442),
+ libjsr305-java (E50AFD55ADD27AAB97163A8B21D20589974B3E96),
+ libspring-java (E50AFD55ADD27AAB97163A8B21D20589974B3E96),
+ libspring-security-2.0-java (E50AFD55ADD27AAB97163A8B21D20589974B3E96),
+ libspring-webflow-2.0-java (E50AFD55ADD27AAB97163A8B21D20589974B3E96),
+ libswarmcache-java (E50AFD55ADD27AAB97163A8B21D20589974B3E96),
+ openjpa (E50AFD55ADD27AAB97163A8B21D20589974B3E96),
+ pyzmq (E50AFD55ADD27AAB97163A8B21D20589974B3E96),
+ svnkit (E50AFD55ADD27AAB97163A8B21D20589974B3E96),
+ tomcat7 (E50AFD55ADD27AAB97163A8B21D20589974B3E96),
+ xpra (E50AFD55ADD27AAB97163A8B21D20589974B3E96)
+
+Fingerprint: 930750035A15E27C3830740728FA801A43BDD637
+Uid: Mike Miller <mtmiller@ieee.org>
+Allow: cdargs (424E14D703E7C6D43D9D6F364E7160ED4AC8EE1D),
+ deltarpm (424E14D703E7C6D43D9D6F364E7160ED4AC8EE1D),
+ network-manager-openconnect (424E14D703E7C6D43D9D6F364E7160ED4AC8EE1D),
+ openconnect (424E14D703E7C6D43D9D6F364E7160ED4AC8EE1D),
+ rlwrap (424E14D703E7C6D43D9D6F364E7160ED4AC8EE1D),
+ vpnc-scripts (424E14D703E7C6D43D9D6F364E7160ED4AC8EE1D)
+
+Fingerprint: F532DA10E563EE84440977A19D0470BDA6CDC457
+Uid: Neutron Soutmun <neo.neutron@gmail.com>
+Allow: flvmeta (32CC490E5A70B23DD6AA5AB2A2EBAED1B6F90241),
+ ipset (3BE9A67148F348F23E1E2076C72B51EE9D80F36D),
+ xiterm+thai (32CC490E5A70B23DD6AA5AB2A2EBAED1B6F90241)
+
+Fingerprint: A9272E9AB03BF2D1DC4C99D4C4DCB25AD71A5972
+Uid: Nicolas Bourdaud <nicolas.bourdaud@mindmaze.ch>
+Allow: cinnamon (A7ADD22D796EA275C7C9D1033036AD8EA51A4FDD),
+ muffin (A7ADD22D796EA275C7C9D1033036AD8EA51A4FDD)
+
+Fingerprint: BAFC6C85F7CB143FEEB6FB157115AFD07710DCF7
+Uid: Ole Streicher <debian@liska.ath.cx>
+Allow: cpl (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ cpl-plugin-amber (BCADFB52B41998D74D99D98E93945348E0DC2840),
+ cpl-plugin-fors (BCADFB52B41998D74D99D98E93945348E0DC2840),
+ cpl-plugin-giraf (BCADFB52B41998D74D99D98E93945348E0DC2840),
+ cpl-plugin-hawki (BCADFB52B41998D74D99D98E93945348E0DC2840),
+ cpl-plugin-kmos (BCADFB52B41998D74D99D98E93945348E0DC2840),
+ cpl-plugin-sinfo (BCADFB52B41998D74D99D98E93945348E0DC2840),
+ cpl-plugin-uves (BCADFB52B41998D74D99D98E93945348E0DC2840),
+ cpl-plugin-xsh (BCADFB52B41998D74D99D98E93945348E0DC2840),
+ esorex (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ fitsverify (BCADFB52B41998D74D99D98E93945348E0DC2840),
+ ftools-fv (2CA20F848E5C29E3E410438221C747D36A461052),
+ funtools (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ iausofa-c (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ python-astropy (BBBD45EA818AB86FF67E7285D3E17383CFA7FF06),
+ python-cpl (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ python-pyds9 (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ python-pywcs (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ saods9 (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ sextractor (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ skycat (BBBD45EA818AB86FF67E7285D3E17383CFA7FF06),
+ starlink-ast (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ starlink-pal (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ tcl-fitstcl (2CA20F848E5C29E3E410438221C747D36A461052),
+ tcl-signal (2CA20F848E5C29E3E410438221C747D36A461052),
+ tk-html3 (2CA20F848E5C29E3E410438221C747D36A461052),
+ tk-table (2CA20F848E5C29E3E410438221C747D36A461052),
+ wcslib (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ wcstools (DEE63CAACE54B9B27DDCD34A88E5D733DD899610),
+ xpa (2CA20F848E5C29E3E410438221C747D36A461052)
+
+Fingerprint: 2EE7A7A517FC124CF115C354651EEFB02527DF13
+Uid: Peter Pentchev <roam@ringlet.net>
+Allow: bomstrip (AEA0C44ECB056E93630D9D33DBBE9D4D99D2A004),
+ cookietool (80E976F14A508A48E9CA3FE9BC372252CA1CF964),
+ donkey (E90F0889545E78C82A9DE74EAF2283AA76E2AC7B),
+ freealut (80E976F14A508A48E9CA3FE9BC372252CA1CF964),
+ gtkcookie (D1E1316E93A760A8104D85FABB3A68018649AA06),
+ mbuffer (E90F0889545E78C82A9DE74EAF2283AA76E2AC7B),
+ mdk (D1E1316E93A760A8104D85FABB3A68018649AA06),
+ prips (E50AFD55ADD27AAB97163A8B21D20589974B3E96),
+ qliss3d (E90F0889545E78C82A9DE74EAF2283AA76E2AC7B),
+ tina (E90F0889545E78C82A9DE74EAF2283AA76E2AC7B)
+
+Fingerprint: 1B49F933916A37A3F45A1812015F4DD4A70FB705
+Uid: Phillip Susi <psusi@ubuntu.com>
+Allow: gparted (C6045C813887B77C2DFF97A57C56ACFE947897D8)
+
+Fingerprint: 0CA75D987B8ECF6EA9443AD839091E8123CE1C09
+Uid: Prach Pongpanich <prachpub@gmail.com>
+Allow: doodle (32CC490E5A70B23DD6AA5AB2A2EBAED1B6F90241),
+ php-cache-lite (E4F0EDDF374F2C50D4735EC097833DC998EF9A49),
+ php-calendar (E4F0EDDF374F2C50D4735EC097833DC998EF9A49),
+ php-html-template-it (E4F0EDDF374F2C50D4735EC097833DC998EF9A49),
+ php-http (E0D3FAAA6F50A5DA9D5B293833961588E1C21845),
+ php-mdb2-driver-mysql (E4F0EDDF374F2C50D4735EC097833DC998EF9A49),
+ php-mdb2-driver-pgsql (E4F0EDDF374F2C50D4735EC097833DC998EF9A49),
+ php-net-dime (E4F0EDDF374F2C50D4735EC097833DC998EF9A49),
+ php-net-ldap (E0D3FAAA6F50A5DA9D5B293833961588E1C21845),
+ php-net-sieve (E4F0EDDF374F2C50D4735EC097833DC998EF9A49),
+ php-net-socket (E0D3FAAA6F50A5DA9D5B293833961588E1C21845),
+ php-net-url (E0D3FAAA6F50A5DA9D5B293833961588E1C21845),
+ php-services-weather (E4F0EDDF374F2C50D4735EC097833DC998EF9A49),
+ php-soap (E4F0EDDF374F2C50D4735EC097833DC998EF9A49),
+ ruby-bson (30414D81DC28290C25686DE3DA4958F611E149E9),
+ ruby-bson-ext (30414D81DC28290C25686DE3DA4958F611E149E9),
+ ruby-mongo (30414D81DC28290C25686DE3DA4958F611E149E9)
+
+Fingerprint: A5F9C48C4059B6886CC57A426F7818A9B98F62B1
+Uid: Ralph Amissah <ralph.amissah@gmail.com>
+Allow: diakonos (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ rant (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ sisu (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E),
+ sisu-markup-samples (709F54E4ECF3195623326AE3F82E5CC04B2B2B9E)
+
+Fingerprint: B51A517CB49EFC4F07E0E79CA49E7C3CDE3CCE66
+Uid: Reto Buerki <reet@codelabs.ch>
+Allow: ahven (BBBD45EA818AB86FF67E7285D3E17383CFA7FF06),
+ apq (BBBD45EA818AB86FF67E7285D3E17383CFA7FF06),
+ apq-postgresql (BBBD45EA818AB86FF67E7285D3E17383CFA7FF06),
+ dbusada (BBBD45EA818AB86FF67E7285D3E17383CFA7FF06),
+ libalog (BBBD45EA818AB86FF67E7285D3E17383CFA7FF06),
+ pcscada (BBBD45EA818AB86FF67E7285D3E17383CFA7FF06)
+
+Fingerprint: 24093F016FFE8602EF449BB84C8EF3DA3FD37230
+Uid: Reuben Thomas <rrt@sc3d.org>
+Allow: libpaper (655426C9FA10C1D1D3D199C221672570174FEE35),
+ plptools (655426C9FA10C1D1D3D199C221672570174FEE35),
+ psutils (655426C9FA10C1D1D3D199C221672570174FEE35)
+
+Fingerprint: AE927C799353DB4609B273BC085A9B327C2CAEB8
+Uid: Rogério Theodoro de Brito <rbrito@ime.usp.br>
+Allow: avr-evtd (C42623B760FA5999693F0782690D6214A504FECA),
+ cdparanoia (C42623B760FA5999693F0782690D6214A504FECA),
+ dvd+rw-tools (C42623B760FA5999693F0782690D6214A504FECA),
+ dvdisaster (C42623B760FA5999693F0782690D6214A504FECA),
+ fontforge (C42623B760FA5999693F0782690D6214A504FECA),
+ fontforge-doc (C42623B760FA5999693F0782690D6214A504FECA),
+ fonts-anonymous-pro (C42623B760FA5999693F0782690D6214A504FECA),
+ fonts-linuxlibertine (C42623B760FA5999693F0782690D6214A504FECA),
+ handbrake (C42623B760FA5999693F0782690D6214A504FECA),
+ libtorrent (C42623B760FA5999693F0782690D6214A504FECA),
+ parallel (C42623B760FA5999693F0782690D6214A504FECA),
+ rtorrent (C42623B760FA5999693F0782690D6214A504FECA),
+ tunesviewer (C42623B760FA5999693F0782690D6214A504FECA),
+ usbmount (C42623B760FA5999693F0782690D6214A504FECA),
+ vrms (C42623B760FA5999693F0782690D6214A504FECA),
+ youtube-dl (C42623B760FA5999693F0782690D6214A504FECA)
+
+Fingerprint: 0386CF054A81948D6D9FA7EA94842BC86E511C31
+Uid: Rolf Leggewie <foss@rolf.leggewie.biz>
+Allow: bugz (3F2568AAC26998F9E813A1C5C3F436CA30F5D8EB),
+ ffgtk (E50AFD55ADD27AAB97163A8B21D20589974B3E96),
+ fslint (3F2568AAC26998F9E813A1C5C3F436CA30F5D8EB),
+ gbirthday (3F2568AAC26998F9E813A1C5C3F436CA30F5D8EB),
+ gjots2 (E50AFD55ADD27AAB97163A8B21D20589974B3E96),
+ isdnutils (3F2568AAC26998F9E813A1C5C3F436CA30F5D8EB),
+ kasumi (3F2568AAC26998F9E813A1C5C3F436CA30F5D8EB),
+ n2n (E50AFD55ADD27AAB97163A8B21D20589974B3E96),
+ pastebinit (3F2568AAC26998F9E813A1C5C3F436CA30F5D8EB),
+ polipo (E50AFD55ADD27AAB97163A8B21D20589974B3E96),
+ scim (3F2568AAC26998F9E813A1C5C3F436CA30F5D8EB),
+ scim-anthy (3F2568AAC26998F9E813A1C5C3F436CA30F5D8EB)
+
+Fingerprint: 42E2C8DE8C173AB102F52C6E7E60A3A686AE8D98
+Uid: Ryan Finnie <ryan@finnie.org>
+Allow: 2ping (8A46ADB3FC4B4B24DF1728BACCDFE49B0A0AC927),
+ digitemp (8A46ADB3FC4B4B24DF1728BACCDFE49B0A0AC927),
+ grepcidr (8A46ADB3FC4B4B24DF1728BACCDFE49B0A0AC927),
+ isomd5sum (8A46ADB3FC4B4B24DF1728BACCDFE49B0A0AC927),
+ robotfindskitten (8A46ADB3FC4B4B24DF1728BACCDFE49B0A0AC927)
+
+Fingerprint: 3412EA181277354B991BC869B2197FDB5EA01078
+Uid: Sam Morris <sam@robots.org.uk>
+Allow: pymsnt (6ADD5093AC6D1072C9129000B1CCD97290267086),
+ sensors-applet (22D52C906D6677F159F10030001CDE6A6B79D401)
+
+Fingerprint: 0EED77DC41D760FDE44035FF5556A34E04A3610B
+Uid: Sascha Steinbiss <satta@tetrinetsucht.de>
+Allow: aragorn (84C1977A89E7EFED3E5CA62E2FD8BEDAC020EED1),
+ genometools (84C1977A89E7EFED3E5CA62E2FD8BEDAC020EED1),
+ ltrsift (84C1977A89E7EFED3E5CA62E2FD8BEDAC020EED1),
+ mussort (84C1977A89E7EFED3E5CA62E2FD8BEDAC020EED1)
+
+Fingerprint: 218F6A654090B4086FD42E9AA35A4E6EF00175CA
+Uid: Sebastien NOEL <me@twolife.org>
+Allow: cksfv (7E0ED3D2B34A03B15F9F3121C7F7F9660D82A682),
+ oss4 (7E0ED3D2B34A03B15F9F3121C7F7F9660D82A682)
+
+Fingerprint: DB667AE2E7C792EF6374EFDF39C9A0B640262AF0
+Uid: Sergey B Kirpichev <skirpichev@gmail.com>
+Allow: festvox-ru (1B23D4F88EC0D9020555E438AB8C00CFF8E26537),
+ libapache2-mod-bw (1B23D4F88EC0D9020555E438AB8C00CFF8E26537),
+ libapache2-mod-qos (3E4FB7117877F589DBCF06D6E619045DF2AC729A),
+ libapache2-mod-rpaf (7C0717F9FA2B2B9D788B141BA6DC24D9DA2493D1),
+ monit (63CB1DF1EF12CF2AC0EE5A329C27B31342B7511D),
+ parser (1B23D4F88EC0D9020555E438AB8C00CFF8E26537),
+ parser-mysql (1B23D4F88EC0D9020555E438AB8C00CFF8E26537),
+ php-geoip (1B23D4F88EC0D9020555E438AB8C00CFF8E26537),
+ php-memcache (C644D0B392F48FE44662B5411558944599E81DA0),
+ php-memcached (C644D0B392F48FE44662B5411558944599E81DA0)
+
+Fingerprint: 19FBC4D3DAE94406B13A9DDE19755664855E7273
+Uid: Simon Chopin <chopin.simon@gmail.com>
+Allow: alot (8F049AD82C92066C7352D28A7B585B30807C2A87),
+ pytest (F78CBA07817BB149A11D339069F2FC516EA71993)
+
+Fingerprint: 0424D4EE81A0E3D119C6F835EDA21E94B565716F
+Uid: Simon Josefsson <jas@extundo.com>
+Allow: gss (E784364E8DDE7BB370FBD9EAD15D313882004173),
+ libidn (C6045C813887B77C2DFF97A57C56ACFE947897D8),
+ libidn2-0 (C6045C813887B77C2DFF97A57C56ACFE947897D8),
+ libntlm (DF813B226DD39A2C530F6F7D0ABA650372FD9571),
+ oath-toolkit (A28411A596193171331802C0B65A4871CA19D717),
+ python-pyhsm (A28411A596193171331802C0B65A4871CA19D717),
+ python-yubico (A28411A596193171331802C0B65A4871CA19D717),
+ shishi (E784364E8DDE7BB370FBD9EAD15D313882004173),
+ ykclient (A28411A596193171331802C0B65A4871CA19D717),
+ yubico-pam (A28411A596193171331802C0B65A4871CA19D717),
+ yubikey-ksm (0EE5BE979282D80B9F7540F1CCD2ED94D21739E9),
+ yubikey-personalization (A28411A596193171331802C0B65A4871CA19D717),
+ yubikey-personalization-gui (A28411A596193171331802C0B65A4871CA19D717),
+ yubikey-val (0EE5BE979282D80B9F7540F1CCD2ED94D21739E9)
+
+Fingerprint: 50713992E9A91E77240128915675ADD39CF02226
+Uid: Simone Rossetto <simros85@gmail.com>
+Allow: qsapecng (FED969C79E6721F57D9552706864730DF095E5E4)
+
+Fingerprint: 4C004DF209F263ABFD8638C795DE059D8D7FCA91
+Uid: Stefan Potyra <sistpoty@ubuntu.com>
+Allow: faucc (1B954398F362AD2DC5E6718BBAB12CF0C7C58F7C),
+ fauhdlc (1B954398F362AD2DC5E6718BBAB12CF0C7C58F7C),
+ faumachine (1B954398F362AD2DC5E6718BBAB12CF0C7C58F7C),
+ invaders (1B954398F362AD2DC5E6718BBAB12CF0C7C58F7C),
+ min12xxw (1B954398F362AD2DC5E6718BBAB12CF0C7C58F7C),
+ trigger-rally (1B954398F362AD2DC5E6718BBAB12CF0C7C58F7C),
+ trigger-rally-data (1B954398F362AD2DC5E6718BBAB12CF0C7C58F7C)
+
+Fingerprint: 3C0B6EB0AB2729E8CE2255A7385AE490868EFA66
+Uid: Stefan Völkel <stefan@bc-bd.org>
+Allow: dtach (CC992DDDD39E75B0B0AAB25CD35BBC99BC7D020A),
+ revelation (CC992DDDD39E75B0B0AAB25CD35BBC99BC7D020A)
+
+Fingerprint: 522D7163831C73A635D12FE5EC371482956781AF
+Uid: Sven Eckelmann <sven@narfation.org>
+Allow: batctl (B8BF54137B09D35CF026FE9D091AB856069AAA1C),
+ batmand (B8BF54137B09D35CF026FE9D091AB856069AAA1C),
+ exactimage (693367FFAECD8EAACD1F063B0171E1828AE09345),
+ g3dviewer (B8BF54137B09D35CF026FE9D091AB856069AAA1C),
+ libg3d (B8BF54137B09D35CF026FE9D091AB856069AAA1C),
+ mupen64plus (1D2FA89858DAAF6217862DF7AEF6F1A2A7457645),
+ mupen64plus-audio-sdl (1D2FA89858DAAF6217862DF7AEF6F1A2A7457645),
+ mupen64plus-core (1D2FA89858DAAF6217862DF7AEF6F1A2A7457645),
+ mupen64plus-input-sdl (1D2FA89858DAAF6217862DF7AEF6F1A2A7457645),
+ mupen64plus-rsp-hle (1D2FA89858DAAF6217862DF7AEF6F1A2A7457645),
+ mupen64plus-rsp-z64 (1D2FA89858DAAF6217862DF7AEF6F1A2A7457645),
+ mupen64plus-ui-console (1D2FA89858DAAF6217862DF7AEF6F1A2A7457645),
+ mupen64plus-video-arachnoid (1D2FA89858DAAF6217862DF7AEF6F1A2A7457645),
+ mupen64plus-video-glide64 (1D2FA89858DAAF6217862DF7AEF6F1A2A7457645),
+ mupen64plus-video-rice (1D2FA89858DAAF6217862DF7AEF6F1A2A7457645),
+ mupen64plus-video-z64 (1D2FA89858DAAF6217862DF7AEF6F1A2A7457645),
+ s3d (B8BF54137B09D35CF026FE9D091AB856069AAA1C)
+
+Fingerprint: 285F2178A82FE496A2E69E103B106E718D6B31AC
+Uid: Sven Joachim <svenjoac@gmx.de>
+Allow: autoconf-dickey (5D2FB320B825D93904D205193938F96BDF50FEA5),
+ ncurses (5D2FB320B825D93904D205193938F96BDF50FEA5),
+ xserver-xorg-video-nouveau (7B27A3F1A6E18CD9588B4AE8310180050905E40C),
+ xterm (7B27A3F1A6E18CD9588B4AE8310180050905E40C)
+
+Fingerprint: 1C17166DC55780466188885142EE72DAC27319AD
+Uid: Thomas Bechtold <thomasbechtold@jpberlin.de>
+Allow: check (2B12EC0512C01AE25D559F3F4BA00E72145B6966),
+ d-feet (09B3AC2ECB169C904345CC546AE1DF0D608F22DC)
+
+Fingerprint: 0D86778C9DDFC4600BBBDB1710A46FFB90D53618
+Uid: Thomas Friedrichsmeier <thomas.friedrichsmeier@ruhr-uni-bochum.de>
+Allow: rkward (73471499CC60ED9EEE805946C5BD6C8F2295D502)
+
+Fingerprint: 76040205597FA696F1313BCA07FC4891042BA65A
+Uid: Thomas Koch <thomas.koch@ymc.ch>
+Allow: maven-debian-helper (E50AFD55ADD27AAB97163A8B21D20589974B3E96),
+ maven-repo-helper (E50AFD55ADD27AAB97163A8B21D20589974B3E96)
+
+Fingerprint: F69AFC03593F5D866B748D908BDEAA5952784983
+Uid: Thomas Krennwallner <tkren@kr.tuwien.ac.at>
+Allow: clasp (467348C85F3A5A9B2B0D169EBF1E9D1F76D52AC4),
+ coala (467348C85F3A5A9B2B0D169EBF1E9D1F76D52AC4),
+ depqbf (467348C85F3A5A9B2B0D169EBF1E9D1F76D52AC4),
+ gringo (467348C85F3A5A9B2B0D169EBF1E9D1F76D52AC4),
+ runlim (467348C85F3A5A9B2B0D169EBF1E9D1F76D52AC4)
+
+Fingerprint: 92429807C9853C0744A68B9AAE07828059A53CC1
+Uid: Thomas Leonard <tal197@users.sourceforge.net>
+Allow: zeroinstall-injector (3BE9A67148F348F23E1E2076C72B51EE9D80F36D)
+
+Fingerprint: E4D7FFE905DC76C272F331D5381D2AC78124B100
+Uid: Thomas Müller <thomas.mueller@tmit.eu>
+Allow: owncloud (B73B7544AA9E528E838E88F9241061CA50064181),
+ php-sabredav (B73B7544AA9E528E838E88F9241061CA50064181),
+ quassel (09B3AC2ECB169C904345CC546AE1DF0D608F22DC)
+
+Fingerprint: D5EA745C0B1FA932EB5CF7DA771750766E1C720B
+Uid: Thomas Pierson <contact@thomaspierson.fr>
+Allow: clementine (12DDFA84AC23B2BBF04B313CAB645F406286A7D0),
+ libqxt (ECF2DEA89EB90C612440B2B84814DEC22B307C3C)
+
+Fingerprint: 752DE27C4DEB17019B4B6623CB703165A88984DC
+Uid: Timo Aaltonen <tjaalton@ubuntu.com>
+Allow: ding-libs (7E0ED3D2B34A03B15F9F3121C7F7F9660D82A682),
+ libwacom (90750D0B5211C8962AD67137853575EC4A08B2FE),
+ sssd (7E0ED3D2B34A03B15F9F3121C7F7F9660D82A682)
+
+Fingerprint: 8CCC1BA8590FF029D17C708FC1BCD3C72AA28B6B
+Uid: Tobias Stefan Richter <tsr@achos.com>
+Allow: nexus (B66CD97ACE820575D38E8B7B79B0126693701EEF)
+
+Fingerprint: 9E932D1E0B31CF850AFC799E6F3E6153163E0577
+Uid: Tom Jampen <tom@cryptography.ch>
+Allow: rt-authen-externalauth (A4AD7A700EE5F70F31B16FA32127371B9BB23062),
+ texstudio (A4AD7A700EE5F70F31B16FA32127371B9BB23062)
+
+Fingerprint: 9E81ED79FA81D45C0F830E139FB9262724B17D29
+Uid: Tomasz Buchert <tomek.buchert@gmail.com>
+Allow: miredo (BBBD45EA818AB86FF67E7285D3E17383CFA7FF06),
+ stellarium (8C470B2A0B31568E110D432516281F2E007C98D1),
+ verbiste (8C470B2A0B31568E110D432516281F2E007C98D1)
+
+Fingerprint: A481824E7DD39C0EC40A488EC654FB332AD59860
+Uid: Tomasz Rybak <bogomips@post.pl>
+Allow: pycuda (1D2FA89858DAAF6217862DF7AEF6F1A2A7457645),
+ pyopencl (1D2FA89858DAAF6217862DF7AEF6F1A2A7457645),
+ pytools (1D2FA89858DAAF6217862DF7AEF6F1A2A7457645)
+
+Fingerprint: CC7A536FE267C679439BE8C429572B2F500BF4A2
+Uid: Tshepang Lekhonkhobe <tshepang@gmail.com>
+Allow: wajig (344C9EC6707AFA176FE260B1099491F791B0D3B7)
+
+Fingerprint: 0D2511F322BFAB1C1580266BE2DCDD9132669BD6
+Uid: Uwe Kleine-König <uwe@kleine-koenig.org>
+Allow: rt-tests (EE01B7C2126C2847EADFC720C24B65A2672C8B12)
+
+Fingerprint: C517C25DE408759D98A4C96B6C8F74AE87700B7E
+Uid: Vasudev Kamath <kamathvasudev@gmail.com>
+Allow: aspell-kn (190A8C7607743E3130603836A1183F8ED1028C8D),
+ ctpp2 (9FE3E9C36691A69FF53CC6842C7C3146C1A00121),
+ dwm (190A8C7607743E3130603836A1183F8ED1028C8D),
+ editorconfig-core (9FE3E9C36691A69FF53CC6842C7C3146C1A00121),
+ fonts-beng (190A8C7607743E3130603836A1183F8ED1028C8D),
+ fonts-beng-extra (190A8C7607743E3130603836A1183F8ED1028C8D),
+ fonts-deva (190A8C7607743E3130603836A1183F8ED1028C8D),
+ fonts-deva-extra (190A8C7607743E3130603836A1183F8ED1028C8D),
+ fonts-eeyek (190A8C7607743E3130603836A1183F8ED1028C8D),
+ fonts-font-awesome (9BFBAEE86C0AA5FFBF2207829AF46B3025771B31),
+ fonts-gubbi (190A8C7607743E3130603836A1183F8ED1028C8D),
+ fonts-gujr (190A8C7607743E3130603836A1183F8ED1028C8D),
+ fonts-gujr-extra (190A8C7607743E3130603836A1183F8ED1028C8D),
+ fonts-guru (190A8C7607743E3130603836A1183F8ED1028C8D),
+ fonts-guru-extra (190A8C7607743E3130603836A1183F8ED1028C8D),
+ fonts-indic (190A8C7607743E3130603836A1183F8ED1028C8D),
+ fonts-johnsmith-induni (D5C2F9BFCA128BBA22A77218872F702C4D6E25A8),
+ fonts-knda (190A8C7607743E3130603836A1183F8ED1028C8D),
+ fonts-knda-extra (190A8C7607743E3130603836A1183F8ED1028C8D),
+ fonts-lato (D5C2F9BFCA128BBA22A77218872F702C4D6E25A8),
+ fonts-lohit-beng-assamese (190A8C7607743E3130603836A1183F8ED1028C8D),
+ fonts-lohit-beng-bengali (190A8C7607743E3130603836A1183F8ED1028C8D),
+ fonts-lohit-deva (190A8C7607743E3130603836A1183F8ED1028C8D),
+ fonts-lohit-gujr (190A8C7607743E3130603836A1183F8ED1028C8D),
+ fonts-lohit-guru (190A8C7607743E3130603836A1183F8ED1028C8D),
+ fonts-lohit-knda (190A8C7607743E3130603836A1183F8ED1028C8D),
+ fonts-lohit-mlym (190A8C7607743E3130603836A1183F8ED1028C8D),
+ fonts-lohit-orya (190A8C7607743E3130603836A1183F8ED1028C8D),
+ fonts-lohit-taml (190A8C7607743E3130603836A1183F8ED1028C8D),
+ fonts-lohit-taml-classical (190A8C7607743E3130603836A1183F8ED1028C8D),
+ fonts-lohit-telu (190A8C7607743E3130603836A1183F8ED1028C8D),
+ fonts-mlym (190A8C7607743E3130603836A1183F8ED1028C8D),
+ fonts-nakula (190A8C7607743E3130603836A1183F8ED1028C8D),
+ fonts-navilu (190A8C7607743E3130603836A1183F8ED1028C8D),
+ fonts-orya (190A8C7607743E3130603836A1183F8ED1028C8D),
+ fonts-orya-extra (190A8C7607743E3130603836A1183F8ED1028C8D),
+ fonts-pagul (190A8C7607743E3130603836A1183F8ED1028C8D),
+ fonts-sahadeva (190A8C7607743E3130603836A1183F8ED1028C8D),
+ fonts-samyak (190A8C7607743E3130603836A1183F8ED1028C8D),
+ fonts-smc (190A8C7607743E3130603836A1183F8ED1028C8D),
+ fonts-taml (190A8C7607743E3130603836A1183F8ED1028C8D),
+ fonts-taml-tamu (190A8C7607743E3130603836A1183F8ED1028C8D),
+ fonts-taml-tscu (190A8C7607743E3130603836A1183F8ED1028C8D),
+ fonts-telu (190A8C7607743E3130603836A1183F8ED1028C8D),
+ fonts-telu-extra (190A8C7607743E3130603836A1183F8ED1028C8D),
+ fonts-teluguvijayam (190A8C7607743E3130603836A1183F8ED1028C8D),
+ pugixml (9FE3E9C36691A69FF53CC6842C7C3146C1A00121),
+ pypdflib (190A8C7607743E3130603836A1183F8ED1028C8D),
+ ttf-indic-fonts (190A8C7607743E3130603836A1183F8ED1028C8D),
+ zimlib (9FE3E9C36691A69FF53CC6842C7C3146C1A00121)
+
+Fingerprint: D53A815A3CB7659AF882E3958EEDCC1BAA1F32FF
+Uid: Vincent Cheng <Vincentc1208@gmail.com>
+Allow: 0ad (314E3B2D605A6EB35A7D8119F628EB934743206C),
+ 0ad-data (314E3B2D605A6EB35A7D8119F628EB934743206C),
+ ardentryst (7A33ECAA188B96F27C917288B3464F896AA15948),
+ bumblebee (693367FFAECD8EAACD1F063B0171E1828AE09345),
+ cherrytree (3A9E7D149697510A3E37CD95C38E8160A17841FE),
+ conky (A6C7B88B9583046A11C5403E0B00FB6CEBE2D002),
+ conky-all (A6C7B88B9583046A11C5403E0B00FB6CEBE2D002),
+ dbus-c++ (7A33ECAA188B96F27C917288B3464F896AA15948),
+ exaile (7A33ECAA188B96F27C917288B3464F896AA15948),
+ gnote (7A33ECAA188B96F27C917288B3464F896AA15948),
+ irrlicht (9FED5C6CE206B70A585770CA965522B9D49AE731),
+ mailnag (3A9E7D149697510A3E37CD95C38E8160A17841FE),
+ mangler (7CE3D0F2EE36C94FAEF6139C721A2B30C154998C),
+ primus (693367FFAECD8EAACD1F063B0171E1828AE09345),
+ pygame (F58548B594504CC6AD853EC3F41FED8E33FC40A4),
+ supertuxkart (9FED5C6CE206B70A585770CA965522B9D49AE731),
+ wesnoth-1.10 (5393ACF308011A978845026331FCE7E7DD079461),
+ wesnoth-1.11 (5393ACF308011A978845026331FCE7E7DD079461)
+
+Fingerprint: 081CB7CDFF042BA994EA36B28B7F7D30CAF14EFC
+Uid: Wolodja Wentland <babilen@gmail.com>
+Allow: leiningen (8F049AD82C92066C7352D28A7B585B30807C2A87),
+ robert-hooke (8F049AD82C92066C7352D28A7B585B30807C2A87)
+
+Fingerprint: 11F4DE9F1FAF6581F2BA3496548662D00E41645E
+Uid: Xavier Grave <xavier.grave@ipno.in2p3.fr>
+Allow: liblog4ada (23499FA0AE0DE2BA9E18560DC7D930259DFFAAD4),
+ libxmlezout (23499FA0AE0DE2BA9E18560DC7D930259DFFAAD4),
+ music123 (23499FA0AE0DE2BA9E18560DC7D930259DFFAAD4),
+ polyorb (23499FA0AE0DE2BA9E18560DC7D930259DFFAAD4),
+ python-gnatpython (23499FA0AE0DE2BA9E18560DC7D930259DFFAAD4)
+
+Fingerprint: B4F77FC8667E12B6587FE893DE39E437F2CC5398
+Uid: Xiangfu Liu <xiangfu@openmobilefree.net>
+Allow: fped (84C1977A89E7EFED3E5CA62E2FD8BEDAC020EED1),
+ xburst-tools (84C1977A89E7EFED3E5CA62E2FD8BEDAC020EED1)
+
+Fingerprint: B86CB5487CC4B58F0CA3856E7EE852DEE6B78725
+Uid: Yauheni Kaliuta <yauheni.kaliuta@nokia.com>
+Allow: dictem (A8DF13269E5D9A38E57CFAC29D20F6503E338888)
+
+Fingerprint: 66A4EA704FE240558D6AC2E69394F354891D7E07
+Uid: Youhei SASAKI <uwabami@gfd-dennou.org>
+Allow: cairo-dock (5E629EE5232197357B84CF4332247FBB40AD1FA6),
+ cairo-dock-plug-ins (5E629EE5232197357B84CF4332247FBB40AD1FA6),
+ cmigemo (5E629EE5232197357B84CF4332247FBB40AD1FA6),
+ coderay (0B29D88E42E6B765B8D8EA507839619DD439668E),
+ howm (0B29D88E42E6B765B8D8EA507839619DD439668E),
+ jekyll (30414D81DC28290C25686DE3DA4958F611E149E9),
+ pry (0B29D88E42E6B765B8D8EA507839619DD439668E),
+ rabbit (0B29D88E42E6B765B8D8EA507839619DD439668E),
+ rail (0B29D88E42E6B765B8D8EA507839619DD439668E),
+ rake-compiler (30414D81DC28290C25686DE3DA4958F611E149E9),
+ rttool (30414D81DC28290C25686DE3DA4958F611E149E9),
+ ruby-albino (30414D81DC28290C25686DE3DA4958F611E149E9),
+ ruby-bacon (30414D81DC28290C25686DE3DA4958F611E149E9),
+ ruby-bluefeather (30414D81DC28290C25686DE3DA4958F611E149E9),
+ ruby-classifier (30414D81DC28290C25686DE3DA4958F611E149E9),
+ ruby-directory-watcher (30414D81DC28290C25686DE3DA4958F611E149E9),
+ ruby-eim-xml (30414D81DC28290C25686DE3DA4958F611E149E9),
+ ruby-fast-stemmer (30414D81DC28290C25686DE3DA4958F611E149E9),
+ ruby-fftw3 (30414D81DC28290C25686DE3DA4958F611E149E9),
+ ruby-grib (30414D81DC28290C25686DE3DA4958F611E149E9),
+ ruby-hdfeos5 (6D2EB3CAF6BCD06EEF42247F60305B31C09FD35A),
+ ruby-hikidoc (30414D81DC28290C25686DE3DA4958F611E149E9),
+ ruby-kramdown (30414D81DC28290C25686DE3DA4958F611E149E9),
+ ruby-lapack (30414D81DC28290C25686DE3DA4958F611E149E9),
+ ruby-mathml (30414D81DC28290C25686DE3DA4958F611E149E9),
+ ruby-method-source (30414D81DC28290C25686DE3DA4958F611E149E9),
+ ruby-multibitnums (30414D81DC28290C25686DE3DA4958F611E149E9),
+ ruby-narray (30414D81DC28290C25686DE3DA4958F611E149E9),
+ ruby-narray-miss (6D2EB3CAF6BCD06EEF42247F60305B31C09FD35A),
+ ruby-net-irc (30414D81DC28290C25686DE3DA4958F611E149E9),
+ ruby-netcdf (6D2EB3CAF6BCD06EEF42247F60305B31C09FD35A),
+ ruby-ole (30414D81DC28290C25686DE3DA4958F611E149E9),
+ ruby-pgplot (30414D81DC28290C25686DE3DA4958F611E149E9),
+ ruby-posix-spawn (30414D81DC28290C25686DE3DA4958F611E149E9),
+ ruby-rack (30414D81DC28290C25686DE3DA4958F611E149E9),
+ ruby-rack-protection (30414D81DC28290C25686DE3DA4958F611E149E9),
+ ruby-rack-test (30414D81DC28290C25686DE3DA4958F611E149E9),
+ ruby-redcarpet (30414D81DC28290C25686DE3DA4958F611E149E9),
+ ruby-sdl (30414D81DC28290C25686DE3DA4958F611E149E9),
+ ruby-sinatra (30414D81DC28290C25686DE3DA4958F611E149E9),
+ ruby-slop (30414D81DC28290C25686DE3DA4958F611E149E9),
+ ruby-tilt (30414D81DC28290C25686DE3DA4958F611E149E9),
+ ruby-unf-ext (30414D81DC28290C25686DE3DA4958F611E149E9)
+
+Fingerprint: 816790FE0A75677E2A6C22C814135D277B88D7E5
+Uid: YunQiang Su <wzssyqa@gmail.com>
+Allow: brise (3A9E7D149697510A3E37CD95C38E8160A17841FE),
+ chmsee (3A9E7D149697510A3E37CD95C38E8160A17841FE),
+ fcitx (3A9E7D149697510A3E37CD95C38E8160A17841FE),
+ fcitx-anthy (3A9E7D149697510A3E37CD95C38E8160A17841FE),
+ fcitx-chewing (3A9E7D149697510A3E37CD95C38E8160A17841FE),
+ fcitx-cloudpinyin (3A9E7D149697510A3E37CD95C38E8160A17841FE),
+ fcitx-configtool (3A9E7D149697510A3E37CD95C38E8160A17841FE),
+ fcitx-fbterm (3A9E7D149697510A3E37CD95C38E8160A17841FE),
+ fcitx-googlepinyin (3A9E7D149697510A3E37CD95C38E8160A17841FE),
+ fcitx-hangul (3A9E7D149697510A3E37CD95C38E8160A17841FE),
+ fcitx-libpinyin (3A9E7D149697510A3E37CD95C38E8160A17841FE),
+ fcitx-m17n (3A9E7D149697510A3E37CD95C38E8160A17841FE),
+ fcitx-qt5 (3A9E7D149697510A3E37CD95C38E8160A17841FE),
+ fcitx-rime (3A9E7D149697510A3E37CD95C38E8160A17841FE),
+ fcitx-sayura (3A9E7D149697510A3E37CD95C38E8160A17841FE),
+ fcitx-sunpinyin (3A9E7D149697510A3E37CD95C38E8160A17841FE),
+ fcitx-table-extra (3A9E7D149697510A3E37CD95C38E8160A17841FE),
+ fcitx-table-other (3A9E7D149697510A3E37CD95C38E8160A17841FE),
+ fcitx-ui-light (3A9E7D149697510A3E37CD95C38E8160A17841FE),
+ fcitx-unikey (3A9E7D149697510A3E37CD95C38E8160A17841FE),
+ ibus-googlepinyin (3A9E7D149697510A3E37CD95C38E8160A17841FE),
+ ibus-sunpinyin (3A9E7D149697510A3E37CD95C38E8160A17841FE),
+ ibus-table (3A9E7D149697510A3E37CD95C38E8160A17841FE),
+ kcm-fcitx (3A9E7D149697510A3E37CD95C38E8160A17841FE),
+ libchewing (3A9E7D149697510A3E37CD95C38E8160A17841FE),
+ libcitygml (3A9E7D149697510A3E37CD95C38E8160A17841FE),
+ libgooglepinyin (3A9E7D149697510A3E37CD95C38E8160A17841FE),
+ libpinyin (3A9E7D149697510A3E37CD95C38E8160A17841FE),
+ libpyzy (3A9E7D149697510A3E37CD95C38E8160A17841FE),
+ librime (3A9E7D149697510A3E37CD95C38E8160A17841FE),
+ lunar-date (3A9E7D149697510A3E37CD95C38E8160A17841FE),
+ nam (3A9E7D149697510A3E37CD95C38E8160A17841FE),
+ ns2 (3A9E7D149697510A3E37CD95C38E8160A17841FE),
+ ns3 (3A9E7D149697510A3E37CD95C38E8160A17841FE),
+ open-gram (3A9E7D149697510A3E37CD95C38E8160A17841FE),
+ opencc (3A9E7D149697510A3E37CD95C38E8160A17841FE),
+ otcl (3A9E7D149697510A3E37CD95C38E8160A17841FE),
+ savi (3A9E7D149697510A3E37CD95C38E8160A17841FE),
+ sheepdog (3A9E7D149697510A3E37CD95C38E8160A17841FE),
+ sunpinyin (3A9E7D149697510A3E37CD95C38E8160A17841FE),
+ tclcl (3A9E7D149697510A3E37CD95C38E8160A17841FE)
+
+Fingerprint: 8206A19620847E6D0DF8B176BC196A94EDDDA1B7
+Uid: Ø£Ø­Ù…د Ø§Ù„محمودي <aelmahmoudy@sabily.org>
+Allow: covered (41352A3B4726ACC590940097F0A98A4C4CD6E3D2),
+ dico (6ADD5093AC6D1072C9129000B1CCD97290267086),
+ drawtiming (41352A3B4726ACC590940097F0A98A4C4CD6E3D2),
+ fonts-hosny-amiri (BD838A2BAAF9E3408BD9646833BE1A0A8C2ED8FF),
+ fribidi (C644D0B392F48FE44662B5411558944599E81DA0),
+ geda-gaf (41352A3B4726ACC590940097F0A98A4C4CD6E3D2),
+ geda-xgsch2pcb (41352A3B4726ACC590940097F0A98A4C4CD6E3D2),
+ gnucap (41352A3B4726ACC590940097F0A98A4C4CD6E3D2),
+ gst123 (BD838A2BAAF9E3408BD9646833BE1A0A8C2ED8FF),
+ gtkwave (41352A3B4726ACC590940097F0A98A4C4CD6E3D2),
+ gwave (41352A3B4726ACC590940097F0A98A4C4CD6E3D2),
+ harfbuzz (90750D0B5211C8962AD67137853575EC4A08B2FE),
+ hijra (BD838A2BAAF9E3408BD9646833BE1A0A8C2ED8FF),
+ islamic-menus (BD838A2BAAF9E3408BD9646833BE1A0A8C2ED8FF),
+ itools (BD838A2BAAF9E3408BD9646833BE1A0A8C2ED8FF),
+ iverilog (41352A3B4726ACC590940097F0A98A4C4CD6E3D2),
+ libitl (BD838A2BAAF9E3408BD9646833BE1A0A8C2ED8FF),
+ libitl-gobject (BD838A2BAAF9E3408BD9646833BE1A0A8C2ED8FF),
+ monajat (BD838A2BAAF9E3408BD9646833BE1A0A8C2ED8FF),
+ othman (BD838A2BAAF9E3408BD9646833BE1A0A8C2ED8FF),
+ pcb (41352A3B4726ACC590940097F0A98A4C4CD6E3D2),
+ pyfribidi (BD838A2BAAF9E3408BD9646833BE1A0A8C2ED8FF),
+ python-whoosh (BD838A2BAAF9E3408BD9646833BE1A0A8C2ED8FF),
+ sl-modem (BD838A2BAAF9E3408BD9646833BE1A0A8C2ED8FF),
+ thawab (BD838A2BAAF9E3408BD9646833BE1A0A8C2ED8FF),
+ verilator (41352A3B4726ACC590940097F0A98A4C4CD6E3D2),
+ xpra (1D2FA89858DAAF6217862DF7AEF6F1A2A7457645),
+ zekr (BD838A2BAAF9E3408BD9646833BE1A0A8C2ED8FF)
+
diff --git a/tests/gnupg/pubring.gpg b/tests/gnupg/pubring.gpg
new file mode 100644 (file)
index 0000000..04a9ed5
Binary files /dev/null and b/tests/gnupg/pubring.gpg differ
diff --git a/tests/gnupg/random_seed b/tests/gnupg/random_seed
new file mode 100644 (file)
index 0000000..cc75662
Binary files /dev/null and b/tests/gnupg/random_seed differ
diff --git a/tests/gnupg/secring.gpg b/tests/gnupg/secring.gpg
new file mode 100644 (file)
index 0000000..7e20df1
Binary files /dev/null and b/tests/gnupg/secring.gpg differ
diff --git a/tests/gnupg/trustdb.gpg b/tests/gnupg/trustdb.gpg
new file mode 100644 (file)
index 0000000..c51ced8
Binary files /dev/null and b/tests/gnupg/trustdb.gpg differ
diff --git a/tests/lib b/tests/lib
new file mode 100644 (file)
index 0000000..52aca71
--- /dev/null
+++ b/tests/lib
@@ -0,0 +1,1032 @@
+#
+
+exec 2>&1
+set -x
+set -o pipefail
+
+. tests/lib-core
+. tests/lib-restricts
+
+t-report-failure () {
+       set +x
+       rc=$1
+       cat <<END >&2
+TEST FAILED
+funcs: ${FUNCNAME[*]}
+lines: ${BASH_LINENO[*]}
+files: ${BASH_SOURCE[*]}
+END
+       exit 16
+}
+
+trap 'test $? = 0 || t-report-failure' EXIT
+
+t-filter-out-git-hyphen-dir
+
+t-set-intree
+
+: ${DGIT_TEST_DEBUG=-D}
+export DGIT_TEST_DEBUG
+
+export GIT_COMMITTER_DATE='1440253867 +0100'
+export GIT_AUTHOR_DATE='1440253867 +0100'
+
+root=`pwd`
+troot=$root/tests
+testname="${DGIT_TEST_TESTNAME-${0##*/}}"
+
+tmp=$ADTTMP
+if [ x"$tmp" = x ]; then
+       mkdir -p tests/tmp
+       tmpbase=$troot/tmp
+       tmp=tests/tmp/$testname
+       rm -rf $tmp
+       mkdir $tmp
+elif [ "x$DGIT_TEST_TMPBASE" != x ]; then
+       tmpbase="$DGIT_TEST_TMPBASE"
+fi
+cd $tmp
+
+tmp=`pwd`
+
+t-set-using-tmp
+
+env -0 >$tmp/.save-env
+
+ln -f $troot/ssh ssh
+
+export DEBCHANGE_VENDOR=dpkg
+
+mkdir -p $tmp/incoming
+cat <<END >$tmp/dput.cf
+[test-dummy]
+method                 = local
+incoming               = $tmp/incoming
+run_dinstall           = 0
+END
+
+: ${t_archive_method:=aq}
+: ${tagpfx:=archive/test-dummy}
+: ${suitespecs:=sid:unstable}
+
+t-git-next-date () {
+       GIT_COMMITTER_DATE="$(( ${GIT_COMMITTER_DATE%% *} + 1 )) ${GIT_COMMITTER_DATE#* }"
+       GIT_AUTHOR_DATE="$GIT_COMMITTER_DATE"
+}
+
+t-expect-fail () {
+       local mpat="$1"; shift
+
+       set +o pipefail
+       LC_MESSAGES=C "$@" 2>&1 | tee $tmp/t.output
+       local ps="${PIPESTATUS[*]}"
+       set -o pipefail
+
+       case $ps in
+       "0 0")  fail "command unexpectedly succeeded (instead of: $mpat)" ;;
+       *" 0")  ;;
+       *)      fail "tee failed"  ;;
+       esac
+
+       t-grep-mpat "$mpat" $tmp/t.output
+}
+
+t-grep-mpat () {
+       local mpat="$1"
+       local file="$2"
+
+       local grepper=fgrep
+       case "$mpat" in
+       [A-Z]:*)
+               case "$mpat" in
+               E:*)    grepper=egrep   ;;
+               F:*)    grepper=fgrep   ;;
+               *)      fail "bad mpat prefix in $mpat";;
+               esac
+               mpat=${mpat#[A-Z]:}
+               ;;
+       esac
+
+       $grepper -e "$mpat" "$file" ||
+               fail "message not found"
+}
+
+t-expect-push-fail () {
+       local mpat="$1"; shift
+
+       local triedpush=`git rev-parse HEAD`
+
+       t-reporefs pre-push
+       t-expect-fail "$mpat"  "$@"
+       t-reporefs post-push
+       diff $tmp/show-refs.{pre,post}-push
+
+       t-git-objects-not-present '' $triedpush
+
+       eval "$t_expect_push_fail_hook"
+}
+
+t-git-objects-not-present () {
+       # t-git-objects-not-present GITDIR|'' OBJID [...]
+       # specifying '' means the repo for package $p
+       local gitdir="${1-$dgitrepo}"
+       local obj
+       if ! [ -e "$gitdir" ]; then return; fi
+       for obj in "$@"; do
+               GIT_DIR=$gitdir \
+               t-expect-fail 'unable to find' \
+               git cat-file -t $obj
+       done
+}
+
+t-reporefs () {
+       local whichoutput=$1; shift
+       local whichrepo=${1-$dgitrepo}
+       local outputfile="$tmp/show-refs.$whichoutput"
+       (set -e
+        exec >"$outputfile"
+        if test -d $whichrepo; then
+               cd $whichrepo
+               git show-ref |sort
+       fi)
+}
+
+t-untar () {
+       local tarfile=$1.tar
+       local edittree=$1.edit
+       if test -d "$edittree"; then
+               cp -a "$edittree"/* .
+       else
+               tar xf "$tarfile"
+       fi
+}
+
+t-worktree () {
+       rm -rf $p
+       t-untar $troot/worktrees/${p}_$1
+}
+
+t-select-package () {
+       p=$1
+       dgitrepo=$tmp/git/$p.git
+}
+
+t-git () {
+       t-select-package $1
+       v=$2
+       mkdir -p $tmp/git
+       local gs=$troot/git-srcs/${p}_$v.git
+       (set -e; cd $tmp/git; t-untar $gs)
+}
+
+t-git-none () {
+       mkdir -p $tmp/git
+       (set -e; cd $tmp/git; tar xf $troot/git-template.tar)
+}
+
+t-git-merge-base () {
+       git merge-base $1 $2 || test $? = 1
+}
+
+t-has-ancestor () {
+       # t-has-ancestor ANCESTOR
+       # (CHILD is implicit, HEAD)
+       local now=`git rev-parse HEAD`
+       local ancestor=`git rev-parse $1^{}`
+       local mbase=`t-git-merge-base $ancestor $now`
+       if [ x$mbase != x$ancestor ]; then
+               fail "not ff $ancestor..$now, $mbase != $ancestor"
+       fi
+}
+
+t-has-parent-or-is () {
+       # t-has-parent-or-is CHILD PARENT
+       local child=$1
+       local parent=$2
+       local parents=$(git show --pretty=format:' %P %H ' "$child")
+       parent=$(git rev-parse "$parent~0")
+       case "$parents" in
+       *" $parent "*)  ;;
+       *)      fail "child $child lacks parent $parent" ;;
+       esac
+}
+
+t-prep-newpackage () {
+       t-select-package $1
+       v=$2
+       t-archive-none $p
+       t-git-none
+       t-worktree $v
+       cd $p
+       if ! git show-ref --verify --quiet refs/heads/master; then
+               git branch -m dgit/sid master
+               git remote rm dgit
+       fi
+       cd ..
+}
+
+t-archive-none () {
+       t-select-package $1
+       t-archive-none-$t_archive_method
+}
+t-archive-none-aq () {
+       mkdir -p $tmp/aq/dsc_in_suite $tmp/mirror/pool/main
+
+       : >$tmp/aq/suites
+       local jsondelim="["
+
+       local suitespec
+       for suitespec in $suitespecs; do
+               local suite=${suitespec%%:*}
+               local sname=${suitespec#*:}
+
+               >$tmp/aq/package.$suite.$p
+               t-aq-archive-updated $suite $p
+
+               >$tmp/aq/package.new.$p
+               t-aq-archive-updated new $p
+
+               ln -sf $suite $tmp/aq/dsc_in_suite/$sname
+
+               cat <<END >>$tmp/aq/suites
+$jsondelim
+   {
+      "archive" : "ftp-master",
+      "codename" : "$suite",
+      "components" : [
+         "main",
+         "contrib",
+         "non-free"
+      ],
+      "name" : "$sname",
+      "dakname" : "$sname"
+END
+
+               jsondelim="   },"
+
+       done
+       cat <<END >>$tmp/aq/suites
+    }
+]
+END
+}
+
+t-aq-archive-updated () {
+       local suite=$1
+       local p=$2
+       local suitedir=$tmp/aq/dsc_in_suite/$suite
+       mkdir -p $suitedir
+       perl <$tmp/aq/package.$suite.$p >$suitedir/$p -wne '
+               use JSON;
+               use strict;
+               our @v;
+               m{^(\S+) (\w+) ([^ \t/]+)/(\S+)} or die;
+               push @v, {
+                       "version" => "$1",
+                       "sha256sum" => "$2",
+                       "component" => "$3",
+                       "filename" => "$4",
+               };
+               END {
+                       my $json = JSON->new->canonical();
+                       print $json->encode(\@v) or die $!;
+               }
+       '
+}
+
+t-archive-process-incoming () {
+       local suite=$1
+       mv $tmp/incoming/${p}_* $tmp/mirror/pool/main/
+       t-archive-query "$suite"
+}
+
+t-archive-query () {
+       local suite=${1-sid}
+       local dscf=main/${p}_${v}.dsc
+       t-archive-query-$t_archive_method "$suite" "$p" "$v" "$dscf"
+}
+t-archive-query-aq () {
+       local suite=$1
+       local p=$2
+       local v=$3
+       local dscf=$4
+       local sha=`sha256sum <$tmp/mirror/pool/$dscf`
+       echo "${v} ${sha%  -} $dscf" >>$tmp/aq/package.$suite.${p}
+       t-aq-archive-updated $suite $p
+}
+
+t-archive () {
+       t-archive-none $1
+       v=$2
+       local dscf=${p}_$2.dsc
+       rm -f $tmp/mirror/pool/main/${p}_*
+       ln $troot/pkg-srcs/${p}_${2%-*}* $tmp/mirror/pool/main/
+       t-archive-query $suite
+       rm -rf $tmp/extract
+       mkdir $tmp/extract
+       (set -e; cd $tmp/extract; dpkg-source -x ../mirror/pool/main/$dscf)
+}
+
+t-git-dir-time-passes () {
+       touch -d 'last year' $dgitrepo
+}
+
+t-git-dir-check () {
+       local gitdir=$dgitrepo
+       case "$1" in
+       enoent)
+               if test -e "$gitdir"; then fail "$gitdir exists"; fi
+               return
+               ;;
+       public) wantstat='7[75]5' ;;
+       secret) wantstat='7[70]0' ;;
+       *)      fail "$1 t-git-dir-check ?" ;;
+       esac
+       gotstat=`stat -c%a $gitdir`
+       case "$gotstat" in
+       *$wantstat) return ;;
+       *)      fail "$gitdir has mode $gotstat, expected $wantstat" ;;
+       esac
+}
+
+t-expect-fsck-fail () {
+       echo >>$tmp/fsck.expected-errors "$1"
+}
+
+t-git-fsck () {
+       set +e
+       LC_MESSAGES=C git fsck --no-dangling --strict 2>&1 \
+               | tee dgit-test-fsck.errs
+       ps="${PIPESTATUS[*]}"
+       set -e
+
+       local pats
+       if [ -f $tmp/fsck.expected-errors ]; then
+               pats=(-w -f $tmp/fsck.expected-errors)
+       else
+               test "$ps" = "0 0"
+       fi
+       pats+=(-e 'notice: HEAD points to an unborn branch')
+       pats+=(-e 'notice: No default references')
+
+       set +e
+       grep -v "${pats[@]}" dgit-test-fsck.errs
+       rc=$?
+       set -e
+       case $rc in
+       1) ;; # no unexpected errors
+       0) fail "unexpected messages from git-fsck" ;;
+       *) fail "grep of git-fsck failed" ;;
+       esac
+}
+
+t-fscks () {
+       (
+       shopt -s nullglob
+       for d in $tmp/*/.git $tmp/git/*.git; do
+               cd "$d"
+               t-git-fsck
+       done
+       )
+}
+
+t-ok () {
+       t-fscks
+       echo ok.
+}
+
+t-rm-dput-dropping () {
+       rm -f $tmp/${p}_${v}_*.upload
+}
+
+t-dgit () {
+       local dgit=${DGIT_TEST-dgit}
+       pwd
+       : '
+{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{'
+       $dgit --dgit=$dgit --dget:-u --dput:--config=$tmp/dput.cf \
+               -dtest-dummy $DGIT_TEST_OPTS $DGIT_TEST_DEBUG \
+               -k39B13D8A $t_dgit_xopts "$@"
+       : '}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
+'
+}
+
+t-diff-nogit () {
+       diff --exclude=.git --exclude=.pc -ruN $*
+}
+
+t-files-notexist () {
+       local f
+       for f in "$@"; do
+               if [ -e $f ]; then
+                       fail "$f exists!"
+               fi
+       done
+}
+
+t-cloned-fetched-good () {
+       t-diff-nogit ../extract/$p-${v%-*} .
+       t-clean-on-branch dgit/sid
+       t-refs-same-start
+       t-refs-same \
+               refs/heads/dgit/sid \
+               refs/remotes/dgit/dgit/sid
+       t-refs-notexist refs/dgit/unstable refs/remotes/dgit/dgit/unstable
+}
+
+t-output () {
+       printf "%s${1:+\n}" "$1" >$tmp/t.want
+       shift
+       "$@" >$tmp/t.got
+       diff $tmp/t.want $tmp/t.got
+}
+
+t-clean-on-branch () {
+       t-output "## $1" git status -b --porcelain
+}
+
+t-setup-done () {
+       local savevars=$1
+       local savedirs=$2
+       local importeval=$3
+
+       local import=IMPORT.${0##*/}
+       exec 4>$tmp/$import.new
+
+       local vn
+       for vn in $savevars; do
+               perl >&4 -I. -MDebian::Dgit -e '
+                       printf "%s=%s\n", $ARGV[0], shellquote $ARGV[1]
+               ' $vn "$(eval "printf '%s\n' \"\$$vn\"")"
+       done
+
+       (set -e; cd $tmp; tar cf $import.tar $savedirs)
+
+       printf >&4 "\n%s\n" "$importeval"
+
+       mv -f $tmp/$import.new $tmp/$import
+}
+
+t-setup-import () {
+       local setupname=$1
+
+       local setupsrc
+       local lock
+       if [ "x$tmpbase" = x ]; then
+               # ADTTMP was set on entry to tests/lib, so we
+               # are not sharing tmp area between tests
+               setupsrc="$tmp"
+               lock="$tmp/.dummy.lock"
+       else
+               setupsrc="$tmpbase/$setupname"
+               lock="$setupsrc.lock"
+       fi
+
+       local simport="$setupsrc/IMPORT.$setupname"
+
+       if ! [ -e "$simport" ]; then
+               with-lock-ex -w "$lock" \
+               xargs -0 -a $tmp/.save-env \
+               bash -xec '
+                       cd "$1"; shift
+                       setupname="$1"; shift
+                       simport="$1"; shift
+                       if [ -e "$simport" ]; then exit 0; fi
+                       env - "$@" \
+                       "tests/setup/$setupname"
+               ' x "$root" "$setupname" "$simport"
+       fi
+
+       if [ "x$setupsrc" != "x$tmp" ]; then
+               (set -e; cd $tmp; tar xf "$simport.tar")
+       fi
+
+       . "$simport"
+}
+
+t-git-get-ref-exact () {
+       local ref=$1
+       # does not dereference, unlike t-git-get-ref
+       case "$ref" in
+       refs/*) ;;
+       *) fail "t-git-get-ref-exact bad $ref" ;;
+       esac
+       git for-each-ref --format='%(objectname)' "[r]efs/${ref#refs/}"
+}
+
+t-git-get-ref () {
+       local ref=$1
+       case "$ref" in
+       refs/*) ;;
+       *) fail "t-git-get-ref bad $ref" ;;
+       esac
+       (git show-ref -d $1 || test $? = 1) | perl -ne '
+               $x = $1 if m#^(\w+) \Q'$1'\E(?:\^\{\})?$#;
+               END { print "$x\n" if length $x; }
+       '
+}
+
+t-ref-same-exact () {
+       local name="$1"
+       local val=`t-git-get-ref-exact $name`
+       t-ref-same-val "$name" $val
+}
+
+t-ref-same () {
+       local name="$1"
+       local val=`t-git-get-ref $name`
+       t-ref-same-val "$name" $val
+}
+
+t-ref-head () {
+       local val=`git rev-parse HEAD`
+       t-ref-same-val HEAD $val
+}
+
+t-ref-same-val () {
+       local name="$1"
+       local val=$2
+       case "${t_ref_val-unset}" in
+       unset)          ;;
+       "$val")         ;;
+       *)              fail "ref varies: ($name)\
+ ${val:-nothing} != ${t_ref_val:-nothing} (${t_ref_names[*]})" ;;
+       esac
+       t_ref_val="$val"
+       t_ref_names+=("$name")
+}
+
+t-refs-same-start () {
+       unset t_ref_val
+       t_ref_names=()
+}
+
+t-refs-same () {
+       local g
+       for g in $*; do
+               t-ref-same $g
+       done
+}
+
+t-refs-notexist () {
+       local val
+       for g in $*; do
+               val=`t-git-get-ref $g`
+               if [ "x$val" != x ]; then
+                       fail "ref $g unexpectedly exists ($val)"
+               fi
+       done
+}
+
+t-v-tag () {
+       echo refs/tags/$tagpfx/${v//\~/_}
+}
+
+t-format-ref () {
+       git log -n1 --pretty=format:"$1" "$2"
+}
+
+t-sametree-parent () {
+       local ref=$1
+       local parent
+       local ctree=$(t-format-ref '%T' "$ref")
+       while :; do
+               local psame=''
+               for parent in $(t-format-ref '%P' "$ref"); do
+                       local ptree=$(t-format-ref '%T' "$parent")
+                       if [ "x$ptree" = "x$ctree" ]; then
+                               psame+=" $parent"
+                       fi
+               done
+               case "$psame" in ""|" * *") break ;; esac
+               ref="${psame# }"
+       done
+       echo "$ref"
+}
+
+t-check-pushed-master () {
+       local master=`t-git-get-ref refs/heads/master`
+       if [ x$master = x$t_ref_val ]; then return; fi
+       if [ x$master = x ]; then fail "failed to push master"; fi
+       # didn't update master, it must be not FF
+       local mbase=`t-git-merge-base $master $t_ref_val`
+       if [ x$mbase = x$master ]; then fail "failed to ff master"; fi
+}
+
+t-pushed-good () {
+       local branch=$1
+       local suite=${2:-sid}
+       t-refs-same \
+               refs/heads/$branch
+       t-pushed-good-core
+}
+       
+t-pushed-good-core () {
+       t-ref-dsc-dgit
+       t-refs-same \
+               `t-v-tag` \
+               refs/remotes/dgit/dgit/$suite
+       t-refs-notexist \
+               refs/heads/dgit/unstable \
+               refs/remotes/dgit/dgit/unstable
+       (set -e; cd $dgitrepo
+        t-refs-same \
+               refs/dgit/$suite \
+               `t-v-tag`
+        ${t_check_pushed_master:- : NOT-DRS-NO-CHECK-PUSHED-MASTER}
+        t-refs-notexist \
+               refs/dgit/unstable
+       )
+       git verify-tag `t-v-tag`
+}
+
+t-splitbrain-pushed-good--unpack () {
+       cd $tmp
+       rm -rf t-unpack
+       mkdir t-unpack
+       cd t-unpack
+       ln -s $tmp/mirror/pool/main/*.orig*.tar* .
+       ln -s $tmp/incoming/*.orig*.tar* . ||:
+       ln -s $incoming_dsc .
+       ln -s ${incoming_dsc/.dsc/.debian.tar}* .
+       dpkg-source "$@" -x *.dsc
+       cd */.
+       git init
+       git fetch ../../$p "refs/tags/*:refs/tags/*"
+}
+
+t-splitbrain-pushed-good--checkprep () {
+       git add -Af .
+       git rm --cached -r --ignore-unmatch .pc
+}
+
+t-splitbrain-pushed-good--checkdiff () {
+       local tag=$1
+       t-splitbrain-pushed-good--checkprep
+       t-output "" git diff --stat --cached $tag
+}
+
+t-splitbrain-pushed-good-start () {
+       dep14tag=refs/tags/test-dummy/${v//\~/_}
+       dgittag=$(t-v-tag)
+       t-output "" git status --porcelain
+       t-ref-head
+       t-refs-same $dep14tag
+       (set -e; cd $dgitrepo; t-refs-same $dep14tag)
+       git merge-base --is-ancestor $dep14tag $dgittag
+
+       t-refs-same-start
+       t-ref-same refs/heads/split.p
+       case "$(t-git-get-ref refs/heads/split.b)" in
+       "$t_ref_val") ;;
+       "$(git rev-parse refs/heads/split.p^0)") ;;
+       "$(git rev-parse refs/heads/split.p^1)") ;;
+       *) fail 'bad b/p' ;;
+       esac
+       t-pushed-good-core
+
+       t-incoming-dsc
+
+       t-splitbrain-pushed-good--unpack
+       t-splitbrain-pushed-good--checkdiff $dgittag
+}
+t-splitbrain-pushed-good-end-made-dep14 () {
+       t-splitbrain-pushed-good--checkdiff $dep14tag
+       cd $tmp/$p
+}
+
+t-splitbrain-rm-gitignore-patch () {
+       perl -i -pe '
+               next unless $_ eq "auto-gitignore\n";
+               die if $counter++;
+               chomp;
+               rename "debian/patches/$_", "../t-auto-gitignore" or die $!;
+               $_ = "";
+       ' debian/patches/series
+}
+
+t-gbp-pushed-good () {
+       local suite=${1:-sid}
+       t-splitbrain-pushed-good-start
+
+       # Right, now we want to check that the maintainer tree and
+       # the dgit tree differ in exactly the ways we expect.  We
+       # achieve this by trying to reconstruct the maintainer tree
+       # from the dgit tree.
+
+       # So, unpack it withut the patches applied
+       t-splitbrain-pushed-good--unpack --skip-patches
+
+       # dgit might have added a .gitignore patch, which we need to
+       # drop and remove
+       t-splitbrain-rm-gitignore-patch
+
+       # Now the result should differ only in non-debian/ .gitignores
+       t-splitbrain-pushed-good--checkprep
+       git diff --cached --name-only $dep14tag >../changed
+       perl -ne '
+               next if !m#^debian/# && m#(^|/)\.gitignore#;
+               die "$_ mismatch";
+       ' <../changed
+
+       # If we actually apply the gitignore patch by hand, it
+       # should be perfect:
+       if [ -f ../t-auto-gitignore ]; then
+               patch --backup-if-mismatch -p1 -u <../t-auto-gitignore
+       fi
+
+       t-splitbrain-pushed-good-end-made-dep14
+}
+
+t-unapplied-pushed-good () {
+       t-splitbrain-pushed-good-start
+       t-splitbrain-pushed-good--unpack --skip-patches
+       t-splitbrain-pushed-good-end-made-dep14
+}
+
+t-dpm-pushed-good () {
+       t-splitbrain-pushed-good-start
+       t-splitbrain-pushed-good--unpack
+       t-splitbrain-rm-gitignore-patch
+       t-splitbrain-pushed-good-end-made-dep14
+}
+
+t-commit-build-push-expect-log () {
+       local msg=$1
+       local mpat=$2
+       t-commit "$msg"
+       t-dgit build
+       LC_MESSAGES=C \
+       t-dgit push --new 2>&1 |tee $tmp/push.log
+       t-grep-mpat "$mpat" $tmp/push.log
+}
+
+t-822-field () {
+       local file=$1
+       local field=$2
+       perl -e '
+               use Dpkg::Control::Hash;
+               my $h = new Dpkg::Control::Hash allow_pgp=>1;
+               $h->parse(\*STDIN,"'"$file"'");
+               my $val = $h->{"'$field'"},"\n";
+               die "'"$file $field"'" unless defined $val;
+               print $val,"\n";
+       ' <$file
+}
+
+t-stunt-envvar () {
+       local var=$1
+       local tstunt=$2
+       eval '
+               case "$'$var'" in
+               "$tstunt:"*)    ;;
+               *":$tstunt:"*)  ;;
+               "")             '$var'="$tstunt" ;;
+               *)              '$var'="$tstunt:$'$var'" ;;
+               esac
+               export '$var'
+       '
+}
+
+t-tstunt () {
+       local tstunt=$tmp/tstunt
+       t-stunt-envvar PATH $tstunt
+       t-stunt-envvar PERLLIB $tstunt
+       local f
+       for f in "$@"; do
+               f="./$f"
+               local d="$tstunt/${f%/*}"
+               mkdir -p $d
+               ln -sf "$troot/tstunt/$f" "$d"/.
+       done
+}
+
+t-tstunt-parsechangelog () {
+       t-tstunt dpkg-parsechangelog Dpkg/Changelog/Parse.pm
+}
+
+t-tstunt-lintian () {
+       t-tstunt lintian
+}
+
+t-tstunt-debuild () {
+       : ${DGIT_TEST_REAL_DEBUILD:=$(type -p debuild)}
+       export DGIT_TEST_REAL_DEBUILD
+       t-tstunt debuild
+}
+
+t-incoming-dsc () {
+       local dsc=${p}_${v}.dsc
+       incoming_dsc=$tmp/incoming/$dsc
+}
+
+t-ref-dsc-dgit () {
+       t-incoming-dsc
+       local val=`t-822-field $incoming_dsc Dgit`
+       perl -e '$_=shift @ARGV; die "Dgit $_ ?" unless m/^\w+\b/;' "$val"
+       t-ref-same-val $incoming_dsc "$val"
+}
+
+t-apply-diff () {
+       local v1=$1
+       local v2=$2
+       (cd $troot/pkg-srcs;
+        debdiff ${p}_${v1}.dsc ${p}_${v2}.dsc || test $? = 1) \
+        | patch -p1 -u
+}
+
+t-gbp-unapplied-pq2qc () {
+       # does `gbp pq export'
+       # commits the resulting debian/patches on  qc/BRANCH
+       # leaves us on qc/BRANCH (eg "qc/quilt-tip"))
+       # qc/BRANCH is not fast-forwarding
+
+       gbp pq export
+
+       branch=`git symbolic-ref HEAD`
+       branch=${branch#refs/heads/}
+
+       case "$branch" in
+       */*) fail "unexpected branch $branch" ;;
+       esac
+
+       git branch -f qc/$branch
+       git checkout qc/$branch
+       git add debian/patches
+       git commit -m 'Commit patch queue'
+}
+
+t-git-pseudo-merge () {
+       # like   git merge -s ours
+       if [ ! "$git_pseuomerge_opts" ]; then
+               if git merge --help \
+                | grep -q allow-unrelated-histories; then
+                       git_pseuomerge_opts='--allow-unrelated-histories'
+               fi
+               git_pseuomerge_opts+=' -s ours'
+       fi
+       git merge $git_pseuomerge_opts "$@"
+}
+
+t-gbp-example-prep-no-ff () {
+       t-tstunt-parsechangelog
+       t-archive example 1.0-1
+       t-git-none
+       t-worktree 1.0
+
+       cd example
+
+       t-dgit fetch
+
+       git checkout -b patch-queue/quilt-tip-2 patch-queue/quilt-tip
+       gbp pq rebase
+
+       echo '/* some comment */' >>src.c
+       git add src.c
+       git commit -m 'Add a comment to an upstream file'
+
+       t-gbp-unapplied-pq2qc
+
+       t-commit 'some updates' 1.0-2
+}
+
+t-gbp-example-prep () {
+       t-gbp-example-prep-no-ff
+
+       t-git-pseudo-merge \
+               -m 'Pseudo-merge to make descendant of archive' \
+               remotes/dgit/dgit/sid
+}
+
+t-commit () {
+       local msg=$1
+       v=${2:-${majorv:-1}.$revision}
+       dch --force-distribution -v$v --distribution ${3:-unstable} "$1"
+       git add debian/changelog
+       debcommit
+       revision=$(( ${revision-0} + 1 ))
+}
+
+t-git-config () {
+       git config --global "$@"
+}
+
+t-drs () {
+       export DGIT_TEST_TROOT=$troot
+ t-git-config dgit-distro.test-dummy.git-url "ext::$troot/drs-git-ext %S "
+ t-git-config dgit-distro.test-dummy.git-check true
+ t-git-config dgit-distro.test-dummy.git-create true
+ t-git-config dgit-distro.test-dummy.dgit-tag-format new,old,maint
+       cp $troot/gnupg/{dd.gpg,dm.gpg,dm.txt} $tmp/.
+       cp $troot/suites $tmp/.
+       cp $troot/suites $tmp/suites-master
+
+       export t_check_pushed_master=t-check-pushed-master
+
+       drs_dispatch=$tmp/distro=test-dummy
+       mkdir $drs_dispatch
+
+       if [ "x$DGIT_TEST_INTREE" != x ]; then
+               ln -sf "$DGIT_TEST_INTREE" $drs_dispatch/dgit-live
+       fi
+
+       ln -sf $tmp/git $drs_dispatch/repos
+       ln -sf $tmp/suites $tmp/suites-master $tmp/dm.txt $drs_dispatch/
+       mkdir -p $drs_dispatch/keyrings
+       ln -sf $tmp/dd.gpg $drs_dispatch/keyrings/debian-keyring.gpg
+       ln -sf $tmp/dm.gpg $drs_dispatch/keyrings/debian-maintainers.gpg
+       ln -sf /bin/true $drs_dispatch/policy-hook
+}
+
+t-newtag () {
+ export tagpfx=archive/test-dummy
+ t-git-config dgit-distro.test-dummy.dgit-tag-format new,maint
+}
+t-oldtag () {
+ export tagpfx=test-dummy
+ t-git-config dgit-distro.test-dummy.dgit-tag-format old
+}
+
+t-dsd () {
+       t-drs
+ t-git-config dgit-distro.test-dummy.ssh "$troot/dsd-ssh"
+ t-git-config dgit-distro.test-dummy.git-check ssh-cmd
+ t-git-config dgit-distro.test-dummy.git-create true
+ t-git-config dgit-distro.test-dummy.git-url \
+               "ext::$troot/dsd-ssh X %S /dgit/test-dummy/repos"
+
+ t-git-config dgit-distro.test-dummy.diverts.drs /drs
+ t-git-config dgit-distro.test-dummy/drs.ssh "$troot/ssh"
+ t-git-config dgit-distro.test-dummy/drs.git-url $tmp/git
+ t-git-config dgit-distro.test-dummy/drs.git-check ssh-cmd
+ t-git-config dgit-distro.test-dummy/drs.git-create ssh-cmd
+
+       echo 'no-such-package* drs' >$drs_dispatch/diverts
+}
+
+t-policy-admin () {
+       ${DGIT_INFRA_PFX}dgit-repos-admin-debian --repos $tmp/git "$@"
+}
+
+t-policy-nonexist () {
+       ln -sf no-such-file-or-directory $drs_dispatch/policy-hook
+}
+
+t-make-hook-link () {
+       local hook=$1 # in infra/
+       local linkpath=$2
+       hook=${DGIT_INFRA_PFX}$hook
+       case $hook in
+       */*)    ;;
+       *)      hook=`type -P $hook` ;;
+       esac
+       ln -sf "$hook" $linkpath
+}
+
+t-policy () {
+       local policyhook=$1
+       t-make-hook-link $policyhook $drs_dispatch/policy-hook
+}
+
+t-debpolicy () {
+       t-dsd
+       t-policy dgit-repos-policy-debian
+
+       mkdir $tmp/git
+       t-policy-admin create-db
+}
+
+t-policy-periodic () {
+       ${DGIT_REPOS_SERVER_TEST-dgit-repos-server} \
+               test-dummy $drs_dispatch '' --cron
+}
+
+t-restrict () {
+       local restriction=$1
+       (cd $root; t-restriction-$restriction >&2)
+}
+
+t-dependencies () {
+       : "Hopefully installed: $*"
+}
+
+t-chain-test () {
+       local ct=$1
+       local d=${0%/*}
+       cd $root
+       export DGIT_TEST_TESTNAME="$testname"
+       export DGIT_TEST_TMPBASE="$tmpbase"
+       export ADTTMP=$tmp
+       exec "$d/$ct"
+}      
+
+t-alt-test () {
+       local t=${0##*/}
+       t-${t%%-*}
+       t-chain-test "${t#*-}"
+}
+
+case "$0" in
+*/gnupg) ;;
+*)     t-setup-import gnupg    ;;
+esac
diff --git a/tests/lib-build-modes b/tests/lib-build-modes
new file mode 100644 (file)
index 0000000..30fbb5a
--- /dev/null
@@ -0,0 +1,235 @@
+
+bm-prep-ownpackage-branches () {
+       cat <<'END' >$tmp/stunt-git
+#!/bin/sh -e
+case "$*" in
+*clean*) echo >&2 "BUILD-MODES PROGRAM git $*" ;;
+esac
+exec git "$@"
+END
+       chmod +x $tmp/stunt-git
+
+       bm_branches="$1"
+}
+
+bm-prep () {
+       t-tstunt-parsechangelog
+
+       t-prep-newpackage example 1.0
+
+       cd $p
+
+       git checkout -b bad-build-deps indep-arch
+       perl -pe 's/Build-Depends.*/$&, x-dgit-no-such-package/' \
+               -i debian/control
+       git commit -a -m bad-build-deps
+
+       bm-prep-ownpackage-branches 'indep-arch bad-build-deps'
+
+       if zgrep 'dpkg-buildpackage: Make dependency checks fatal for -S' \
+               /usr/share/doc/dpkg-dev/changelog.gz; then
+                       dpkgbuildpackage_deps_for_clean=true
+       else
+                       dpkgbuildpackage_deps_for_clean=false
+       fi
+
+       cleanmodes_default="git none dpkg-source dpkg-source-d"
+       cleanmodes_all="$cleanmodes_default git-ff check"
+       cleanmodes="$cleanmodes_default"
+}
+
+bm-gbp-example-acts () {
+       t-gbp-example-prep
+
+       git checkout -b for-build-modes qc/quilt-tip-2
+       # build-modes cannot cope with branches containing /
+
+       bm-prep-ownpackage-branches for-build-modes
+
+       cleanmodes='git dpkg-source'
+
+       for act in "$@"; do
+               bm-guess-e-source-e-targets "$act"
+               real_act="--quilt=gbp $act"
+               case "$act" in
+               sbuild*)    bm_quirk_after_act=bm-quirk-sbuild-after-act ;;
+               gbp-*)      real_act="$real_act --git-ignore-branch" ;;
+               *)          bm_quirk_after_act='' ;;
+               esac
+               bm-act-iterate
+       done
+}
+
+bm-guess-e-source-e-targets () {
+       local some_act=$1
+       case "$some_act" in
+       sbuild*" --no-arch-all"*)
+               e_source=true;  e_targets='build-arch binary-arch'      ;;
+       build-source)
+               e_source=true;  e_targets=''                            ;;
+       *" -b") e_source=false; e_targets='build binary'                ;;
+       *" -B") e_source=false; e_targets='build-arch binary-arch'      ;;
+       *" -A") e_source=false; e_targets='build-indep binary-indep'    ;;
+       *" -S") e_source=true;  e_targets=' '                           ;;
+       *" -F") e_source=true;  e_targets='build binary'                ;;
+       *" -G") e_source=true;  e_targets='build-arch binary-arch'      ;;
+       *" -g") e_source=true;  e_targets='build-indep binary-indep'    ;;
+       *)      e_source=true;  e_targets='build binary'                ;;
+       esac
+}
+
+bm-quirk-sbuild-after-act () {
+       # sbuild likes to run the package clean target in the chroot,
+       # which isn't necessary in our case.  We don't disable it in
+       # dgit because we want to do what sbuild does, in case there
+       # are packages which don't build unless their clean target was
+       # run.  We know it must be running it in the chroot because we
+       # provide sbuild with the dsc, not the tree, so we simply
+       # ignore all executions of the clean target by schroot.
+       local arch=$(dpkg-architecture -qDEB_BUILD_ARCH)
+       local sblog=../example_${v}_$arch.build
+       if [ -e $sblog ]; then
+               sed '
+                       s/^EXAMPLE RULES TARGET clean/HOOK SUPPRESSED &/;
+               ' <$sblog >>$bmlog
+       else
+               echo "SBUILD LOG FILE ($sblog) MISSING"
+       fi
+}
+
+bm-report-test () {
+       local desc=$1; shift
+       if "$@"; then
+               echo >&4 "$desc EXISTS"
+       else
+               echo >&4 "$desc MISSING"
+       fi
+}
+
+bm-build-deps-ok () {
+       case "$branch" in
+       *bad-build-deps*)       return 1        ;;
+       *)                      return 0        ;;
+       esac
+}
+
+bm-compute-expected () {
+       require_fail=unexpect # or required
+       tolerate_fail=unexpect # or tolerate
+
+       exec 4>$bmexp
+       echo >&4 "$heading"
+
+       case $cleanmode in
+       git)            echo >&4 'BUILD-MODES PROGRAM git clean -xdf' ;;
+       git-ff)         echo >&4 'BUILD-MODES PROGRAM git clean -xdff' ;;
+       check)          echo >&4 'BUILD-MODES PROGRAM git clean -xdn' ;;
+       dpkg-source-d)  echo >&4 "EXAMPLE RULES TARGET clean" ;;
+       dpkg-source)    bm-build-deps-ok || tolerate_fail=tolerate
+                       echo >&4 "EXAMPLE RULES TARGET clean"
+                       ;;
+       none)           ;;
+       *)              fail "t-compute-expected-run $cleanmode ??" ;;
+       esac
+
+       if [ "x$e_targets" != x ]; then
+               # e_targets can be " " to mean `/may/ fail due to b-d'
+               bm-build-deps-ok || tolerate_fail=tolerate
+       fi
+
+       for t in $e_targets; do
+               bm-build-deps-ok || require_fail=required
+               echo >&4 "EXAMPLE RULES TARGET $t"
+       done
+
+       bm-report-test "SOURCE FILE" $e_source
+       bm-report-test "SOURCE IN CHANGES" $e_source
+       bm-report-test "DEBS IN CHANGES" expr "$e_targets" : '.*binary.*'
+
+       exec 4>&-
+}
+
+bm-run-one () {
+       local args="$DGIT_TEST_BM_BASEARGS --clean=$cleanmode $real_act"
+
+       bmid="$act,$cleanmode,$branch"
+       bmid=${bmid// /_}
+
+       rm -f ../${p}_{v}_*.changes
+
+       heading="===== [$bmid] dgit $args ====="
+
+       bmlog=$tmp/run.$bmid.output
+       bmexp=$tmp/run.$bmid.expected
+       bmgot=$tmp/run.$bmid.results
+
+       bm-compute-expected
+
+       git checkout $branch
+       git clean -xdf # since we might not do any actual cleaning
+
+       dsc="../example_$v.dsc"
+       rm -f $dsc
+
+       set +o pipefail
+       t-dgit --rm-old-changes --git=$tmp/stunt-git $args 2>&1 | tee $bmlog
+       local ps="${PIPESTATUS[*]}"
+       set -o pipefail
+
+       $bm_quirk_after_act
+
+       exec 4>$bmgot
+       echo >&4 "$heading"
+
+       case $ps in
+       "0 0")  actual_status=success ;;
+       *" 0")  actual_status=failure; echo >&4 "OPERATION FAILED"; ;;
+       *)      fail "tee failed" ;;
+       esac
+
+       case "$require_fail-$tolerate_fail-$actual_status" in
+       required-********-failure) echo >>$bmexp "REQUIRED FAILURE" ;;
+       ********-tolerate-failure) echo >>$bmexp "TOLERATED FAILURE" ;;
+       unexpect-********-success) ;;
+       *)      fail "RF=$require_fail TF=$tolerate_fail AS=$actual_status" ;;
+       esac
+
+       egrep >&4 '^EXAMPLE RULES TARGET|^BUILD-MODES' $bmlog || [ $? = 1 ]
+
+       bm-report-test "SOURCE FILE" [ -e $dsc ]
+
+       if [ $actual_status = success ]; then
+               local changes=$(echo ../example_${v}_*.changes)
+               case "$changes" in
+               *' '*)  fail "build generated ambiguous .changes: $changes" ;;
+               esac
+
+               perl -ne 'print if m/^files:/i ... m/^\S/' \
+                       <$changes >$changes.files
+
+               bm-report-test "SOURCE IN CHANGES" grep '\.dsc$' $changes.files
+               bm-report-test "DEBS IN CHANGES" grep '\.deb$' $changes.files
+       fi
+
+       exec 4>&-
+
+       $bm_quirk_before_diff
+
+       [ $actual_status = failure ] || diff -U10 $bmexp $bmgot
+}
+
+bm-act-iterate () {
+       for cleanmode in $cleanmodes; do
+               for branch in $bm_branches; do
+                       bm-run-one
+               done
+       done
+       : bm-act-iterate done.
+}
+
+bm-alwayssplit () {
+       local t=${0##*/}
+       DGIT_TEST_BM_BASEARGS+=' --always-split-source-build'
+       export DGIT_TEST_BM_BASEARGS
+       t-chain-test "${t%%-asplit}"
+}
diff --git a/tests/lib-core b/tests/lib-core
new file mode 100644 (file)
index 0000000..7ed2761
--- /dev/null
@@ -0,0 +1,36 @@
+#
+
+fail () {
+       echo >&2 "failed: $*"
+       exit 1
+}
+
+t-set-intree () {
+       if [ "x$DGIT_TEST_INTREE" = x ]; then return; fi
+       : ${DGIT_TEST:=$DGIT_TEST_INTREE/dgit}
+       : ${DGIT_REPOS_SERVER_TEST:=$DGIT_TEST_INTREE/infra/dgit-repos-server}
+       : ${DGIT_SSH_DISPATCH_TEST:=$DGIT_TEST_INTREE/infra/dgit-ssh-dispatch}
+       : ${DGIT_INFRA_PFX:=$DGIT_TEST_INTREE${DGIT_TEST_INTREE:+/infra/}}
+       export DGIT_TEST DGIT_REPOS_SERVER_TEST DGIT_SSH_DISPATCH_TEST
+       export PERLLIB="$DGIT_TEST_INTREE${PERLLIB:+:}${PERLLIB}"
+}
+
+t-set-using-tmp () {
+       export HOME=$tmp
+       export DGIT_TEST_DUMMY_DIR=$tmp
+       export DGIT_TEST_TMP=$tmp
+       export GNUPGHOME=$tmp/nonexistent
+       git config --global user.email 'dgit-test@debian.example.net'
+       git config --global user.name 'dgit test git user'
+}
+
+t-filter-out-git-hyphen-dir () {
+       local pathent=$(type -p git-rev-parse ||:)
+       case "$pathent" in '') return ;; esac
+       pathent=${pathent%/*}
+       local path=":$PATH:"
+       path="${path//:$pathent:/}"
+       path="${path#:}"
+       path="${path%:}"
+       PATH="$path"
+}
diff --git a/tests/lib-import-chk b/tests/lib-import-chk
new file mode 100644 (file)
index 0000000..ee33cbe
--- /dev/null
@@ -0,0 +1,84 @@
+
+t-import-chk1 () {
+       p=$1
+       v=$2
+
+       t-archive $p $v
+}
+t-import-chk2() {
+       t-git-none
+       rm -rf $p
+       t-dgit --no-rm-on-error clone $p
+
+       # And now we make an update using the same orig tarball, and
+       # check that the orig import is stable.
+
+       cd $p
+
+       git branch first-import
+
+       m='Commit for import check'
+       echo "$m" >>import-check
+
+       v=${v%-*}-99
+       dch -v $v -D unstable -m "$m"
+
+       git add import-check debian/changelog
+       git commit -m "$m"
+
+       t-dgit -wgf quilt-fixup
+       t-dgit -wgf build-source
+
+       # The resulting .dsc does not have a Dgit line (because dgit push
+       # puts that in).  So we just shove it in the archive now
+
+       ln ../${p}_${v}.* $tmp/mirror/pool/main/
+       t-archive-query
+
+       t-dgit fetch
+
+       git branch first-2nd-import remotes/dgit/dgit/sid
+
+       t-git-next-date
+
+       git update-ref refs/remotes/dgit/dgit/sid first-import
+
+       t-dgit fetch
+
+       t-refs-same-start
+       t-ref-same refs/remotes/dgit/dgit/sid
+       t-ref-same refs/heads/first-2nd-import
+
+       for orig in ../${p}_${v%-*}.orig*.tar.*; do
+               tar -atf $orig | LC_ALL=C sort >../files.o
+               pfx=$(perl <../files.o -ne '
+                       while (<>) {
+                               m#^([^/]+/)# or exit 0;
+                               $x //= $1;
+                               $x eq $1 or exit 0;
+                       }
+                       print "$x\n";
+               ')
+               perl -i~ -pe '
+                       s#^\Q'"$pfx"'\E##;
+                       $_="" if m/^$/ || m#/$# || m#^\.git/#;
+               ' ../files.o
+               orig=${orig#../}
+               pat="^Import ${orig//./\\.}\$"
+               t-refs-same-start
+               for start in first-import first-2nd-import; do
+                       git log --pretty='tformat:%H' --grep "$pat" $start \
+                               >../t.imp
+                       test $(wc -l <../t.imp) = 1
+                       imp=$(cat ../t.imp)
+                       t-ref-same-val "$orig $start" "$imp"
+               done
+               git ls-tree -r --name-only "$t_ref_val:" \
+                       | sort >../files.g
+               diff ../files.{o,g}
+       done
+       cd ..
+}
+
+t-import-chk() { t-import-chk1 "$@"; t-import-chk2; }
+
diff --git a/tests/lib-mirror b/tests/lib-mirror
new file mode 100644 (file)
index 0000000..8aa751a
--- /dev/null
@@ -0,0 +1,40 @@
+
+t-mirror-setup () {
+       # p must be set already
+
+       reposmirror=$tmp/git-mirror
+       pmirror=$reposmirror/$p.git
+       queuedir=$tmp/git/_mirror-queue
+
+       mkdir $reposmirror
+
+       mirror_hook=$drs_dispatch/mirror-hook
+       t-make-hook-link dgit-mirror-rsync $mirror_hook
+
+       >$drs_dispatch/mirror-settings
+       t-mirror-set remoterepos="$reposmirror"
+
+       t-mirror-hook setup
+}
+
+t-mirror-set () {
+       echo >>$drs_dispatch/mirror-settings "$1"
+}
+
+t-mirror-hook () {
+       "$mirror_hook" "$drs_dispatch" "$@"
+}
+
+t-check-mirrored () {
+       t-reporefs master
+       t-reporefs mirror $pmirror
+       diff $tmp/show-refs.{master,mirror}
+       cat $queuedir/$p.log ||:
+       t-files-notexist $queuedir/$p.{n,a,lock,err}
+}
+
+t-check-not-mirrored () {
+       # uses previous t-reporefs-master
+       t-reporefs mirror $pmirror
+       diff $tmp/show-refs.{master,mirror}
+}
diff --git a/tests/lib-orig-include-exclude b/tests/lib-orig-include-exclude
new file mode 100644 (file)
index 0000000..75a9656
--- /dev/null
@@ -0,0 +1,62 @@
+# designed to be .'d
+
+t-tstunt-parsechangelog
+
+t-archive example 1.0-1
+t-git-none
+
+t-dgit clone $p
+
+origs='orig orig-docs'
+usvsns='1.0 1.1'
+
+for o in $origs; do
+       cp ${p}_{1.0,1.1}.${o}.tar.gz
+done
+
+mkdir -p "$tmp/aq/file_in_archive/%"
+
+cd $p
+
+test-push-1 () {
+       v=$1
+       ch=$2
+       suite=$3
+
+       t-commit $v $v $suite
+       t-dgit $ch build
+}
+
+test-push-2 () {
+       $test_push_2_hook
+       t-dgit $ch "$@" push
+}
+
+test-push-1 1.0-2 --ch:-sa
+
+grep orig ../${p}_${v}_*.changes
+
+test-push-2
+
+# check that dgit stripped out the orig update
+find $tmp/mirror -name '*orig*' -ls >../before
+
+t-archive-process-incoming sid
+
+find $tmp/mirror -name '*orig*' -ls >../after
+diff -u ../before ../after
+
+test-push-1 1.1-1.2 --ch:-sd
+
+test-push-2
+
+t-archive-process-incoming sid
+
+cd ..
+mkdir get
+cd get
+
+t-dgit clone $p
+# ^ checks that all the origs are there, ie that dgit added the origs
+
+cd ..
diff --git a/tests/lib-reprepro b/tests/lib-reprepro
new file mode 100644 (file)
index 0000000..d025d48
--- /dev/null
@@ -0,0 +1,71 @@
+# -*- bash -*-
+
+t-reprepro () {
+
+ t_archive_method=reprepro
+
+ t-git-config dgit-distro.test-dummy.archive-query aptget:
+ t-git-config dgit-distro.test-dummy.mirror file://$tmp/mirror/
+
+ mkdir $tmp/etc-apt
+ cat >$tmp/etc-apt/conf <<END
+Dir::Etc "$tmp/etc-apt";
+END
+ export APT_CONFIG=$tmp/etc-apt/conf
+ gpg --export Hannibal >han.pgp
+ fakeroot apt-key add <han.pgp
+ mkdir $tmp/etc-apt/apt.conf.d
+}
+
+t-archive-none-reprepro () {
+       t-reprepro-setup
+       t-reprepro-regen
+}
+t-archive-query-reprepro () {
+       local suite=$1
+       local p=$2
+       local v=$3
+       local dscf=$4
+       t-run-reprepro includedsc $suite $tmp/mirror/pool/$dscf
+}
+
+t-reprepro-setup () {
+       local rrc=$tmp/mirror/conf
+       mkdir -p $rrc
+       mkdir -p $tmp/mirror/pool/main
+
+       exec 3>$rrc/distributions
+
+       local arch=`dpkg --print-architecture`
+
+       for suitespec in $suitespecs; do
+               local suite=${suitespec%%:*}
+               local sname=${suitespec#*:}
+
+               mkdir -p $tmp/mirror/dists
+               if [ $sname != $suite ]; then
+                       rm -f $tmp/mirror/dists/$sname
+                       ln -s $suite $tmp/mirror/dists/$sname
+               fi
+
+               cat >&3 <<END
+Suite: $sname
+Codename: $suite
+Components: main
+Architectures: source binary-$arch
+SignWith: Hannibal
+
+END
+       done
+}
+
+t-run-reprepro () {
+       reprepro \
+               --outdir $tmp/mirror \
+               --basedir $tmp/mirror \
+               "$@"
+}
+
+t-reprepro-regen () {
+       t-run-reprepro export
+}
diff --git a/tests/lib-restricts b/tests/lib-restricts
new file mode 100644 (file)
index 0000000..bffe13a
--- /dev/null
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+t-restriction-x-dgit-intree-only () {
+       if [ "x$DGIT_TEST_INTREE" != x ]; then return 0; fi
+       echo 'running installed package version'
+       return 1
+}
+
+t-restriction-x-dgit-git-only () {
+       if test -d .git; then return 0; fi
+       echo 'not running out of git clone'
+       return 1
+}
+
+t-restriction-x-dgit-schroot-build () {
+       schroot -l -c build 2>&1 >/dev/null || return 1
+}
+
+t-restriction-x-dgit-unfinished () {
+       echo 'unfinished test, or unfinished feature'
+       return 1
+}
diff --git a/tests/pkg-srcs/example_1.0-1+absurd.debian.tar.xz b/tests/pkg-srcs/example_1.0-1+absurd.debian.tar.xz
new file mode 100644 (file)
index 0000000..9a2dd12
Binary files /dev/null and b/tests/pkg-srcs/example_1.0-1+absurd.debian.tar.xz differ
diff --git a/tests/pkg-srcs/example_1.0-1+absurd.dsc b/tests/pkg-srcs/example_1.0-1+absurd.dsc
new file mode 100644 (file)
index 0000000..1ab743d
--- /dev/null
@@ -0,0 +1,22 @@
+Format: 3.0 (quilt)
+Source: example
+Binary: example
+Architecture: all
+Version: 1.0-1+absurd
+Maintainer: Ian Jackson <ijackson@chiark.greenend.org.uk>
+Standards-Version: 3.9.4.0
+Build-Depends: debhelper (>= 8)
+Package-List:
+ example deb devel extra arch=all
+Checksums-Sha1:
+ 2bc730f941db49de57e9678fb0b07bd95507bb44 236 example_1.0.orig-docs.tar.gz
+ 4bff9170ce9b10cb59937195c5ae2c73719fe150 373 example_1.0.orig.tar.gz
+ dafb6f0db0580179ff246dba1dc2892246e84a2c 1416 example_1.0-1+absurd.debian.tar.xz
+Checksums-Sha256:
+ ad9671f6b25cdd9f0573f803f702448a45a45183db1d79701aa760bccbeed29c 236 example_1.0.orig-docs.tar.gz
+ a3ef7c951152f3ec754f96fd483457aa88ba06df3084e6f1cc7c25b669567c17 373 example_1.0.orig.tar.gz
+ 4003c34398894e46823bb3fda69f4351dbd5649e321259cde266a135f0428c51 1416 example_1.0-1+absurd.debian.tar.xz
+Files:
+ cb0cb5487b1e5bcb82547396b4fe93e5 236 example_1.0.orig-docs.tar.gz
+ 599f47808a7754c66aea3cda1b3208d6 373 example_1.0.orig.tar.gz
+ 0e88c1ed094f09ee7bf57607132d55ee 1416 example_1.0-1+absurd.debian.tar.xz
diff --git a/tests/pkg-srcs/example_1.0-1.100.debian.tar.xz b/tests/pkg-srcs/example_1.0-1.100.debian.tar.xz
new file mode 100644 (file)
index 0000000..ea8ec34
Binary files /dev/null and b/tests/pkg-srcs/example_1.0-1.100.debian.tar.xz differ
diff --git a/tests/pkg-srcs/example_1.0-1.100.dsc b/tests/pkg-srcs/example_1.0-1.100.dsc
new file mode 100644 (file)
index 0000000..5b075b5
--- /dev/null
@@ -0,0 +1,22 @@
+Format: 3.0 (quilt)
+Source: example
+Binary: example
+Architecture: all
+Version: 1.0-1.100
+Maintainer: Ian Jackson <ijackson@chiark.greenend.org.uk>
+Standards-Version: 3.9.4.0
+Build-Depends: debhelper (>= 8)
+Package-List:
+ example deb devel extra arch=all
+Checksums-Sha1:
+ 2bc730f941db49de57e9678fb0b07bd95507bb44 236 example_1.0.orig-docs.tar.gz
+ 4bff9170ce9b10cb59937195c5ae2c73719fe150 373 example_1.0.orig.tar.gz
+ 86c31eba5e08c1765f8e557b97e59d7e1fd9c208 2108 example_1.0-1.100.debian.tar.xz
+Checksums-Sha256:
+ ad9671f6b25cdd9f0573f803f702448a45a45183db1d79701aa760bccbeed29c 236 example_1.0.orig-docs.tar.gz
+ a3ef7c951152f3ec754f96fd483457aa88ba06df3084e6f1cc7c25b669567c17 373 example_1.0.orig.tar.gz
+ 163f1a753f0ea382148df8d9553240d503781badf03c600946f1400534da1349 2108 example_1.0-1.100.debian.tar.xz
+Files:
+ cb0cb5487b1e5bcb82547396b4fe93e5 236 example_1.0.orig-docs.tar.gz
+ 599f47808a7754c66aea3cda1b3208d6 373 example_1.0.orig.tar.gz
+ 4b7f5d286eff2608107c77c96584a01a 2108 example_1.0-1.100.debian.tar.xz
diff --git a/tests/pkg-srcs/example_1.0-1.debian.tar.xz b/tests/pkg-srcs/example_1.0-1.debian.tar.xz
new file mode 100644 (file)
index 0000000..84ca563
Binary files /dev/null and b/tests/pkg-srcs/example_1.0-1.debian.tar.xz differ
diff --git a/tests/pkg-srcs/example_1.0-1.dsc b/tests/pkg-srcs/example_1.0-1.dsc
new file mode 100644 (file)
index 0000000..bb65f6e
--- /dev/null
@@ -0,0 +1,22 @@
+Format: 3.0 (quilt)
+Source: example
+Binary: example
+Architecture: all
+Version: 1.0-1
+Maintainer: Ian Jackson <ijackson@chiark.greenend.org.uk>
+Standards-Version: 3.9.4.0
+Build-Depends: debhelper (>= 8)
+Package-List:
+ example deb devel extra arch=all
+Checksums-Sha1:
+ 2bc730f941db49de57e9678fb0b07bd95507bb44 236 example_1.0.orig-docs.tar.gz
+ 4bff9170ce9b10cb59937195c5ae2c73719fe150 373 example_1.0.orig.tar.gz
+ f2398be1e588e10d11b20ee9bc5ca0eb16e4c158 1304 example_1.0-1.debian.tar.xz
+Checksums-Sha256:
+ ad9671f6b25cdd9f0573f803f702448a45a45183db1d79701aa760bccbeed29c 236 example_1.0.orig-docs.tar.gz
+ a3ef7c951152f3ec754f96fd483457aa88ba06df3084e6f1cc7c25b669567c17 373 example_1.0.orig.tar.gz
+ fd97c0fb879bfa8084f24a0d0f808a56beb533f17d92c808dc293ff297007925 1304 example_1.0-1.debian.tar.xz
+Files:
+ cb0cb5487b1e5bcb82547396b4fe93e5 236 example_1.0.orig-docs.tar.gz
+ 599f47808a7754c66aea3cda1b3208d6 373 example_1.0.orig.tar.gz
+ fd7840d249ee3dba5bdc3dcde7217bbe 1304 example_1.0-1.debian.tar.xz
diff --git a/tests/pkg-srcs/example_1.0.orig-docs.tar.gz b/tests/pkg-srcs/example_1.0.orig-docs.tar.gz
new file mode 100644 (file)
index 0000000..f8427aa
Binary files /dev/null and b/tests/pkg-srcs/example_1.0.orig-docs.tar.gz differ
diff --git a/tests/pkg-srcs/example_1.0.orig.tar.gz b/tests/pkg-srcs/example_1.0.orig.tar.gz
new file mode 100644 (file)
index 0000000..e5d2cf3
Binary files /dev/null and b/tests/pkg-srcs/example_1.0.orig.tar.gz differ
diff --git a/tests/pkg-srcs/pari-extra_3-1.diff.gz b/tests/pkg-srcs/pari-extra_3-1.diff.gz
new file mode 100644 (file)
index 0000000..81f7f2e
Binary files /dev/null and b/tests/pkg-srcs/pari-extra_3-1.diff.gz differ
diff --git a/tests/pkg-srcs/pari-extra_3-1.dsc b/tests/pkg-srcs/pari-extra_3-1.dsc
new file mode 100644 (file)
index 0000000..8d67ed0
--- /dev/null
@@ -0,0 +1,30 @@
+-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA1
+
+Format: 1.0
+Source: pari-extra
+Binary: pari-extra
+Architecture: all
+Version: 3-1
+Maintainer: Bill Allombert <ballombe@debian.org>
+Standards-Version: 3.9.2.0
+Build-Depends: debhelper (>= 5)
+Package-List: 
+ pari-extra deb math optional
+Checksums-Sha1: 
+ ff281e103ab11681324b0c694dd3514d78436c51 121 pari-extra_3.orig.tar.gz
+ 080078dbc51e4194d209cb5abe57e2b25705fcaa 2358 pari-extra_3-1.diff.gz
+Checksums-Sha256: 
+ ac1ef39f9da80b582d1c0b2adfb09b041e3860ed20ddcf57a0e922e3305239df 121 pari-extra_3.orig.tar.gz
+ bf4672acd5302b9eebee2f3bf5269022279e531204d7172b8761bb10fae3517a 2358 pari-extra_3-1.diff.gz
+Files: 
+ 76bcf03be979d3331f9051aa88439b8b 121 pari-extra_3.orig.tar.gz
+ 02a39965adb84da9b3e6b5c5a0a4c378 2358 pari-extra_3-1.diff.gz
+
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.10 (GNU/Linux)
+
+iEYEARECAAYFAk5CvdoACgkQeDPs8bVESBX0mACeK3Yf9y22T2b6tw8eVQ8XSYxH
+ix4AoJJ3jrGJ4HXJNv/wbvmvBkkybvYJ
+=hkic
+-----END PGP SIGNATURE-----
diff --git a/tests/pkg-srcs/pari-extra_3-2~dummy1.diff.gz b/tests/pkg-srcs/pari-extra_3-2~dummy1.diff.gz
new file mode 100644 (file)
index 0000000..f5dff2b
Binary files /dev/null and b/tests/pkg-srcs/pari-extra_3-2~dummy1.diff.gz differ
diff --git a/tests/pkg-srcs/pari-extra_3-2~dummy1.dsc b/tests/pkg-srcs/pari-extra_3-2~dummy1.dsc
new file mode 100644 (file)
index 0000000..1042f09
--- /dev/null
@@ -0,0 +1,19 @@
+Format: 1.0
+Source: pari-extra
+Binary: pari-extra
+Architecture: all
+Version: 3-2~dummy1
+Maintainer: Bill Allombert <ballombe@debian.org>
+Standards-Version: 3.9.2.0
+Build-Depends: debhelper (>= 5), package-does-not-exist
+Package-List: 
+ pari-extra deb math optional
+Checksums-Sha1: 
+ ff281e103ab11681324b0c694dd3514d78436c51 121 pari-extra_3.orig.tar.gz
+ 810c43d186ad2552d65949acf4a065fcfc823636 2484 pari-extra_3-2~dummy1.diff.gz
+Checksums-Sha256: 
+ ac1ef39f9da80b582d1c0b2adfb09b041e3860ed20ddcf57a0e922e3305239df 121 pari-extra_3.orig.tar.gz
+ 41f47f24df7f50555f43549bd8377cce046750d29f69903e04b7fbfe396a0a73 2484 pari-extra_3-2~dummy1.diff.gz
+Files: 
+ 76bcf03be979d3331f9051aa88439b8b 121 pari-extra_3.orig.tar.gz
+ eff09e2ace409a150646c4994f17f800 2484 pari-extra_3-2~dummy1.diff.gz
diff --git a/tests/pkg-srcs/pari-extra_3.orig.tar.gz b/tests/pkg-srcs/pari-extra_3.orig.tar.gz
new file mode 100644 (file)
index 0000000..ff30279
Binary files /dev/null and b/tests/pkg-srcs/pari-extra_3.orig.tar.gz differ
diff --git a/tests/pkg-srcs/ruby-rails-3.2_3.2.6-1.debian.tar.gz b/tests/pkg-srcs/ruby-rails-3.2_3.2.6-1.debian.tar.gz
new file mode 100644 (file)
index 0000000..633e6db
Binary files /dev/null and b/tests/pkg-srcs/ruby-rails-3.2_3.2.6-1.debian.tar.gz differ
diff --git a/tests/pkg-srcs/ruby-rails-3.2_3.2.6-1.dsc b/tests/pkg-srcs/ruby-rails-3.2_3.2.6-1.dsc
new file mode 100644 (file)
index 0000000..4f2e290
--- /dev/null
@@ -0,0 +1,37 @@
+-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA1
+
+Format: 3.0 (quilt)
+Source: ruby-rails-3.2
+Binary: ruby-rails-3.2, rails3
+Architecture: all
+Version: 3.2.6-1
+Maintainer: Debian Ruby Extras Maintainers <pkg-ruby-extras-maintainers@lists.alioth.debian.org>
+Uploaders: Antonio Terceiro <terceiro@debian.org>
+Dm-Upload-Allowed: yes
+Homepage: http://www.rubyonrails.org
+Standards-Version: 3.9.3
+Vcs-Browser: http://git.debian.org/?p=pkg-ruby-extras/ruby-rails.git;a=summary
+Vcs-Git: git://git.debian.org/pkg-ruby-extras/ruby-rails-3.2.git
+Build-Depends: debhelper (>= 7.0.50~), gem2deb (>= 0.3.0~)
+Package-List: 
+ rails3 deb ruby optional
+ ruby-rails-3.2 deb ruby optional
+Checksums-Sha1: 
+ f36c3866b22de8ff6875fdbbfbcfb8d18e1f5a89 953 ruby-rails-3.2_3.2.6.orig.tar.gz
+ 7208250afe7083e258d1fa36cc3a60527608df11 2297 ruby-rails-3.2_3.2.6-1.debian.tar.gz
+Checksums-Sha256: 
+ 207cfb1ef70aa9458c776deeda8e38ac977cbc852209828793b27d55bebc7bea 953 ruby-rails-3.2_3.2.6.orig.tar.gz
+ 55decdcdc8248a4153fb7e5688ffdc3c3a2661a95f3870edba3e1eaf40907088 2297 ruby-rails-3.2_3.2.6-1.debian.tar.gz
+Files: 
+ 05a3954762c2a2101a10dd2efddf7000 953 ruby-rails-3.2_3.2.6.orig.tar.gz
+ 87bdb28ef5053d825bda80e959e2fd1c 2297 ruby-rails-3.2_3.2.6-1.debian.tar.gz
+Ruby-Versions: all
+
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.12 (GNU/Linux)
+
+iEYEARECAAYFAk/nrgIACgkQDOM8kQ+cso9TjgCfcDl8MvUtKVZP6bPP9IrO93hP
+TnAAn1aA67N088u6u/S2VA8UhYjNXhpO
+=7sbS
+-----END PGP SIGNATURE-----
diff --git a/tests/pkg-srcs/ruby-rails-3.2_3.2.6.orig.tar.gz b/tests/pkg-srcs/ruby-rails-3.2_3.2.6.orig.tar.gz
new file mode 100644 (file)
index 0000000..88ab3fd
Binary files /dev/null and b/tests/pkg-srcs/ruby-rails-3.2_3.2.6.orig.tar.gz differ
diff --git a/tests/pkg-srcs/sunxi-tools_1.2-2.~~dgittest.debian.tar.gz b/tests/pkg-srcs/sunxi-tools_1.2-2.~~dgittest.debian.tar.gz
new file mode 100644 (file)
index 0000000..c376961
Binary files /dev/null and b/tests/pkg-srcs/sunxi-tools_1.2-2.~~dgittest.debian.tar.gz differ
diff --git a/tests/pkg-srcs/sunxi-tools_1.2-2.~~dgittest.dsc b/tests/pkg-srcs/sunxi-tools_1.2-2.~~dgittest.dsc
new file mode 100644 (file)
index 0000000..e0161cd
--- /dev/null
@@ -0,0 +1,22 @@
+Format: 3.0 (quilt)
+Source: sunxi-tools
+Binary: sunxi-tools
+Architecture: any
+Version: 1.2-2.~~dgittest
+Maintainer: Ian Campbell <ijc@hellion.org.uk>
+Homepage: http://linux-sunxi.org/Sunxi-tools
+Standards-Version: 3.9.5
+Vcs-Browser: http://git.debian.org/?p=collab-maint/sunxi-tools.git
+Vcs-Git: git://git.debian.org/collab-maint/sunxi-tools.git
+Build-Depends: debhelper (>= 9), pkg-config, libusb-1.0-0-dev, u-boot-tools
+Package-List: 
+ sunxi-tools deb utils optional
+Checksums-Sha1: 
+ 2457216dbbf5552c413753f7211f7be3db6aff54 35076 sunxi-tools_1.2.orig.tar.gz
+ 6f30698cd897b350a4f92b2b5dded69adca6f82e 5182 sunxi-tools_1.2-2.~~dgittest.debian.tar.gz
+Checksums-Sha256: 
+ 03a63203ff79389e728d88ad705e546aa6362a6d08b9901392acb8639998ef95 35076 sunxi-tools_1.2.orig.tar.gz
+ 0a513f3254d245b59aaffbeb5c43159a6461617c1f6f3c6824646c4259cda406 5182 sunxi-tools_1.2-2.~~dgittest.debian.tar.gz
+Files: 
+ dbc55f60559f9db497559176c3c753dd 35076 sunxi-tools_1.2.orig.tar.gz
+ a6ec0eb0d897b0121dc978fc00db2ea6 5182 sunxi-tools_1.2-2.~~dgittest.debian.tar.gz
diff --git a/tests/pkg-srcs/sunxi-tools_1.2.orig.tar.gz b/tests/pkg-srcs/sunxi-tools_1.2.orig.tar.gz
new file mode 100644 (file)
index 0000000..fe397fa
Binary files /dev/null and b/tests/pkg-srcs/sunxi-tools_1.2.orig.tar.gz differ
diff --git a/tests/run-all b/tests/run-all
new file mode 100755 (executable)
index 0000000..e53b963
--- /dev/null
@@ -0,0 +1,21 @@
+#!/bin/bash
+set -e
+# convenience script for running the tests outside adt-run
+# usage: tests/using-intree tests/run-all
+
+set -o pipefail
+
+set +e
+jcpus=`perl -MSys::CPU -we 'printf "-j%d\n",Sys::CPU::cpu_count()'`
+set -e
+
+if [ $# != 0 ]; then
+       set TESTSCRIPTS="$*"
+fi
+
+mkdir -p tests/tmp
+
+(
+ set -x
+ exec make $jcpus -k -f tests/Makefile "$@"
+) 2>&1 |tee tests/tmp/run-all.log
diff --git a/tests/setup/examplegit b/tests/setup/examplegit
new file mode 100755 (executable)
index 0000000..112e27a
--- /dev/null
@@ -0,0 +1,53 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+suitespecs+=' stable testing'
+
+t-tstunt-parsechangelog
+
+t-prep-newpackage example 1.0
+
+cd $p
+
+revision=1
+
+push-to () {
+       t-refs-same-start
+       t-ref-head
+       t-dgit build
+       t-dgit push --new $2
+       t-pushed-good $1 $2
+       t-archive-process-incoming $2
+}
+
+echo ancestor >which
+git add which
+t-commit Ancestor '' stable
+push-to master stable
+
+git checkout -b stable
+
+echo stable >which
+git add which
+t-commit Stable '' stable
+push-to stable stable
+
+git checkout master
+
+majorv=2
+revision=0
+
+echo sid >which
+git add which
+t-commit Sid
+push-to master sid
+
+echo sid-again >>which
+git add which
+t-commit Sid
+push-to master sid
+
+t-setup-done 'p v suitespecs majorv revision' "aq git mirror $p"
+
+t-ok
diff --git a/tests/setup/gnupg b/tests/setup/gnupg
new file mode 100755 (executable)
index 0000000..d23ecfa
--- /dev/null
@@ -0,0 +1,15 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+mkdir -p $tmp/gnupg
+cp $troot/gnupg/* $tmp/gnupg
+chmod go-rw $tmp/gnupg/*
+
+export GNUPGHOME=$tmp/gnupg
+
+gpg --list-secret
+
+t-setup-done 'GNUPGHOME' 'gnupg'
+
+t-ok
diff --git a/tests/ssh b/tests/ssh
new file mode 100755 (executable)
index 0000000..9481a1e
--- /dev/null
+++ b/tests/ssh
@@ -0,0 +1,5 @@
+#!/bin/sh
+set -e
+cd /
+userhost="$1"; shift
+exec sh -c "$*"
diff --git a/tests/suites b/tests/suites
new file mode 100644 (file)
index 0000000..ea5cccf
--- /dev/null
@@ -0,0 +1 @@
+sid
diff --git a/tests/tartree-edit b/tests/tartree-edit
new file mode 100755 (executable)
index 0000000..96a5bcb
--- /dev/null
@@ -0,0 +1,219 @@
+#!/bin/sh
+set -e
+fail () { echo >&2 "$0: $*"; exit 1; }
+
+play=.git/tartree-edit-work
+
+git_manip_play () {
+       local wd=$(pwd)
+       case "$wd" in
+       *.edit) fail "bad idea to run gitfetchinfo into a .edit tree!" ;;
+       esac
+       rm -rf $play
+       mkdir $play
+}
+
+gitfetchdiff_list () {
+       git for-each-ref --format '%(refname) %(objectname)' \
+               refs/remotes/"$1" \
+       | sed 's/^refs\/remotes\/[^\/]*\///' \
+       | sort >"$play/$2"
+}
+
+gitfetchdiff () {
+       local how="$1"
+       local a="$2"
+       local b="$3"
+       git_manip_play
+
+       rrab=refs/remotes/"$a+$b"
+
+       ulf=\
+"delete refs/remotes/$a/%l
+delete refs/remotes/$b/%l
+"
+       case "$how" in
+       diff)
+               git for-each-ref --format 'delete %(refname)' $rrab \
+                       | git update-ref --stdin
+               ;;
+       merge)
+               ulf=\
+"create $rrab/%l
+$ulf"
+               ;;
+       *)
+               fail "internal error bad how ($how)"
+               ;;
+       esac
+
+       gitfetchdiff_list "$a" a
+       gitfetchdiff_list "$b" b
+
+       diff --old-line-format='' --new-line-format='' \
+               --unchanged-line-format="$ulf" \
+               $play/a $play/b >$play/updates \
+       || test $? = 1
+
+       git update-ref --stdin <$play/updates
+       exit 0
+}
+
+case "$#.$1" in
+2.edit|2.done) mode="$1"; arg="$2" ;;
+3.gitfetchinfo)        mode="$1"; arg="$2"; remote="$3" ;;
+3.gitfetchinfo-diff)   gitfetchdiff diff "$2" "$3"     ;;
+3.gitfetchinfo-merge)  gitfetchdiff merge "$2" "$3"    ;;
+?.-*)  fail "no options understood"                    ;;
+*)     fail "usage:
+    tartree-edit edit|done DIRECTORY|TARBALL
+    tartree-edit gitfetchinfo DIRECTORY|TARBALL REMOTE
+    tartree-edit gitfetchinfo-merge REMOTE-A REMOTE-B" ;;
+    # we don't document gitfetchinfo-diff because it's rather poor
+esac
+
+case "$arg" in
+*.tar)         base=${arg%.tar}                        ;;
+*.edit)                base=${arg%.edit}                       ;;
+*)             base=${arg}                             ;;
+esac
+
+tryat_pre () {
+       local b="$1"
+       rm -rf "$b.tmp"
+       if test -f "$b.tar" && test -f "$b.edit"; then
+               echo "$b.edit exists, deleting possibly-obsolete $b.tar"
+               rm "$b.tar"
+       fi
+}
+
+tryat_edit () {
+       local b="$1"
+       if test -d "$b.edit"; then
+               echo "$b.edit already exists"
+               exit 0
+       fi
+       if test -f "$b.tar"; then
+               mkdir "$b.tmp"
+               (set -e; cd "$b.tmp"; tar xf "$b.tar")
+               mv "$b.tmp" "$b.edit"
+               rm "$b.tar"
+               echo "$b.edit ready"
+               exit 0
+       fi
+}
+
+gitfetchinfo_perhaps_commit () {
+       local m="$1"
+       set +e
+       git diff --cached --quiet --exit-code HEAD
+       local rc=$?
+       set -e
+       case "$rc" in
+       0)   return ;;
+       1)   git commit --allow-empty --author='tartree-edit <>' -m "$m" ;;
+       *)   fail "git diff failed ($rc)" ;;
+       esac
+}
+
+tryat_gitfetchinfo () {
+       git_manip_play
+
+       if test -d "$b.edit"; then
+               cp -a "$b.edit"/. "$play"/.
+       else
+               exec 3<"$b.tar"
+               tar -C $play -f - <&3 -x
+               exec 3<&-
+       fi
+
+       local innerwd="$(echo $play/*)"
+
+       git for-each-ref --format='%(refname)' refs/remotes >$play/l
+       perl -w -ne '
+           our %remerge;
+           use strict;
+           chomp;
+           next unless m#^refs/remotes/([^/]+)/#;
+           my $old = $_;
+           my $ab = $1;
+           my $rhs = $'\'';
+           my @ab = split /\+/, $ab;
+           next unless @ab == 2;
+           next unless (grep { $_ eq "'"$remote"'" } @ab) == 1;
+           $remerge{"@ab"} = 1;
+           print "update refs/remotes/$_/$rhs $old\n" or die $! foreach @ab;
+           print "delete $old\n" or die $!;
+           END {
+               open REMERGE, ">&3" or die $!;
+               print REMERGE "$_\n" or die $! foreach sort keys %remerge;
+               close REMERGE or die $!;
+           }
+       ' <$play/l >$play/unmerge 3>$play/remerge
+       git update-ref --stdin <$play/unmerge
+
+       git remote remove "$remote" 2>/dev/null ||:
+       git remote add "$remote" $innerwd
+       git fetch --no-tags -p "$remote" \
+               +"HEAD:refs/remotes/$remote/TT-HEAD"
+       cd $innerwd
+       GIT_AUTHOR_DATE=$(git log -n1 --pretty=format:'%ai')
+       GIT_COMMITTER_DATE=$GIT_AUTHOR_DATE
+       export GIT_COMMITTER_DATE GIT_AUTHOR_DATE
+       git checkout -b WORKTREE
+       gitfetchinfo_perhaps_commit 'UNCOMMITTED INDEX'
+       git add -Af .
+       gitfetchinfo_perhaps_commit 'UNCOMMITTED WORKING TREE'
+       cd ../../..
+       git fetch --no-tags "$remote" --refmap \
+               +"refs/*:refs/remotes/$remote/*" \
+               +"refs/*:refs/remotes/$remote/*"
+
+       exec 3<$play/remerge
+       # $play will be destroyed by what follows, but we have
+       # an fd open onto remerge, so this will work
+       while read <&3 a b; do
+             echo "Updating gitfetchinfo-merge $a $b"
+             "$0" gitfetchinfo-merge $a $b
+       done
+
+       exit 0
+}
+
+tryat_done () {
+       local b="$1"
+       if test -d "$b.edit"; then
+               (set -e; cd "$b.edit"; tar cf "$b.tmp" *)
+               mv "$b.tmp" "$b.tar"
+               mv "$b.edit" "$b.tmp"
+               rm -rf "$b.tmp"
+               echo "$b.tar regenerated"
+               exit 0
+       fi
+       if test -f "$b.tar"; then
+               echo "$b.tar already exists and $b.edit doesn't"
+               exit 0
+       fi
+}
+
+tryat () {
+       local b="$1"
+       if ! test -f "$b.tar" && ! test -d "$b.edit"; then
+               return
+       fi
+       tryat_pre "$b"
+       tryat_$mode "$b"
+       fail "unexpected situation in $b.*"
+}
+
+case "$arg" in
+/*)            tryat "$base"
+               ;;
+*)
+               pwd=`pwd`
+               tryat "$pwd/$base"
+               tryat "$pwd/git-srcs/$base"
+               tryat "$pwd/tests/git-srcs/$base"
+               fail "could not find $base..."
+               ;;
+esac
diff --git a/tests/tests/absurd-gitapply b/tests/tests/absurd-gitapply
new file mode 100755 (executable)
index 0000000..90c44be
--- /dev/null
@@ -0,0 +1,16 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+t-archive example 1.0-1+absurd
+t-git-none
+
+t-expect-fail 'gbp pq import failed' \
+t-dgit --force-import-gitapply-no-absurd clone $p
+
+t-dgit clone $p
+
+cd $p
+grep moo moo
+
+t-ok
diff --git a/tests/tests/build-modes b/tests/tests/build-modes
new file mode 100755 (executable)
index 0000000..c476ec8
--- /dev/null
@@ -0,0 +1,35 @@
+#!/bin/bash
+set -e
+. tests/lib
+. $troot/lib-build-modes
+
+bm-prep
+
+for act in                                     \
+       'build'                                 \
+       'build -S'                              \
+       'build -b'                              \
+       'build -B'                              \
+       'build -A'                              \
+       'build -F'                              \
+       'build -g'                              \
+       'build -G'                              \
+       build-source                            \
+; do
+       bm-guess-e-source-e-targets "$act"
+
+       case $act in
+       build-source)
+               cleanmodes="$cleanmodes_all"
+               ;;
+       *)
+               cleanmodes="$cleanmodes_default"
+               ;;
+       esac
+
+       real_act="$act"
+
+       bm-act-iterate
+done
+
+t-ok
diff --git a/tests/tests/build-modes-asplit b/tests/tests/build-modes-asplit
new file mode 100755 (executable)
index 0000000..fa3bf8a
--- /dev/null
@@ -0,0 +1,5 @@
+#!/bin/bash
+set -e
+. tests/lib
+. $troot/lib-build-modes
+bm-alwayssplit
diff --git a/tests/tests/build-modes-gbp b/tests/tests/build-modes-gbp
new file mode 100755 (executable)
index 0000000..50a6288
--- /dev/null
@@ -0,0 +1,39 @@
+#!/bin/bash
+set -e
+. tests/lib
+. $troot/lib-build-modes
+
+t-dependencies git-buildpackage
+
+quirk-clean-fixup () {
+       case $cleanmode in
+       dpkg-source*)
+               # git-buildpackage runs the clean target twice somehow
+               perl -i.unfixed -ne '
+                       print unless
+                               $_ eq $last &&
+                               $_ eq "EXAMPLE RULES TARGET clean\n";
+                       $last = $_;
+               ' $bmgot
+               ;;
+       esac
+}
+bm_quirk_before_diff=quirk-clean-fixup
+
+bm-prep
+
+for act in                                     \
+       'gbp-build -S'                          \
+       'gbp-build -b'                          \
+       'gbp-build -B'                          \
+       'gbp-build -A'                          \
+       'gbp-build -F'                          \
+       'gbp-build -g'                          \
+       'gbp-build -G'                          \
+; do
+       bm-guess-e-source-e-targets "$act"
+       real_act="$act --git-ignore-branch"
+       bm-act-iterate
+done
+
+t-ok
diff --git a/tests/tests/build-modes-gbp-asplit b/tests/tests/build-modes-gbp-asplit
new file mode 100755 (executable)
index 0000000..fa3bf8a
--- /dev/null
@@ -0,0 +1,5 @@
+#!/bin/bash
+set -e
+. tests/lib
+. $troot/lib-build-modes
+bm-alwayssplit
diff --git a/tests/tests/build-modes-sbuild b/tests/tests/build-modes-sbuild
new file mode 100755 (executable)
index 0000000..19dcc8a
--- /dev/null
@@ -0,0 +1,18 @@
+#!/bin/bash
+set -e
+. tests/lib
+. $troot/lib-build-modes
+
+t-dependencies sbuild
+t-restrict x-dgit-schroot-build
+
+bm_quirk_after_act=bm-quirk-sbuild-after-act
+bm-prep
+
+act="sbuild -c build --no-arch-all"
+real_act="$act"
+
+bm-guess-e-source-e-targets "$act"
+bm-act-iterate
+
+t-ok
diff --git a/tests/tests/clone-clogsigpipe b/tests/tests/clone-clogsigpipe
new file mode 100755 (executable)
index 0000000..4465cf3
--- /dev/null
@@ -0,0 +1,10 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+t-archive example 1.0-1.100
+t-git-none
+
+t-dgit clone $p
+
+t-ok
diff --git a/tests/tests/clone-gitnosuite b/tests/tests/clone-gitnosuite
new file mode 100755 (executable)
index 0000000..83c996d
--- /dev/null
@@ -0,0 +1,11 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+t-archive pari-extra 3-1
+t-git-none
+cp -a $tmp/git/_template $dgitrepo
+
+t-dgit clone $p
+
+t-ok
diff --git a/tests/tests/clone-nogit b/tests/tests/clone-nogit
new file mode 100755 (executable)
index 0000000..e99dac3
--- /dev/null
@@ -0,0 +1,25 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+t-archive pari-extra 3-1
+t-git-none
+
+t-dgit clone $p
+
+cd $p
+t-cloned-fetched-good
+
+v=3-2~dummy1
+t-apply-diff 3-1 $v
+debcommit -a
+
+t-refs-same-start
+t-ref-head
+
+t-dgit --dpkg-buildpackage:-d build
+t-dgit push
+
+t-pushed-good dgit/sid
+
+t-ok
diff --git a/tests/tests/clone-reprepro b/tests/tests/clone-reprepro
new file mode 100755 (executable)
index 0000000..063a138
--- /dev/null
@@ -0,0 +1,33 @@
+#!/bin/bash
+set -e
+. tests/lib
+. $troot/lib-reprepro
+
+suitespecs+=' stable'
+
+t-dependencies reprepro
+t-reprepro
+t-tstunt-parsechangelog
+
+t-archive example 1.0-1
+t-git-none
+
+t-dgit clone $p
+
+cd $p
+t-cloned-fetched-good
+
+add_pari () {
+       local p
+       local v
+       local suite=stable
+       t-archive pari-extra 3-1
+}
+add_pari
+
+t-dgit fetch unstable,stable
+
+t-refs-same-start
+t-refs-same refs/remotes/dgit/sid,stable refs/remotes/dgit/sid
+
+t-ok
diff --git a/tests/tests/debpolicy-dbretry b/tests/tests/debpolicy-dbretry
new file mode 100755 (executable)
index 0000000..a9f2334
--- /dev/null
@@ -0,0 +1,67 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+t-tstunt-parsechangelog
+
+t-debpolicy
+t-prep-newpackage example 1.0
+
+cd $p
+revision=1
+git tag start
+
+echo DUMMY >some-file
+git add some-file
+git commit -m some-file
+taint=`git rev-parse HEAD`
+t-policy-admin taint --global $taint dummy
+git reset --hard HEAD~
+
+t-commit 'Make something to autotaint'
+t-dgit build
+t-dgit push --new
+
+autotaint=`t-git-get-ref "refs/tags/$tagpfx/$v"`
+
+git reset --hard start
+t-commit 'Thing which will autotaint'
+t-dgit build
+
+fifo=$tmp/sqlite-cmds
+mkfifo $fifo
+exec 3<>$fifo
+sqlite3 -interactive $tmp/git/policy.sqlite3 0<$fifo 3>&- &
+sqlite3_pid=$!
+
+taintsout=$tmp/sqlite3.taints-out
+echo >&3 'begin;';
+echo >&3 ".output $taintsout"
+echo >&3 'select * from taints;';
+echo >&3 'create table dummy (x text);'
+
+t-dgit build
+
+while ! grep $taint $taintsout; do sleep 0.1; done
+
+DGIT_RPD_TEST_DBLOOP_HOOK='
+       print STDERR "DBLOOP HOOK $sleepy\n";
+       $poldbh->sqlite_busy_timeout(2500);
+       if ($sleepy > 2) {
+               system '\''
+                       set -ex
+                       echo >'"$fifo"' "rollback;"
+                       touch '"$tmp/sqlite3.rolled-back"'
+               '\'' and die "$? $!";
+       }
+' \
+t-dgit push --deliberately-not-fast-forward
+
+exec 3>&-
+wait $sqlite3_pid
+
+ls $tmp/sqlite3.rolled-back
+
+t-policy-admin list-taints | tee $tmp/taints-list | grep $autotaint
+
+t-ok
diff --git a/tests/tests/debpolicy-newreject b/tests/tests/debpolicy-newreject
new file mode 100755 (executable)
index 0000000..1fa6751
--- /dev/null
@@ -0,0 +1,121 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+t-tstunt-parsechangelog
+
+t-debpolicy
+t-prep-newpackage example 1.0
+
+cd $p
+revision=1
+git tag start
+t-dgit setup-mergechangelogs
+
+echo FORBIDDEN >debian/some-file
+git add debian/some-file
+t-commit 'Commit a forbidden thing'
+
+bad=`git rev-parse HEAD:debian/some-file`
+t-policy-admin taint --global "$bad" "forbidden for testing"
+t-policy-admin taint --global "$bad" "forbidden for testing - again"
+
+t_expect_push_fail_hook+='
+t-git-objects-not-present "" $bad
+'
+
+t-dgit build
+t-expect-push-fail 'forbidden for testing' \
+t-dgit push --new
+t-git-dir-check enoent
+
+git reset --hard start
+t-commit 'will vanish from NEW'
+vanished=$v
+t-dgit build
+t-dgit push --new
+t-git-dir-check secret
+
+t-policy-periodic
+t-git-dir-check secret
+
+# pretend it vanished from new:
+rm $tmp/incoming/*
+t-archive-none example
+
+t-git-dir-time-passes
+
+t-policy-periodic
+t-git-dir-check enoent
+
+t-commit 'should require --deliberately...questionable'
+t-dgit build
+
+t-expect-push-fail E:"tag $tagpfx/${vanished//./\\.} referred to this object.*all previously pushed versions were found to have been removed" \
+t-dgit push --new
+t-git-dir-check enoent
+
+vanished=$v
+
+t-dgit push --new --deliberately-include-questionable-history
+t-git-dir-check secret
+
+t-policy-periodic
+t-git-dir-check secret
+
+t-archive-process-incoming new
+t-git-dir-time-passes
+
+t-policy-periodic
+t-git-dir-check secret
+
+oldobj=`git rev-parse HEAD`
+git reset --hard start
+t-commit 'should require --deliberately..not-ff'
+t-dgit build
+
+t-expect-push-fail "HEAD is not a descendant of the archive's version" \
+t-dgit push
+
+t-expect-push-fail \
+    "Package is in NEW and has not been accepted or rejected yet" \
+t-dgit --deliberately-TEST-dgit-only-not-fast-forward push
+
+t-dgit --deliberately-not-fast-forward push
+
+cd $dgitrepo
+t-expect-push-fail "Not a valid object name" \
+git cat-file -p $oldobj
+cd $tmp/$p
+
+t-commit 'Still not accepted, will override taint'
+t-dgit build
+t-expect-push-fail \
+    "Package is in NEW and has not been accepted or rejected yet" \
+t-dgit push
+
+t-dgit push --deliberately-include-questionable-history
+
+t-archive-process-incoming sid
+
+t-commit 'Check taint is no longer there'
+t-dgit build
+t-dgit push
+
+git checkout -b stoats $tagpfx/$vanished
+t-commit 'Simulate accidentally building on rejected version'
+t-dgit build
+t-expect-push-fail "HEAD is not a descendant of the archive's version" \
+t-dgit push
+
+: "check that uploader can't force it now"
+t-expect-push-fail "not fast forward on dgit branch" \
+t-dgit --deliberately-not-fast-forward push
+
+t-dgit pull
+t-dgit build
+t-expect-push-fail \
+    'Reason: rewound suite sid; --deliberately-not-fast-forward specified' \
+t-dgit push
+
+t-ok
diff --git a/tests/tests/debpolicy-quilt-gbp b/tests/tests/debpolicy-quilt-gbp
new file mode 100755 (executable)
index 0000000..915f9d3
--- /dev/null
@@ -0,0 +1,4 @@
+#!/bin/bash
+set -e
+. tests/lib
+t-alt-test
diff --git a/tests/tests/distropatches-reject b/tests/tests/distropatches-reject
new file mode 100755 (executable)
index 0000000..75f43db
--- /dev/null
@@ -0,0 +1,81 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+t-archive ruby-rails-3.2 3.2.6-1
+t-git-none
+
+cp $troot/pkg-srcs/${p}_3.2.6.orig.tar.gz .
+t-worktree test
+cd $p
+
+t-dgit --quilt=smash -wgf quilt-fixup
+
+build () {
+       t-dgit -wg --dpkg-buildpackage:-d build
+}
+
+expect-fail-distro-series () {
+       local why=$1; shift
+       t-expect-fail \
+       E:"Found active distro-specific series file.*(.*$why.*)" \
+       "$@"
+}
+
+mkdir -p debian/patches
+
+cat >debian/patches/boo <<'END'
+Description: add boo
+Author: Ian Jackson <ijackson@chiark.greenend.org.uk>
+
+---
+
+--- a/boo
++++ b/boo
+@@ -0,0 +1 @@
++content
+END
+
+echo boo >debian/patches/test-dummy.series
+
+git add debian/patches/boo
+git add debian/patches/test-dummy.series
+t-commit 'Add boo (on test-dummy)' 3.2.6-2
+
+expect-fail-distro-series 'distro being accessed' \
+build
+
+defaultvendor=$(perl -we '
+       use Dpkg::Vendor;
+       print lc Dpkg::Vendor::get_current_vendor
+')
+git mv debian/patches/test-dummy.series \
+       debian/patches/$defaultvendor.series
+t-commit 'Move boo (to default vendor)' 3.2.6-3
+
+expect-fail-distro-series 'current vendor' \
+build
+
+git mv debian/patches/$defaultvendor.series \
+       debian/patches/test-dummy-aside.series
+t-commit 'Move boo (to test-dummy-aside)' 3.2.6-4
+
+build
+
+DEB_VENDOR=test-dummy-aside \
+expect-fail-distro-series DEB_VENDOR \
+t-dgit push
+
+t-dgit push
+
+cd ..
+perl -i~ -pe 's/^Dgit:.*\n//' incoming/${p}_${v}.dsc
+t-archive-process-incoming sid
+
+rm -rf $p
+
+DEB_VENDOR=test-dummy-aside \
+expect-fail-distro-series DEB_VENDOR \
+t-dgit clone $p
+
+t-ok
diff --git a/tests/tests/drs-clone-nogit b/tests/tests/drs-clone-nogit
new file mode 100755 (executable)
index 0000000..915f9d3
--- /dev/null
@@ -0,0 +1,4 @@
+#!/bin/bash
+set -e
+. tests/lib
+t-alt-test
diff --git a/tests/tests/drs-push-masterupdate b/tests/tests/drs-push-masterupdate
new file mode 100755 (executable)
index 0000000..c8a5c5a
--- /dev/null
@@ -0,0 +1,50 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+t-drs
+t-tstunt-parsechangelog
+
+t-prep-newpackage example 1.0
+
+cd $p
+
+git tag common-ancestor
+
+revision=1
+t-dgit build
+t-dgit push --new
+
+push_and_check () {
+       git push $dgitrepo $1
+
+       oldmaster=`cd $dgitrepo && t-git-get-ref refs/heads/master`
+
+       t-refs-same-start
+       git checkout master
+       t-commit 'Empty update'
+       t-dgit build
+       t-dgit push --new
+
+       t-pushed-good master
+}
+
+t-check-master-undisturbed () {
+       local master=`t-git-get-ref refs/heads/master`
+       if [ x$master != x$oldmaster ]; then fail "bad update to master"; fi
+}
+
+t_check_pushed_master=t-check-master-undisturbed
+
+git checkout -b divergent common-ancestor
+git commit --allow-empty -m 'Has common ancestor'
+git push $dgitrepo HEAD:master
+
+push_and_check HEAD:master
+
+git checkout --orphan newroot
+git commit --allow-empty -m 'Has no common ancestor'
+
+push_and_check +HEAD:master
+
+t-ok
diff --git a/tests/tests/drs-push-rejects b/tests/tests/drs-push-rejects
new file mode 100755 (executable)
index 0000000..8c4ad83
--- /dev/null
@@ -0,0 +1,209 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+t-drs
+t-git-none
+
+t-select-package pari-extra
+t-worktree drs
+
+cd $p
+
+mustfail () {
+       local mpat="$1"; shift
+       t-expect-push-fail "$mpat" \
+       git push origin "$@"
+}
+
+mustsucceed () {
+       t-reporefs pre-push
+       git push origin "$@"
+       t-reporefs post-push
+       if diff $tmp/show-refs.{pre,post}-push >$tmp/show-refs.diff; then
+               fail "no refs updated"
+       fi
+}
+
+prep () {
+       local suite=$1
+       csuite=$2
+       cp $tmp/masters/* $tmp/.
+       tag_signer='-u Senatus'
+       tag_message="$p release $version for $suite ($csuite) [dgit]"
+       re-prep
+}
+re-prep () {
+       tag_name=$tagpfx/$version
+       push_spec1="HEAD:refs/dgit/$csuite"
+       push_spec2="refs/tags/$tag_name"
+       push_spec="$push_spec1 $push_spec2"
+}
+mktag () {
+       t-git-next-date
+       git tag -f $tag_signer -m "$tag_message" $tag_name "$@"
+}
+
+mkdir $tmp/masters
+cp $tmp/d[dm].* $tmp/masters
+
+version=3-2_dummy1
+
+prep unstable sid
+tag_signer='-a'
+mktag
+mustfail 'missing signature' $push_spec
+
+git cat-file tag $tag_name >goodtag
+
+for h in object type tag; do
+  for how in missing dupe; do
+
+    case $how in
+    missing) perl -pe 's/^tag /wombat$&/ if 1..m/^$/'  <goodtag >badtag ;;
+    dupe)   perl -pe 'print if 1..m/^$/ and m/^'$h' /' <goodtag >badtag ;;
+    esac
+
+    rm -f badtag.asc
+    gpg --detach-sign --armor -u Senatus badtag
+    cat badtag.asc >>badtag
+
+    set +e
+    LC_MESSAGES=C git hash-object -w -t tag badtag >badtag.hash 2>badtag.err
+    rc=$?
+    set -e
+
+    if [ $rc = 128 ] && grep 'fatal: corrupt tag' badtag.err; then
+      continue
+    elif [ $rc != 0 ]; then
+      cat badtag.err
+      fail "could not make tag"
+    fi
+
+    read <badtag.hash badtag
+    git update-ref refs/tags/$tag_name $badtag
+
+    mustfail 'multiple headers '$h' in signed tag object' $push_spec
+
+    t-expect-fsck-fail $badtag
+  done
+done
+
+prep unstable sid
+tag_message='something'
+mktag
+mustfail 'tag message not in expected format' $push_spec
+
+prep unstable sid
+mktag
+mustfail 'sid != sponge' HEAD:refs/dgit/sponge $push_spec2
+
+# fixme test --sig-policy-url string
+# fixme cannot test   reject "signature is not of type 00!";
+
+prep unstable sid
+mktag
+mustfail 'push is missing tag ref update' $push_spec1
+mustfail 'push is missing head ref update' +$push_spec2
+mustfail 'pushing unexpected ref' $push_spec HEAD:refs/wombat
+mustfail 'pushing multiple heads' $push_spec HEAD:refs/dgit/wombat
+mustfail E:'pushing multiple tags|pushing too many similar tags' \
+       $push_spec HEAD:refs/tags/$tagpfx/wombat
+
+prep unstable sid
+mktag
+echo woody >$tmp/suites
+mustfail 'unknown suite' $push_spec
+cp $root/tests/suites $tmp/.
+
+# fixme:
+#    or reject "command string not understood";
+#    reject "unknown method" unless $mainfunc;
+
+
+prep unstable sid
+mktag
+cp $tmp/dm.gpg $tmp/dd.gpg
+mustfail 'key not found in keyrings' $push_spec
+
+prep unstable sid
+mktag HEAD~
+mustfail 'tag refers to wrong commit' $push_spec
+
+prep unstable sid
+mktag HEAD~:
+mustfail 'tag refers to wrong kind of object' $push_spec
+
+prep unstable sid
+tag_name=$tagpfx/wombat
+mktag
+#git update-ref $tagpfx/$version $tagpfx/wombat
+mustfail 'tag name in tag is wrong' \
+       refs/tags/$tagpfx/wombat:refs/tags/$tagpfx/$version $push_spec1
+
+echo ====
+badcommit=$(
+       git cat-file commit HEAD | \
+       perl -pe 's/^committer.*\n//' | \
+       git hash-object -w -t commit --stdin
+)
+t-expect-fsck-fail $badcommit
+git checkout -b broken $badcommit
+prep unstable sid
+mktag
+mustfail "corrupted object $badcommit" $push_spec
+
+git checkout dgit/sid
+prep unstable sid
+mktag
+mustsucceed $push_spec # succeeds
+
+mktag
+mustfail 'push is missing head ref update' $push_spec1 +$push_spec2
+
+git commit --allow-empty -m 'Dummy update'
+mktag
+mustfail 'not replacing previously-pushed version' +$push_spec1 +$push_spec2
+
+t-newtag
+re-prep
+mktag
+mustfail 'not replacing previously-pushed version' +$push_spec1 +$push_spec2
+
+git reset --hard HEAD~
+
+prep_dm_mangle () {
+       prep unstable sid
+       perl -i.bak -pe '
+               next unless m/^fingerprint: 3A82860837A0CD32/i../^$/;
+       ' -e "$1" $tmp/dm.txt
+       tag_signer='-u Populus'
+       mktag
+}
+
+git commit --amend --message 'Make it not a fast forward'
+version=3-2_dummy2
+prep unstable sid
+mktag
+mustfail 'not fast forward on dgit branch' +$push_spec1 +$push_spec2
+
+git checkout v2
+version=3-2_dummy2
+
+prep_dm_mangle ''
+perl -i.bak -ne 'print if 1..s/(pari-extra).*\n/$1/' $tmp/dm.txt
+mustfail '' $push_spec # malformed (truncated) dm.txt; don't care about msg
+
+prep_dm_mangle 's/allow:/asponge:/i'
+mustfail 'missing Allow section in permission' $push_spec
+
+prep_dm_mangle 's/\bpari-extra\b/sponge/i'
+mustfail "package $p not allowed for key" $push_spec
+
+prep_dm_mangle 'y/0-9/5-90-4/ if m/^fingerprint:/i'
+mustfail "not in permissions list although in keyring" $push_spec
+
+prep_dm_mangle ''
+mustsucceed $push_spec # succeeds
+
+t-ok
diff --git a/tests/tests/dsd-clone-drs b/tests/tests/dsd-clone-drs
new file mode 100755 (executable)
index 0000000..4346579
--- /dev/null
@@ -0,0 +1,16 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+t-restrict x-dgit-intree-only
+t-restrict x-dgit-git-only
+
+t-dsd
+
+cd $tmp
+t-dgit clone-dgit-repos-server drs-cloned
+
+cd drs-cloned
+ls -al infra/dgit-repos-server
+
+t-ok
diff --git a/tests/tests/dsd-clone-nogit b/tests/tests/dsd-clone-nogit
new file mode 100755 (executable)
index 0000000..915f9d3
--- /dev/null
@@ -0,0 +1,4 @@
+#!/bin/bash
+set -e
+. tests/lib
+t-alt-test
diff --git a/tests/tests/dsd-divert b/tests/tests/dsd-divert
new file mode 100755 (executable)
index 0000000..3020a56
--- /dev/null
@@ -0,0 +1,7 @@
+#!/bin/bash
+set -e
+. tests/lib
+t-dsd
+rm $drs_dispatch/repos
+echo '* drs' >>$drs_dispatch/diverts
+t-chain-test fetch-somegit-notlast
diff --git a/tests/tests/fetch-localgitonly b/tests/tests/fetch-localgitonly
new file mode 100755 (executable)
index 0000000..7d603b2
--- /dev/null
@@ -0,0 +1,20 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+t-archive pari-extra 3-2~dummy1
+t-git-none
+t-worktree 3-1
+cd $p
+old=`git rev-parse HEAD`
+
+# pretend that we previously fetched 3-1 (otherwise, dgit
+# is entitled to, and will, make a new history)
+git update-ref refs/remotes/dgit/dgit/sid refs/heads/dgit/sid
+
+t-dgit pull
+
+t-cloned-fetched-good
+t-has-ancestor $old
+
+t-ok
diff --git a/tests/tests/fetch-somegit-notlast b/tests/tests/fetch-somegit-notlast
new file mode 100755 (executable)
index 0000000..63abe8a
--- /dev/null
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+set -e
+. tests/lib
+
+t-git pari-extra 3-1
+t-archive pari-extra 3-2~dummy1
+
+t-dgit clone $p
+cd $p
+
+t-cloned-fetched-good
+t-has-ancestor debian/3-1
+
+t-ok
diff --git a/tests/tests/gbp-orig b/tests/tests/gbp-orig
new file mode 100755 (executable)
index 0000000..beea121
--- /dev/null
@@ -0,0 +1,77 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+t-tstunt-parsechangelog
+t-tstunt-debuild
+t-tstunt-lintian
+
+t-archive-none example
+t-git-none
+t-worktree 1.0
+
+cd $p
+
+: '----- construct an unpatched branch with patches -----'
+
+git checkout patch-queue/quilt-tip
+gbp pq export
+: 'now on quilt-tip'
+git add debian/patches
+git commit -m 'Commit patch queue'
+
+: '----- construct an upstream branch -----'
+
+git checkout --orphan upstream
+git reset --hard
+git clean -xdf
+
+tar --strip-components=1 -xf $troot/pkg-srcs/${p}_1.0.orig.tar.gz
+
+mkdir docs
+cd docs
+tar --strip-components=1 -xf $troot/pkg-srcs/${p}_1.0.orig-docs.tar.gz
+cd ..
+
+git add -Af .
+git commit -m 'Import 1.0'
+git tag upstream/1.0
+
+git checkout quilt-tip
+t-git-pseudo-merge upstream
+
+v=1.0-1
+
+: '----- let gbp build a .orig for comparison -----'
+
+gbp buildpackage --git-ignore-branch --git-no-sign-tags -us -uc
+
+mkdir ../gbp-output
+mv ../*1.0* ../gbp-output/.
+rm -f ../*.changes
+
+: '----- now do it ourselves -----'
+
+t-dgit -wgf --dgit-view-save=split.b gbp-build --git-ignore-branch
+
+t-dgit -wgf --quilt=gbp clean # gbp leaves dirty trees :-/
+
+t-dgit -wgf --dgit-view-save=split.p --quilt=gbp push --new
+
+t-gbp-pushed-good
+
+: '----- check .origs are the same -----'
+
+# if gbp weren't weird about .gitignore we could just debdiff the .dscs
+
+for d in . gbp-output; do
+       cd $tmp/$d
+       mkdir tar-x
+       cd tar-x
+       tar zxf ../${p}_${v%-*}.orig.tar.gz
+done
+
+cd $tmp
+diff -ruN gbp-output/tar-x tar-x
+
+t-ok
diff --git a/tests/tests/gitconfig b/tests/tests/gitconfig
new file mode 100755 (executable)
index 0000000..12b342b
--- /dev/null
@@ -0,0 +1,38 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+t-tstunt-parsechangelog
+
+t-prep-newpackage example 1.0
+
+cd $p
+
+t-dgit clean | tee ../t.output
+grep 'EXAMPLE RULES TARGET clean' ../t.output
+
+t-git-config dgit.default.clean-mode git
+
+t-dgit clean | tee ../t.output
+
+set +e
+grep 'EXAMPLE RULES TARGET clean' ../t.output
+rc=$?
+set -e
+test $rc = 1
+
+git config dgit.default.clean-mode dpkg-source-d
+
+t-dgit clean | tee ../t.output
+grep 'EXAMPLE RULES TARGET clean' ../t.output
+
+t-git-config dgit.default.opts-dpkg-buildpackage --dgit-fail-global
+git config --add dgit.default.opts-dpkg-buildpackage --dgit-fail-foo
+git config --add dgit.default.opts-dpkg-buildpackage --dgit-fail-bar
+
+t-expect-fail '--dgit-fail-global --dgit-fail-foo --dgit-fail-bar' \
+t-dgit clean
+
+t-dgit -cdgit.default.clean-mode=none clean
+
+t-ok
diff --git a/tests/tests/import-dsc b/tests/tests/import-dsc
new file mode 100755 (executable)
index 0000000..b86eef6
--- /dev/null
@@ -0,0 +1,99 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+t-setup-import examplegit
+
+p=example
+
+check-import () {
+       path=$1
+       v=$2
+       opts=$3
+       branch=t.$v
+
+       dsc=${path}/${p}_${v}.dsc
+       t-dgit $opts import-dsc $dsc $branch
+
+       git checkout $branch
+
+       check-imported $dsc
+}
+
+check-imported () {
+       local dsc=$1
+       (
+       rm -rf ../t.unpack
+       mkdir ../t.unpack
+       cd ../t.unpack
+       dpkg-source -x $dsc
+       )
+
+       git checkout HEAD~0
+       git branch -D u.$v ||:
+       git checkout -b u.$v $branch
+       git rm -rf .
+       git clean -xdf
+       cp -al ../t.unpack/*/. .
+       git add -Af .
+
+       git diff --stat --exit-code
+}
+
+cd $p
+
+check-import ../mirror/pool/main 1.2
+
+dgit12=`git rev-parse HEAD`
+
+dsc2=../mirror/pool/main/${p}_2.0.dsc
+
+git checkout $branch
+t-expect-fail 'is checked out - will not update' \
+t-dgit import-dsc $dsc2 $branch
+
+git checkout HEAD~0
+
+t-expect-fail 'Not fast forward' \
+t-dgit import-dsc $dsc2 $branch
+
+t-expect-fail 'Not fast forward' \
+t-dgit import-dsc $dsc2 ..$branch
+
+t-dgit import-dsc $dsc2 +$branch
+check-imported $dsc2
+
+cd ..
+mkdir $p.2
+cd $p.2
+
+git init
+
+check-import $troot/pkg-srcs 1.0-1
+
+t-expect-fail "Your git tree does not have that object" \
+check-import ../mirror/pool/main 1.2
+
+check-import ../mirror/pool/main 1.2 --force-import-dsc-with-dgit-field
+
+v=1.0-1.100
+dsc2=$troot/pkg-srcs/${p}_${v}.dsc
+
+t-expect-fail E:'Branch.*already exists' \
+t-dgit import-dsc $dsc2 $branch
+
+git branch merge-reset
+t-dgit import-dsc $dsc2 ..$branch
+t-has-ancestor merge-reset $branch
+
+git push . +merge-reset:$branch
+
+t-dgit import-dsc $dsc2 +$branch
+
+mb=$(t-git-merge-base merge-reset $branch)
+test "x$mb" = x
+
+t-expect-fail 'signature check failed' \
+t-dgit import-dsc --require-valid-signature $dsc2 +$branch
+
+t-ok
diff --git a/tests/tests/import-native b/tests/tests/import-native
new file mode 100755 (executable)
index 0000000..1e09343
--- /dev/null
@@ -0,0 +1,69 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+t-setup-import examplegit
+t-tstunt-parsechangelog
+
+mkdir $tmp/aside
+
+versions=""
+for f in $(find $tmp/mirror -name \*.dsc | sort); do
+       perl -i -pe '
+               $_="" if m/^-----BEGIN PGP SIGNED/..!m/\S/;
+               $_="" if m/^-----BEGIN PGP SIGNATURE/..0;
+               $_="" if m/^Dgit: /;
+       ' $f
+       mv $f $tmp/aside/.
+       version="${f%.dsc}"
+       version="${version##*/${p}_}"
+       versions+=" $version"
+done
+
+echo $versions
+
+rm -rf $tmp/git/$p.git
+t-archive-none $p
+
+cd $p
+
+lrref=refs/remotes/dgit/dgit/sid
+
+git update-ref -d $lrref
+
+for v in $versions; do
+       git show-ref
+
+       mv $tmp/aside/${p}_${v}.dsc $tmp/mirror/pool/main/
+       t-archive-query
+
+       t-dgit fetch
+
+       set +e
+       git merge-base HEAD remotes/dgit/dgit/sid
+       rc=$?
+       set -e
+       test $rc = 1
+
+       t-refs-same-start
+       t-ref-same-exact refs/tags/$p/$v:
+       t-ref-same-exact refs/remotes/dgit/dgit/sid:
+
+       first_imp=first-import/$v
+       git tag first-import/$v $lrref
+
+       if [ "$lastv_imp" ]; then
+               git update-ref $lrref $lastv_imp
+
+               t-git-next-date
+               t-dgit fetch
+
+               t-refs-same-start
+               t-ref-same $first_imp
+               t-ref-same $lrref
+       fi
+
+       lastv_imp=$this_imp
+done
+
+t-ok
diff --git a/tests/tests/import-nonnative b/tests/tests/import-nonnative
new file mode 100755 (executable)
index 0000000..3568563
--- /dev/null
@@ -0,0 +1,17 @@
+#!/bin/bash
+set -e
+. tests/lib
+. $troot/lib-import-chk
+
+t-tstunt-parsechangelog
+
+# 1.0 with diff
+t-import-chk pari-extra 3-1
+
+# 3.0 (quilt), multiple patches, multiple origs
+t-import-chk example 1.0-1
+
+# 3.0 (quilt), single-debian-patch, one orig
+t-import-chk sunxi-tools 1.2-2.~~dgittest
+
+t-ok
diff --git a/tests/tests/import-tarbomb b/tests/tests/import-tarbomb
new file mode 100755 (executable)
index 0000000..9b7f65a
--- /dev/null
@@ -0,0 +1,49 @@
+#!/bin/bash
+set -e
+. tests/lib
+. $troot/lib-import-chk
+
+t-tstunt-parsechangelog
+
+mangle1 () {
+       rm -f ${1}_*
+       t-import-chk1 "$@"
+       cd $tmp/mirror/pool/main
+       dpkg-source -x ${p}_${v}.dsc td
+       orig=${p}_${v%-*}.orig.tar.gz
+       tar zxf $orig
+       rm $orig ${p}_${v}.*
+       cd $p
+       mkdir urk
+       echo urk >urk/urk
+       export GZIP=-1
+}
+mangle2 () {
+       cd ..
+       dpkg-source -b td
+       rm -rf $p td
+       cd $tmp
+       t-archive-none $p
+       t-archive-query
+       t-import-chk2
+}
+
+# 3.0 (quilt), multiple patches, tarbomb orig
+mangle1 example 1.0-1
+tar zvcf ../$orig *
+mangle2
+
+# 3.0 (quilt), multiple patches, tarbomb orig with dot
+mangle1 example 1.0-1
+tar zvcf ../$orig .
+mangle2
+
+# 3.0 (quilt), multiple patches, tarbomb orig with dot and .git and .pc
+mangle1 example 1.0-1
+git init
+mkdir .pc
+echo SPONG >.pc/SPONG
+tar zvcf ../$orig .
+mangle2
+
+t-ok
diff --git a/tests/tests/inarchivecopy b/tests/tests/inarchivecopy
new file mode 100755 (executable)
index 0000000..b1e0c5f
--- /dev/null
@@ -0,0 +1,79 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+t-setup-import examplegit
+t-tstunt-parsechangelog
+
+cd $p
+git checkout -b dgit/stable dgit/dgit/stable
+cd ..
+
+t-inarchive-copy () {
+       local vm=$1
+       local from=${2:-sid}
+       local to=${3:-stable}
+       egrep "^${vm//./\\.}" aq/package.$from.$p >>aq/package.$to.$p
+       t-aq-archive-updated $to $p
+}
+
+copy-check-good () {
+       git diff $vtag
+       t-refs-same refs/remotes/dgit/dgit/$tosuite
+       t-ref-head
+       t-has-parent-or-is HEAD $vtag
+}
+
+copy-check () {
+       local vm=$1
+       local tosuite=${2:-stable}
+       t-inarchive-copy $vm '' $tosuite
+
+       vtag=$(v=$vm t-v-tag)
+
+       cd $p
+        t-refs-same-start
+        t-dgit fetch $tosuite
+        git merge --ff-only dgit/dgit/$tosuite
+
+        copy-check-good
+        local fetched=$(t-sametree-parent HEAD)
+       cd ..
+
+       rm -rf example.cloned
+       t-dgit clone $p $tosuite example.cloned
+
+       cd example.cloned
+        t-refs-same-start
+        copy-check-good
+        local cloned=$(t-sametree-parent HEAD)  
+       cd ..
+
+       rm -rf example.initd
+       mkdir example.initd
+       cd example.initd
+        git init
+        t-refs-same-start
+        t-dgit -p $p fetch $tosuite
+        git reset --hard refs/remotes/dgit/dgit/$tosuite
+        copy-check-good
+        local initd=$(t-sametree-parent HEAD)
+       cd ..
+
+       t-refs-same-start
+       t-ref-same-val fetched $fetched
+       t-ref-same-val cloned $cloned
+       t-ref-same-val initd $initd
+}
+
+copy-check 2.0
+
+copy-check 2.1
+
+cd $p
+git checkout -b dgit/testing $(v=1.1 t-v-tag)
+cd ..
+
+copy-check 2.1 testing
+
+t-ok
diff --git a/tests/tests/mirror b/tests/tests/mirror
new file mode 100755 (executable)
index 0000000..4947688
--- /dev/null
@@ -0,0 +1,80 @@
+#!/bin/bash
+set -e
+. tests/lib
+. $troot/lib-mirror
+
+t-dependencies rsync
+
+t-drs
+
+: ---- "basic test" ----
+
+t-tstunt-parsechangelog
+t-prep-newpackage example 1.0
+
+t-mirror-setup
+
+cd $p
+revision=1
+t-dgit build
+t-dgit push --new
+
+t-check-mirrored
+
+: ---- "stunt ssh test" ----
+
+sentinel=$tmp/stunt-ssh-sentinel
+
+cat <<END >$tmp/stunt-ssh
+#!/bin/sh
+set -ex
+: $sentinel
+cat >&2 $sentinel
+shift # eat HOST
+sh -c "\$*"
+END
+chmod +x $tmp/stunt-ssh
+
+t-mirror-set rsyncssh=$tmp/stunt-ssh
+t-mirror-set remoterepos=HOST:$reposmirror
+
+# mirror should fail due to lack of stunt-ssh-sentinel
+
+t-commit-build-push-expect-log "stunt ssh test" \
+       E:'mirror hook failed: .*exited'
+
+ls -al $queuedir/$p.a
+t-check-not-mirrored
+
+touch $sentinel
+
+t-mirror-hook backlog
+t-check-mirrored
+
+: ----- "stall timeout test" -----
+
+rm -f $sentinel
+mkfifo $sentinel
+
+t-mirror-set hooktimeout=5
+
+t-commit-build-push-expect-log "stall timeout test" \
+       E:'mirror hook failed: .*timeout'
+
+t-check-not-mirrored
+
+exec 3<>$sentinel
+exec 3>&-
+
+attempts=100
+while [ -f $queuedir/$p.lock ]; do
+       if [ $attempts = 0 ]; then \
+               fail "timed out waiting for lock to go away"
+       fi
+       attempts=$(( $attempts - 1 ))
+       sleep 0.1
+done
+
+t-check-mirrored
+
+t-ok
diff --git a/tests/tests/mirror-debnewgit b/tests/tests/mirror-debnewgit
new file mode 100755 (executable)
index 0000000..59b96ef
--- /dev/null
@@ -0,0 +1,26 @@
+#!/bin/bash
+set -e
+. tests/lib
+. $troot/lib-mirror
+
+t-dependencies rsync
+
+t-debpolicy
+
+t-archive pari-extra 3-1
+t-git-none
+
+t-mirror-setup
+
+t-dgit clone $p
+
+cd $p
+t-cloned-fetched-good
+
+t-commit 'test commit' 3-2
+
+t-dgit build
+t-dgit push
+t-check-mirrored
+
+t-ok
diff --git a/tests/tests/mirror-private b/tests/tests/mirror-private
new file mode 100755 (executable)
index 0000000..c26ba89
--- /dev/null
@@ -0,0 +1,26 @@
+#!/bin/bash
+set -e
+. tests/lib
+. $troot/lib-mirror
+
+t-dependencies rsync
+
+t-debpolicy
+
+t-tstunt-parsechangelog
+t-prep-newpackage example 1.0
+
+t-mirror-setup
+
+cd $p
+revision=1
+
+t-reporefs master
+
+t-dgit build
+t-dgit push --new
+
+t-check-not-mirrored
+t-files-notexist $reposmirror/$p.*
+
+t-ok
diff --git a/tests/tests/mismatches-contents b/tests/tests/mismatches-contents
new file mode 100755 (executable)
index 0000000..ea0d724
--- /dev/null
@@ -0,0 +1,24 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+t-tstunt-parsechangelog
+
+t-prep-newpackage example 1.0
+
+ln -s $troot/pkg-srcs/${p}_${v%-*}.orig.tar.* .
+
+cd $p
+
+v=1.0-1
+dch -v $v -D unstable -m 'Make a revision'
+echo foo >us-file
+git add us-file debian/changelog
+git commit -m "Commit $v"
+
+t-dgit build-source
+
+t-expect-fail 'debian/TRASH' \
+t-dgit push --new
+
+t-ok
diff --git a/tests/tests/mismatches-dscchanges b/tests/tests/mismatches-dscchanges
new file mode 100755 (executable)
index 0000000..85c7086
--- /dev/null
@@ -0,0 +1,34 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+t-tstunt-parsechangelog
+
+t-prep-newpackage example 1.0
+
+cd $p
+revision=1
+
+check () {
+       local fext=$1
+       local emsgpat=$2
+
+       t-dgit -wgf build-source
+
+       perl -i~ -pe 's/^ ([0-9a-f])/ sprintf " %x", (hex $1)^1 /e' \
+               ../*.$fext
+
+       t-expect-fail "$emsgpat" \
+       t-dgit -wgf push --new
+}
+
+check dsc E:'dpkg-source.*error.*checksum'
+check changes E:'dgit.*hash or size.*varies'
+
+# and finally check that our test is basically working
+
+t-dgit -wgf build-source
+
+t-dgit -wgf push --new
+
+t-ok
diff --git a/tests/tests/multisuite b/tests/tests/multisuite
new file mode 100755 (executable)
index 0000000..d39475b
--- /dev/null
@@ -0,0 +1,57 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+t-setup-import examplegit
+t-tstunt-parsechangelog
+
+cd $p
+
+rsta=$(t-git-get-ref refs/remotes/dgit/dgit/stable)
+rsid=$(t-git-get-ref refs/remotes/dgit/dgit/sid)
+
+multi-good () {
+       t-refs-same-start
+       t-refs-same refs/remotes/dgit/dgit/stable
+       t-ref-same-val "previous stable" $rsta
+
+       t-refs-same-start
+       t-refs-same refs/remotes/dgit/dgit/sid
+       t-ref-same-val "previous sid" $rsid
+
+       t-refs-same-start
+       t-refs-same refs/remotes/dgit/dgit/stable,sid
+       t-ref-same-val "previous combined" $rcombined
+}
+
+t-dgit fetch stable,unstable
+
+rcombined=$(t-git-get-ref refs/remotes/dgit/dgit/stable,sid)
+
+multi-good
+
+cd ..
+
+t-dgit clone --no-rm-on-error $p stable,unstable ./$p.clone
+
+cd $p.clone
+
+multi-good
+
+t-commit bogus 3.0 stable,unstable
+t-expect-fail "does not support multiple" \
+t-dgit -wgf build
+
+cd ..
+
+t-dgit clone --no-rm-on-error $p stable ./$p.pull
+cd $p.pull
+git checkout -b x
+git commit --allow-empty -m X
+t-dgit pull stable,unstable
+
+multi-good
+
+t-has-parent-or-is HEAD $rcombined
+
+t-ok
diff --git a/tests/tests/newtag-clone-nogit b/tests/tests/newtag-clone-nogit
new file mode 100755 (executable)
index 0000000..915f9d3
--- /dev/null
@@ -0,0 +1,4 @@
+#!/bin/bash
+set -e
+. tests/lib
+t-alt-test
diff --git a/tests/tests/oldnewtagalt b/tests/tests/oldnewtagalt
new file mode 100755 (executable)
index 0000000..098fe19
--- /dev/null
@@ -0,0 +1,25 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+t-setup-import examplegit
+t-tstunt-parsechangelog
+
+cd $p
+
+test-push () {
+       t-commit "$1"
+       t-dgit build-source
+       t-dgit push
+}
+
+for count in 1 2; do
+       t-oldtag
+       test-push "oldtag $count"
+
+       t-newtag
+       test-push "newtag $count"
+done
+
+t-ok
+
diff --git a/tests/tests/oldtag-clone-nogit b/tests/tests/oldtag-clone-nogit
new file mode 100755 (executable)
index 0000000..915f9d3
--- /dev/null
@@ -0,0 +1,4 @@
+#!/bin/bash
+set -e
+. tests/lib
+t-alt-test
diff --git a/tests/tests/orig-include-exclude b/tests/tests/orig-include-exclude
new file mode 100755 (executable)
index 0000000..a37f997
--- /dev/null
@@ -0,0 +1,21 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+suitespecs+=' stable'
+
+. $troot/lib-orig-include-exclude
+
+ofb=example_1.1.orig.tar
+zcat $ofb.gz >$ofb.SPONG
+gzip -1Nv $ofb.SPONG
+mv $ofb.SPONG.gz $ofb.gz
+
+cd $p
+
+test-push-1 1.1-1.3 '' stable
+
+t-expect-fail E:'archive contains .* with different checksum' \
+test-push-2 --new
+
+t-ok
diff --git a/tests/tests/orig-include-exclude-chkquery b/tests/tests/orig-include-exclude-chkquery
new file mode 100755 (executable)
index 0000000..f8eac57
--- /dev/null
@@ -0,0 +1,33 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+t-git-config dgit-distro.test-dummy.archive-query ftpmasterapi:
+# ^ that will crash if it gets unexpected file_in_archive queries
+
+# orig-include-exclude will set origs and usvsns
+update-files_in_archive () {
+       for o in $origs; do for usvsn in $usvsns; do \
+               of=${p}_${v%-*}.${o}.tar.gz
+               pat="%/${of//_/\\_}"
+               # curl url-decodes these things so we have to have literals
+               find $tmp/mirror -name $of | \
+                       xargs -r sha256sum | \
+                       perl -pe '
+                               BEGIN { print "["; }
+                               chomp;
+                               s/^/{"sha256sum":"/;
+                               s/  /","filename":"/;
+                               s/$/"}$delim/;
+                               $delim=",";
+                               END { print "]\n"; }
+                       ' \
+                       >$tmp/aq/"file_in_archive/$pat"
+       done; done
+}
+
+test_push_2_hook=update-files_in_archive
+
+. $troot/lib-orig-include-exclude
+
+t-ok
diff --git a/tests/tests/overwrite-chkclog b/tests/tests/overwrite-chkclog
new file mode 100755 (executable)
index 0000000..3544390
--- /dev/null
@@ -0,0 +1,39 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+t-setup-import examplegit
+
+cd example
+
+suite=stable
+
+t-commit 'No changes, just send to stable' '' stable
+
+t-dgit -wgf build
+
+t-expect-fail 'Perhaps debian/changelog does not mention' \
+t-dgit push --overwrite stable
+
+t-dgit setup-mergechangelogs
+
+t-expect-fail 'fix conflicts and then commit the result' \
+git merge dgit/dgit/stable
+
+git checkout master which
+EDITOR=: git commit
+
+t-dgit -wgf build
+
+perl -i~ -pe 's/^(\w+ \(\S+)(\) stable)/$1+X$2/ if $.>1' debian/changelog
+git add debian/changelog
+git commit -m 'Break changelog'
+
+t-expect-fail 'Perhaps debian/changelog does not mention' \
+t-dgit push --overwrite stable
+
+git revert --no-edit 'HEAD^{/Break changelog}'
+
+t-dgit push --overwrite stable
+
+t-ok
diff --git a/tests/tests/overwrite-junk b/tests/tests/overwrite-junk
new file mode 100755 (executable)
index 0000000..e11d1f8
--- /dev/null
@@ -0,0 +1,22 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+t-setup-import examplegit
+
+cd example
+
+suite=stable
+
+t-commit 'No changes, just send to stable' '' stable
+
+t-dgit -wgf build
+
+(
+ : "make a bit of a wrongness, which we still want to be able to overwrite"
+ cd $tmp/git/$p.git; git tag -f $tagpfx/1.2 $tagpfx/1.1
+)
+
+t-dgit push --overwrite=1.2 stable
+
+t-ok
diff --git a/tests/tests/overwrite-splitbrains b/tests/tests/overwrite-splitbrains
new file mode 100755 (executable)
index 0000000..0ef03f6
--- /dev/null
@@ -0,0 +1,27 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+t-tstunt-parsechangelog
+
+t-gbp-example-prep-no-ff
+t-newtag
+
+t-dgit --quilt=gbp --dgit-view-save=split.b build-source
+
+t-dgit fetch
+
+t-refs-same-start
+t-ref-head
+
+t-expect-fail 'check failed (maybe --overwrite is needed' \
+t-dgit --quilt=gbp --dgit-view-save=split.p push
+
+t-refs-same-start
+t-ref-head
+
+t-dgit --quilt=gbp --dgit-view-save=split.p --overwrite push
+
+t-gbp-pushed-good
+
+t-ok
diff --git a/tests/tests/overwrite-version b/tests/tests/overwrite-version
new file mode 100755 (executable)
index 0000000..34301ac
--- /dev/null
@@ -0,0 +1,20 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+t-setup-import examplegit
+
+cd example
+
+suite=stable
+
+t-commit 'No changes, just send to stable' '' stable
+
+t-dgit -wgf build
+
+t-expect-fail 'HEAD is not a descendant' \
+t-dgit push stable
+
+t-dgit push --overwrite=1.2 stable
+
+t-ok
diff --git a/tests/tests/push-buildproductsdir b/tests/tests/push-buildproductsdir
new file mode 100755 (executable)
index 0000000..505d105
--- /dev/null
@@ -0,0 +1,31 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+t-archive pari-extra 3-1
+t-git pari-extra 3-1
+
+t-dgit clone $p
+
+cd $p
+t-cloned-fetched-good
+
+v=3-2~dummy1
+t-apply-diff 3-1 $v
+debcommit -a
+
+t-refs-same-start
+t-ref-head
+
+t-dgit --dpkg-buildpackage:-d build
+
+cd ..
+mkdir bpd
+mv $p*_* bpd/
+cd $p
+
+t-dgit --build-products-dir=../bpd push
+
+t-pushed-good dgit/sid
+
+t-ok
diff --git a/tests/tests/push-newpackage b/tests/tests/push-newpackage
new file mode 100755 (executable)
index 0000000..79355e3
--- /dev/null
@@ -0,0 +1,30 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+t-prep-newpackage pari-extra 3-1
+
+cd $p
+t-refs-same-start
+t-ref-head
+
+t-expect-push-fail 'package appears to be new in this suite' \
+t-dgit push
+
+t-dgit build
+
+git checkout bogus
+
+set +e
+(set -e; DGIT_TEST_DEBUG=' ' t-dgit push --new)
+rc=$?
+set -e
+if [ $rc = 0 ]; then fail "push succeeded when tree mismatch"; fi
+
+git checkout master
+
+t-dgit push --new
+
+t-pushed-good master
+
+t-ok
diff --git a/tests/tests/push-nextdgit b/tests/tests/push-nextdgit
new file mode 100755 (executable)
index 0000000..d0436ab
--- /dev/null
@@ -0,0 +1,25 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+t-archive pari-extra 3-1
+t-git pari-extra 3-1
+
+t-dgit clone $p
+
+cd $p
+t-cloned-fetched-good
+
+v=3-2~dummy1
+t-apply-diff 3-1 $v
+debcommit -a
+
+t-refs-same-start
+t-ref-head
+
+t-dgit --dpkg-buildpackage:-d build
+t-dgit push
+
+t-pushed-good dgit/sid
+
+t-ok
diff --git a/tests/tests/quilt b/tests/tests/quilt
new file mode 100755 (executable)
index 0000000..1a921b3
--- /dev/null
@@ -0,0 +1,96 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+t-archive ruby-rails-3.2 3.2.6-1
+t-git-none
+
+mkdir -p incoming
+cd incoming
+t-worktree test
+cd ..
+
+t-dgit clone $p
+
+cd $p
+t-cloned-fetched-good
+
+git fetch $tmp/incoming/$p dgit/sid:incoming
+
+dummy=0
+
+iteration () {
+       dummy=$(( $dummy + 1))
+       v=3.2.6-2~dummy${dummy}
+
+       t-refs-same-start
+       t-dgit --dpkg-buildpackage:-d build
+       t-dgit push
+       t-pushed-good dgit/sid
+}
+
+git cherry-pick -x incoming~1; iteration
+git cherry-pick -x incoming~0; iteration
+
+git fetch $tmp/incoming/$p incoming-branch:branch
+git checkout branch
+git rebase --onto dgit/sid incoming
+git checkout dgit/sid
+git merge branch
+iteration
+
+diff <<END - debian/patches/series
+ups-topic/ups-yml
+spongiform-upstream-new-file-incl-change
+zorkmid-options-=-42
+END
+
+for f in `cat debian/patches/series`; do
+       egrep -q '^From.*ijackson@chiark' debian/patches/$f
+done
+
+t-822-field ../${p}_${v}_*.changes Changes |
+ grep -Fx 'ruby-rails-3.2 (3.2.6-2~dummy1) unstable; urgency=low'
+
+t-git-next-date
+
+# Now we are going to check that our dgit-generated patches round
+# trip to similar git commits when imported by gbp pq:
+
+git clean -xdf
+
+# We need to make a patches-unapplied version
+unpa=$(git log --pretty=format:'%H' --grep '^\[dgit import unpatched')
+git checkout -b for-gbp
+git reset "$unpa" .
+git reset HEAD debian
+git commit -m UNAPPY
+git reset --hard
+git clean -xdf
+
+export GIT_AUTHOR_NAME='Someone Else'
+export GIT_AUTHOR_EMAIL='else@example.com'
+export GIT_COMMITTER_NAME='Someone Else'
+export GIT_COMMITTER_EMAIL='else@example.com'
+
+gbp pq import
+
+for compare in $(git log --pretty='format:%H' \
+       --grep 'Change something in the upstream yml')
+do
+       git cat-file commit $compare >../this.cmp
+       # normalise
+       perl -i~$compare~ -0777 -pe '
+               s/\n+$//; $_ .= "\n";
+               s/^(?:committer|parent|tree) .*\n//gm;
+               s/\n+(\(cherry picked from .*\)\n)\n*/\n\n/m
+                       and s/$/$1/;
+               s/\n+$//; $_ .= "\n";
+       ' ../this.cmp
+       if test -f ../last.cmp; then
+               diff -u ../last.cmp ../this.cmp
+       fi
+       mv ../this.cmp ../last.cmp
+done
+
+t-ok
diff --git a/tests/tests/quilt-gbp b/tests/tests/quilt-gbp
new file mode 100755 (executable)
index 0000000..3ef89e8
--- /dev/null
@@ -0,0 +1,61 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+t-tstunt-parsechangelog
+
+t-gbp-example-prep
+
+t-expect-fail 'quilt fixup cannot be linear' \
+  t-dgit build-source
+
+t-git-config dgit-distro.test-dummy.dgit-tag-format new
+t-expect-fail 'requires split view so server needs to support' \
+t-dgit -wgf --quilt=gbp build-source
+t-newtag
+
+t-dgit --quilt=gbp --dgit-view-save=split.b1 build-source
+git rev-parse split.b1
+
+t-dgit --quilt=gbp --gbp-pq=no-such-command-gbp build-source
+
+echo spong >debian/pointless-for-dgit-test
+git add debian/pointless-for-dgit-test
+git commit -m Pointless
+
+t-expect-fail no-such-command-gbp \
+t-dgit --quilt=gbp --clean=git --gbp-pq=no-such-command-gbp build-source
+
+test-push-1 () {
+       t-refs-same-start
+       t-ref-head
+}
+
+test-push-2 () {
+       t-dgit --quilt=gbp --dgit-view-save=split.p push
+
+       t-gbp-pushed-good
+}
+
+test-push-1
+
+t-dgit --quilt=gbp --clean=git --dgit-view-save=split.b build-source
+
+t-expect-fail "HEAD specifies a different tree to $p" \
+  t-dgit push
+
+test-push-2
+
+echo wombat >>debian/pointless-for-dgit-test
+git add debian/pointless-for-dgit-test
+git commit -m 'Pointless 2'
+
+t-commit 'Check pseudomerge' 1.0-3
+
+test-push-1
+
+t-dgit --quilt=gbp --clean=git --dgit-view-save=split.b build-source
+
+test-push-2
+
+t-ok
diff --git a/tests/tests/quilt-gbp-build-modes b/tests/tests/quilt-gbp-build-modes
new file mode 100755 (executable)
index 0000000..cd77dab
--- /dev/null
@@ -0,0 +1,13 @@
+#!/bin/bash
+set -e
+. tests/lib
+. $troot/lib-build-modes
+
+bm-gbp-example-acts                            \
+       'build-source'                          \
+       'build -b'                              \
+       'build -S'                              \
+       'gbp-build -S'                          \
+       'gbp-build -b'                          \
+
+t-ok
diff --git a/tests/tests/quilt-gbp-build-modes-sbuild b/tests/tests/quilt-gbp-build-modes-sbuild
new file mode 100755 (executable)
index 0000000..4c86bfe
--- /dev/null
@@ -0,0 +1,12 @@
+#!/bin/bash
+set -e
+. tests/lib
+. $troot/lib-build-modes
+
+t-dependencies sbuild
+t-restrict x-dgit-schroot-build
+
+bm-gbp-example-acts                            \
+       'sbuild -c build --arch-all'            \
+
+t-ok
diff --git a/tests/tests/quilt-singlepatch b/tests/tests/quilt-singlepatch
new file mode 100755 (executable)
index 0000000..3b6e228
--- /dev/null
@@ -0,0 +1,39 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+t-archive sunxi-tools 1.2-2.~~dgittest
+t-git-none
+
+t-dgit clone $p
+cd $p
+t-cloned-fetched-good
+
+echo EXTRA-LINE-1 >>fel.c
+echo EXTRA-LINE-2 >>fel.c
+echo EXTRA-LINE-3 >>.gitignore
+
+git add fel.c .gitignore
+
+t-commit 'commit our stuff' 1.2-3
+
+t-dgit -wgf quilt-fixup
+
+t-refs-same-start
+t-ref-head
+
+t-dgit -wgf build-source
+
+t-dgit push
+t-pushed-good dgit/sid
+
+diff <<END - debian/patches/series
+debian-changes
+END
+
+diff <<END - <(ls debian/patches)
+debian-changes
+series
+END
+
+t-ok
diff --git a/tests/tests/quilt-splitbrains b/tests/tests/quilt-splitbrains
new file mode 100755 (executable)
index 0000000..9f0ae5f
--- /dev/null
@@ -0,0 +1,140 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+suitespecs+=' stable'
+
+# This test script tests each of the split brain quilt modes, and
+# --quilt=linear, with a tree suitable for each of those, and pushes
+# them in sequence.  The idea is to check that each tree is rejected
+# by the wrong quilt modes, and accepted and processed correctly by
+# the right ones.
+
+t-tstunt-parsechangelog
+
+t-newtag
+
+# Easiest way to make a patches-unapplied but not-gbp tree is
+# to take the patches-unapplied tree and by-hand commit the .gitignore
+# changes as a debian patch.
+t-gbp-example-prep
+
+suite=sid
+
+want-success () {
+       local qmode=$1; shift
+       t-refs-same-start
+       t-ref-head
+
+       t-dgit "$@" --quilt=$qmode --dgit-view-save=split.b build-source
+
+       t-dgit "$@" --quilt=$qmode --dgit-view-save=split.p push
+       t-$qmode-pushed-good
+}
+
+
+echo "===== testing tree suitable for --quilt=gbp (only) ====="
+
+t-expect-fail 'grep: new-upstream-file: No such file or directory' \
+t-dgit --quilt=dpm build-source
+
+t-expect-fail 'git tree differs from result of applying' \
+t-dgit -wgf --quilt=dpm build-source
+
+t-expect-fail 'git tree differs from orig in upstream files' \
+t-dgit -wgf --quilt=unapplied build-source
+
+t-expect-fail 'This might be a patches-unapplied branch' \
+t-dgit -wgf build-source
+
+# testing success with --quilt=gbp are done in quilt-gbp test case
+
+
+echo "===== making tree suitable for --quilt=unapplied (only) ====="
+
+pf=debian/patches/test-gitignore
+
+cat >$pf <<END
+From: Senatus <spqr@example.com>
+Subject: Add .gitignore
+
+---
+END
+
+git diff /dev/null .gitignore >>$pf || test $? = 1
+echo ${pf##*/} >>debian/patches/series
+
+git add debian/patches
+git rm -f .gitignore
+git commit -m 'Turn gitignore into a debian patch'
+gitigncommit=`git rev-parse HEAD`
+
+t-commit unapplied 1.0-3
+
+echo "----- testing tree suitable for --quilt=unapplied (only) -----"
+
+t-expect-fail 'git tree differs from result of applying' \
+t-dgit -wgf --quilt=dpm build-source
+
+t-expect-fail 'gitignores: but, such patches exist' \
+t-dgit -wgf --quilt=gbp build-source
+
+t-expect-fail 'This might be a patches-unapplied branch' \
+t-dgit -wgf build-source
+
+want-success unapplied -wgf
+
+
+echo "===== making fully-applied tree suitable for --quilt-check ====="
+
+git checkout master
+git merge --ff-only dgit/dgit/sid
+
+t-commit vanilla 1.0-4
+
+echo "----- testing fully-applied tree suitable for --quilt-check -----"
+
+t-expect-fail 'gitignores: but, such patches exist' \
+t-dgit --quilt=dpm build-source
+
+t-expect-fail 'git tree differs from orig in upstream files' \
+t-dgit --quilt=gbp build-source
+
+t-expect-fail 'git tree differs from orig in upstream files' \
+t-dgit --quilt=unapplied build-source
+
+t-dgit --quilt=nofix build-source
+t-refs-same-start
+t-ref-head
+t-dgit --quilt=nofix push
+t-pushed-good-core
+
+
+echo "===== making tree suitable for --quilt=dpm (only) ====="
+
+git checkout master
+git merge --ff-only dgit/dgit/sid
+
+git revert --no-edit $gitigncommit
+
+t-commit dpmish 1.0-5
+
+echo "----- testing tree suitable for --quilt=dpm (only) -----"
+
+t-expect-fail 'git tree differs from orig in upstream files' \
+t-dgit -wgf --quilt=gbp build-source
+
+t-expect-fail 'git tree differs from orig in upstream files' \
+t-dgit -wgf --quilt=unapplied build-source
+
+t-expect-fail 'This might be a patches-applied branch' \
+t-dgit -wgf build-source
+
+want-success dpm
+
+suite=stable
+t-commit dpmish-stable 1.0-6 $suite
+
+want-success dpm --new
+
+t-ok
diff --git a/tests/tests/rpush b/tests/tests/rpush
new file mode 100755 (executable)
index 0000000..71bbe2b
--- /dev/null
@@ -0,0 +1,31 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+t-archive pari-extra 3-1
+t-git pari-extra 3-1
+
+t-dgit clone $p
+
+cd $p
+t-cloned-fetched-good
+
+v=3-2~dummy1
+t-apply-diff 3-1 $v
+debcommit -a
+
+t-refs-same-start
+t-ref-head
+
+t-dgit --dpkg-buildpackage:-d build
+
+mkdir $tmp/empty
+cd $tmp/empty
+#t-dgit --ssh=$troot/ssh rpush somehost:$troot/$p
+#echo $?
+t-dgit --ssh=$troot/ssh rpush somehost:$tmp/$p
+
+cd $tmp/$p
+t-pushed-good dgit/sid
+
+t-ok
diff --git a/tests/tests/spelling b/tests/tests/spelling
new file mode 100755 (executable)
index 0000000..ed1290c
--- /dev/null
@@ -0,0 +1,16 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+t-restrict x-dgit-git-only
+
+cd $root
+
+set +e
+git grep -q -i 'ps[u]edo'
+rc=$?
+set -e
+
+test $rc = 1
+
+t-ok
diff --git a/tests/tests/tag-updates b/tests/tests/tag-updates
new file mode 100755 (executable)
index 0000000..824fd1e
--- /dev/null
@@ -0,0 +1,37 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+t-tstunt-parsechangelog
+t-prep-newpackage example 1.0
+
+cd $p
+revision=1
+t-dgit build
+t-dgit push --new
+
+tagref=`t-v-tag`
+tagname=${tn#refs/tags}
+
+(set -e
+ cd $dgitrepo
+ git tag -m UNWANTED unwanted dgit/sid)
+
+fetch-check () {
+       t-dgit fetch
+       t-ref-same-exact $tagref
+       t-refs-notexist refs/tags/unwanted
+}
+
+t-ref-same-exact $tagref
+fetch-check
+
+git tag -d $tagname
+fetch-check
+
+git tag -f -m BOGUS $tagname HEAD
+t-refs-same-start
+t-ref-same-exact $tagref
+fetch-check
+
+t-ok
diff --git a/tests/tests/test-list-uptodate b/tests/tests/test-list-uptodate
new file mode 100755 (executable)
index 0000000..1e5f199
--- /dev/null
@@ -0,0 +1,11 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+: "just verifies internal consistency of test suite"
+
+cd $root
+$troot/enumerate-tests gencontrol >$tmp/control-expected
+diff debian/tests/control $tmp/control-expected
+
+t-ok
diff --git a/tests/tests/trustingpolicy-replay b/tests/tests/trustingpolicy-replay
new file mode 100755 (executable)
index 0000000..ad731f5
--- /dev/null
@@ -0,0 +1,85 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+t-tstunt-parsechangelog
+
+t-git-config dgit.default.dep14tag no
+
+t-dsd
+t-policy dgit-repos-policy-trusting
+t-prep-newpackage example 1.0
+
+cd $p
+revision=1
+git tag start
+
+t-dgit build
+t-dgit push --new
+
+t-commit 'Prep v1.1 which will be rewound'
+t-dgit build
+t-dgit push
+
+t-rm-dput-dropping
+git checkout $tagpfx/1.0
+t-dgit build
+t-dgit push --deliberately-fresh-repo
+
+remote="`git config dgit-distro.test-dummy.git-url`/$p.git"
+
+t-expect-push-fail 'Replay of previously-rewound upload' \
+git push "$remote" \
+       $tagpfx/1.1 \
+       $tagpfx/1.1~0:refs/dgit/sid
+
+git checkout master
+
+
+: "More subtle replay prevention checks"
+
+prepare-replay () {
+       delib=$1
+
+       # We have to stop the pushes succeeding because if they work they
+       # record the tag, which prevents the replays.  We are simulating
+       # abortive pushes (since we do want to avoid a situation where
+       # dangerous old signed tags can exist).
+       t-policy-nonexist
+
+       t-commit "request with $delib that we will replay"
+       t-dgit build
+       t-expect-push-fail 'system: No such file or directory' \
+       t-dgit push $delib
+
+       t-policy dgit-repos-policy-trusting
+
+       replayv=$v
+}
+
+attempt-replay () {
+       local mpat=$1
+       git show $tagpfx/$replayv | grep -e $delib
+       t-expect-push-fail "$mpat" \
+       git push "$remote" \
+               $tagpfx/$replayv \
+               +$tagpfx/$replayv~0:refs/dgit/sid
+}
+
+prepare-replay --deliberately-fresh-repo
+
+# simulate some other thing that we shouldn't delete
+git push $dgitrepo +master:refs/heads/for-testing
+
+attempt-replay 'does not declare previously heads/for-testing'
+
+prepare-replay --deliberately-not-fast-forward
+
+t-commit 'later version to stop not fast forward rewinding'
+t-dgit build
+t-dgit push
+
+attempt-replay "does not declare previously tags/$tagpfx/$v"
+
+
+t-ok
diff --git a/tests/tests/unrepresentable b/tests/tests/unrepresentable
new file mode 100755 (executable)
index 0000000..0d02c6a
--- /dev/null
@@ -0,0 +1,52 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+t-tstunt-parsechangelog
+
+t-prep-newpackage example 1.0
+
+ln -s $troot/pkg-srcs/${p}_${v%-*}.orig.tar.* .
+
+cd $p
+
+start () { git checkout quilt-tip~0; }
+attempt () { t-dgit -wgf --quilt=smash quilt-fixup; }
+
+badly-1 () {
+       wrongfn=$1
+       wrongmsg=$2
+       start
+}
+
+badly-2 () {
+       git commit -m "Commit wrongness $wrongfn ($wrongmsg)"
+       t-expect-fail E:"cannot represent change: $wrongmsg .*: $wrongfn" \
+       attempt
+}
+
+badly-1 symlink 'not a plain file'
+       ln -s TARGET symlink
+       git add symlink
+badly-2
+
+start
+       git rm src.c
+       git commit -m deleted
+attempt
+
+badly-1 src.c 'mode changed'
+       chmod +x src.c
+       git add src.c
+badly-2
+
+badly-1 new 'non-default mode'
+       echo hi >new
+       chmod 755 new
+       git add new
+badly-2
+
+start
+attempt
+
+t-ok
diff --git a/tests/tests/version-opt b/tests/tests/version-opt
new file mode 100755 (executable)
index 0000000..22c35e7
--- /dev/null
@@ -0,0 +1,32 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+# NOT t-tstunt-parsechangelog
+# because that doesn't honour the perl option corresponding to -v
+
+t-debpolicy
+t-prep-newpackage example 1.0
+
+cd $p
+revision=1
+git tag start
+t-dgit setup-mergechangelogs
+
+t-dgit build-source
+t-dgit push --new
+
+t-archive-process-incoming sid
+
+for v in 1.1 1.2; do
+       dch -v $v -D unstable -m "Update to version $v"
+
+       git add debian/changelog
+       git commit -m "Commit changelog for $v"
+
+       t-dgit build-source
+done
+
+fgrep 'Update to version 1.1' ../${p}_${v}_source.changes
+
+t-ok
diff --git a/tests/tstunt/Dpkg/Changelog/Parse.pm b/tests/tstunt/Dpkg/Changelog/Parse.pm
new file mode 100644 (file)
index 0000000..d69b7df
--- /dev/null
@@ -0,0 +1,71 @@
+# -*- perl -*-
+#
+# Copyright (C) 2015-2016  Ian Jackson
+#
+# Some bits stolen from the proper Dpkg::Changelog::Parse
+# (from dpkg-dev 1.16.16):
+#
+# Copyright (C) 2005, 2007 Frank Lichtenheld <frank@lichtenheld.de>
+# Copyright (C) 2009       Raphael Hertzog <hertzog@debian.org>
+#
+#    This program is free software; you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation; either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+package Dpkg::Changelog::Parse;
+
+use strict;
+use warnings;
+
+our $VERSION = "1.00";
+
+use Dpkg::Control::Changelog;
+
+use base qw(Exporter);
+our @EXPORT = qw(changelog_parse);
+
+die +(join " ", %ENV)." ?" if $ENV{'DGIT_NO_TSTUNT_CLPARSE'};
+
+sub changelog_parse {
+    my (%options) = @_; # largely ignored
+
+#use Data::Dumper;
+#print STDERR "CLOG PARSE ", Dumper(\%options);
+#
+# We can't do this because lots of things use `since' which
+# we don't implement, and it's the test cases that arrange that
+# the since value happens to be such that we are to print one output.
+#
+#    foreach my $k (keys %options) {
+#      my $v = $options{$k};
+#      if ($k eq 'file') { }
+#      elsif ($k eq 'offset') { die "$v ?" unless $v <= 1; } # wtf, 1==0 ?
+#      elsif ($k eq 'count') { die "$v ?" unless $v == 1; }
+#      else { die "$k ?"; }
+#    }
+
+    $options{'file'} //= 'debian/changelog';
+
+    open P, "dpkg-parsechangelog -l$options{'file'} |" or die $!;
+
+    my $fields = Dpkg::Control::Changelog->new();
+    $fields->parse(\*P, "output of stunt changelog parser");
+
+#use Data::Dumper;
+#print STDERR "PARSE $0 ", Dumper($fields);
+
+    close P or die "$! $?";
+
+    return $fields;
+}
+
+1;
diff --git a/tests/tstunt/debuild b/tests/tstunt/debuild
new file mode 100755 (executable)
index 0000000..2b2ca71
--- /dev/null
@@ -0,0 +1,4 @@
+#!/bin/bash
+set -e
+echo "DGIT TEST STUNT DEBUILD $*" >&2
+"${DGIT_TEST_REAL_DEBUILD}" --preserve-env --preserve-envvar PATH "$@"
diff --git a/tests/tstunt/dpkg-parsechangelog b/tests/tstunt/dpkg-parsechangelog
new file mode 100755 (executable)
index 0000000..6a9198a
--- /dev/null
@@ -0,0 +1,78 @@
+#!/usr/bin/perl -w
+#
+# In an example:
+#
+# $ time dpkg-parsechangelog >/dev/null
+#
+# real    0m0.712s
+# user    0m0.656s
+# sys     0m0.048s
+# $ time ~/things/Dgit/dgit/tests/tstunt/dpkg-parsechangelog >/dev/null
+#
+# real    0m0.016s
+# user    0m0.000s
+# sys     0m0.012s
+# $
+
+$SIG{__WARN__} = sub { die $_[0]; }; # no use of system, so we avoid #793471
+
+my $infile = "debian/changelog";
+
+#print STDERR ">@ARGV<\n";
+
+my @orgargv = @ARGV;
+
+if (@ARGV && $ARGV[0] =~ s/^-l//) {
+    $infile = shift @ARGV;
+}
+
+if (@ARGV) {
+    my $strip = $0;
+    $strip =~ s#/[^/]+$## or die "$0 ?";
+    foreach my $k (qw(PATH PERLLIB)) {
+       my @opath = defined $ENV{$k} ? split /\:/, $ENV{$k} : ();
+       my @npath = grep { $_ ne $strip } @opath;
+       @npath != @opath  or die "$0 $k ".($ENV{$k}//"(undef)")." ?";
+       $ENV{$k} = join ':', @npath;
+       delete $ENV{$k} if !@npath;
+    }
+    die if $ENV{'DGIT_NO_TSTUNT_CLPARSE'}++;
+    exec 'dpkg-parsechangelog', @orgargv;
+}
+
+use strict;
+open C, $infile or die $!;
+
+$!=0; $_ = <C>;
+m/^(\S+) \(([^()]+)\) (\S+)\; urgency=(\S+)$/ or die "$!, $_ ?";
+print <<END or die $!;
+Source: $1
+Version: $2
+Distribution: $3
+Urgency: $4
+Changes: 
+ $&
+END
+
+my $blanks = 0;
+for (;;) {
+    $!=0; $_ = <C>;
+    if (m/^ -- ([^<>]+\<\S+\>)  (\w[^<>]+\w)$/) {
+       print <<END or die $!;
+Maintainer: $1
+Date: $2
+END
+       print "Timestamp: " or die $!;
+       exec qw(date +%s -d), $2; die $!;
+    } elsif (m/^ --\s*$/) {
+       last;
+    } elsif (!m/\S/) {
+       $blanks++;
+    } elsif (m/^  .*\n/) {
+       print " .\n" x $blanks or die $!;
+       $blanks=0;
+       print " $_" or die $!;
+    } else {
+       die "$!, $_ ?";
+    }
+}
diff --git a/tests/tstunt/lintian b/tests/tstunt/lintian
new file mode 100755 (executable)
index 0000000..f7c2985
--- /dev/null
@@ -0,0 +1,3 @@
+#!/bin/sh
+echo >&2 'W: dgit test suite stunt lintian detects no problems'
+exit 0
diff --git a/tests/using-intree b/tests/using-intree
new file mode 100755 (executable)
index 0000000..0235c20
--- /dev/null
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# usage:
+#   cd .../dgit.git
+#   tests/using-intree tests/test/some-test
+# or
+#   cd .../dgit.git
+#   tests/using-intree tests/run-all
+#
+# effects:
+#   sets DGIT_TEST_INTREE which causes tests/lib to have test scripts
+#   using programs etc. from the working tree
+
+set -e
+pwd=`pwd`
+export DGIT_TEST_INTREE="$pwd"
+exec "$@"
diff --git a/tests/worktrees/example_1.0.tar b/tests/worktrees/example_1.0.tar
new file mode 100644 (file)
index 0000000..50baa33
Binary files /dev/null and b/tests/worktrees/example_1.0.tar differ
diff --git a/tests/worktrees/pari-extra_3-1.tar b/tests/worktrees/pari-extra_3-1.tar
new file mode 100644 (file)
index 0000000..62efeb9
Binary files /dev/null and b/tests/worktrees/pari-extra_3-1.tar differ
diff --git a/tests/worktrees/pari-extra_drs.tar b/tests/worktrees/pari-extra_drs.tar
new file mode 100644 (file)
index 0000000..1030178
Binary files /dev/null and b/tests/worktrees/pari-extra_drs.tar differ
diff --git a/tests/worktrees/ruby-rails-3.2_test.tar b/tests/worktrees/ruby-rails-3.2_test.tar
new file mode 100644 (file)
index 0000000..85365fe
Binary files /dev/null and b/tests/worktrees/ruby-rails-3.2_test.tar differ