# along with this program. If not, see <http://www.gnu.org/licenses/>.
use strict;
+$SIG{__WARN__} = sub { die $_[0]; };
use IO::Handle;
use Data::Dumper;
use Digest::MD5;
use Config;
+use Debian::Dgit;
+
our $our_version = 'UNRELEASED'; ###substituted###
our $rpushprotovsn = 2;
our $new_package = 0;
our $ignoredirty = 0;
our $rmonerror = 1;
+our @deliberatelies;
+our %supersedes;
our $existing_package = 'dpkg';
our $cleanmode = 'dpkg-source';
our $changes_since_version;
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 stripepoch ($) {
my ($vsn) = @_;
}
our $us = 'dgit';
-our $debugprefix = '';
+initdebug('');
our @end;
END {
}
}
-sub printdebug { print DEBUG $debugprefix, @_ or die $!; }
-
sub fail {
my $s = "@_\n";
my $prefix = $us.($we_are_responder ? " (build host)" : "").": ";
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 ($) {
+ return !!grep { $_[0] eq $_ } @deliberatelies;
}
#---------- remote protocol support, common ----------
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 ($!) {
}
sub runcmd {
- printcmd(\*DEBUG,$debugprefix."+",@_) if $debug>0;
+ debugcmd "+",@_;
$!=0; $?=0;
failedcmd @_ if system @_;
}
sub cmdoutput_errok {
die Dumper(\@_)." ?" if grep { !defined } @_;
- printcmd(\*DEBUG,$debugprefix."|",@_) if $debug>0;
+ debugcmd "|",@_;
open P, "-|", @_ or die $!;
my $d;
$!=0; $?=0;
{ local $/ = undef; $d = <P>; }
die $! if P->error;
- if (!close P) { printdebug "=>!$?\n" if $debug>0; return undef; }
+ if (!close P) { printdebug "=>!$?\n"; return undef; }
chomp $d;
$d =~ m/^.*/;
- printdebug "=> \`$&'",(length $' ? '...' : ''),"\n" if $debug>0; #';
+ printdebug "=> \`$&'",(length $' ? '...' : ''),"\n" if $debuglevel>0; #';
return $d;
}
'dgit.default.ssh' => 'ssh',
'dgit.default.archive-query' => 'madison:',
'dgit.default.sshpsql-dbname' => 'service=projectb',
- 'dgit-distro.debian.archive-query' => 'sshpsql:',
+ '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' => '/dgit/debian/repos',
+ 'dgit-distro.debian.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',
'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.git-check' => 'ssh-cmd',
- 'dgit-distro.debian.git-create' => 'ssh-cmd',
- 'dgit-distro.debian.sshpsql-host' => 'mirror.ftp-master.debian.org',
+ '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*',
'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',
);
my @cmd = (@git, qw(config --), $c);
my $v;
{
- local ($debug) = $debug-1;
+ local ($debuglevel) = $debuglevel-2;
$v = cmdoutput_errok @cmd;
};
if ($?==0) {
return $d;
}
+our %rmad;
+
+sub archive_query ($) {
+ my ($method) = @_;
+ 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 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);
+}
+
+#---------- `ftpmasterapi' archive query method (nascent) ----------
+
sub archive_api_query_cmd ($) {
my ($subpath) = @_;
my @cmd = qw(curl -sS);
return @cmd;
}
-our %rmad;
+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 archive_query ($) {
- my ($method) = @_;
- 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 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 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 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(@_);
}
return $r[0][2];
}
+#---------- `sshpsql' archive query method ----------
+
sub sshpsql ($$$) {
my ($data,$runeinfo,$sql) = @_;
if (!length $data) {
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 (<P>) {
chomp or die;
return $rows[0];
}
+#---------- `dummycat' archive query method ----------
+
sub canonicalise_suite_dummycat ($$) {
my ($proto,$data) = @_;
my $dpath = "$data/suite.$isuite";
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';
" 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;
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();
}
fetch_from_archive() or no_such_package;
my $vcsgiturl = $dsc->{'Vcs-Git'};
- $vcsgiturl =~ s/\s+-b\s+\S+//g;
if (length $vcsgiturl) {
+ $vcsgiturl =~ s/\s+-b\s+\S+//g;
runcmd @git, qw(remote add vcs-git), $vcsgiturl;
}
runcmd @git, qw(reset --hard), lrref();
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) {
# 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 <<END or die $!;
object $head
tagger $authline
$package release $cversion for $clogsuite ($csuite) [dgit]
+[dgit distro=$declaredistro$delibs]
END
+ foreach my $ref (sort keys %supersedes) {
+ print TO <<END or die $!;
+[dgit supersede:$ref=$supersedes{$ref}]
+END
+ }
+
close TO or die $!;
my $tagobjfn = $tfn->('.tmp');
$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) {
responder_send_command("param head $head");
responder_send_command("param csuite $csuite");
+ my $forceflag = deliberately('not-fast-forward') ? '+' : '';
+ if ($forceflag && defined $lastpush_hash) {
+ git_for_each_tag_referring($lastpush_hash, sub {
+ my ($objid,$fullrefname,$tagname) = @_;
+ responder_send_command("supersedes $fullrefname=$objid");
+ $supersedes{$fullrefname} = $objid;
+ });
+ }
+
my $tfn = sub { ".git/dgit/tag$_[0]"; };
my $tagobjfn;
create_remote_git_repo();
}
runcmd_ordryrun @git, qw(push),access_giturl(),
- "HEAD:".rrref(), "refs/tags/$tag";
+ $forceflag."HEAD:".rrref(), "refs/tags/$tag";
runcmd_ordryrun @git, qw(update-ref -m), 'dgit push', lrref(), 'HEAD';
if ($we_are_responder) {
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;
$i_param{$1} = $2;
}
+sub i_resp_supersedes ($) {
+ $_[0] =~ m#^(refs/tags/\S+)=(\w+)$#
+ or badproto \*RO, "bad supersedes spec";
+ my $r = system qw(git check-ref-format), $1;
+ die "bad supersedes ref spec ($r)" if $r;
+ $supersedes{$1} = $2;
+}
+
our %i_wanted;
sub i_resp_want ($) {
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";
}
} elsif (m/^--no-rm-on-error$/s) {
push @ropts, $_;
$rmonerror = 0;
+ } elsif (m/^--deliberately-($suite_re)$/s) {
+ push @ropts, $_;
+ push @deliberatelies, $&;
} else {
badusage "unknown long option \`$_'";
}
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;