X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=blobdiff_plain;f=dgit;h=8d3addbb3be91082514a3e8a5f33765f5e732020;hb=8359fd512497cc33029f6f3c696e331a844229ab;hp=af710657502fc822ac6a04e64e68329941890840;hpb=2479a589cce5c9c12044c89b3d530912efedb78e;p=dgit.git
diff --git a/dgit b/dgit
index af710657..8d3addbb 100755
--- a/dgit
+++ b/dgit
@@ -2,7 +2,7 @@
# dgit
# Integration between git and Debian-style archives
#
-# Copyright (C)2013 Ian Jackson
+# Copyright (C)2013-2015 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
@@ -18,6 +18,7 @@
# along with this program. If not, see .
use strict;
+$SIG{__WARN__} = sub { die $_[0]; };
use IO::Handle;
use Data::Dumper;
@@ -31,7 +32,8 @@ use POSIX;
use IPC::Open2;
use Digest::SHA;
use Digest::MD5;
-use Config;
+
+use Debian::Dgit;
our $our_version = 'UNRELEASED'; ###substituted###
@@ -48,11 +50,14 @@ our $changesfile;
our $buildproductsdir = '..';
our $new_package = 0;
our $ignoredirty = 0;
-our $noquilt = 0;
our $rmonerror = 1;
+our @deliberatelies;
+our %previously;
our $existing_package = 'dpkg';
our $cleanmode = 'dpkg-source';
our $changes_since_version;
+our $quilt_mode;
+our $quilt_modes_re = 'linear|smash|auto|nofix|nocheck';
our $we_are_responder;
our $initiator_tempdir;
@@ -93,27 +98,20 @@ our %opts_opt_cmdonly = ('gpg' => 1);
our $keyid;
-our $debug = 0;
-open DEBUG, ">/dev/null" or die $!;
-
autoflush STDOUT 1;
our $remotename = 'dgit';
our @ourdscfield = qw(Dgit Vcs-Dgit-Master);
-our $branchprefix = 'dgit';
our $csuite;
our $instead_distro;
sub lbranch () { return "$branchprefix/$csuite"; }
my $lbranch_re = '^refs/heads/'.$branchprefix.'/([^/.]+)$';
sub lref () { return "refs/heads/".lbranch(); }
-sub lrref () { return "refs/remotes/$remotename/$branchprefix/$csuite"; }
-sub rrref () { return "refs/$branchprefix/$csuite"; }
-sub debiantag ($) {
- my ($v) = @_;
- $v =~ y/~:/_%/;
- return "debian/$v";
-}
+sub lrref () { return "refs/remotes/$remotename/".server_branch($csuite); }
+sub rrref () { return server_ref($csuite); }
+
+sub lrfetchrefs () { return "refs/dgit-fetch/$isuite"; }
sub stripepoch ($) {
my ($vsn) = @_;
@@ -132,7 +130,7 @@ sub dscfn ($) {
}
our $us = 'dgit';
-our $debugprefix = '';
+initdebug('');
our @end;
END {
@@ -143,29 +141,6 @@ END {
}
};
-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 printdebug { print DEBUG $debugprefix, @_ or die $!; }
-
-sub fail {
- die $us.($we_are_responder ? " (build host)" : "").": @_\n";
-}
-
sub badcfg { print STDERR "$us: invalid configuration: @_\n"; exit 12; }
sub no_such_package () {
@@ -184,11 +159,15 @@ sub changedir ($) {
chdir $newdir or die "chdir: $newdir: $!";
}
-sub stat_exists ($) {
- my ($f) = @_;
- return 1 if stat $f;
- return 0 if $!==&ENOENT;
- die "stat $f: $!";
+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-$_");
+ }
}
#---------- remote protocol support, common ----------
@@ -379,42 +358,8 @@ sub url_get {
our ($dscdata,$dscurl,$dsc,$dsc_checked,$skew_warning_vsn);
-sub shellquote {
- my @out;
- local $_;
- foreach my $a (@_) {
- $_ = $a;
- if (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 failedcmd {
- { local ($!); printcmd \*STDERR, "$us: failed command:", @_ or die $!; };
- if ($!) {
- fail "failed to fork/exec: $!";
- } elsif ($?) {
- fail "subprocess ".waitstatusmsg();
- } else {
- fail "subprocess produced invalid output";
- }
-}
-
sub runcmd {
- printcmd(\*DEBUG,$debugprefix."+",@_) if $debug>0;
+ debugcmd "+",@_;
$!=0; $?=0;
failedcmd @_ if system @_;
}
@@ -430,27 +375,6 @@ sub printdone {
}
}
-sub cmdoutput_errok {
- die Dumper(\@_)." ?" if grep { !defined } @_;
- printcmd(\*DEBUG,$debugprefix."|",@_) if $debug>0;
- open P, "-|", @_ or die $!;
- my $d;
- $!=0; $?=0;
- { local $/ = undef; $d =
; }
- die $! if P->error;
- if (!close P) { printdebug "=>!$?\n" if $debug>0; return undef; }
- chomp $d;
- $d =~ m/^.*/;
- printdebug "=> \`$&'",(length $' ? '...' : ''),"\n" if $debug>0; #';
- return $d;
-}
-
-sub cmdoutput {
- my $d = cmdoutput_errok @_;
- defined $d or failedcmd @_;
- return $d;
-}
-
sub dryrun_report {
printcmd(\*STDERR,$debugprefix."#",@_);
}
@@ -519,13 +443,28 @@ our %defcfg = ('dgit.default.distro' => 'debian',
'dgit.default.ssh' => 'ssh',
'dgit.default.archive-query' => 'madison:',
'dgit.default.sshpsql-dbname' => 'service=projectb',
- 'dgit-distro.debian.archive-query' => 'sshpsql:',
- 'dgit-distro.debian.git-host' => 'git.debian.org',
+ 'dgit-distro.debian.archive-query' => 'ftpmasterapi:',
+ 'dgit-distro.debian.git-host' => 'dgit-git.debian.net',
+ 'dgit-distro.debian.git-user-force' => 'dgit',
'dgit-distro.debian.git-proto' => 'git+ssh://',
- 'dgit-distro.debian.git-path' => '/git/dgit-repos/repos',
+ 'dgit-distro.debian.git-path' => '/dgit/debian/repos',
'dgit-distro.debian.git-check' => 'ssh-cmd',
- 'dgit-distro.debian.git-create' => 'ssh-cmd',
- 'dgit-distro.debian.sshpsql-host' => 'mirror.ftp-master.debian.org',
+ '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.diverts.alioth' => '/alioth',
+ 'dgit-distro.debian/alioth.git-host' => 'git.debian.org',
+ 'dgit-distro.debian/alioth.git-user-force' => '',
+ 'dgit-distro.debian/alioth.git-proto' => 'git+ssh://',
+ 'dgit-distro.debian/alioth.git-path' => '/git/dgit-repos/repos',
+ 'dgit-distro.debian/alioth.git-create' => 'ssh-cmd',
'dgit-distro.debian.upload-host' => 'ftp-master', # for dput
'dgit-distro.debian.mirror' => 'http://ftp.debian.org/debian/',
'dgit-distro.debian.backports-quirk' => '(squeeze)-backports*',
@@ -539,7 +478,8 @@ our %defcfg = ('dgit.default.distro' => 'debian',
'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' => "dummycat:$td/aq",
+ 'dgit-distro.test-dummy.archive-query' => "ftpmasterapi:",
+ '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',
);
@@ -550,7 +490,7 @@ sub cfg {
my @cmd = (@git, qw(config --), $c);
my $v;
{
- local ($debug) = $debug-1;
+ local ($debuglevel) = $debuglevel-2;
$v = cmdoutput_errok @cmd;
};
if ($?==0) {
@@ -664,7 +604,9 @@ sub access_runeinfo ($) {
sub access_someuserhost ($) {
my ($some) = @_;
- my $user = access_cfg("$some-user",'username');
+ 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;
}
@@ -740,23 +682,6 @@ sub parsechangelog {
return $c;
}
-sub git_get_ref ($) {
- my ($refname) = @_;
- my $got = cmdoutput_errok @git, qw(show-ref --), $refname;
- if (!defined $got) {
- $?==256 or fail "git show-ref failed (status $?)";
- printdebug "ref $refname= [show-ref exited 1]\n";
- return '';
- }
- if ($got =~ m/^(\w+) \Q$refname\E$/m) {
- printdebug "ref $refname=$1\n";
- return $1;
- } else {
- printdebug "ref $refname= [no match]\n";
- return '';
- }
-}
-
sub must_getcwd () {
my $d = getcwd();
defined $d or fail "getcwd failed: $!";
@@ -780,6 +705,101 @@ sub pool_dsc_subpath ($$) {
return "/pool/$component/$prefix/$package/".dscfn($vsn);
}
+#---------- `ftpmasterapi' archive query method (nascent) ----------
+
+sub archive_api_query_cmd ($) {
+ my ($subpath) = @_;
+ my @cmd = qw(curl -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) = @_;
+ badcfg "ftpmasterapi archive query method takes no data part"
+ if length $data;
+ my @cmd = archive_api_query_cmd($subpath);
+ my $json = cmdoutput @cmd;
+ 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 @rows;
+}
+
+#---------- `madison' archive query method ----------
+
sub archive_query_madison {
return map { [ @$_[0..1] ] } madison_get_parse(@_);
}
@@ -826,6 +846,8 @@ sub canonicalise_suite_madison {
return $r[0][2];
}
+#---------- `sshpsql' archive query method ----------
+
sub sshpsql ($$$) {
my ($data,$runeinfo,$sql) = @_;
if (!length $data) {
@@ -837,9 +859,9 @@ sub sshpsql ($$$) {
my @rows;
my @cmd = (access_cfg_ssh, $userhost,
access_runeinfo("ssh-psql $runeinfo").
- " export LANG=C;".
+ " export LC_MESSAGES=C; export LC_CTYPE=C;".
" ".shellquote qw(psql -A), $dbname, qw(-c), $sql);
- printcmd(\*DEBUG,$debugprefix."|",@cmd) if $debug>0;
+ debugcmd "|",@cmd;
open P, "-|", @cmd or die $!;
while (
) {
chomp or die;
@@ -899,6 +921,8 @@ END
return $rows[0];
}
+#---------- `dummycat' archive query method ----------
+
sub canonicalise_suite_dummycat ($$) {
my ($proto,$data) = @_;
my $dpath = "$data/suite.$isuite";
@@ -938,6 +962,8 @@ sub archive_query_dummycat ($$) {
return sort { -version_compare($a->[0],$b->[0]); } @rows;
}
+#---------- archive query entrypoints and rest of program ----------
+
sub canonicalise_suite () {
return if defined $csuite;
fail "cannot operate on $isuite suite" if $isuite eq 'UNRELEASED';
@@ -967,9 +993,9 @@ sub get_archive_dsc () {
" archive told us to expect $digest";
}
my $dscfh = new IO::File \$dscdata, '<' or die $!;
- printdebug Dumper($dscdata) if $debug>1;
+ printdebug Dumper($dscdata) if $debuglevel>1;
$dsc = parsecontrolfh($dscfh,$dscurl,1);
- printdebug Dumper($dsc) if $debug>1;
+ printdebug Dumper($dsc) if $debuglevel>1;
my $fmt = getfield $dsc, 'Format';
fail "unsupported source format $fmt, sorry" unless $format_ok{$fmt};
$dsc_checked = !!$digester;
@@ -1039,6 +1065,12 @@ sub mktree_in_ud_here () {
symlink '../../../../objects','.git/objects' or die $!;
}
+sub git_write_tree () {
+ my $tree = cmdoutput @git, qw(write-tree);
+ $tree =~ m/^\w+$/ or die "$tree ?";
+ return $tree;
+}
+
sub mktree_in_ud_from_only_subdir () {
# changes into the subdir
my (@dirs) = <*/.>;
@@ -1053,8 +1085,7 @@ sub mktree_in_ud_from_only_subdir () {
rmtree '.pc';
}
runcmd @git, qw(add -Af);
- my $tree = cmdoutput @git, qw(write-tree);
- $tree =~ m/^\w+$/ or die "$tree ?";
+ my $tree=git_write_tree();
return ($tree,$dir);
}
@@ -1247,24 +1278,13 @@ sub ensure_we_have_orig () {
}
}
-sub rev_parse ($) {
- return cmdoutput @git, qw(rev-parse), "$_[0]~0";
-}
-
-sub is_fast_fwd ($$) {
- my ($ancestor,$child) = @_;
- my @cmd = (@git, qw(merge-base), $ancestor, $child);
- my $mb = cmdoutput_errok @cmd;
- if (defined $mb) {
- return rev_parse($mb) eq rev_parse($ancestor);
- } else {
- $?==256 or failedcmd @cmd;
- return 0;
- }
-}
-
sub git_fetch_us () {
runcmd_ordryrun_local @git, qw(fetch),access_giturl(),fetchspec();
+ if (deliberately_not_fast_forward) {
+ runcmd_ordryrun_local @git, qw(fetch -p), access_giturl(),
+ map { "+refs/$_/*:".lrfetchrefs."/$_/*" }
+ qw(tags heads);
+ }
}
sub fetch_from_archive () {
@@ -1309,8 +1329,8 @@ $later_warning_msg
END
$hash = $lastpush_hash;
} else {
- fail "archive's .dsc refers to ".$dsc_hash.
- " but this is an ancestor of ".$lastpush_hash;
+ fail "git head (".lrref()."=$lastpush_hash) is not a ".
+ "descendant of archive's .dsc hash ($dsc_hash)";
}
} elsif ($dsc) {
$hash = generate_commit_from_dsc();
@@ -1375,6 +1395,7 @@ sub clone ($) {
my ($dstdir) = @_;
canonicalise_suite();
badusage "dry run makes no sense with clone" unless act_local();
+ my $hasgit = check_for_git();
mkdir $dstdir or die "$dstdir $!";
changedir $dstdir;
runcmd @git, qw(init -q);
@@ -1386,7 +1407,7 @@ sub clone ($) {
close H or die $!;
runcmd @git, qw(remote add), 'origin', $giturl;
}
- if (check_for_git()) {
+ if ($hasgit) {
progress "fetching existing git history";
git_fetch_us();
runcmd_ordryrun_local @git, qw(fetch origin);
@@ -1396,6 +1417,7 @@ sub clone ($) {
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;
}
runcmd @git, qw(reset --hard), lrref();
@@ -1420,7 +1442,7 @@ sub pull () {
sub check_not_dirty () {
return if $ignoredirty;
my @cmd = (@git, qw(diff --quiet HEAD));
- printcmd(\*DEBUG,$debugprefix."+",@cmd) if $debug>0;
+ debugcmd "+",@cmd;
$!=0; $?=0; system @cmd;
return if !$! && !$?;
if (!$! && $?==256) {
@@ -1468,7 +1490,7 @@ sub get_source_format () {
sub madformat ($) {
my ($format) = @_;
return 0 unless $format eq '3.0 (quilt)';
- if ($noquilt) {
+ if ($quilt_mode eq 'nocheck') {
progress "Not doing any fixup of \`$format' due to --no-quilt-fixup";
return 0;
}
@@ -1524,6 +1546,8 @@ sub push_mktag ($$$$$$$) {
# We make the git tag by hand because (a) that makes it easier
# to control the "tagger" (b) we can do remote signing
my $authline = clogp_authline $clogp;
+ my $delibs = join(" ", "",@deliberatelies);
+ my $declaredistro = access_basedistro();
open TO, '>', $tfn->('.tmp') or die $!;
print TO <('.tmp');
@@ -1566,7 +1597,8 @@ sub sign_changes ($) {
}
}
-sub dopush () {
+sub dopush ($) {
+ my ($forceflag) = @_;
printdebug "actually entering push\n";
prep_ud();
@@ -1601,9 +1633,9 @@ sub dopush () {
$dscpath =~ m#^/# ? $dscpath : "../../../$dscpath";
my ($tree,$dir) = mktree_in_ud_from_only_subdir();
changedir '../../../..';
- my $diffopt = $debug>0 ? '--exit-code' : '--quiet';
+ my $diffopt = $debuglevel>0 ? '--exit-code' : '--quiet';
my @diffcmd = (@git, qw(diff), $diffopt, $tree);
- printcmd \*DEBUG,$debugprefix."+",@diffcmd;
+ debugcmd "+",@diffcmd;
$!=0; $?=0;
my $r = system @diffcmd;
if ($r) {
@@ -1622,7 +1654,7 @@ sub dopush () {
# runcmd @git, qw(fetch -p ), "$alioth_git/$package.git",
# map { lref($_).":".rref($_) }
# (uploadbranch());
- my $head = rev_parse('HEAD');
+ my $head = git_rev_parse('HEAD');
if (!$changesfile) {
my $multi = "$buildproductsdir/".
"${package}_".(stripepoch $cversion)."_multi.changes";
@@ -1645,6 +1677,15 @@ sub dopush () {
responder_send_command("param head $head");
responder_send_command("param csuite $csuite");
+ 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 $tfn = sub { ".git/dgit/tag$_[0]"; };
my $tagobjfn;
@@ -1668,7 +1709,7 @@ sub dopush () {
create_remote_git_repo();
}
runcmd_ordryrun @git, qw(push),access_giturl(),
- "HEAD:".rrref(), "refs/tags/$tag";
+ $forceflag."HEAD:".rrref(), $forceflag."refs/tags/$tag";
runcmd_ordryrun @git, qw(update-ref -m), 'dgit push', lrref(), 'HEAD';
if ($we_are_responder) {
@@ -1801,17 +1842,26 @@ sub cmd_push {
if (check_for_git()) {
git_fetch_us();
}
+ my $forceflag = '';
if (fetch_from_archive()) {
- is_fast_fwd(lrref(), 'HEAD') or
+ if (is_fast_fwd(lrref(), 'HEAD')) {
+ # ok
+ } elsif (deliberately_not_fast_forward) {
+ $forceflag = '+';
+ } else {
fail "dgit push: HEAD is not a descendant".
" of the archive's version.\n".
- "$us: To overwrite it, use git merge -s ours ".lrref().".";
+ "dgit: To overwrite its contents,".
+ " use git merge -s ours ".lrref().".\n".
+ "dgit: To rewind history, if permitted by the archive,".
+ " use --deliberately-not-fast-forward";
+ }
} else {
$new_package or
fail "package appears to be new in this suite;".
" if this is intentional, use --new";
}
- dopush();
+ dopush($forceflag);
}
#---------- remote commands' implementation ----------
@@ -1827,6 +1877,7 @@ sub cmd_remote_push_build_host {
# offered several)
$debugprefix = ' ';
$we_are_responder = 1;
+ $us .= " (build host)";
open PI, "<&STDIN" or die $!;
open STDIN, "/dev/null" or die $!;
@@ -1892,7 +1943,7 @@ sub cmd_rpush {
push @rdgit, qw(remote-push-build-host), (scalar @rargs), @rargs;
push @rdgit, @ARGV;
my @cmd = (@ssh, $host, shellquote @rdgit);
- printcmd \*DEBUG,$debugprefix."+",@cmd;
+ debugcmd "+",@cmd;
if (defined $initiator_tempdir) {
rmtree $initiator_tempdir;
@@ -1949,6 +2000,14 @@ sub i_resp_param ($) {
$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 ($) {
@@ -2020,8 +2079,276 @@ our $version;
our $sourcechanges;
our $dscfn;
+#----- `3.0 (quilt)' handling -----
+
our $fakeeditorenv = 'DGIT_FAKE_EDITOR_QUILT';
+sub quiltify_dpkg_commit ($$$;$) {
+ my ($patchname,$author,$msg, $xinfo) = @_;
+ $xinfo //= '';
+
+ mkpath '.git/dgit';
+ my $descfn = ".git/dgit/quilt-description.tmp";
+ open O, '>', $descfn or die "$descfn: $!";
+ $msg =~ s/\s+$//g;
+ $msg =~ s/\n/\n /g;
+ $msg =~ s/^\s+$/ ./mg;
+ print O < $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 $;
+ };
+ 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";
+ }
+ 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 = time;
+ 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;
+
+ $msg =~ s/^(.*)\n*/$1\n/ or die "$cc $msg ?";
+
+ my $title = $1;
+ my $patchname = $title;
+ $patchname =~ s/[.:]$//;
+ $patchname =~ y/ A-Z/-a-z/;
+ $patchname =~ y/-a-z0-9_.+=~//cd;
+ $patchname =~ s/^\W/x-$&/;
+ $patchname = substr($patchname,0,40);
+ 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,
+ "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=get_source_format;
return unless madformat $format;
@@ -2072,7 +2399,7 @@ sub build_maybe_quilt_fixup () {
# 6. Back in the main tree, fast forward to the new HEAD
my $clogp = parsechangelog();
- my $headref = rev_parse('HEAD');
+ my $headref = git_rev_parse('HEAD');
prep_ud();
changedir $ud;
@@ -2140,33 +2467,7 @@ END
rename '../fake/.pc','.pc' or die $!;
}
- my $author = getfield $clogp, 'Maintainer';
- my $time = time;
- my $ncommits = 3;
- my $patchname = "auto-$version-$headref-$time";
- my $msg = cmdoutput @git, qw(log), "-n$ncommits";
- mkpath '.git/dgit';
- my $descfn = ".git/dgit/quilt-description.tmp";
- open O, '>', $descfn or die "$descfn: $!";
- $msg =~ s/\n/\n /g;
- $msg =~ s/^\s+$/ ./mg;
- print O <{Version})
- Last (up to) $ncommits git changes, FYI:
- .
- $msg
-Author: $author
-
----
-
-END
- close O or die $!;
- {
- local $ENV{'EDITOR'} = cmdoutput qw(realpath --), $0;
- local $ENV{'VISUAL'} = $ENV{'EDITOR'};
- local $ENV{$fakeeditorenv} = cmdoutput qw(realpath --), $descfn;
- runcmd_ordryrun_local @dpkgsource, qw(--commit .), $patchname;
- }
+ quiltify($clogp,$headref);
if (!open P, '>>', ".pc/applied-patches") {
$!==&ENOENT or die $!;
@@ -2177,7 +2478,7 @@ END
commit_quilty_patch();
if ($mustdeletepc) {
- runcmd @git, qw(rm -rq .pc);
+ runcmd @git, qw(rm -rqf .pc);
commit_admin "Commit removal of .pc (quilt series tracking data)";
}
@@ -2204,6 +2505,8 @@ sub quilt_fixup_editor () {
exit 0;
}
+#----- other building -----
+
sub clean_tree () {
if ($cleanmode eq 'dpkg-source') {
runcmd_ordryrun_local @dpkgbuildpackage, qw(-T clean);
@@ -2341,6 +2644,23 @@ sub cmd_quilt_fixup {
build_maybe_quilt_fixup();
}
+sub cmd_archive_api_query {
+ badusage "need only 1 subpath argument" unless @ARGV==1;
+ my ($subpath) = @ARGV;
+ my @cmd = archive_api_query_cmd($subpath);
+ debugcmd ">",@cmd;
+ exec @cmd or fail "exec curl: $!\n";
+}
+
+sub cmd_clone_dgit_repos_server {
+ badusage "need destination argument" unless @ARGV==1;
+ my ($destdir) = @ARGV;
+ $package = '_dgit-repos-server';
+ my @cmd = (@git, qw(clone), access_giturl(), $destdir);
+ debugcmd ">",@cmd;
+ exec @cmd or fail "exec git clone: $!\n";
+}
+
#---------- argument parsing and main program ----------
sub cmd_version {
@@ -2410,15 +2730,23 @@ sub parseopts () {
$cleanmode = $1;
} elsif (m/^--clean=(.*)$/s) {
badusage "unknown cleaning mode \`$1'";
+ } elsif (m/^--quilt=($quilt_modes_re)$/s) {
+ push @ropts, $_;
+ $quilt_mode = $1;
+ } elsif (m/^--quilt=(.*)$/s) {
+ badusage "unknown quilt fixup mode \`$1'";
} elsif (m/^--ignore-dirty$/s) {
push @ropts, $_;
$ignoredirty = 1;
} elsif (m/^--no-quilt-fixup$/s) {
push @ropts, $_;
- $noquilt = 1;
+ $quilt_mode = 'nocheck';
} elsif (m/^--no-rm-on-error$/s) {
push @ropts, $_;
$rmonerror = 0;
+ } elsif (m/^--deliberately-($deliberately_re)$/s) {
+ push @ropts, $_;
+ push @deliberatelies, $&;
} else {
badusage "unknown long option \`$_'";
}
@@ -2434,9 +2762,8 @@ sub parseopts () {
cmd_help();
} elsif (s/^-D/-/) {
push @ropts, $&;
- open DEBUG, ">&STDERR" or die $!;
- autoflush DEBUG 1;
- $debug++;
+ $debuglevel++;
+ enabledebug();
} elsif (s/^-N/-/) {
push @ropts, $&;
$new_package=1;
@@ -2450,24 +2777,27 @@ sub parseopts () {
} elsif (s/^-c(.*=.*)//s) {
push @ropts, $&;
push @git, '-c', $1;
- } elsif (s/^-d(.*)//s) {
+ } elsif (s/^-d(.+)//s) {
push @ropts, $&;
$idistro = $1;
- } elsif (s/^-C(.*)//s) {
+ } elsif (s/^-C(.+)//s) {
push @ropts, $&;
$changesfile = $1;
if ($changesfile =~ s#^(.*)/##) {
$buildproductsdir = $1;
}
- } elsif (s/^-k(.*)//s) {
+ } elsif (s/^-k(.+)//s) {
$keyid=$1;
- } elsif (s/^-wn//s) {
+ } elsif (m/^-[vdCk]$/) {
+ badusage
+ "option \`$_' requires an argument (and no space before the argument)";
+ } elsif (s/^-wn$//s) {
push @ropts, $&;
$cleanmode = 'none';
- } elsif (s/^-wg//s) {
+ } elsif (s/^-wg$//s) {
push @ropts, $&;
$cleanmode = 'git';
- } elsif (s/^-wd//s) {
+ } elsif (s/^-wd$//s) {
push @ropts, $&;
$cleanmode = 'dpkg-source';
} else {
@@ -2493,6 +2823,15 @@ if (!@ARGV) {
my $cmd = shift @ARGV;
$cmd =~ y/-/_/;
+if (!defined $quilt_mode) {
+ $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;
+}
+
my $fn = ${*::}{"cmd_$cmd"};
$fn or badusage "unknown operation $cmd";
$fn->();