summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore15
-rw-r--r--DEVELOPER-CERTIFICATE38
-rw-r--r--Debian/Dgit.pm368
-rw-r--r--Debian/Dgit/Infra.pm17
-rw-r--r--Debian/Dgit/Policy/Debian.pm42
-rw-r--r--Makefile106
-rw-r--r--README.dsc-import106
-rwxr-xr-xabsurd/git115
-rw-r--r--debian/changelog1100
-rw-r--r--debian/compat1
-rw-r--r--debian/control39
-rw-r--r--debian/copyright72
-rwxr-xr-xdebian/rules59
-rw-r--r--debian/tests/control31
-rw-r--r--debian/tests/control.in2
-rwxr-xr-xdgit6339
-rw-r--r--dgit-maint-gbp.7.pod126
-rw-r--r--dgit-maint-merge.7.pod436
-rw-r--r--dgit-maint-native.7.pod116
-rw-r--r--dgit-nmu-simple.7.pod139
-rw-r--r--dgit-sponsorship.7.pod317
-rw-r--r--dgit-user.7.pod432
-rw-r--r--dgit.11081
-rw-r--r--dgit.7385
-rwxr-xr-xinfra/cgit-regen-config26
-rwxr-xr-xinfra/dgit-mirror-rsync171
-rwxr-xr-xinfra/dgit-repos-admin-debian220
-rwxr-xr-xinfra/dgit-repos-policy-debian538
-rwxr-xr-xinfra/dgit-repos-policy-trusting58
-rwxr-xr-xinfra/dgit-repos-server1177
-rwxr-xr-xinfra/dgit-ssh-dispatch181
-rwxr-xr-xinfra/drs-cron-wrap14
-rwxr-xr-xinfra/get-dm-txt21
-rwxr-xr-xinfra/get-suites26
-rwxr-xr-xinfra/ssh-wrap10
-rwxr-xr-xlocal-pod-man13
-rw-r--r--tests/Makefile14
-rwxr-xr-xtests/adhoc51
-rwxr-xr-xtests/drs-git-ext15
-rwxr-xr-xtests/dsd-ssh18
-rwxr-xr-xtests/enumerate-tests109
-rw-r--r--tests/git-srcs/pari-extra_3-1.git.tarbin0 -> 51200 bytes
-rw-r--r--tests/git-template.tarbin0 -> 30720 bytes
-rw-r--r--tests/gnupg/dd.gpgbin0 -> 1204 bytes
-rw-r--r--tests/gnupg/dm.gpgbin0 -> 1203 bytes
-rw-r--r--tests/gnupg/dm.txt1800
-rw-r--r--tests/gnupg/pubring.gpgbin0 -> 3608 bytes
-rw-r--r--tests/gnupg/random_seedbin0 -> 600 bytes
-rw-r--r--tests/gnupg/secring.gpgbin0 -> 7514 bytes
-rw-r--r--tests/gnupg/trustdb.gpgbin0 -> 1440 bytes
-rw-r--r--tests/lib1032
-rw-r--r--tests/lib-build-modes235
-rw-r--r--tests/lib-core36
-rw-r--r--tests/lib-import-chk84
-rw-r--r--tests/lib-mirror40
-rw-r--r--tests/lib-orig-include-exclude62
-rw-r--r--tests/lib-reprepro71
-rw-r--r--tests/lib-restricts22
-rw-r--r--tests/pkg-srcs/example_1.0-1+absurd.debian.tar.xzbin0 -> 1416 bytes
-rw-r--r--tests/pkg-srcs/example_1.0-1+absurd.dsc22
-rw-r--r--tests/pkg-srcs/example_1.0-1.100.debian.tar.xzbin0 -> 2108 bytes
-rw-r--r--tests/pkg-srcs/example_1.0-1.100.dsc22
-rw-r--r--tests/pkg-srcs/example_1.0-1.debian.tar.xzbin0 -> 1304 bytes
-rw-r--r--tests/pkg-srcs/example_1.0-1.dsc22
-rw-r--r--tests/pkg-srcs/example_1.0.orig-docs.tar.gzbin0 -> 236 bytes
-rw-r--r--tests/pkg-srcs/example_1.0.orig.tar.gzbin0 -> 373 bytes
-rw-r--r--tests/pkg-srcs/pari-extra_3-1.diff.gzbin0 -> 2358 bytes
-rw-r--r--tests/pkg-srcs/pari-extra_3-1.dsc30
-rw-r--r--tests/pkg-srcs/pari-extra_3-2~dummy1.diff.gzbin0 -> 2484 bytes
-rw-r--r--tests/pkg-srcs/pari-extra_3-2~dummy1.dsc19
-rw-r--r--tests/pkg-srcs/pari-extra_3.orig.tar.gzbin0 -> 121 bytes
-rw-r--r--tests/pkg-srcs/ruby-rails-3.2_3.2.6-1.debian.tar.gzbin0 -> 2297 bytes
-rw-r--r--tests/pkg-srcs/ruby-rails-3.2_3.2.6-1.dsc37
-rw-r--r--tests/pkg-srcs/ruby-rails-3.2_3.2.6.orig.tar.gzbin0 -> 953 bytes
-rw-r--r--tests/pkg-srcs/sunxi-tools_1.2-2.~~dgittest.debian.tar.gzbin0 -> 5182 bytes
-rw-r--r--tests/pkg-srcs/sunxi-tools_1.2-2.~~dgittest.dsc22
-rw-r--r--tests/pkg-srcs/sunxi-tools_1.2.orig.tar.gzbin0 -> 35076 bytes
-rwxr-xr-xtests/run-all21
-rwxr-xr-xtests/setup/examplegit53
-rwxr-xr-xtests/setup/gnupg15
-rwxr-xr-xtests/ssh5
-rw-r--r--tests/suites1
-rwxr-xr-xtests/tartree-edit219
-rwxr-xr-xtests/tests/absurd-gitapply16
-rwxr-xr-xtests/tests/build-modes35
-rwxr-xr-xtests/tests/build-modes-asplit5
-rwxr-xr-xtests/tests/build-modes-gbp39
-rwxr-xr-xtests/tests/build-modes-gbp-asplit5
-rwxr-xr-xtests/tests/build-modes-sbuild18
-rwxr-xr-xtests/tests/clone-clogsigpipe10
-rwxr-xr-xtests/tests/clone-gitnosuite11
-rwxr-xr-xtests/tests/clone-nogit25
-rwxr-xr-xtests/tests/clone-reprepro33
-rwxr-xr-xtests/tests/debpolicy-dbretry67
-rwxr-xr-xtests/tests/debpolicy-newreject121
-rwxr-xr-xtests/tests/debpolicy-quilt-gbp4
-rwxr-xr-xtests/tests/distropatches-reject81
-rwxr-xr-xtests/tests/drs-clone-nogit4
-rwxr-xr-xtests/tests/drs-push-masterupdate50
-rwxr-xr-xtests/tests/drs-push-rejects209
-rwxr-xr-xtests/tests/dsd-clone-drs16
-rwxr-xr-xtests/tests/dsd-clone-nogit4
-rwxr-xr-xtests/tests/dsd-divert7
-rwxr-xr-xtests/tests/fetch-localgitonly20
-rwxr-xr-xtests/tests/fetch-somegit-notlast15
-rwxr-xr-xtests/tests/gbp-orig77
-rwxr-xr-xtests/tests/gitconfig38
-rwxr-xr-xtests/tests/import-dsc99
-rwxr-xr-xtests/tests/import-native69
-rwxr-xr-xtests/tests/import-nonnative17
-rwxr-xr-xtests/tests/import-tarbomb49
-rwxr-xr-xtests/tests/inarchivecopy79
-rwxr-xr-xtests/tests/mirror80
-rwxr-xr-xtests/tests/mirror-debnewgit26
-rwxr-xr-xtests/tests/mirror-private26
-rwxr-xr-xtests/tests/mismatches-contents24
-rwxr-xr-xtests/tests/mismatches-dscchanges34
-rwxr-xr-xtests/tests/multisuite57
-rwxr-xr-xtests/tests/newtag-clone-nogit4
-rwxr-xr-xtests/tests/oldnewtagalt25
-rwxr-xr-xtests/tests/oldtag-clone-nogit4
-rwxr-xr-xtests/tests/orig-include-exclude21
-rwxr-xr-xtests/tests/orig-include-exclude-chkquery33
-rwxr-xr-xtests/tests/overwrite-chkclog39
-rwxr-xr-xtests/tests/overwrite-junk22
-rwxr-xr-xtests/tests/overwrite-splitbrains27
-rwxr-xr-xtests/tests/overwrite-version20
-rwxr-xr-xtests/tests/push-buildproductsdir31
-rwxr-xr-xtests/tests/push-newpackage30
-rwxr-xr-xtests/tests/push-nextdgit25
-rwxr-xr-xtests/tests/quilt96
-rwxr-xr-xtests/tests/quilt-gbp61
-rwxr-xr-xtests/tests/quilt-gbp-build-modes13
-rwxr-xr-xtests/tests/quilt-gbp-build-modes-sbuild12
-rwxr-xr-xtests/tests/quilt-singlepatch39
-rwxr-xr-xtests/tests/quilt-splitbrains140
-rwxr-xr-xtests/tests/rpush31
-rwxr-xr-xtests/tests/spelling16
-rwxr-xr-xtests/tests/tag-updates37
-rwxr-xr-xtests/tests/test-list-uptodate11
-rwxr-xr-xtests/tests/trustingpolicy-replay85
-rwxr-xr-xtests/tests/unrepresentable52
-rwxr-xr-xtests/tests/version-opt32
-rw-r--r--tests/tstunt/Dpkg/Changelog/Parse.pm71
-rwxr-xr-xtests/tstunt/debuild4
-rwxr-xr-xtests/tstunt/dpkg-parsechangelog78
-rwxr-xr-xtests/tstunt/lintian3
-rwxr-xr-xtests/using-intree17
-rw-r--r--tests/worktrees/example_1.0.tarbin0 -> 122880 bytes
-rw-r--r--tests/worktrees/pari-extra_3-1.tarbin0 -> 81920 bytes
-rw-r--r--tests/worktrees/pari-extra_drs.tarbin0 -> 153600 bytes
-rw-r--r--tests/worktrees/ruby-rails-3.2_test.tarbin0 -> 215040 bytes
152 files changed, 20563 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..13e2c4b
--- /dev/null
+++ b/.gitignore
@@ -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
index 0000000..912d22e
--- /dev/null
+++ b/DEVELOPER-CERTIFICATE
@@ -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
index 0000000..e9921d6
--- /dev/null
+++ b/Debian/Dgit.pm
@@ -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
index 0000000..eff460b
--- /dev/null
+++ b/Debian/Dgit/Infra.pm
@@ -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
index 0000000..12f1ee1
--- /dev/null
+++ b/Debian/Dgit/Policy/Debian.pm
@@ -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
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
index 0000000..1ec53b0
--- /dev/null
+++ b/README.dsc-import
@@ -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
index 0000000..0f562b5
--- /dev/null
+++ b/absurd/git
@@ -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
index 0000000..be2c165
--- /dev/null
+++ b/debian/changelog
@@ -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
index 0000000..ec63514
--- /dev/null
+++ b/debian/compat
@@ -0,0 +1 @@
+9
diff --git a/debian/control b/debian/control
new file mode 100644
index 0000000..9402fd2
--- /dev/null
+++ b/debian/control
@@ -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
index 0000000..a5b8245
--- /dev/null
+++ b/debian/copyright
@@ -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
index 0000000..9249f88
--- /dev/null
+++ b/debian/rules
@@ -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
index 0000000..7bd2a13
--- /dev/null
+++ b/debian/tests/control
@@ -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
index 0000000..c032ba8
--- /dev/null
+++ b/debian/tests/control.in
@@ -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
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
index 0000000..c31dfa5
--- /dev/null
+++ b/dgit-maint-gbp.7.pod
@@ -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
index 0000000..dc5bfa3
--- /dev/null
+++ b/dgit-maint-merge.7.pod
@@ -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
index 0000000..03aee59
--- /dev/null
+++ b/dgit-maint-native.7.pod
@@ -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
index 0000000..3ebc68a
--- /dev/null
+++ b/dgit-nmu-simple.7.pod
@@ -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
index 0000000..3fc59d2
--- /dev/null
+++ b/dgit-sponsorship.7.pod
@@ -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
index 0000000..ad0cca1
--- /dev/null
+++ b/dgit-user.7.pod
@@ -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
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
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
index 0000000..36228a1
--- /dev/null
+++ b/infra/cgit-regen-config
@@ -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
index 0000000..0d29ffb
--- /dev/null
+++ b/infra/dgit-mirror-rsync
@@ -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
index 0000000..6d1e4d0
--- /dev/null
+++ b/infra/dgit-repos-admin-debian
@@ -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
index 0000000..cff5d06
--- /dev/null
+++ b/infra/dgit-repos-policy-debian
@@ -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
index 0000000..b551d50
--- /dev/null
+++ b/infra/dgit-repos-policy-trusting
@@ -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
index 0000000..eb4b377
--- /dev/null
+++ b/infra/dgit-repos-server
@@ -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
index 0000000..c5861d2
--- /dev/null
+++ b/infra/dgit-ssh-dispatch
@@ -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
index 0000000..52e819b
--- /dev/null
+++ b/infra/drs-cron-wrap
@@ -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
index 0000000..0b9ab10
--- /dev/null
+++ b/infra/get-dm-txt
@@ -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
index 0000000..c5a4c56
--- /dev/null
+++ b/infra/get-suites
@@ -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
index 0000000..deccc38
--- /dev/null
+++ b/infra/ssh-wrap
@@ -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
index 0000000..3c3e0ea
--- /dev/null
+++ b/local-pod-man
@@ -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
index 0000000..5bd3eee
--- /dev/null
+++ b/tests/Makefile
@@ -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
index 0000000..b45251f
--- /dev/null
+++ b/tests/adhoc
@@ -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
index 0000000..ad27c9b
--- /dev/null
+++ b/tests/drs-git-ext
@@ -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
index 0000000..d5df5aa
--- /dev/null
+++ b/tests/dsd-ssh
@@ -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
index 0000000..2c00f97
--- /dev/null
+++ b/tests/enumerate-tests
@@ -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
index 0000000..1673c72
--- /dev/null
+++ b/tests/git-srcs/pari-extra_3-1.git.tar
Binary files differ
diff --git a/tests/git-template.tar b/tests/git-template.tar
new file mode 100644
index 0000000..030bb8a
--- /dev/null
+++ b/tests/git-template.tar
Binary files differ
diff --git a/tests/gnupg/dd.gpg b/tests/gnupg/dd.gpg
new file mode 100644
index 0000000..bc16981
--- /dev/null
+++ b/tests/gnupg/dd.gpg
Binary files differ
diff --git a/tests/gnupg/dm.gpg b/tests/gnupg/dm.gpg
new file mode 100644
index 0000000..d58af9d
--- /dev/null
+++ b/tests/gnupg/dm.gpg
Binary files differ
diff --git a/tests/gnupg/dm.txt b/tests/gnupg/dm.txt
new file mode 100644
index 0000000..da257fe
--- /dev/null
+++ b/tests/gnupg/dm.txt
@@ -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
index 0000000..04a9ed5
--- /dev/null
+++ b/tests/gnupg/pubring.gpg
Binary files differ
diff --git a/tests/gnupg/random_seed b/tests/gnupg/random_seed
new file mode 100644
index 0000000..cc75662
--- /dev/null
+++ b/tests/gnupg/random_seed
Binary files differ
diff --git a/tests/gnupg/secring.gpg b/tests/gnupg/secring.gpg
new file mode 100644
index 0000000..7e20df1
--- /dev/null
+++ b/tests/gnupg/secring.gpg
Binary files differ
diff --git a/tests/gnupg/trustdb.gpg b/tests/gnupg/trustdb.gpg
new file mode 100644
index 0000000..c51ced8
--- /dev/null
+++ b/tests/gnupg/trustdb.gpg
Binary files differ
diff --git a/tests/lib b/tests/lib
new file mode 100644
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
index 0000000..30fbb5a
--- /dev/null
+++ b/tests/lib-build-modes
@@ -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
index 0000000..7ed2761
--- /dev/null
+++ b/tests/lib-core
@@ -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
index 0000000..ee33cbe
--- /dev/null
+++ b/tests/lib-import-chk
@@ -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
index 0000000..8aa751a
--- /dev/null
+++ b/tests/lib-mirror
@@ -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
index 0000000..75a9656
--- /dev/null
+++ b/tests/lib-orig-include-exclude
@@ -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
index 0000000..d025d48
--- /dev/null
+++ b/tests/lib-reprepro
@@ -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
index 0000000..bffe13a
--- /dev/null
+++ b/tests/lib-restricts
@@ -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
index 0000000..9a2dd12
--- /dev/null
+++ b/tests/pkg-srcs/example_1.0-1+absurd.debian.tar.xz
Binary files 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
index 0000000..1ab743d
--- /dev/null
+++ b/tests/pkg-srcs/example_1.0-1+absurd.dsc
@@ -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
index 0000000..ea8ec34
--- /dev/null
+++ b/tests/pkg-srcs/example_1.0-1.100.debian.tar.xz
Binary files 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
index 0000000..5b075b5
--- /dev/null
+++ b/tests/pkg-srcs/example_1.0-1.100.dsc
@@ -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
index 0000000..84ca563
--- /dev/null
+++ b/tests/pkg-srcs/example_1.0-1.debian.tar.xz
Binary files 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
index 0000000..bb65f6e
--- /dev/null
+++ b/tests/pkg-srcs/example_1.0-1.dsc
@@ -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
index 0000000..f8427aa
--- /dev/null
+++ b/tests/pkg-srcs/example_1.0.orig-docs.tar.gz
Binary files 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
index 0000000..e5d2cf3
--- /dev/null
+++ b/tests/pkg-srcs/example_1.0.orig.tar.gz
Binary files 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
index 0000000..81f7f2e
--- /dev/null
+++ b/tests/pkg-srcs/pari-extra_3-1.diff.gz
Binary files 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
index 0000000..8d67ed0
--- /dev/null
+++ b/tests/pkg-srcs/pari-extra_3-1.dsc
@@ -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
index 0000000..f5dff2b
--- /dev/null
+++ b/tests/pkg-srcs/pari-extra_3-2~dummy1.diff.gz
Binary files 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
index 0000000..1042f09
--- /dev/null
+++ b/tests/pkg-srcs/pari-extra_3-2~dummy1.dsc
@@ -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
index 0000000..ff30279
--- /dev/null
+++ b/tests/pkg-srcs/pari-extra_3.orig.tar.gz
Binary files 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
index 0000000..633e6db
--- /dev/null
+++ b/tests/pkg-srcs/ruby-rails-3.2_3.2.6-1.debian.tar.gz
Binary files 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
index 0000000..4f2e290
--- /dev/null
+++ b/tests/pkg-srcs/ruby-rails-3.2_3.2.6-1.dsc
@@ -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
index 0000000..88ab3fd
--- /dev/null
+++ b/tests/pkg-srcs/ruby-rails-3.2_3.2.6.orig.tar.gz
Binary files 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
index 0000000..c376961
--- /dev/null
+++ b/tests/pkg-srcs/sunxi-tools_1.2-2.~~dgittest.debian.tar.gz
Binary files 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
index 0000000..e0161cd
--- /dev/null
+++ b/tests/pkg-srcs/sunxi-tools_1.2-2.~~dgittest.dsc
@@ -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
index 0000000..fe397fa
--- /dev/null
+++ b/tests/pkg-srcs/sunxi-tools_1.2.orig.tar.gz
Binary files differ
diff --git a/tests/run-all b/tests/run-all
new file mode 100755
index 0000000..e53b963
--- /dev/null
+++ b/tests/run-all
@@ -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
index 0000000..112e27a
--- /dev/null
+++ b/tests/setup/examplegit
@@ -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
index 0000000..d23ecfa
--- /dev/null
+++ b/tests/setup/gnupg
@@ -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
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
index 0000000..ea5cccf
--- /dev/null
+++ b/tests/suites
@@ -0,0 +1 @@
+sid
diff --git a/tests/tartree-edit b/tests/tartree-edit
new file mode 100755
index 0000000..96a5bcb
--- /dev/null
+++ b/tests/tartree-edit
@@ -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
index 0000000..90c44be
--- /dev/null
+++ b/tests/tests/absurd-gitapply
@@ -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
index 0000000..c476ec8
--- /dev/null
+++ b/tests/tests/build-modes
@@ -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
index 0000000..fa3bf8a
--- /dev/null
+++ b/tests/tests/build-modes-asplit
@@ -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
index 0000000..50a6288
--- /dev/null
+++ b/tests/tests/build-modes-gbp
@@ -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
index 0000000..fa3bf8a
--- /dev/null
+++ b/tests/tests/build-modes-gbp-asplit
@@ -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
index 0000000..19dcc8a
--- /dev/null
+++ b/tests/tests/build-modes-sbuild
@@ -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
index 0000000..4465cf3
--- /dev/null
+++ b/tests/tests/clone-clogsigpipe
@@ -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
index 0000000..83c996d
--- /dev/null
+++ b/tests/tests/clone-gitnosuite
@@ -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
index 0000000..e99dac3
--- /dev/null
+++ b/tests/tests/clone-nogit
@@ -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
index 0000000..063a138
--- /dev/null
+++ b/tests/tests/clone-reprepro
@@ -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
index 0000000..a9f2334
--- /dev/null
+++ b/tests/tests/debpolicy-dbretry
@@ -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
index 0000000..1fa6751
--- /dev/null
+++ b/tests/tests/debpolicy-newreject
@@ -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
index 0000000..915f9d3
--- /dev/null
+++ b/tests/tests/debpolicy-quilt-gbp
@@ -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
index 0000000..75f43db
--- /dev/null
+++ b/tests/tests/distropatches-reject
@@ -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
index 0000000..915f9d3
--- /dev/null
+++ b/tests/tests/drs-clone-nogit
@@ -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
index 0000000..c8a5c5a
--- /dev/null
+++ b/tests/tests/drs-push-masterupdate
@@ -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
index 0000000..8c4ad83
--- /dev/null
+++ b/tests/tests/drs-push-rejects
@@ -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
index 0000000..4346579
--- /dev/null
+++ b/tests/tests/dsd-clone-drs
@@ -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
index 0000000..915f9d3
--- /dev/null
+++ b/tests/tests/dsd-clone-nogit
@@ -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
index 0000000..3020a56
--- /dev/null
+++ b/tests/tests/dsd-divert
@@ -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
index 0000000..7d603b2
--- /dev/null
+++ b/tests/tests/fetch-localgitonly
@@ -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
index 0000000..63abe8a
--- /dev/null
+++ b/tests/tests/fetch-somegit-notlast
@@ -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
index 0000000..beea121
--- /dev/null
+++ b/tests/tests/gbp-orig
@@ -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
index 0000000..12b342b
--- /dev/null
+++ b/tests/tests/gitconfig
@@ -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
index 0000000..b86eef6
--- /dev/null
+++ b/tests/tests/import-dsc
@@ -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
index 0000000..1e09343
--- /dev/null
+++ b/tests/tests/import-native
@@ -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
index 0000000..3568563
--- /dev/null
+++ b/tests/tests/import-nonnative
@@ -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
index 0000000..9b7f65a
--- /dev/null
+++ b/tests/tests/import-tarbomb
@@ -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
index 0000000..b1e0c5f
--- /dev/null
+++ b/tests/tests/inarchivecopy
@@ -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
index 0000000..4947688
--- /dev/null
+++ b/tests/tests/mirror
@@ -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
index 0000000..59b96ef
--- /dev/null
+++ b/tests/tests/mirror-debnewgit
@@ -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
index 0000000..c26ba89
--- /dev/null
+++ b/tests/tests/mirror-private
@@ -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
index 0000000..ea0d724
--- /dev/null
+++ b/tests/tests/mismatches-contents
@@ -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
index 0000000..85c7086
--- /dev/null
+++ b/tests/tests/mismatches-dscchanges
@@ -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
index 0000000..d39475b
--- /dev/null
+++ b/tests/tests/multisuite
@@ -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
index 0000000..915f9d3
--- /dev/null
+++ b/tests/tests/newtag-clone-nogit
@@ -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
index 0000000..098fe19
--- /dev/null
+++ b/tests/tests/oldnewtagalt
@@ -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
index 0000000..915f9d3
--- /dev/null
+++ b/tests/tests/oldtag-clone-nogit
@@ -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
index 0000000..a37f997
--- /dev/null
+++ b/tests/tests/orig-include-exclude
@@ -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
index 0000000..f8eac57
--- /dev/null
+++ b/tests/tests/orig-include-exclude-chkquery
@@ -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
index 0000000..3544390
--- /dev/null
+++ b/tests/tests/overwrite-chkclog
@@ -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
index 0000000..e11d1f8
--- /dev/null
+++ b/tests/tests/overwrite-junk
@@ -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
index 0000000..0ef03f6
--- /dev/null
+++ b/tests/tests/overwrite-splitbrains
@@ -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
index 0000000..34301ac
--- /dev/null
+++ b/tests/tests/overwrite-version
@@ -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
index 0000000..505d105
--- /dev/null
+++ b/tests/tests/push-buildproductsdir
@@ -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
index 0000000..79355e3
--- /dev/null
+++ b/tests/tests/push-newpackage
@@ -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
index 0000000..d0436ab
--- /dev/null
+++ b/tests/tests/push-nextdgit
@@ -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
index 0000000..1a921b3
--- /dev/null
+++ b/tests/tests/quilt
@@ -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
index 0000000..3ef89e8
--- /dev/null
+++ b/tests/tests/quilt-gbp
@@ -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
index 0000000..cd77dab
--- /dev/null
+++ b/tests/tests/quilt-gbp-build-modes
@@ -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
index 0000000..4c86bfe
--- /dev/null
+++ b/tests/tests/quilt-gbp-build-modes-sbuild
@@ -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
index 0000000..3b6e228
--- /dev/null
+++ b/tests/tests/quilt-singlepatch
@@ -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
index 0000000..9f0ae5f
--- /dev/null
+++ b/tests/tests/quilt-splitbrains
@@ -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
index 0000000..71bbe2b
--- /dev/null
+++ b/tests/tests/rpush
@@ -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
index 0000000..ed1290c
--- /dev/null
+++ b/tests/tests/spelling
@@ -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
index 0000000..824fd1e
--- /dev/null
+++ b/tests/tests/tag-updates
@@ -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
index 0000000..1e5f199
--- /dev/null
+++ b/tests/tests/test-list-uptodate
@@ -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
index 0000000..ad731f5
--- /dev/null
+++ b/tests/tests/trustingpolicy-replay
@@ -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
index 0000000..0d02c6a
--- /dev/null
+++ b/tests/tests/unrepresentable
@@ -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
index 0000000..22c35e7
--- /dev/null
+++ b/tests/tests/version-opt
@@ -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
index 0000000..d69b7df
--- /dev/null
+++ b/tests/tstunt/Dpkg/Changelog/Parse.pm
@@ -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
index 0000000..2b2ca71
--- /dev/null
+++ b/tests/tstunt/debuild
@@ -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
index 0000000..6a9198a
--- /dev/null
+++ b/tests/tstunt/dpkg-parsechangelog
@@ -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
index 0000000..f7c2985
--- /dev/null
+++ b/tests/tstunt/lintian
@@ -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
index 0000000..0235c20
--- /dev/null
+++ b/tests/using-intree
@@ -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
index 0000000..50baa33
--- /dev/null
+++ b/tests/worktrees/example_1.0.tar
Binary files differ
diff --git a/tests/worktrees/pari-extra_3-1.tar b/tests/worktrees/pari-extra_3-1.tar
new file mode 100644
index 0000000..62efeb9
--- /dev/null
+++ b/tests/worktrees/pari-extra_3-1.tar
Binary files differ
diff --git a/tests/worktrees/pari-extra_drs.tar b/tests/worktrees/pari-extra_drs.tar
new file mode 100644
index 0000000..1030178
--- /dev/null
+++ b/tests/worktrees/pari-extra_drs.tar
Binary files 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
index 0000000..85365fe
--- /dev/null
+++ b/tests/worktrees/ruby-rails-3.2_test.tar
Binary files differ