dgit/ 0000775 0000000 0000000 00000000000 13232222764 006700 5 ustar dgit/.gitignore 0000664 0000000 0000000 00000000363 13036763223 010674 0 ustar *~
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
dgit/DEVELOPER-CERTIFICATE 0000664 0000000 0000000 00000002616 13035555425 011701 0 ustar 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.
dgit/Debian/ 0000775 0000000 0000000 00000000000 13232213177 010060 5 ustar dgit/Debian/Dgit/ 0000775 0000000 0000000 00000000000 13035555425 010755 5 ustar dgit/Debian/Dgit/Infra.pm 0000664 0000000 0000000 00000001024 13035555425 012347 0 ustar # -*- 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;
dgit/Debian/Dgit/Policy/ 0000775 0000000 0000000 00000000000 13033772471 012214 5 ustar dgit/Debian/Dgit/Policy/Debian.pm 0000664 0000000 0000000 00000001313 13033772337 013733 0 ustar # -*- 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;
dgit/Debian/Dgit.pm 0000664 0000000 0000000 00000040533 13232213177 011312 0 ustar # -*- 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 .
package Debian::Dgit;
use strict;
use warnings;
use Carp;
use POSIX;
use IO::Handle;
use Config;
use Digest::SHA;
use Data::Dumper;
use IPC::Open2;
use File::Path;
use File::Basename;
BEGIN {
use Exporter ();
our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS);
$VERSION = 1.00;
@ISA = qw(Exporter);
@EXPORT = qw(setup_sigwarn forkcheck_setup forkcheck_mainprocess
dep14_version_mangle
debiantags debiantag_old debiantag_new
server_branch server_ref
stat_exists link_ltarget
hashfile
fail ensuredir must_getcwd executable_on_path
waitstatusmsg failedcmd_waitstatus
failedcmd_report_cmd failedcmd
runcmd cmdoutput cmdoutput_errok
git_rev_parse git_cat_file
git_get_ref git_for_each_ref
git_for_each_tag_referring is_fast_fwd
$package_re $component_re $deliberately_re
$distro_re $versiontag_re $series_filename_re
$branchprefix
initdebug enabledebug enabledebuglevel
printdebug debugcmd
$debugprefix *debuglevel *DEBUG
shellquote printcmd messagequote
$negate_harmful_gitattrs
changedir git_slurp_config_src
playtree_setup);
# implicitly uses $main::us
%EXPORT_TAGS = ( policyflags => [qw(NOFFCHECK FRESHREPO NOCOMMITCHECK)],
playground => [qw(record_maindir $maindir $local_git_cfg
$maindir_gitdir $maindir_gitcommon
fresh_playground $playground
ensure_a_playground)]);
@EXPORT_OK = ( @{ $EXPORT_TAGS{policyflags} },
@{ $EXPORT_TAGS{playground} } );
}
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 $distro_re = $component_re;
our $versiontag_re = qr{[-+.\%_0-9a-zA-Z/]+};
our $branchprefix = 'dgit';
our $series_filename_re = qr{(?:^|\.)series(?!\n)$}s;
# 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;
our $negate_harmful_gitattrs = "-text -eol -crlf -ident -filter";
our $forkcheck_mainprocess;
sub forkcheck_setup () {
$forkcheck_mainprocess = $$;
}
sub forkcheck_mainprocess () {
# You must have called forkcheck_setup or setup_sigwarn already
getppid != $forkcheck_mainprocess;
}
sub setup_sigwarn () {
forkcheck_setup();
$SIG{__WARN__} = sub {
die $_[0] if forkcheck_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 $_;
defined or confess 'internal error' foreach @_;
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 must_getcwd () {
my $d = getcwd();
defined $d or fail "getcwd failed: $!";
return $d;
}
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 runcmd {
debugcmd "+",@_;
$!=0; $?=-1;
failedcmd @_ if system @_;
}
sub cmdoutput_errok {
confess Dumper(\@_)." ?" if grep { !defined } @_;
debugcmd "|",@_;
open P, "-|", @_ or die "$_[0] $!";
my $d;
$!=0; $?=0;
{ local $/ = undef; $d =
; }
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_cat_file ($) {
my ($objname) = @_;
# => ($type, $data) or ('missing', undef)
# in scalar context, just the data
our ($gcf_pid, $gcf_i, $gcf_o);
if (!$gcf_pid) {
my @cmd = qw(git cat-file --batch);
debugcmd "GCF|", @cmd;
$gcf_pid = open2 $gcf_o, $gcf_i, @cmd or die $!;
}
printdebug "GCF>| ", $objname, "\n";
print $gcf_i $objname, "\n" or die $!;
my $x = <$gcf_o>;
printdebug "GCF<| ", $x;
if ($x =~ m/ (missing)$/) { return ($1, undef); }
my ($type, $size) = $x =~ m/^.* (\w+) (\d+)\n/ or die "$objname ?";
my $data;
(read $gcf_o, $data, $size) == $size or die "$objname $!";
$x = <$gcf_o>;
$x eq "\n" or die "$objname ($_) $!";
return ($type, $data);
}
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 () {
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;
}
}
sub changedir ($) {
my ($newdir) = @_;
printdebug "CD $newdir\n";
chdir $newdir or confess "chdir: $newdir: $!";
}
sub git_slurp_config_src ($) {
my ($src) = @_;
# returns $r such that $r->{KEY}[] = VALUE
my @cmd = (qw(git config -z --get-regexp), "--$src", qw(.*));
debugcmd "|",@cmd;
local ($debuglevel) = $debuglevel-2;
local $/="\0";
my $r = { };
open GITS, "-|", @cmd or die $!;
while () {
chomp or die;
printdebug "=> ", (messagequote $_), "\n";
m/\n/ or die "$_ ?";
push @{ $r->{$`} }, $'; #';
}
$!=0; $?=0;
close GITS
or ($!==0 && $?==256)
or failedcmd @cmd;
return $r;
}
# ========== playground handling ==========
# terminology:
#
# $maindir user's git working tree
# $playground area in .git/ where we can make files, unpack, etc. etc.
# playtree git working tree sharing object store with the user's
# inside playground, or identical to it
#
# other globals
#
# $local_git_cfg hash of arrays of values: git config from $maindir
#
# expected calling pattern
#
# firstly
#
# [record_maindir]
# must be run in directory containing .git
# assigns to $maindir if not already set
# also calls git_slurp_config_src to record git config
# in $local_git_cfg, unless it's already set
#
# fresh_playground SUBDIR_PATH_COMPONENTS
# e.g fresh_playground 'dgit/unpack' ('.git/' is implied)
# default SUBDIR_PATH_COMPONENTS is $playground_subdir
# calls record_maindir
# sets up a new playground (destroying any old one)
# assigns to $playground and returns the same pathname
# caller may call multiple times with different subdir paths
# createing different playgrounds; but $playground global can
# refer only to one, obv.
#
# ensure_a_playground SUBDIR_PATH_COMPONENTS
# like fresh_playground except:
# merely ensures the directory exists; does not delete an existing one
# never sets global $playground
#
# then can use
#
# changedir $playground
# changedir $maindir
#
# playtree_setup $local_git_cfg
# # ^ call in some (perhaps trivial) subdir of $playground
#
# rmtree $playground
# ----- maindir -----
# these three all go together
our $maindir;
our $maindir_gitdir;
our $maindir_gitcommon;
our $local_git_cfg;
sub record_maindir () {
if (!defined $maindir) {
$maindir = must_getcwd();
if (!stat "$maindir/.git") {
fail "cannot stat $maindir/.git: $!";
}
if (-d _) {
# we fall back to this in case we have a pre-worktree
# git, which may not know git rev-parse --git-common-dir
$maindir_gitdir = "$maindir/.git";
$maindir_gitcommon = "$maindir/.git";
} else {
$maindir_gitdir = cmdoutput qw(git rev-parse --git-dir);
$maindir_gitcommon = cmdoutput qw(git rev-parse --git-common-dir);
}
}
$local_git_cfg //= git_slurp_config_src 'local';
}
# ----- playgrounds -----
our $playground;
sub ensure_a_playground_parent ($) {
my ($spc) = @_;
record_maindir();
$spc = "$maindir_gitdir/$spc";
my $parent = dirname $spc;
mkdir $parent or $!==EEXIST
or fail "failed to mkdir playground parent $parent: $!";
return $spc;
}
sub ensure_a_playground ($) {
my ($spc) = @_;
$spc = ensure_a_playground_parent $spc;
mkdir $spc or $!==EEXIST or fail "failed to mkdir a playground $spc: $!";
return $spc;
}
sub fresh_playground ($) {
my ($spc) = @_;
$spc = ensure_a_playground_parent $spc;
rmtree $spc;
mkdir $spc or fail "failed to mkdir the playground $spc: $!";
return $playground = $spc;
}
# ----- playtrees -----
sub playtree_setup (;$) {
my ($t_local_git_cfg) = @_;
$t_local_git_cfg //= $local_git_cfg;
# for use in the playtree
# $maindir must be set, eg by calling record_maindir or fresh_playground
runcmd qw(git init -q);
runcmd qw(git config gc.auto 0);
foreach my $copy (qw(user.email user.name user.useConfigOnly
core.sharedRepository
core.compression core.looseCompression
core.bigFileThreshold core.fsyncObjectFiles)) {
my $v = $t_local_git_cfg->{$copy};
next unless $v;
runcmd qw(git config), $copy, $_ foreach @$v;
}
# this is confusing: we have
# . playtree, not a worktree, has .git/, our cwd
# $maindir might be a worktree so
# $maindir_gitdir contains our main working "dgit", HEAD, etc.
# $maindir_gitcommon the shared stuff, including .objects
rmtree('.git/objects');
symlink "$maindir_gitcommon/objects",'.git/objects' or die $!;
ensuredir '.git/info';
open GA, "> .git/info/attributes" or die $!;
print GA "* $negate_harmful_gitattrs\n" or die $!;
close GA or die $!;
}
1;
dgit/Makefile 0000664 0000000 0000000 00000006352 13042164514 010343 0 ustar # 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 .
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 dgit-badcommit-fixup
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,,$@) \
$^ $@
%.view: %
man -l $*
dgit/README.dsc-import 0000664 0000000 0000000 00000010665 13035555425 011654 0 ustar 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)').
dgit/absurd/ 0000775 0000000 0000000 00000000000 13035556125 010162 5 ustar dgit/absurd/git 0000775 0000000 0000000 00000003657 13035556125 010706 0 ustar #!/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 <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
dgit/debian/ 0000775 0000000 0000000 00000000000 13232222764 010122 5 ustar dgit/debian/changelog 0000664 0000000 0000000 00000200210 13232222764 011767 0 ustar dgit (4.3) unstable; urgency=high
Documentation improvements:
* dgit(7): Discuss file executability. Closes:#886444.
* dgit(7): Discuss git-unrepresentable properties of source trees.
* dgit-maint-merge(7): Don't suggest using debian/source/patch-header
for 1.0 source format. Closes:#887850. [Sean Whitton]
Bugfixes:
* dgit archive-api-query: Avoid crashing due to lack of $isuite.
This breaks the infrastructure. Closes:#886592.
-- Ian Jackson Thu, 25 Jan 2018 00:33:56 +0000
dgit (4.2) unstable; urgency=low
* Upload dgit 4.x series to unstable.
New features [Sean Whitton]:
* New 'push-source' subcommand for source-only uploads. Closes:#848931
* When dgit builds a source package, such as in the 'build-source'
subcommand, it now bypasses dpkg-buildpackage and invokes dpkg-source
directly. This avoids a _source.buildinfo file in the .changes, which
doesn't make sense when using dgit. See
.
Documentation improvements:
* dgit(1): Add a bit more rationale (polemic, even). Closes:#874221.
* Recommend mk-build-deps rather than apt-get build-dep.
Suggestion from Nikolaus Rath. Closes:#863361.
* dgit-maint-merge(7): many updates. [Sean Whitton]
Closes:#864873,#878433.
* dgit-*(7): Mention first upload trick. [Andrew Shadura,
Sean Whitton] Closes:#856402.
Minor fixes:
* When source discrepancy involves file mode changes, report them
specially. Closes:#886442.
* In split brain mode, with unexpected diffs, print dgit view
commitid in suggested diff rune. (HEAD is wrong.) Closes:#886443.
* Fix message about missing quilt cache entry to refer to
HEAD rather than tree, since dgit needs a commit. Closes:#884646.
* Fix grammar error in 4.1 changelog entry. [Sean Whitton]
* Remove some whitespace "errors". [Sean Whitton]
Packaging:
* Remove dependency alternative on realpath (package last existed in
Debian wheezy). Closes:#877552.
Test suite:
* dpkgsourceignores-docs: Correct restriction (so autopkgtest
won't try to run it).
* Additional workarounds for gnupg races (#868550) including
retrying each individual test once, and more sophisticated
wrapper for gpg (with locking and, sometimes saves stdin).
* oldnewtagalt: Fix regression when running outside git tree,
introduced in 4.1.
-- Ian Jackson Sun, 07 Jan 2018 21:45:29 +0000
dgit (3.13) unstable; urgency=high
Important bugfixes to dgit:
* Add missing `use' for Dpkg::Compression et al.
Thanks to report from Didier 'OdyX' Raboud. (Closes:#879526.)
Test suite:
* Add missing `chiark-utils-bin' to Test-Depends.
-- Ian Jackson Sun, 22 Oct 2017 17:51:12 +0100
dgit (4.1) experimental; urgency=medium
Important improvements to dgit:
* Support for `git worktree' worktrees. There may still be
bugs; the tests for this are not very comprehensive. And
worktrees on different filesystems may not work; that's a
matter for the future. Closes:#868515.
* Change the dpkg-source -i argument to exclude exactly the right
set of things. (Sadly this is not a simple rune.)
Other improvements to dgit:
* New print-dpkg-source-ignores option to print the big rune
you need to pass to dpkg-source to make it work exactly right.
* Properly shell-quote the --git-builder argument to gbp.
Documentation:
* dgit-user(7): Provide information about how to use sbuild.
Quite ugly due to #868527. Closes:#868526.
* dgit-user(7): Fixed example rune to use curl (which prints
to stdout, as the rune expects). [reported by Simon Tatham]
Minor improvements:
* Do not leave many clog-* files in .git/dgit.
Internal changes:
* using-these: New script to help with ad-hoc-testing.
* Refactoring in preparation for push-source [Sean Whitton].
Test suite:
* sbuild-gitish: New test case to check running sbuild from git
* Work around gnupg agent connection races by having our stunt
gpg wrapper simply try running gpg again, once, if it exits 2.
This does not fully suppress the bug but it does significantly reduce
the probability.
* Other tests for new features.
* Various refactoring.
-- Ian Jackson Mon, 14 Aug 2017 09:31:03 +0100
dgit (4.0) experimental; urgency=low
* dgit: --deliberately-not-fast-forward works properly in
split view quilt modes (suppressing the pseudomerge).
-- Ian Jackson Sun, 12 Feb 2017 22:22:31 +0000
dgit (3.12) unstable; urgency=high
Important bugfixes to dgit:
* Pass --no-renames to git diff-tree -z, avoiding potential trouble.
* Defend against commit subject lines which would generate patches which
look like series files, etc. Involves adding .patch to all generated
patch filenames.
* dgit import: Defend against broken symlinks in ..
* dgit import: Right error message for missing files in ..
* dgit import: Avoid making broken symlinks in ..
* quilt fixup: Tolerate deletion of executable files.
* quilt fixup: Tolerate symlink creation (make patches). Closes:#857382.
Important bugfixes to other components:
* dgit-repos-server: Do not reject commits with no author/committer
email address (but still insist on date, and hence on the actual
committer and author commit header fields). Peter Green reports that
eg 66c65d90db100435 in upstream linux.git is such a commit (and is
accepted by github). Closes:#863353.
Test suite:
* t-report-fail: print $PWD as part of failure message.
* import-dsc: Test missing files, particularly in ..
* run git gc on tests/worktrees/example_1.0.tar.
* quilt fixup: Check we can delete files with funny modes
* quilt fixup: Check that funny changes are represented properly
-- Ian Jackson Sun, 16 Jul 2017 21:36:24 +0100
dgit (3.11~deb9u1) stretch; urgency=high
* Rebuild and upload to stretch.
-- Ian Jackson Tue, 11 Jul 2017 09:28:15 +0100
dgit (3.11) unstable; urgency=high
Important bugfixes to dgit:
* Fix rpush+buildinfo: Transfer buildinfos for signing. Closes:#867693.
* Cope if the archive server sends an HTTP redirect,
by passing -L to curl. Closes:#867185,#867309.
* Cope with newer git which hates --local outside a tree. Closes:#865863.
* rpush: Honour local git config from build host working tree.
* Tolerate compressor terminating with SIGPIPE. Closes:#857694.
* Honour more pre-tree git config options in our private trees sharing
the user's object store. In particular, core.sharedRepository.
Prompted by #867603.
* Clone multisuite works even without --no-rm-on-error. Closes:#867434.
* Work if "git init" does not create $GIT/info. Closes:#858054.
* Actually understand foo,-security (!) Closes:#867189.
Important bugfixes to other components:
* dgit-badcommit-fixup: Honour core.sharedRepository. Closes:#867603.
* infrastructure: Cope with new git-receive-pack which has
quarantine feature: ie, work around #867702.
Test suite:
* Cope with git restricting ext:: protocols.
* multisuite: Test clone without --rm-on-error.
-- Ian Jackson Sat, 08 Jul 2017 22:40:15 +0100
dgit (3.10) unstable; urgency=medium
Bugfixes:
* dgit: Copy several user.* settings from main tree git local config
to dgit private workarea. Closes:#853085.
* dgit: Strip initial newline from Changes line from dpkg-parsechangelog
so as to avoid blank line in commit messages. Closes:#853093.
* dgit: Do not fail when run with detached HEAD. Closes:#853022.
* dgit: Be much better about commas in maintainer changelog names.
Closes:#852661.
Test suite:
* quilt-useremail: New test for user config copying (#853085).
* lib-import-chk: Test that commits have smae authorship as appears in
the changelog. (Or, at least, the same authorship set.)
* import-maintmangle: New test for changelog Maintainer mangling.
Documentation:
* Fix typos. Closes:#853125. [Nicholas D Steeves]
-- Ian Jackson Mon, 06 Feb 2017 17:49:39 +0000
dgit (3.9) unstable; urgency=medium
Improvements:
* dgit --overwrite: Check that the overwritten version's changelog entry
is not UNRELEASED. This could easily happen if this release was being
made from a git branch which predates the previous package upload.
Documentation:
* dgit-maint-merge(7): Get git clone url right. Closes:#852609.
* dgit-maint-merge(7): Quote sample clone commands. Closes:#852615.
Test suite:
* overwrite-chkclog: test UNRELEASED handling.
-- Ian Jackson Wed, 25 Jan 2017 16:21:53 +0000
dgit (3.8) unstable; urgency=medium
Bugfixes:
* Make dgit-setup-* work in default distro.
Test suite:
* defdistro-setup: Test that setup-* functions distro selection works.
-- Ian Jackson Mon, 23 Jan 2017 16:21:30 +0000
dgit (3.7) unstable; urgency=medium
Bugfixes:
* Fix clone-dgit-repos-server and print-dgit-repos-server-print-url.
Closes:#851906.
Documentation:
* dgit-maint-merge(7): Explain when workflow is unsuitable
(Closes:#852090) and improve the patch-header (Closes:#851897.)
Internal changes:
* New %.view target: `make dgit-maint-merge.7.view' runs `man -l ...'
Test suite:
* defdistro-dsd-clone-drs: New test which would have detected
#851906 (and hopefully #850521).
-- Ian Jackson Sun, 22 Jan 2017 17:30:24 +0000
dgit (3.6) unstable; urgency=medium
Bugfixes:
* Actually use the url from a Dgit .dsc field naming an unknown distro.
Closes:#851728.
* Add dummy implementation of file_in_archive_aptget copied from
file_in_archive_dummycat. Re:#851697. [ Peter Green ]
Minor improvements:
* Use `confess' to print a stack trace in a couple of internal error
rcases.
Infrastructure:
* Properly honour NOCOMMITCHECK policy hook exit status.
Closes:#851800.
* Do not reject commits with no author/committer name (but still insist
on email address and date). Peter Green reports that eg
71e128629ec786f3 in upstream xen.git is such a commit (and is accepted
by github). Closes:#851716.
Test suite:
* downstream-gitless: Test import of .dsc from unknown distro.
* downstream-gitless: Test import of .dsc with unsafe url.
-- Ian Jackson Thu, 19 Jan 2017 01:15:03 +0000
dgit (3.5) unstable; urgency=medium
Bugfixes:
* gitattributes: Defuse gitattributes in private working area even if we
don't do it in the user's tree (because of user configuration).
* gitattributes: When cloning, do not print spurious warning about
actually-defused gitattributes. Closes:#851624.
* gitattributes: Improve comment left in .git/info/attributes.
Test suite:
* gitattributes: Many improvements to test case.
-- Ian Jackson Tue, 17 Jan 2017 22:36:01 +0000
dgit (3.4) unstable; urgency=low
Test suite:
* drs-push-rejects: Set origin's url to an ad-hoc expression
which produces the right ext:: rune, as dgit would.
Closes:#851580.
* Replace references to /home/ian in various worktrees with
references to /nonexistent, to catch inadvertant accesses.
-- Ian Jackson Mon, 16 Jan 2017 17:27:35 +0000
dgit (3.3) unstable; urgency=medium
Behavioural changes to work around gitattributes file transformations:
* Suppress file-transforming gitattributes in private work areas.
* Configure suppression in user's trees in dgit clone and setup-new-tree.
* Provide dgit setup-gitattributes to do this explicitly.
* Documentation.
Bugfixes:
* dgit: Remove a leftover debugging print.
* dgit: Set default dsc import distro when there is no Dgit field.
* dgit: Set default dsc import distro when suppressing Dgit field.
* dgit: Option parsing: Fix undefined $suite in some import-dsc.
Closes:#851213.
Packaging:
* Remove redundant use of List::Util qw(any). Closes:#851280.
* Remove redundant Recommends on libtext-iconv-perl.
Test suite:
* Move default dsc distro config setting to lib. We need this
for the .dscs we have in tests/pkg-srcs/.
* defdistro-import-dsc: Drop this test.
* protocol-compat: check that we use the right distro
information when importing.
* Internal change: fix handling of nonempty distro=
* gitattributes: New test for .gitattributes handling.
-- Ian Jackson Mon, 16 Jan 2017 10:03:08 +0000
dgit (3.2) unstable; urgency=medium
Bugfixes:
* dgit: Do not execute END blocks in children. So far symptoms of this
bug seem to be limited to duplicated error messages but I have not
done a thorough analysis. Closes:#850052.
* dgit-infrastructure: dgit-repos-policy-debian: Remirror a package when
it becomes public (ie, make the repo available much more promptly when
the package passes NEW). Closes:#849789.
* dgit: Fix a warning message about ref (mainly, tag) updates.
Documentation:
* dgit-maint-merge(7): Use git-deborig(1).
[Sean Whitton] Closes:#850953.
* dgit-user(7): Fix some typos.
Internals:
* Fix a typo in a comment.
Test suite:
* infra: mirroring and policy hooks: Improve some debugging output.
* infra: mirror-private: test that package becomes public. (#849789)
-- Ian Jackson Thu, 12 Jan 2017 02:11:34 +0000
dgit (3.1) unstable; urgency=medium
Bugfixes:
* dgit import-dsc: Do not crash with undefined $isuite. Closes:#850781.
* dgit build: Do not sometimes crash with undefined $isuite.
* dgit: Do not nedlessly re-fetch the rewrite map.
* dgit: After downloading .debian.* files, save them in `..', too
(ie do this not just for .origs).
* dgit: When fetching, refetch files with hash mismatches (and save them
as `...,fetch'), so we can distinguish them from any built locally.
Closes:#850824.
Test suite:
* Add test for import-dsc with default distro. (Detects #850781.)
Administrivia:
* Fix a dgit 3.0 changelog bullet referring to refs/dgit-fetch/DISTRO.
-- Ian Jackson Tue, 10 Jan 2017 17:50:27 +0000
dgit (3.0) unstable; urgency=medium
Protocol change:
* Dgit: field now records the nominal distro name, and a hint
for a tag and url where the git objects (including any rewrite
map) can be fetched.
* Use this information, where provided. Closes:#850431.
Bugfixes:
* dgit config handling: Honour command-line and context-provided
suite and distro more reliably and consistently.
* Parsing of extended Dgit fields by import-dsc was broken;
and is now fixed even for more-extended ones.
* dgit clone-dgit-repos-server uses readonly access.
Closes:#850521.
* fetch and pull ignore the changelog suite when it is UNRELEASED.
Closes:#848646.
* dgit-badcommit-fixup: Do not investigate symrefs. Closes:#850547.
Minor new feature:
* distro alias facility in config space. (Primarily for testing.)
* Undocumented --config-lookup-explode= feature. (For testing.)
* Provide `dgit print-dgit-repos-server-source-url'. Re:#850521.
* Honour dgit-distro.*.default-suite and dgit.default.default-suite.
Other improvements:
* Improve debugging output a bit.
* Use refs/dgit-fetch/DISTRO rather than refs/dgit-fetch/SUITE,
which leads to less duplication and so less clutter.
* Enforce a reasonable syntax for nominal distro names.
* When generating orig+debian/patches view, copy debian/ from
HEAD. This makes less noise in diffs. Closes:#850095.
Docuentation [Sean Whitton and Ian Jackson]:
* dgit-sponsorship(7): Use --no-dep14tag. Closes:#849105.
* dgit-maint-merge(7): Use debian/source/patch-header. Closes:849120.
* dgit(7): Updated `trouble' section to suggest having dpkg-source
delete the autotools output (with a patch if necessary).
* dgit(1): Several minor updates and fixes. Closes:#850519.
Test suite:
* Internal improvements.
* badcommit-rewrite: Fix operation using installed version of fixup.
* Arrange to pass --debug-quick-random to gpg-agent.
* Strip block count out of find -ls output - it is unstable!
* gbp-orig: Add a missing -m, without which git would run an
editor if stdout was a tty (!)
* Add t-stunt-parsechangelog to a few tests which were missing it.
* Tests for the new protocol feature.
* Fail tests if we look up any configuration relating to Debian.
-- Ian Jackson Mon, 09 Jan 2017 16:43:10 +0000
dgit (2.16.2) unstable; urgency=low
dgit-badcommit-fixup:
* Fix crash when running for 2nd time in bare repo.
* In --check mode, exit with status 2 if things are not fine.
-- Ian Jackson Sat, 07 Jan 2017 13:31:50 +0000
dgit (2.16.1) UNRELEASED; urgency=low
* dgit-badcommit-fixup: New mode --check which is readonly.
-- Ian Jackson Sat, 07 Jan 2017 13:04:49 +0000
dgit (2.16) unstable; urgency=low
Dealing with fallout from #849041:
* Provide dgit-badcommit-fixup history-rewriting script.
* New rewrite map feature, which allows dgit git server to adjust
clients' interpretation of Dgit fields, so that history-rewriting is
effective. (Feature is only partially implemented right now -
enough to dig current Debian users out of the hole.) Re:#850431.
Test suite:
* New test case for history-rewriting.
* Change `local foo=$(bar)' idiom to `local foo; foo=$(bar)' since
the former does not trip set -e even if bar fails :-(.
-- Ian Jackson Fri, 06 Jan 2017 20:46:30 +0000
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 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 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 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 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 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 .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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 ' 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 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 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 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 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 by default. Closes:#732781.
-- Ian Jackson 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 Wed, 27 Nov 2013 18:27:17 +0000
dgit (0.18.2) unstable; urgency=high
Bump archive upload urgency to high.
-- Ian Jackson 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 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 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).
* 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 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/.
-- Ian Jackson 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 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 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 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 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 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 Sun, 25 Aug 2013 17:00:43 +0100
dgit (0.10) unstable; urgency=low
* Create .pc/applied-patches - do not empty it (!)
-- Ian Jackson 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 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 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/
so that we avoid a warning from git about ambiguous branch names.
-- Ian Jackson 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 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 Thu, 22 Aug 2013 15:38:00 +0100
dgit (0.4~pre2) experimental; urgency=low
* Mangle debian/ tags the way git-buildpackage does
(as of git-buildpackage 0.5.5, 3c6bbd0f4992f8da).
* Support dgit-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 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 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 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 Fri, 16 Aug 2013 16:52:17 +0100
dgit (0.1) experimental; urgency=low
* Initial experimental (partial) version.
-- Ian Jackson Thu, 15 Aug 2013 12:09:01 +0100
dgit/debian/compat 0000664 0000000 0000000 00000000002 13033772337 011325 0 ustar 9
dgit/debian/control 0000664 0000000 0000000 00000002667 13230431257 011535 0 ustar Source: dgit
Section: devel
Priority: optional
Maintainer: Ian Jackson
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~),
libdigest-sha-perl, dput, curl, apt,
libjson-perl, ca-certificates,
libtext-iconv-perl, libtext-glob-perl
Recommends: ssh-client
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.
dgit/debian/copyright 0000664 0000000 0000000 00000005124 13230431257 012054 0 ustar dgit
Integration between git and Debian-style archives
Copyright (C)2013-2017 Ian Jackson
Copyright (C)2016-2017 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.
dgit/debian/rules 0000775 0000000 0000000 00000003724 13035555425 011214 0 ustar #!/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 .
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/*[^~#])
dgit/debian/tests/ 0000775 0000000 0000000 00000000000 13230431257 011261 5 ustar dgit/debian/tests/control 0000664 0000000 0000000 00000005676 13230431257 012702 0 ustar Tests: build-modes-gbp
Tests-Directory: tests/tests
Depends: dgit, dgit-infrastructure, devscripts, debhelper (>=8), fakeroot, build-essential, chiark-utils-bin, git-buildpackage
Tests: clone-reprepro downstream-gitless
Tests-Directory: tests/tests
Depends: dgit, dgit-infrastructure, devscripts, debhelper (>=8), fakeroot, build-essential, chiark-utils-bin, reprepro
Tests: dpkgsourceignores-docs
Tests-Directory: tests/tests
Depends: dgit, dgit-infrastructure, devscripts, debhelper (>=8), fakeroot, build-essential, chiark-utils-bin
Restrictions: x-dgit-intree-only
Tests: defdistro-dsd-clone-drs dsd-clone-drs
Tests-Directory: tests/tests
Depends: dgit, dgit-infrastructure, devscripts, debhelper (>=8), fakeroot, build-essential, chiark-utils-bin
Restrictions: x-dgit-intree-only x-dgit-git-only
Tests: gitattributes
Tests-Directory: tests/tests
Depends: dgit, dgit-infrastructure, devscripts, debhelper (>=8), fakeroot, build-essential, chiark-utils-bin, bsdgames, man-db, git-man
Tests: defdistro-mirror mirror mirror-debnewgit mirror-private
Tests-Directory: tests/tests
Depends: dgit, dgit-infrastructure, devscripts, debhelper (>=8), fakeroot, build-essential, chiark-utils-bin, rsync
Tests: build-modes-sbuild quilt-gbp-build-modes-sbuild
Tests-Directory: tests/tests
Depends: dgit, dgit-infrastructure, devscripts, debhelper (>=8), fakeroot, build-essential, chiark-utils-bin, sbuild
Restrictions: x-dgit-schroot-build
Tests: sbuild-gitish
Tests-Directory: tests/tests
Depends: dgit, dgit-infrastructure, devscripts, debhelper (>=8), fakeroot, build-essential, chiark-utils-bin, sbuild, man-db
Restrictions: x-dgit-schroot-build
Tests: spelling
Tests-Directory: tests/tests
Depends: dgit, dgit-infrastructure, devscripts, debhelper (>=8), fakeroot, build-essential, chiark-utils-bin
Restrictions: x-dgit-git-only
Tests: absurd-gitapply badcommit-rewrite build-modes build-modes-asplit build-modes-gbp-asplit clone-clogsigpipe clone-gitnosuite clone-nogit debpolicy-dbretry debpolicy-newreject debpolicy-quilt-gbp defdistro-rpush defdistro-setup distropatches-reject dpkgsourceignores-correct drs-clone-nogit drs-push-masterupdate drs-push-rejects dsd-clone-nogit dsd-divert fetch-localgitonly fetch-somegit-notlast gbp-orig gitconfig gitworktree import-dsc import-maintmangle import-native import-nonnative import-tarbomb inarchivecopy mismatches-contents mismatches-dscchanges multisuite newtag-clone-nogit oldnewtagalt oldtag-clone-nogit orig-include-exclude orig-include-exclude-chkquery overwrite-chkclog overwrite-junk overwrite-splitbrains overwrite-version protocol-compat push-buildproductsdir push-newpackage push-nextdgit push-source push-source-with-changes quilt quilt-gbp quilt-gbp-build-modes quilt-singlepatch quilt-splitbrains quilt-useremail rpush tag-updates test-list-uptodate trustingpolicy-replay unrepresentable version-opt
Tests-Directory: tests/tests
Depends: dgit, dgit-infrastructure, devscripts, debhelper (>=8), fakeroot, build-essential, chiark-utils-bin
dgit/debian/tests/control.in 0000664 0000000 0000000 00000000212 13230431257 013264 0 ustar Tests-Directory: tests/tests
Depends: dgit, dgit-infrastructure, devscripts, debhelper (>=8), fakeroot, build-essential, chiark-utils-bin
dgit/dgit 0000775 0000000 0000000 00000616311 13232213177 007563 0 ustar #!/usr/bin/perl -w
# dgit
# Integration between git and Debian-style archives
#
# Copyright (C)2013-2017 Ian Jackson
# Copyright (C)2017 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.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
use strict;
use Debian::Dgit qw(:DEFAULT :playground);
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 Dpkg::Compression;
use Dpkg::Compression::Process;
use POSIX;
use IPC::Open2;
use Digest::SHA;
use Digest::MD5;
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 $cmd;
our $subcommand;
our $isuite;
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 $split_brain_save;
our $we_are_responder;
our $we_are_initiator;
our $initiator_tempdir;
our $patches_applied_dirtily = 00;
our $tagformat_want;
our $tagformat;
our $tagformatfn;
our $chase_dsc_distro=1;
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 $rewritemap = 'dgit-rewrite/map';
our @dpkg_source_ignores = qw(-i(?:^|/)\.git(?:/|$) -I.git);
our (@git) = qw(git);
our (@dget) = qw(dget);
our (@curl) = (qw(curl --proto-redir), '-all,http,https', qw(-L));
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), @dpkg_source_ignores);
our (@dpkgsource) = (qw(dpkg-source), @dpkg_source_ignores);
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 parseopts_late_defaults();
sub setup_gitattrs(;$);
sub check_gitattrs($$);
our $keyid;
autoflush STDOUT 1;
our $supplementary_message = '';
our $need_split_build_invocation = 0;
our $split_brain = 0;
END {
local ($@, $?);
return unless forkcheck_mainprocess();
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 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 ($?);
return unless forkcheck_mainprocess();
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 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;
}
sub dgit_privdir () {
our $dgit_privdir_made //= ensure_a_playground 'dgit';
}
#---------- remote protocol support, common ----------
# remote push initiator/responder protocol:
# $ dgit remote-push-build-host ... ...
# where is ,... ...
# < dgit-remote-push-ready
#
# 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
#
# > param buildinfo-filename P_V_X.buildinfo # zero or more times
# > file buildinfo # for buildinfos to sign
#
# > previously REFNAME=OBJNAME # if --deliberately-not-fast-forward
# # goes into tag, for replay prevention
#
# > 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]
# < data-block NBYTES [transfer of each signed buildinfo
# [etc] same number and order as "file buildinfo"]
# ...
# < 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 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 = < sign tag and package with 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= set git config option (used directly by dgit too)
END
our $later_warning_msg = < 'debian',
'dgit.default.default-suite' => 'unstable',
'dgit.default.old-dsc-distro' => 'debian',
'dgit-suite.*-security.distro' => 'debian-security',
'dgit.default.username' => '',
'dgit.default.archive-query-default-component' => 'main',
'dgit.default.ssh' => 'ssh',
'dgit.default.archive-query' => 'madison:',
'dgit.default.sshpsql-dbname' => 'service=projectb',
'dgit.default.aptget-components' => 'main',
'dgit.default.dgit-tag-format' => 'new,old,maint',
'dgit.dsc-url-proto-ok.http' => 'true',
'dgit.dsc-url-proto-ok.https' => 'true',
'dgit.dsc-url-proto-ok.git' => 'true',
'dgit.default.dsc-url-proto-ok' => 'false',
# old means "repo server accepts pushes with old dgit tags"
# new means "repo server accepts pushes with new dgit tags"
# maint means "repo server accepts split brain pushes"
# 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);
our $invoked_in_git_tree = 1;
sub git_slurp_config () {
# 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
$gitcfgs{$src} = git_slurp_config_src $src;
}
}
sub git_get_config ($) {
my ($c) = @_;
foreach my $src (@gitcfgsources) {
my $l = $gitcfgs{$src}{$c};
confess "internal error ($l $c)" if $l && !ref $l;
printdebug"C $c ".(defined $l ?
join " ", map { messagequote "'$_'" } @$l :
"undef")."\n"
if $debuglevel >= 4;
$l or next;
@$l==1 or badcfg "multiple values for $c".
" (in $src git config)" if @$l > 1;
return $l->[0];
}
return undef;
}
sub cfg {
foreach my $c (@_) {
return undef if $c =~ /RETURN-UNDEF/;
printdebug "C? $c\n" if $debuglevel >= 5;
my $v = git_get_config($c);
return $v if defined $v;
my $dv = $defcfg{$c};
if (defined $dv) {
printdebug "CD $c $dv\n" if $debuglevel >= 4;
return $dv;
}
}
badcfg "need value for one of: @_\n".
"$us: distro or suite appears not to be (properly) supported";
}
sub not_necessarily_a_tree () {
# needs to be called from pre_*
@gitcfgsources = grep { $_ ne 'local' } @gitcfgsources;
$invoked_in_git_tree = 0;
}
sub access_basedistro__noalias () {
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_basedistro () {
my $noalias = access_basedistro__noalias();
my $canon = cfg("dgit-distro.$noalias.alias-canon",'RETURN-UNDEF');
return $canon // $noalias;
}
sub access_nomdistro () {
my $base = access_basedistro();
my $r = cfg("dgit-distro.$base.nominal-distro",'RETURN-UNDEF') // $base;
$r =~ m/^$distro_re$/ or badcfg
"bad syntax for (nominal) distro \`$r' (does not match /^$distro_re$/)";
return $r;
}
sub access_quirk () {
# returns (quirk name, distro to use instead or undef, quirk-specific info)
my $basedistro = access_basedistro();
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
parseopts_late_defaults();
}
sub notpushing () {
parseopts_late_defaults();
}
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;
my $mclog = dgit_privdir()."clog";
runcmd shell_cmd "exec >$mclog", @git, qw(cat-file blob),
"$objid:debian/changelog";
$commit_getclogp_memo{$objid} = parsechangelog("-l$mclog");
}
sub 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 <) {
next unless stat_exists $oldlist;
my ($mtime) = (stat _)[9];
utime $oldatime, $mtime, $oldlist or die "$oldlist $!";
}
runcmd_ordryrun_local aptget_aptget(), qw(update);
my @releasefiles;
foreach my $oldlist (<$aptget_base/lists/*Release>) {
next unless stat_exists $oldlist;
my ($atime) = (stat _)[8];
next if $atime == $oldatime;
push @releasefiles, $oldlist;
}
my @inreleasefiles = grep { m#/InRelease$# } @releasefiles;
@releasefiles = @inreleasefiles if @inreleasefiles;
die "apt updated wrong number of Release files (@releasefiles), erk"
unless @releasefiles == 1;
($aptget_releasefile) = @releasefiles;
}
sub canonicalise_suite_aptget {
my ($proto,$data) = @_;
aptget_prep($data);
my $release = parsecontrol $aptget_releasefile, "Release file", 1;
foreach my $name (qw(Codename Suite)) {
my $val = $release->{$name};
if (defined $val) {
printdebug "release file $name: $val\n";
$val =~ m/^$suite_re$/o or fail
"Release file ($aptget_releasefile) specifies intolerable $name";
cfg_apply_map(\$val, 'suite rmap',
access_cfg('aptget-suite-rmap', 'RETURN-UNDEF'));
return $val
}
}
return $isuite;
}
sub archive_query_aptget {
my ($proto,$data) = @_;
aptget_prep($data);
ensuredir "$aptget_base/source";
foreach my $old (<$aptget_base/source/*.dsc>) {
unlink $old or die "$old: $!";
}
my $showsrc = cmdoutput aptget_aptcache(), qw(showsrc), $package;
return () unless $showsrc =~ m/^package:\s*\Q$package\E\s*$/mi;
# avoids apt-get source failing with ambiguous error code
runcmd_ordryrun_local
shell_cmd 'cd "$1"/source; shift', $aptget_base,
aptget_aptget(), qw(--download-only --only-source source), $package;
my @dscs = <$aptget_base/source/*.dsc>;
fail "apt-get source did not produce a .dsc" unless @dscs;
fail "apt-get source produced several .dscs (@dscs)" unless @dscs==1;
my $pre_dsc = parsecontrol $dscs[0], $dscs[0], 1;
use URI::Escape;
my $uri = "file://". uri_escape $dscs[0];
$uri =~ s{\%2f}{/}gi;
return [ (getfield $pre_dsc, 'Version'), $uri ];
}
sub file_in_archive_aptget () { return undef; }
#---------- `dummyapicat' archive query method ----------
sub archive_query_dummycatapi { archive_query_ftpmasterapi @_; }
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 () {
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 () {
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", <[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", <[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; $_ = ;
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 () {
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 ($dsc_distro, $dsc_hint_tag, $dsc_hint_url);
sub prep_ud () {
dgit_privdir(); # ensures that $dgit_privdir_made is based on $maindir
fresh_playground 'dgit/unpack';
}
sub mktree_in_ud_here () {
playtree_setup $gitcfgs{local};
}
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 () {
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;
}
# This function determines whether a .changes file is source-only from
# the point of view of dak. Thus, it permits *_source.buildinfo
# files.
#
# It does not, however, permit any other buildinfo files. After a
# source-only upload, the buildds will try to upload files like
# foo_1.2.3_amd64.buildinfo. If the package maintainer included files
# named like this in their (otherwise) source-only upload, the uploads
# of the buildd can be rejected by dak. Fixing the resultant
# situation can require manual intervention. So we block such
# .buildinfo files when the user tells us to perform a source-only
# upload (such as when using the push-source subcommand with the -C
# option, which calls this function).
#
# Note, though, that when dgit is told to prepare a source-only
# upload, such as when subcommands like build-source and push-source
# without -C are used, dgit has a more restrictive notion of
# source-only .changes than dak: such uploads will never include
# *_source.buildinfo files. This is because there is no use for such
# files when using a tool like dgit to produce the source package, as
# dgit ensures the source is identical to git HEAD.
sub test_source_only_changes ($) {
my ($changes) = @_;
foreach my $l (split /\n/, getfield $changes, 'Files') {
$l =~ m/\S+$/ or next;
# \.tar\.[a-z0-9]+ covers orig.tar and the tarballs in native packages
unless ($& =~ m/(?:\.dsc|\.diff\.gz|\.tar\.[a-z0-9]+|_source\.buildinfo)$/) {
print "purportedly source-only changes polluted by $&\n";
return 0;
}
}
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 <{$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';
if ($author =~ m/^[^"\@]+\,/) {
# single entry Maintainer field with unquoted comma
$author = ($& =~ y/,//rd).$'; # strip the comma
}
# git wants a single author; any remaining commas in $author
# are by now preceded by @ (or "). It seems safer to punt on
# "..." for now rather than attempting to dequote or something.
$author =~ s#,.*##ms unless $author =~ m/"/;
my $date = cmdoutput qw(date), '+%s %z', qw(-d), getfield($clogp,'Date');
my $authline = "$author $date";
$authline =~ m/$git_authline_re/o or
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 () {
next unless m/\S/;
next if m/^\s+\#/;
print STDERR <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
# 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 $playground;
my @dfi = dsc_files_info();
foreach my $fi (@dfi) {
my $f = $fi->{Filename};
die "$f ?" if $f =~ m#/|^\.|\.dsc$|\.tmp$#;
my $upper_f = "$maindir/../$f";
printdebug "considering reusing $f: ";
if (link_ltarget "$upper_f,fetch", $f) {
printdebug "linked (using ...,fetch).\n";
} elsif ((printdebug "($!) "),
$! != ENOENT) {
fail "accessing ../$f,fetch: $!";
} elsif (link_ltarget $upper_f, $f) {
printdebug "linked.\n";
} elsif ((printdebug "($!) "),
$! != ENOENT) {
fail "accessing ../$f: $!";
} else {
printdebug "absent.\n";
}
my $refetched;
complete_file_from_dsc('.', $fi, \$refetched)
or next;
printdebug "considering saving $f: ";
if (link $f, $upper_f) {
printdebug "linked.\n";
} elsif ((printdebug "($!) "),
$! != EEXIST) {
fail "saving ../$f: $!";
} elsif (!$refetched) {
printdebug "no need.\n";
} elsif (link $f, "$upper_f,fetch") {
printdebug "linked (using ...,fetch).\n";
} elsif ((printdebug "($!) "),
$! != EEXIST) {
fail "saving ../$f,fetch: $!";
} else {
printdebug "cannot.\n";
}
}
# 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;
@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 ? ($?==SIGPIPE || 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 $/=""; ; };
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';
$changes =~ s/^\n//; # Changes: \n
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} ? <{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 <{Commit}
END
print C <{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(</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 < $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 => < 1 });
Record $package ($cversion) in archive suite $csuite
END
} elsif ($vcmp > 0) {
print STDERR <{Filename}"
# and will set $$refetched=1 if it did so (or tried to).
my $f = $fi->{Filename};
my $tf = "$dstdir/$f";
my $downloaded = 0;
my $got;
my $checkhash = sub {
open F, "<", "$tf" or die "$tf: $!";
$fi->{Digester}->reset();
$fi->{Digester}->addfile(*F);
F->error and die $!;
$got = $fi->{Digester}->hexdigest();
return $got eq $fi->{Hash};
};
if (stat_exists $tf) {
if ($checkhash->()) {
progress "using existing $f";
return 1;
}
if (!$refetched) {
fail "file $f has hash $got but .dsc".
" demands hash $fi->{Hash} ".
"(perhaps you should delete this file?)";
}
progress "need to fetch correct version of $f";
unlink $tf or die "$tf $!";
$$refetched = 1;
} else {
printdebug "$tf does not exist, need to fetch\n";
}
my $furl = $dscurl;
$furl =~ s{/[^/]+$}{};
$furl .= "/$f";
die "$f ?" unless $f =~ m/^\Q${package}\E_/;
die "$f ?" if $f =~ m#/#;
runcmd_ordryrun_local @curl,qw(-f -o),$tf,'--',"$furl";
return 0 if !act_local();
$checkhash->() or
fail "file $f has hash $got but .dsc".
" demands hash $fi->{Hash} ".
"(got wrong file from archive!)";
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;
}
}
#---------- git fetch ----------
sub lrfetchrefs () { return "refs/dgit-fetch/".access_basedistro(); }
sub lrfetchref () { return lrfetchrefs.'/'.server_branch($csuite); }
# We fetch some parts of lrfetchrefs/*. Ideally we delete these
# locally fetched refs because they have unhelpful names and clutter
# up gitk etc. So we track whether we have "used up" head ref (ie,
# whether we have made another local ref which refers to this object).
#
# (If we deleted them unconditionally, then we might end up
# re-fetching the same git objects each time dgit fetch was run.)
#
# So, each use of lrfetchrefs needs to be accompanied by arrangements
# in git_fetch_us to fetch the refs in question, and possibly a call
# to lrfetchref_used.
our (%lrfetchrefs_f, %lrfetchrefs_d);
# $lrfetchrefs_X{lrfetchrefs."/heads/whatever"} = $objid
sub lrfetchref_used ($) {
my ($fullrefname) = @_;
my $objid = $lrfetchrefs_f{$fullrefname};
$lrfetchrefs_d{$fullrefname} = $objid if defined $objid;
}
sub git_lrfetch_sane {
my ($url, $supplementary, @specs) = @_;
# Make a 'refs/'.lrfetchrefs.'/*' be just like on server,
# at least as regards @specs. Also leave the results in
# %lrfetchrefs_f, and arrange for lrfetchref_used to be
# able to clean these up.
#
# With $supplementary==1, @specs must not contain wildcards
# and we add to our previous fetches (non-atomically).
# This is rather miserable:
# When git fetch --prune is passed a fetchspec ending with a *,
# 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_lrfetch_sane suppl=$supplementary specs @specs\n";
my $specre = join '|', map {
my $x = $_;
$x =~ s/\W/\\$&/g;
my $wildcard = $x =~ s/\\\*$/.*/;
die if $wildcard && $supplementary;
"(?:refs/$x)";
} @specs;
printdebug "git_lrfetch_sane specre=$specre\n";
my $wanted_rref = sub {
local ($_) = @_;
return m/^(?:$specre)$/;
};
my $fetch_iteration = 0;
FETCH_ITERATION:
for (;;) {
printdebug "git_lrfetch_sane iteration $fetch_iteration\n";
if (++$fetch_iteration > 10) {
fail "too many iterations trying to get sane fetch!";
}
my @look = map { "refs/$_" } @specs;
my @lcmd = (@git, qw(ls-remote -q --refs), $url, @look);
debugcmd "|",@lcmd;
my %wantr;
open GITLS, "-|", @lcmd or die $!;
while () {
printdebug "=> ", $_;
m/^(\w+)\s+(\S+)\n/ or die "ls-remote $_ ?";
my ($objid,$rrefname) = ($1,$2);
if (!$wanted_rref->($rrefname)) {
print STDERR <($rrefname)) {
printdebug <';
my $want = $wantr{$rrefname};
next if $got eq $want;
if (!defined $objgot{$want}) {
print STDERR <('*',access_nomdistro) }
\&debiantag_new, \&debiantag_maintview)
: debiantags('*',access_nomdistro));
push @specs, server_branch($csuite);
push @specs, $rewritemap;
push @specs, qw(heads/*) if deliberately_not_fast_forward;
my $url = access_giturl();
git_lrfetch_sane $url, 0, @specs;
my %here;
my @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 updating $lref from $here{$lref} to $objid.\n";
}
});
}
#---------- dsc and archive handling ----------
sub mergeinfo_getclogp ($) {
# Ensures thit $mi->{Clogp} exists and returns it
my ($mi) = @_;
$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 parse_dsc_field_def_dsc_distro () {
$dsc_distro //= cfg qw(dgit.default.old-dsc-distro
dgit.default.distro);
}
sub parse_dsc_field ($$) {
my ($dsc, $what) = @_;
my $f;
foreach my $field (@ourdscfield) {
$f = $dsc->{$field};
last if defined $f;
}
if (!defined $f) {
progress "$what: NO git hash";
parse_dsc_field_def_dsc_distro();
} elsif (($dsc_hash, $dsc_distro, $dsc_hint_tag, $dsc_hint_url)
= $f =~ m/^(\w+)\s+($distro_re)\s+($versiontag_re)\s+(\S+)(?:\s|$)/) {
progress "$what: specified git info ($dsc_distro)";
$dsc_hint_tag = [ $dsc_hint_tag ];
} elsif ($f =~ m/^\w+\s*$/) {
$dsc_hash = $&;
parse_dsc_field_def_dsc_distro();
$dsc_hint_tag = [ debiantags +(getfield $dsc, 'Version'),
$dsc_distro ];
progress "$what: specified git hash";
} else {
fail "$what: invalid Dgit info";
}
}
sub resolve_dsc_field_commit ($$) {
my ($already_distro, $already_mapref) = @_;
return unless defined $dsc_hash;
my $mapref =
defined $already_mapref &&
($already_distro eq $dsc_distro || !$chase_dsc_distro)
? $already_mapref : undef;
my $do_fetch;
$do_fetch = sub {
my ($what, @fetch) = @_;
local $idistro = $dsc_distro;
my $lrf = lrfetchrefs;
if (!$chase_dsc_distro) {
progress
"not chasing .dsc distro $dsc_distro: not fetching $what";
return 0;
}
progress
".dsc names distro $dsc_distro: fetching $what";
my $url = access_giturl();
if (!defined $url) {
defined $dsc_hint_url or fail <("rewrite map", $rewritemap) or return;
$mapref = $lrf.'/'.$rewritemap;
}
my $rewritemapdata = git_cat_file $mapref.':map';
if (defined $rewritemapdata
&& $rewritemapdata =~ m/^$dsc_hash(?:[ \t](\w+))/m) {
progress
"server's git history rewrite map contains a relevant entry!";
$dsc_hash = $1;
if (defined $dsc_hash) {
progress "using rewritten git hash in place of .dsc value";
} else {
progress "server data says .dsc hash is to be disregarded";
}
}
}
if (!defined git_cat_file $dsc_hash) {
my @tags = map { "tags/".$_ } @$dsc_hint_tag;
my $lrf = $do_fetch->("additional commits", @tags) &&
defined git_cat_file $dsc_hash
or fail < $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 <{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 = dgit_privdir()."/mergecommit";
open MC, ">", $mcf or die "$mcf $!";
print MC <{Commit} } @mergeinputs;
@parents = reverse @parents if $compat_info->{ReverseParents};
print MC <{Commit}
END
print MC <{Message}) {
print MC $compat_info->{Message} or die $!;
} else {
print MC <{Info}
or die $!;
};
$message_add_info->($mergeinputs[0]);
print MC <($_) 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) {
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 <", "$attrs.new" or die "$attrs.new $!";
if (!open ATTRS, "<", $attrs) {
$!==ENOENT or die "$attrs: $!";
} else {
while () {
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 open_main_gitattrs () {
confess 'internal error no maindir' unless defined $maindir;
my $gai = new IO::File "$maindir_gitcommon/info/attributes"
or $!==ENOENT
or die "open $maindir_gitcommon/info/attributes: $!";
return $gai;
}
sub is_gitattrs_setup () {
my $gai = open_main_gitattrs();
return 0 unless $gai;
while (<$gai>) {
return 1 if m{^\[attr\]dgit-defuse-attrs\s};
}
$gai->error and die $!;
return 0;
}
sub setup_gitattrs (;$) {
my ($always) = @_;
return unless $always || access_cfg_bool(1, 'setup-gitattributes');
if (is_gitattrs_setup()) {
progress < $af.new" or die $!;
print GAO <) {
chomp;
print GAO $_, "\n" or die $!;
}
$gai->error and die $!;
}
close GAO or die $!;
rename "$af.new", "$af" or die "install $af: $!";
}
sub setup_new_tree () {
setup_mergechangelogs();
setup_useremail();
setup_gitattrs();
}
sub check_gitattrs ($$) {
my ($treeish, $what) = @_;
return if is_gitattrs_setup;
local $/="\0";
my @cmd = (@git, qw(ls-tree -lrz --), "${treeish}:");
debugcmd "|",@cmd;
my $gafl = new IO::File;
open $gafl, "-|", @cmd or die $!;
while (<$gafl>) {
chomp or die;
s/^\d+\s+\w+\s+\w+\s+(\d+)\t// or die;
next if $1 == 0;
next unless m{(?:^|/)\.gitattributes$};
# oh dear, found one
print STDERR <(), and returns undef
# in parent, returns canonical suite name for $tsuite
my $canonsuitefh = IO::File::new_tmpfile;
my $pid = fork // die $!;
if (!$pid) {
forkcheck_setup();
$isuite = $tsuite;
$us .= " [$isuite]";
$debugprefix .= " ";
progress "fetching $tsuite...";
canonicalise_suite();
print $canonsuitefh $csuite, "\n" or die $!;
close $canonsuitefh or die $!;
$fn->();
return undef;
}
waitpid $pid,0 == $pid or die $!;
fail "failed to obtain $tsuite: ".waitstatusmsg() if $? && $?!=256*4;
seek $canonsuitefh,0,0 or die $!;
local $csuite = <$canonsuitefh>;
die $! unless defined $csuite && chomp $csuite;
if ($? == 256*4) {
printdebug "multisuite $tsuite missing\n";
return $csuite;
}
printdebug "multisuite $tsuite ok (canon=$csuite)\n";
push @$merginputs, {
Ref => lrref,
Info => $csuite,
};
return $csuite;
}
sub fork_for_multisuite ($) {
my ($before_fetch_merge) = @_;
# if nothing unusual, just returns ''
#
# if multisuite:
# returns 0 to caller in child, to do first of the specified suites
# in child, $csuite is not yet set
#
# returns 1 to caller in parent, to finish up anything needed after
# in parent, $csuite is set to canonicalised portmanteau
my $org_isuite = $isuite;
my @suites = split /\,/, $isuite;
return '' unless @suites > 1;
printdebug "fork_for_multisuite: @suites\n";
my @mergeinputs;
my $cbasesuite = multisuite_suite_child($suites[0], \@mergeinputs,
sub { });
return 0 unless defined $cbasesuite;
fail "package $package missing in (base suite) $cbasesuite"
unless @mergeinputs;
my @csuites = ($cbasesuite);
$before_fetch_merge->();
foreach my $tsuite (@suites[1..$#suites]) {
$tsuite =~ s/^-/$cbasesuite-/;
my $csubsuite = multisuite_suite_child($tsuite, \@mergeinputs,
sub {
@end = ();
fetch();
exit 0;
});
# xxx collecte the ref here
$csubsuite =~ s/^\Q$cbasesuite\E-/-/;
push @csuites, $csubsuite;
}
foreach my $mi (@mergeinputs) {
my $ref = git_get_ref $mi->{Ref};
die "$mi->{Ref} ?" unless length $ref;
$mi->{Commit} = $ref;
}
$csuite = join ",", @csuites;
my $previous = git_get_ref lrref;
if ($previous) {
unshift @mergeinputs, {
Commit => $previous,
Info => "local combined tracking branch",
Warning =>
"archive seems to have rewound: local tracking branch is ahead!",
};
}
foreach my $ix (0..$#mergeinputs) {
$mergeinputs[$ix]{Index} = $ix;
}
@mergeinputs = sort {
-version_compare(mergeinfo_version $a,
mergeinfo_version $b) # highest version first
or
$a->{Index} <=> $b->{Index}; # earliest in spec first
} @mergeinputs;
my @needed;
NEEDED:
foreach my $mi (@mergeinputs) {
printdebug "multisuite merge check $mi->{Info}\n";
foreach my $previous (@needed) {
next unless is_fast_fwd $mi->{Commit}, $previous->{Commit};
printdebug "multisuite merge un-needed $previous->{Info}\n";
next NEEDED;
}
push @needed, $mi;
printdebug "multisuite merge this-needed\n";
$mi->{Character} = '+';
}
$needed[0]{Character} = '*';
my $output = $needed[0]{Commit};
if (@needed > 1) {
printdebug "multisuite merge nontrivial\n";
my $tree = cmdoutput qw(git rev-parse), $needed[0]{Commit}.':';
my $commit = "tree $tree\n";
my $msg = "Combine archive branches $csuite [dgit]\n\n".
"Input branches:\n";
foreach my $mi (sort { $a->{Index} <=> $b->{Index} } @mergeinputs) {
printdebug "multisuite merge include $mi->{Info}\n";
$mi->{Character} //= ' ';
$commit .= "parent $mi->{Commit}\n";
$msg .= sprintf " %s %-25s %s\n",
$mi->{Character},
(mergeinfo_version $mi),
$mi->{Info};
}
my $authline = clogp_authline mergeinfo_getclogp $needed[0];
$msg .= "\nKey\n".
" * marks the highest version branch, which choose to use\n".
" + marks each branch which was not already an ancestor\n\n".
"[dgit multi-suite $csuite]\n";
$commit .=
"author $authline\n".
"committer $authline\n\n";
$output = make_commit_text $commit.$msg;
printdebug "multisuite merge generated $output\n";
}
fetch_from_archive_record_1($output);
fetch_from_archive_record_2($output);
progress "calculated combined tracking suite $csuite";
return 1;
}
sub clone_set_head () {
open H, "> .git/HEAD" or die $!;
print H "ref: ".lref()."\n" or die $!;
close H or die $!;
}
sub clone_finish ($) {
my ($dstdir) = @_;
runcmd @git, qw(reset --hard), lrref();
runcmd qw(bash -ec), <<'END';
set -o pipefail
git ls-tree -r --name-only -z HEAD | \
xargs -0r touch -h -r . --
END
printdone "ready for work in $dstdir";
}
sub clone ($) {
# in multisuite, returns twice!
# once in parent after first suite fetched,
# and then again in child after everything is finished
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;
record_maindir();
});
if ($multi_fetched) {
printdebug "multi clone after fetch merge\n";
clone_set_head();
clone_finish($dstdir);
return;
}
printdebug "clone main body\n";
canonicalise_suite();
my $hasgit = check_for_git();
mkdir $dstdir or fail "create \`$dstdir': $!";
changedir $dstdir;
runcmd @git, qw(init -q);
record_maindir();
setup_new_tree();
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;
}
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 <) {
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->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 "$1"; shift', $maindir,
@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 <[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 ? <[0], $desc->[0]) or fail <[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 ...";
my $cd;
eval {
my @xa = ("-f$v", "-t$v");
my $vclogp = parsechangelog @xa;
my $gf = sub {
my ($fn) = @_;
[ (getfield $vclogp, $fn),
"$fn field from dpkg-parsechangelog @xa" ];
};
my $cv = $gf->('Version');
infopair_cond_equal($i_arch_v, $cv);
$cd = $gf->('Distribution');
};
if ($@) {
$@ =~ s/^dgit: //gm;
fail "$@".
"Perhaps debian/changelog does not mention $v ?";
}
fail <[0] =~ m/UNRELEASED/;
$cd->[1] is $cd->[0]
Your tree seems to based on earlier (not uploaded) $v.
END
}
}
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;
my $pmf = dgit_privdir()."/pseudomerge";
open MC, ">", $pmf or die "$pmf $!";
print MC < $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;
return $dgitview if deliberately_not_fast_forward();
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 <[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';
if (!$we_are_initiator) {
# rpush initiator can't do this because it doesn't have $isuite yet
my $tag = debiantag($cversion, access_nomdistro);
runcmd @git, qw(check-ref-format), $tag;
}
my $dscfn = dscfn($cversion);
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 < \&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';
my $declaredistro = access_nomdistro();
my $reader_giturl = do { local $access_forpush=0; access_giturl(); };
$dsc->{$ourdscfield[0]} = join " ",
$tagwants->[0]{Objid}, $declaredistro, $tagwants->[0]{Tag},
$reader_giturl;
$dsc->save("$dscfn.tmp") or die $!;
my $changes = parsecontrol($changesfile,$changesfilewhat);
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 $mktag = sub {
my ($tw) = @_;
my $tfn = $tw->{Tfn};
my $head = $tw->{Objid};
my $tag = $tw->{Tag};
open TO, '>', $tfn->('.tmp') or die $!;
print TO <{View} eq 'dgit') {
print TO <{View} eq 'maint') {
print TO <('.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
rpush_handle_protovsn_bothends() if $we_are_initiator;
select_tagformat();
my $clogpfn = dgit_privdir()."/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 $playground;
quilt_make_fake_dsc($upstreamversion);
my $cachekey;
($dgithead, $cachekey) =
quilt_check_splitbrain_cache($actualhead, $upstreamversion);
$dgithead or fail
"--quilt=$quilt_mode but no cached dgit view:
perhaps HEAD changed since dgit build[-source] ?";
$split_brain = 1;
$dgithead = splitbrain_pseudomerge($clogp,
$actualhead, $dgithead,
$archive_hash);
$maintviewhead = $actualhead;
changedir $maindir;
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 $playground;
progress "checking that $dscfn corresponds to HEAD";
runcmd qw(dpkg-source -x --),
$dscpath =~ m#^/# ? $dscpath : "$maindir/$dscpath";
my ($tree,$dir) = mktree_in_ud_from_only_subdir("source package");
check_for_vendor_patches() if madformat($dsc->{format});
changedir $maindir;
my @diffcmd = (@git, qw(diff --quiet), $tree, $dgithead);
debugcmd "+",@diffcmd;
$!=0; $?=-1;
my $r = system @diffcmd;
if ($r) {
if ($r==256) {
my $referent = $split_brain ? $dgithead : 'HEAD';
my $diffs = cmdoutput @git, qw(diff --stat), $tree, $dgithead;
my @mode_changes;
my $raw = cmdoutput @git,
qw(diff --no-renames -z -r --raw), $tree, $dgithead;
my $changed;
foreach (split /\0/, $raw) {
if (defined $changed) {
push @mode_changes, "$changed: $_\n" if $changed;
$changed = undef;
next;
} elsif (m/^:0+ 0+ /) {
$changed = '';
} elsif (m/^:(?:10*)?(\d+) (?:10*)?(\d+) /) {
$changed = "Mode change from $1 to $2"
} else {
die "$_ ?";
}
}
if (@mode_changes) {
fail <= 4;
responder_send_command("param maint-view $maintviewhead");
}
# Perhaps send buildinfo(s) for signing
my $changes_files = getfield $changes, 'Files';
my @buildinfos = ($changes_files =~ m/ .* (\S+\.buildinfo)$/mg);
foreach my $bi (@buildinfos) {
responder_send_command("param buildinfo-filename $bi");
responder_send_file('buildinfo', "$buildproductsdir/$bi");
}
if (deliberately_not_fast_forward) {
git_for_each_ref(lrfetchrefs, sub {
my ($objid,$objtype,$lrfetchrefname,$reftail) = @_;
my $rrefname= substr($lrfetchrefname, length(lrfetchrefs) + 1);
responder_send_command("previously $rrefname=$objid");
$previously{$rrefname} = $objid;
});
}
my @tagwants = push_tagwants($cversion, $dgithead, $maintviewhead,
dgit_privdir()."/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";
my @rfiles = ($dscpath, $changesfile);
push @rfiles, map { "$buildproductsdir/$_" } @buildinfos;
responder_receive_files('signed-dsc-changes',
map { "$_$dryrunsuffix" } @rfiles);
} else {
if (act_local()) {
rename "$dscpath.tmp",$dscpath or die "$dscfn $!";
} else {
progress "[new .dsc left in $dscpath.tmp]";
}
sign_changes $changesfile;
}
supplementary_message(<&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;
changedir $dir;
}
sub cmd_remote_push_build_host {
responder_send_command("dgit-remote-push-ready $protovsn");
&cmd_push;
}
sub pre_remote_push_responder { pre_remote_push_build_host(); }
sub cmd_remote_push_responder { cmd_remote_push_build_host(); }
# ... for compatibility with proto vsn.1 dgit (just so that user gets
# a good error message)
sub 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 {
return unless forkcheck_mainprocess();
i_cleanup();
}
sub i_method {
my ($base,$selector,@args) = @_;
$selector =~ s/\-/_/g;
{ no strict qw(refs); &{"${base}_${selector}"}(@args); }
}
sub pre_rpush () {
not_necessarily_a_tree();
}
sub cmd_rpush {
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;
$we_are_initiator=1;
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;
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}++;
defined $i_param{'csuite'} or badproto \*RO, "premature desire, no csuite";
$isuite = $i_param{'isuite'} // $i_param{'csuite'};
die unless $isuite =~ m/^$suite_re$/;
pushing();
rpush_handle_protovsn_bothends();
fail "rpush negotiated protocol version $protovsn".
" which does not support quilt mode $quilt_mode"
if quiltmode_splitbrain;
my @localpaths = i_method "i_want", $keyword;
printdebug "[[ $keyword @localpaths\n";
foreach my $localpath (@localpaths) {
protocol_send_file \*RI, $localpath;
}
print RI "files-end\n" or die $!;
}
our ($i_clogp, $i_version, $i_dscfn, $i_changesfn, @i_buildinfos);
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_buildinfo ($) {
my $bi = $i_param{'buildinfo-filename'};
defined $bi or badproto \*RO, "buildinfo before filename";
defined $i_changesfn or badproto \*RO, "buildinfo before changes";
$bi =~ m{^\Q$package\E_[!-.0-~]*\.buildinfo$}s
or badproto \*RO, "improper buildinfo filename";
return $&;
}
sub i_file_buildinfo {
my $bi = $i_param{'buildinfo-filename'};
my $bd = parsecontrol "$i_tmp/$bi", $bi;
my $ch = parsecontrol "$i_tmp/$i_changesfn", 'changes';
if (!forceing [qw(buildinfo-changes-mismatch)]) {
files_compare_inputs($bd, $ch);
(getfield $bd, $_) eq (getfield $ch, $_) or
fail "buildinfo mismatch $_"
foreach qw(Source Version);
!defined $bd->{$_} or
fail "buildinfo contains $_"
foreach qw(Changes Changed-by Distribution);
}
push @i_buildinfos, $bi;
delete $i_param{'buildinfo-filename'};
}
sub i_localname_changes {
defined $i_dscfn or badproto \*RO, "dsc (before parsed-changelog)";
$i_changesfn = $i_dscfn;
$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'} // '';
$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, @i_buildinfos);
}
#---------- 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'; # we are in playtree
my $descfn = ".git/dgit/quilt-description.tmp";
open O, '>', $descfn or die "$descfn: $!";
$msg =~ s/\n+/\n\n/;
print O <{$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 --no-renames));
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 or symlink\n"
unless $newmode =~ m/^(?:10|12)\d{4}$/ ||
$oldmode =~ m/^(?:10|12)\d{4}$/;
if ($oldmode =~ m/[^0]/ &&
$newmode =~ m/[^0]/) {
# both old and new files exist
die "mode or type changed\n" if $oldmode ne $newmode;
die "modified symlink\n" unless $newmode =~ m/^10/;
} elsif ($oldmode =~ m/[^0]/) {
# deletion
die "deletion of symlink\n"
unless $oldmode =~ m/^10/;
} else {
# creation
die "creation with non-default mode\n"
unless $newmode =~ m/^100644$/ or
$newmode =~ m/^120000$/;
}
};
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 <{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 <{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 <>$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 <>'
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 < 1000000000 +0000
committer Dgit 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 "$playground/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 "is series file\n" if m{$series_filename_re}o;
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);
$patchname .= ".patch";
}
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 <{'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 $maindir;
runcmd_ordryrun_local
@git, qw(pull --ff-only -q), "$playground/work", qw(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 (<$maindir/../*>) { #/){
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 <' or die $!;
print $fakedsc <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 "$maindir/$maybe";
push @files, $maybe;
}
my $debtar= srcfn $fakeversion,'.debian.tar.gz';
runcmd qw(env GZIP=-1n tar -zcf), "./$debtar", qw(-C), $maindir, @files;
$dscaddfile->($debtar);
close $fakedsc or die $!;
}
sub quilt_check_splitbrain_cache ($$) {
my ($headref, $upstreamversion) = @_;
# Called only if we are in (potentially) split brain mode.
# Called in playground.
# 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 $maindir or die $!;
if (!stat "$maindir_gitcommon/logs/refs/$splitbraincache") {
$! == ENOENT or die $!;
printdebug ">(no reflog)\n";
exit 0;
}
exec @cmd; die $!;
}
while () {
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';
runcmd @git, qw(checkout -f), $headref, qw(-- debian);
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 < 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)], <{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 () { print O or die $!; } I1->error and die $!;
my $copying = 0;
while () {
$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 <[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";
}
}
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 < 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=".(shellquote @dbp));
if ($gbp_make_orig) {
my $priv = dgit_privdir();
my $ok = "$priv/origs-gen-ok";
unlink $ok or $!==&ENOENT or die $!;
my @origs_cmd = @cmd;
push @origs_cmd, qw(--git-cleaner=true);
push @origs_cmd, "--git-prebuild=".
"touch ".(shellquote $ok)." ".(shellquote "$priv/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_for_push {
build_source();
maybe_unapply_patches_again();
$changesfile = $sourcechanges;
}
sub build_source {
build_prep_early();
build_prep();
$sourcechanges = changespat $version,'source';
if (act_local()) {
unlink "../$sourcechanges" or $!==ENOENT
or fail "remove $sourcechanges: $!";
}
$dscfn = dscfn($version);
my @cmd = (@dpkgsource, qw(-b --));
if ($split_brain) {
changedir $playground;
runcmd_ordryrun_local @cmd, "work";
my @udfiles = <${package}_*>;
changedir $maindir;
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 "$playground/$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 {
build_prep_early();
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(<; };
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();
$package = getfield $dsc, 'Source';
parse_dsc_field($dsc, "Dgit metadata in .dsc")
unless forceing [qw(import-dsc-with-dgit-field)];
parse_dsc_field_def_dsc_distro();
$isuite = 'DGIT-IMPORT-DSC';
$idistro //= $dsc_distro;
notpushing();
if (defined $dsc_hash) {
progress "dgit: import-dsc of .dsc with Dgit field, using git hash";
resolve_dsc_field_commit undef, undef;
}
if (defined $dsc_hash) {
my @cmd = (qw(sh -ec),
"echo $dsc_hash | git cat-file --batch-check");
my $objgot = cmdoutput @cmd;
if ($objgot =~ m#^\w+ missing\b#) {
fail < 0) {
progress "Not fast forward, forced update.";
} else {
fail "Not fast forward to $dsc_hash";
}
}
import_dsc_result $dstbranch, $dsc_hash,
"dgit import-dsc (Dgit): $info",
"updated git ref $dstbranch";
return 0;
}
fail <{Filename};
my $here = "../$f";
if (lstat $here) {
next if stat $here;
fail "lstat $here works but stat gives $! !";
}
fail "stat $here: $!" unless $! == ENOENT;
my $there = $dscfn;
if ($dscfn =~ m#^(?:\./+)?\.\./+#) {
$there = $';
} elsif ($dscfn =~ m#^/#) {
$there = $dscfn;
} else {
fail "cannot import $dscfn which seems to be inside working tree!";
}
$there =~ s#/+[^/]+$## or
fail "import $dscfn requires ../$f, but it does not exist";
$there .= "/$f";
my $test = $there =~ m{^/} ? $there : "../$there";
stat $test or fail "import $dscfn requires $test, but: $!";
symlink $there, $here or fail "symlink $there to $here: $!";
progress "made symlink $here -> $there";
# 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 <",@cmd;
exec @cmd or fail "exec curl: $!\n";
}
sub repos_server_url () {
$package = '_dgit-repos-server';
local $access_forpush = 1;
local $isuite = 'DGIT-REPOS-SERVER';
my $url = access_giturl();
}
sub pre_clone_dgit_repos_server () {
not_necessarily_a_tree();
}
sub cmd_clone_dgit_repos_server {
badusage "need destination argument" unless @ARGV==1;
my ($destdir) = @ARGV;
my $url = repos_server_url();
my @cmd = (@git, qw(clone), $url, $destdir);
debugcmd ">",@cmd;
exec @cmd or fail "exec git clone: $!\n";
}
sub pre_print_dgit_repos_server_source_url () {
not_necessarily_a_tree();
}
sub cmd_print_dgit_repos_server_source_url {
badusage "no arguments allowed to dgit print-dgit-repos-server-source-url"
if @ARGV;
my $url = repos_server_url();
print $url, "\n" or die $!;
}
sub pre_print_dpkg_source_ignores {
not_necessarily_a_tree();
}
sub cmd_print_dpkg_source_ignores {
badusage "no arguments allowed to dgit print-dpkg-source-ignores"
if @ARGV;
print "@dpkg_source_ignores\n" or die $!;
}
sub cmd_setup_mergechangelogs {
badusage "no arguments allowed to dgit setup-mergechangelogs" if @ARGV;
local $isuite = 'DGIT-SETUP-TREE';
setup_mergechangelogs(1);
}
sub cmd_setup_useremail {
badusage "no arguments allowed to dgit setup-useremail" if @ARGV;
local $isuite = 'DGIT-SETUP-TREE';
setup_useremail(1);
}
sub cmd_setup_gitattributes {
badusage "no arguments allowed to dgit setup-useremail" if @ARGV;
local $isuite = 'DGIT-SETUP-TREE';
setup_gitattrs(1);
}
sub cmd_setup_new_tree {
badusage "no arguments allowed to dgit setup-tree" if @ARGV;
local $isuite = 'DGIT-SETUP-TREE';
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 (%funcopts_long);
our @rvalopts;
our (@modeopt_cfgs);
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 defoptmodes ($@) {
my ($varref, $cfgkey, $default, %optmap) = @_;
my %permit;
while (my ($opt,$val) = each %optmap) {
$funcopts_long{$opt} = sub { $$varref = $val; };
$permit{$val} = $val;
}
push @modeopt_cfgs, {
Var => $varref,
Key => $cfgkey,
Default => $default,
Vals => \%permit
};
}
defoptmodes \$dodep14tag, qw( dep14tag want
--dep14tag want
--no-dep14tag no
--always-dep14tag always );
sub parseopts () {
my $om;
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/^--no-chase-dsc-distro$/s) {
push @ropts, $_;
$chase_dsc_distro = 0;
} elsif (m/^--overwrite$/s) {
push @ropts, $_;
$overwrite_version = '';
} elsif (m/^--overwrite=(.+)$/s) {
push @ropts, $_;
$overwrite_version = $1;
} 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/^--config-lookup-explode=(.+)$/s) {
# undocumented, for testing
push @ropts, $_;
$gitcfgs{cmdline}{$1} = 'CONFIG-LOOKUP-EXPLODE';
# ^ it's supposed to be an array ref
} elsif (m/^(--[-0-9a-z]+)(=|$)/ && ($oi = $valopts_long{$1})) {
$val = $2 ? $' : undef; #';
$valopt->($oi->{Long});
} elsif ($funcopts_long{$_}) {
push @ropts, $_;
$funcopts_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 <[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 (!defined $rmchanges) {
local $access_forpush;
$rmchanges = access_cfg_bool(0, 'rm-old-changes');
}
if (!defined $quilt_mode) {
local $access_forpush;
$quilt_mode = cfg('dgit.force.quilt-mode', 'RETURN-UNDEF')
// access_cfg('quilt-mode', 'RETURN-UNDEF')
// 'linear';
$quilt_mode =~ m/^($quilt_modes_re)$/
or badcfg "unknown quilt-mode \`$quilt_mode'";
$quilt_mode = $1;
}
foreach my $moc (@modeopt_cfgs) {
local $access_forpush;
my $vr = $moc->{Var};
next if defined $$vr;
$$vr = access_cfg($moc->{Key}, 'RETURN-UNDEF') // $moc->{Default};
my $v = $moc->{Vals}{$$vr};
badcfg "unknown $moc->{Key} setting \`$$vr'" unless defined $v;
$$vr = $v;
}
$need_split_build_invocation ||= quiltmode_splitbrain();
if (!defined $cleanmode) {
local $access_forpush;
$cleanmode = access_cfg('clean-mode', 'RETURN-UNDEF');
$cleanmode //= 'dpkg-source';
badcfg "unknown clean-mode \`$cleanmode'" unless
$cleanmode =~ m/^($cleanmode_re)$(?!\n)/s;
}
}
if ($ENV{$fakeeditorenv}) {
git_slurp_config();
quilt_fixup_editor();
}
parseopts();
check_env_sanity();
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;
}
$cmd = $subcommand = shift @ARGV;
$cmd =~ y/-/_/;
my $pre_fn = ${*::}{"pre_$cmd"};
$pre_fn->() if $pre_fn;
record_maindir if $invoked_in_git_tree;
git_slurp_config();
my $fn = ${*::}{"cmd_$cmd"};
$fn or badusage "unknown operation $cmd";
$fn->();
dgit/dgit-badcommit-fixup 0000775 0000000 0000000 00000016171 13136200065 012642 0 ustar #!/usr/bin/perl -w
#
# Script to help with fallout from #849041.
#
# usage:
# dgit-badcommit-fixup --check
# dgit-badcommit-fixup --test
# dgit-badcommit-fixup --real
# Update procedure, from server operator's point of view:
#
# 1. Test in an offline tree that this DTRT
#
# 2. Announce a transition time. Tell everyone that between
# the transition time and their next upload, they must
# run this script.
#
# 3. At the transition time, run this script in every repo.
#
# 4. Run the mirror script to push changes, if necessary.
use strict;
use POSIX;
use IPC::Open2;
use Data::Dumper;
our $our_version = 'UNRELEASED'; ###substituted###
my $real;
foreach my $a (@ARGV) {
if ($a eq '--test') {
$real = 0;
} elsif ($a eq '--real') {
$real = 1;
} elsif ($a eq '--check') {
$real = -1;
} else {
die "$a ?";
}
}
die unless defined $real;
my $gcfpid = open2 \*GCFO, \*GCFI, 'git cat-file --batch' or die $!;
our %count;
no warnings qw(recursion);
sub runcmd {
system @_ and die "@_ $! $?";
}
$!=0; $?=0;
my $bare = `git rev-parse --is-bare-repository`;
die "$? $!" if $?;
chomp $bare or die;
our @configs;
foreach my $k (qw(core.sharedRepository)) {
$?=0; $!=0; my $v = `set -x; git config --local $k`;
if (defined $v && $?==0 && chomp $v) {
push @configs, [ $k, $v ];
} elsif (defined $v && $?==256 && $v eq '') {
} else {
die "git-config --local $k => $v $? $! ?";
}
}
sub getobj ($$) {
my ($obj, $type) = @_;
print GCFI $obj, "\n" or die $!;
my $x = ;
my ($gtype, $gsize) = $x =~ m/^\w+ (\w+) (\d+)\n/ or die "$obj ?";
$gtype eq $type or die "$obj $gtype != $type ?";
my $gdata;
(read GCFO, $gdata, $gsize) == $gsize or die "$obj $!";
$x = ;
$x eq "\n" or die "$obj ($_) $!";
$count{inspected}++;
return $gdata;
}
sub hashobj ($$) {
my ($data,$type) = @_;
my $gwopid = open2 \*GWO, \*GWI,
"git hash-object -w -t $type --stdin"
or die $!;
print GWI $data or die $!;
close GWI or die $!;
$_ = ;
close GWO or die $!;
waitpid $gwopid,0 == $gwopid or die $!;
die $? if $?;
m/^(\w+)\n/ or die "$_ ?";
$count{"rewritten $type"}++;
return $1;
}
our %memo;
sub rewrite_commit ($);
sub rewrite_commit ($) {
my ($obj) = @_;
my $m = \ $memo{$obj};
return $$m if defined $$m;
my $olddata = getobj $obj, 'commit';
$olddata =~ m/(?<=\n)(?=\n)/ or die "$obj ?";
my $msg = $';
local $_ = $`;
s{^(parent )(\w+)$}{ $1 . rewrite_commit($2) }gme;
$count{'fix overwrite'} += s{^commiter }{committer }gm;
if (!m{^author }m && !m{^committer }m) {
m{^parent (\w+)}m or die "$obj ?";
my $parent = getobj $1, 'commit';
$parent =~ m/^(?:.+\n)+(author .*\ncommitter .*\n)/;
m/\n$/ or die "$obj ?";
$_ .= $1;
$count{'fix import'}++;
}
my $newdata = $_.$msg;
my $newobj;
if ($newdata eq $olddata) {
$newobj = $obj;
$count{unchanged}++;
#print STDERR "UNCHANGED $obj\n";
} else {
$newobj = hashobj $newdata, 'commit';
#print STDERR "REWRITTEN $obj $newobj\n";
}
$$m= $newobj;
return $newobj;
}
our @updates;
sub filter_updates () {
@updates = grep { $_->[1] ne $_->[2] } @updates;
}
sub rewrite_tag ($) {
my ($obj) = @_;
$_ = getobj $obj, 'tag';
m/^type (\w+)\n/m or die "$obj ?";
if ($1 ne 'commit') {
$count{"oddtags $1"}++;
return $obj;
}
m/^object (\w+)\n/m or die "$obj ?";
my $oldref = $1;
my $newref = rewrite_commit $oldref;
if ($oldref eq $newref) {
return $obj;
}
s/^(object )\w+$/ $1.$newref /me or die "$obj ($_) ?";
s/^-----BEGIN PGP SIGNATURE-----\n.*^-----END PGP SIGNATURE-----\n$//sm;
return hashobj $_, 'tag';
}
sub edit_rewrite_map ($) {
my ($old) = @_;
filter_updates();
return $old unless @updates;
my $td = 'dgit-broken-fixup.tmp';
runcmd qw(rm -rf), $td;
mkdir $td, 0700 or die "$td $!";
chdir $td or die $!;
runcmd qw(git init -q);
runcmd qw(git config gc.auto 0);
runcmd qw(rm -rf .git/objects);
symlink "../../objects", ".git/objects" or die $!;
foreach my $c (@configs) {
runcmd qw(git config), $c->[0], $c->[1];
}
my %map;
if ($old) {
runcmd qw(git checkout -q), $old;
open M, "map" or die $!;
while () {
m/^(\w+)(?:\s+(\w+))?$/ or die;
$map{$1} = $2;
$count{rewrite_map_previous}++;
}
M->error and die $!;
close M or die $!;
}
foreach my $oldc (keys %memo) {
my $newc = $memo{$oldc};
next if $oldc eq $newc;
$map{$oldc} = $newc;
}
foreach my $up (@updates) { # catches tags
$map{ $up->[1] } = $up->[2];
}
open M, ">", "map" or die $!;
printf M "%s%s\n",
$_, (defined $map{$_} ? " $map{$_}" : "")
or die $!
foreach keys %map;
close M or die $!;
if (!$old) {
runcmd qw(git add map);
}
runcmd qw(git commit -q), qw(-m), </dev/null git symbolic-ref -q "$1"', qw(x),
$refname;
if ($?==0) {
$count{symrefs_ignored}++;
next;
}
die "$? $!" unless $?==256;
my $rewrite;
if ($type eq 'commit') {
$rewrite = rewrite_commit($obj);
} elsif ($type eq 'tag') {
$rewrite = rewrite_tag($obj);
} else {
warn "ref $refname refers to $type\n";
next;
}
push @updates, [ $refname, $obj, $rewrite ];
}
if ($bare eq 'true') {
my $new_rewrite_map = edit_rewrite_map($org_rewrite_map);
push @updates, [ 'refs/dgit-rewrite/map',
($org_rewrite_map // '0'x40),
($new_rewrite_map // '0'x40),
1 ];
}
filter_updates();
if (!@updates) {
print Dumper(\%count), "all is well - nothing to do\n";
exit 0;
}
#print Dumper(\@updates);
open U, "|git update-ref -m 'dgit bad commit fixup' --stdin" or die $!
if $real >= 0;
for my $up (@updates) {
my ($ref, $old, $new, $nobackup) = @$up;
my $otherref = $ref;
$otherref =~ s{^refs/}{};
if ($real > 0) {
print U <= 0) {
$?=0; $!=0;
close U or die "$? $!";
die $? if $?;
}
print Dumper(\%count);
if ($real >= 0) {
print "old values saved in refs/dgit-badcommit/\n" or die $!;
} elsif ($real == 0) {
print "testing output saved in refs/dgit-badfixuptest/\n" or die $!;
} else {
print STDERR "found work to do, exiting status 2\n";
exit 2;
}
dgit/dgit-maint-gbp.7.pod 0000664 0000000 0000000 00000006475 13230431257 012365 0 ustar =head1 NAME
dgit - tutorial for package maintainers already using git-buildpackage(1)
=head1 INTRODUCTION
This document explains how B 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 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 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 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 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.
If this is first ever dgit push of the package, consider passing
I<--deliberately-not-fast-forward> instead of I<--overwrite>. This
avoids introducing a new origin commit into the dgit view of your git
history. (This origin commit would represent the most recent non-dgit
upload of the package, but this should already be represented in your
git history.)
=head1 INCORPORATING NMUS
B 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 .
dgit/dgit-maint-merge.7.pod 0000664 0000000 0000000 00000031663 13232215531 012706 0 ustar =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. 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,
in straightforward cases,
the information such a series would contain is readily
available from B.
=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
This workflow is less suitable for some packages.
When the Debian delta contains multiple pieces which interact,
or which you aren't going to be able to upstream soon,
it might be preferable to
maintain the delta as a rebasing patch series.
For such a workflow see for example
dgit-maint-gbp(7).
=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 detaches 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 in addition to B, 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 directory. If you need
to patch the upstream source, just make commits that change files
outside of the I directory. It is best to separate commits
that touch I 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:
=over 4
% git deborig
=back
See git-deborig(1) if this fails.
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, 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-deborig(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:
=over 4
[DEFAULT]
upstream-branch = upstream
debian-branch = master
upstream-tag = %(version)s
sign-tags = True
pristine-tar = False
pristine-tar-commit = False
[import-orig]
merge-mode = merge
=back
gbp-import-orig(1) requires a pre-existing upstream branch:
=over 4
% git add debian/gbp.conf && git commit -m "create gbp.conf"
% git checkout --orphan upstream
% git rm -rf .
% git commit --allow-empty -m "initial, empty branch for upstream source"
% git checkout -f master
=back
Then we can import the upstream version:
=over 4
% gbp import-orig --merge-mode=replace ../foo_1.2.2.orig.tar.xz
=back
Our upstream branch cannot be pushed to B, but since we
will need it whenever we import a new upstream version, we must push
it somewhere. The usual choice is B:
=over 4
% git remote add -f origin git.debian.org:/git/collab-maint/foo.git
% git push --follow-tags -u origin master upstream
=back
You are now ready to proceed as above, making commits to both the
upstream source and the I directory.
=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, if you don't already have the git history locally, clone it,
and obtain the corresponding orig.tar from the archive:
=over 4
% git clone git.debian.org:collab-maint/foo
% cd foo
% origtargz
=back
Now 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
=for dgit-test dpkg-source-ignores begin
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
and then unpacked the resultant source package.
=for dgit-test dpkg-source-ignores end
To achieve this, you might need to delete
I. One way to have dgit check your
progress is to run B.
The first dgit push will require I<--overwrite>. If this is the first
ever dgit push of the package, consider passing
I<--deliberately-not-fast-forward> instead of I<--overwrite>. This
avoids introducing a new origin commit into your git history. (This
origin commit would represent the most recent non-dgit upload of the
package, but this should already be represented in your git history.)
=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 debian/source/patch-header
It is a good idea to explain how a user can obtain a breakdown of the
changes to the upstream source:
=over 4
The Debian packaging of foo is maintained in git,
using the merging workflow described in dgit-maint-merge(7).
There isn't a patch queue that can be represented as a quilt series.
A detailed breakdown of the changes is available from their
canonical representation -
git commits in the packaging repository.
For example, to see the changes made by the Debian maintainer in the
first upload of upstream version 1.2.3, you could use:
=over 4
% git clone https://git.dgit.debian.org/foo
% cd foo
% git log --oneline 1.2.3..debian/1.2.3-1 -- . ':!debian'
=back
(If you have dgit, use `dgit clone foo`,
rather than plain `git clone`.)
A single combined diff, containing all the changes, follows.
=back
If you are using the version 1.0 source package format, this text
should be added to README.source instead. The version 1.0 source
package format ignores debian/source/patch-header.
If you're using the version 3.0 (quilt) source package format, you
could add this text to README.source instead of
debian/source/patch-header, but this might distract from more
important information present in README.source.
=head1 BUILDING AND UPLOADING
Use B, B, B, and B 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 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 Obtaining the release
=head3 When upstream tags releases in git
=over 4
% git remote update
=back
=head3 When upstream releases only tarballs
You will need the I from "When upstream releases only
tarballs", above. You will also need your upstream branch. Above, we
pushed this to B. You will need to clone or fetch
from there, instead of relying on B/B alone.
Then, either
=over 4
% gbp import-orig --no-merge ../foo_1.2.3.orig.tar.xz
=back
or if you have a working watch file
=over 4
% gbp import-orig --no-merge --uscan
=back
=head2 Reviewing & merging the release
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 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 merge 1.2.3
% dch -v1.2.3-1 New upstream release.
% git add debian/changelog && git commit -m changelog
=back
If you obtained a tarball from upstream, you are ready to try a build.
If you merged a git tag from upstream, you will first need to generate
a tarball:
=over 4
% git deborig
=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.
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 field to
I, and a B setting in
I. 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 . It contains contributions from other dgit contributors too - see the dgit copyright file.
dgit/dgit-maint-native.7.pod 0000664 0000000 0000000 00000004355 13230431257 013076 0 ustar =head1 NAME
dgit - tutorial for package maintainers of Debian-native packages
=head1 INTRODUCTION
This document describes elements of a workflow for using B 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.
Alternatively,
if this was the first ever dgit push of the package,
you can avoid this merge commit by
passing C<--deliberately-not-fast-forward>.
instead of C<--overwrite>.
This avoids introducing a new origin commit into
your git history.
=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)
dgit/dgit-nmu-simple.7.pod 0000664 0000000 0000000 00000010136 13133470757 012574 0 ustar =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 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 commits with good commit messages.
The commit messages will be published in various ways,
including perhaps being used as the cover messages for
generated 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
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.
There is a pseudo-remote called C which also contains a branch
like C, so you do things like
C
to see what changes you have made.
=head1 KEEPING YOUR WORKING TREE TIDY
Don't forget to C 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.
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)
dgit/dgit-sponsorship.7.pod 0000664 0000000 0000000 00000017345 13230431257 013074 0 ustar =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 yourself.
For a straightforward NMU, consult L.
If you are the (prospective) maintainer,
you can adopt any suitable (dgit-compatible)
git workflow.
The L 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)
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 or C,
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.
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 or C 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.
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 will get you an up-to-date
C
showing what's in the archive already.
C
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.
=head2 Doing the upload
When you have completed your source review,
and use
C
or similar, to to the build, and then
C
to do the upload.
Check whether the sponsee made a debian/I tag.
If they did,
ensure you have their tag in the repository you are pushing from,
or pass C<--no-dep14tag>.
This avoids identically named, non-identical tags,
which can be confusing.
(It is possible to upload from
the quilt-cache dgit view.
If you want to do this,
B pass the C<--quilt> or C<--gbp> or C<--dpm> options again,
and B pass C<--no-dep14tag>,
since the debian/I tag
should go on the sponsee's branch.)
If this was the first upload done with dgit,
you may need to pass
C<--overwrite>
to dgit.
Alternatively,
if this was the first ever dgit push of the package,
you can pass C<--deliberately-not-fast-forward>
instead of C<--overwrite>.
This avoids introducing a new origin commit
into the dgit view of
the sponsee's git history
which is unnecessary and could be confusing.
=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)
dgit/dgit-user.7.pod 0000664 0000000 0000000 00000030344 13230431257 011455 0 ustar =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, L,
or L and L.
=head1 SUMMARY
(These runes will be discussed later.)
=over 4
% dgit clone glibc jessie,-security
% cd glibc
% curl '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
% mk-build-deps --root-cmd=sudo --install
% 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 (Debian 8) is Debian C; and
the current version of Ubuntu is C (Yakkety Yak, 16.10).
You can specify either
the codename C or the alias C.
If you don't say, you get C,
which is Debian C - 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 becomes C.
(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 (yes, with a comma in the branch name).
For each release (like C)
there is a tracking branch for the contents of the archive, called
C