diff options
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 @@ -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) @@ -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/ @@ -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 Binary files differnew file mode 100644 index 0000000..1673c72 --- /dev/null +++ b/tests/git-srcs/pari-extra_3-1.git.tar diff --git a/tests/git-template.tar b/tests/git-template.tar Binary files differnew file mode 100644 index 0000000..030bb8a --- /dev/null +++ b/tests/git-template.tar diff --git a/tests/gnupg/dd.gpg b/tests/gnupg/dd.gpg Binary files differnew file mode 100644 index 0000000..bc16981 --- /dev/null +++ b/tests/gnupg/dd.gpg diff --git a/tests/gnupg/dm.gpg b/tests/gnupg/dm.gpg Binary files differnew file mode 100644 index 0000000..d58af9d --- /dev/null +++ b/tests/gnupg/dm.gpg 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 Binary files differnew file mode 100644 index 0000000..04a9ed5 --- /dev/null +++ b/tests/gnupg/pubring.gpg diff --git a/tests/gnupg/random_seed b/tests/gnupg/random_seed Binary files differnew file mode 100644 index 0000000..cc75662 --- /dev/null +++ b/tests/gnupg/random_seed diff --git a/tests/gnupg/secring.gpg b/tests/gnupg/secring.gpg Binary files differnew file mode 100644 index 0000000..7e20df1 --- /dev/null +++ b/tests/gnupg/secring.gpg diff --git a/tests/gnupg/trustdb.gpg b/tests/gnupg/trustdb.gpg Binary files differnew file mode 100644 index 0000000..c51ced8 --- /dev/null +++ b/tests/gnupg/trustdb.gpg 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 Binary files differnew file mode 100644 index 0000000..9a2dd12 --- /dev/null +++ b/tests/pkg-srcs/example_1.0-1+absurd.debian.tar.xz 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 Binary files differnew file mode 100644 index 0000000..ea8ec34 --- /dev/null +++ b/tests/pkg-srcs/example_1.0-1.100.debian.tar.xz 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 Binary files differnew file mode 100644 index 0000000..84ca563 --- /dev/null +++ b/tests/pkg-srcs/example_1.0-1.debian.tar.xz 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 Binary files differnew file mode 100644 index 0000000..f8427aa --- /dev/null +++ b/tests/pkg-srcs/example_1.0.orig-docs.tar.gz diff --git a/tests/pkg-srcs/example_1.0.orig.tar.gz b/tests/pkg-srcs/example_1.0.orig.tar.gz Binary files differnew file mode 100644 index 0000000..e5d2cf3 --- /dev/null +++ b/tests/pkg-srcs/example_1.0.orig.tar.gz diff --git a/tests/pkg-srcs/pari-extra_3-1.diff.gz b/tests/pkg-srcs/pari-extra_3-1.diff.gz Binary files differnew file mode 100644 index 0000000..81f7f2e --- /dev/null +++ b/tests/pkg-srcs/pari-extra_3-1.diff.gz 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 Binary files differnew file mode 100644 index 0000000..f5dff2b --- /dev/null +++ b/tests/pkg-srcs/pari-extra_3-2~dummy1.diff.gz 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 Binary files differnew file mode 100644 index 0000000..ff30279 --- /dev/null +++ b/tests/pkg-srcs/pari-extra_3.orig.tar.gz 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 Binary files differnew file mode 100644 index 0000000..633e6db --- /dev/null +++ b/tests/pkg-srcs/ruby-rails-3.2_3.2.6-1.debian.tar.gz 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 Binary files differnew file mode 100644 index 0000000..88ab3fd --- /dev/null +++ b/tests/pkg-srcs/ruby-rails-3.2_3.2.6.orig.tar.gz 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 Binary files differnew file mode 100644 index 0000000..c376961 --- /dev/null +++ b/tests/pkg-srcs/sunxi-tools_1.2-2.~~dgittest.debian.tar.gz 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 Binary files differnew file mode 100644 index 0000000..fe397fa --- /dev/null +++ b/tests/pkg-srcs/sunxi-tools_1.2.orig.tar.gz 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 Binary files differnew file mode 100644 index 0000000..50baa33 --- /dev/null +++ b/tests/worktrees/example_1.0.tar diff --git a/tests/worktrees/pari-extra_3-1.tar b/tests/worktrees/pari-extra_3-1.tar Binary files differnew file mode 100644 index 0000000..62efeb9 --- /dev/null +++ b/tests/worktrees/pari-extra_3-1.tar diff --git a/tests/worktrees/pari-extra_drs.tar b/tests/worktrees/pari-extra_drs.tar Binary files differnew file mode 100644 index 0000000..1030178 --- /dev/null +++ b/tests/worktrees/pari-extra_drs.tar diff --git a/tests/worktrees/ruby-rails-3.2_test.tar b/tests/worktrees/ruby-rails-3.2_test.tar Binary files differnew file mode 100644 index 0000000..85365fe --- /dev/null +++ b/tests/worktrees/ruby-rails-3.2_test.tar |