# usages:
# dgit-repos-server DISTRO DISTRO-DIR AUTH-SPEC [<settings>] --ssh
# dgit-repos-server DISTRO DISTRO-DIR AUTH-SPEC [<settings>] --cron
+# dgit-repos-server DISTRO DISTRO-DIR AUTH-SPEC [<settings>] \
+# --tag2upload URL TAGNAME
# settings
# --repos=GIT-REPOS-DIR default DISTRO-DIR/repos/
# --suites=SUITES-FILE default DISTRO-DIR/suites
# (With --cron AUTH-SPEC is not used and may be the empty string.)
use strict;
+use Carp;
+use IO::Handle;
use Debian::Dgit::Infra; # must precede Debian::Dgit; - can change @INC!
use Debian::Dgit qw(:DEFAULT :policyflags);
die "\ndgit-repos-server: reject: $why\n\n";
}
-sub runcmd {
- debugcmd '+',@_;
- $!=0; $?=0;
- my $r = system @_;
- die (shellquote @_)." $? $!" if $r;
-}
-
sub policyhook {
my ($policyallowbits, @polargs) = @_;
# => ($exitstatuspolicybitmap);
STDIN->error and die $!;
reject "push is missing tag ref update" unless %tags;
- my @newtags = grep { m#^archive/# } keys %tags;
- my @omtags = grep { !m#^archive/# } keys %tags;
- reject "pushing too many similar tags" if @newtags>1 || @omtags>1;
- if (@newtags) {
- ($tagname) = @newtags;
- ($maint_tagname) = @omtags;
- } else {
- ($tagname) = @omtags or die;
- }
+ my @dtags = grep { m#^archive/# } keys %tags;
+ reject "need exactly one archive/* tag" if @dtags!=1;
+ my @mtags = grep { !m#^archive/# } keys %tags;
+ reject "pushing too many non-dgit tags" if @mtags>1;
+ ($tagname) = @dtags;
+ ($maint_tagname) = @mtags;
$tagval = $tags{$tagname};
$maint_tagval = $tags{$maint_tagname // ''};
printdebug " updates ok.\n";
}
-sub parsetag () {
- printdebug " parsetag...\n";
+sub readtag () {
+ printdebug " readtag...\n";
+
open PT, ">dgit-tmp/plaintext" or die $!;
open DS, ">dgit-tmp/plaintext.asc" or die $!;
open T, "-|", qw(git cat-file tag), $tagval or die $!;
}
}
$!=0; $_=<T>; defined or die $!;
- m/^($package_re) release (\S+) for \S+ \((\S+)\) \[dgit\]$/ or
- reject "tag message not in expected format";
+}
- die unless $1 eq $package;
- $version = $2;
- die "$3 != $suite " unless $3 eq $suite;
+sub parsetag_general ($$) {
+ my ($dgititemfn, $distrofn) = @_;
+ printdebug " parsetag...\n";
my $copyl = $_;
for (;;) {
if (m/^\[dgit ([^"].*)\]$/) { # [dgit "something"] is for future
$_ = $1." ";
while (length) {
- if (s/^distro\=(\S+) //) {
- die "$1 != $distro" unless $1 eq $distro;
- } elsif (s/^(--deliberately-$deliberately_re) //) {
- push @deliberatelies, $1;
- } elsif (s/^previously:(\S+)=(\w+) //) {
- die "previously $1 twice" if defined $previously{$1};
- $previously{$1} = $2;
+ if ($dgititemfn->()) {
+ } elsif (s/^distro\=(\S+) //) {
+ $distrofn->($1);
} elsif (s/^[-+.=0-9a-z]\S* //) {
} else {
die "unknown dgit info in tag ($_)";
}
last if m/^-----BEGIN PGP/;
}
+
$_ = $copyl;
for (;;) {
print DS or die $!;
printdebug " parsetag ok.\n";
}
+sub parsetag () {
+ readtag();
+ m/^($package_re) release (\S+) for \S+ \((\S+)\) \[dgit\]$/ or
+ reject "tag message not in expected format";
+ die unless $1 eq $package;
+ $version = $2;
+ die "$3 != $suite " unless $3 eq $suite;
+
+ parsetag_general sub {
+ if (s/^(--deliberately-$deliberately_re) //) {
+ push @deliberatelies, $1;
+ } elsif (s/^previously:(\S+)=(\w+) //) {
+ die "previously $1 twice" if defined $previously{$1};
+ $previously{$1} = $2;
+ } else {
+ return 0;
+ }
+ return 1;
+ }, sub {
+ my ($gotdistro) = @_;
+ die "$gotdistro != $distro" unless $gotdistro eq $distro;
+ };
+}
+
sub checksig_keyring ($) {
my ($keyringfile) = @_;
# returns primary-keyid if signed by a key in this keyring
return $vals->[0];
}
-sub checks () {
+sub basic_tag_checks() {
printdebug "checks\n";
tagh1('type') eq 'commit' or reject "tag refers to wrong kind of object";
tagh1('object') eq $commit or reject "tag refers to wrong commit";
tagh1('tag') eq $tagname or reject "tag name in tag is wrong";
+}
+
+sub checks () {
+ basic_tag_checks();
my @expecttagnames = debiantags($version, $distro);
printdebug "expected tag @expecttagnames\n";
grep { $tagname eq $_ } @expecttagnames or die;
foreach my $othertag (grep { $_ ne $tagname } @expecttagnames) {
- reject "tag $othertag (pushed with differing dgit version)".
- " already exists -".
+ reject "tag $othertag already exists -".
" not replacing previously-pushed version"
if git_get_ref "refs/tags/".$othertag;
}
# workrepo and destrepo handled ad-hoc
+sub mode_tag2upload () {
+ # PROTOTYPE
+ # CALLER MUST PREVENT MULTIPLE CONCURRENT RUNS IN SAME CWD
+ # If we fail (exit nonzero), caller should capture our stderr,
+ # and retry some bounded number of times in some appropriate way
+ @ARGV==2 or die;
+
+ my $url;
+ ($url,$tagval) = @ARGV;
+
+ $ENV{DGIT_DRS_EMAIL_NOREPLY} // die;
+
+ my $start = time // die;
+ my @t = gmtime $start;
+
+ die if $url =~ m/[^[:graph:]]/;
+ die if $tagval =~ m/[^[:graph:]]/;
+
+ open OL, ">>overall.log" or die $!;
+ autoflush OL 1;
+ my $quit = sub {
+ printf OL "%04d-%02d-%02d %02d:%02d:%02d (%04ds): %s %s: %s\n",
+ $t[5] + 1900, @t[4,3,2,1,0], (time-$start), $url, $tagval, $_[0];
+ exit 0;
+ };
+
+ $ENV{DGIT_DRS_ANY_URL} or $url =~ m{^https://}s
+ or $quit->("url scheme not as expected");
+
+ $tagval =~ m{^$distro/($versiontag_re)$}s
+ or $quit->("tag name not for us");
+
+ $version = $1;
+ $version =~ y/_\%\#/:~/d;
+
+ my $work = 'work';
+
+ my $tagref = "refs/tags/$tagval";
+
+ rmtree $work;
+ mkdir $work or die $!;
+ changedir $work;
+ runcmd qw(git init -q);
+ runcmd qw(git remote add origin), $url;
+ runcmd qw(git fetch --depth=1 origin), "$tagref:$tagref";
+ changedir ".git";
+ mkdir 'dgit-tmp' or die $!;
+
+ my $tagger;
+ open T, "-|", qw(git cat-file tag), $tagval or die $!;
+ {
+ local $/ = undef;
+ $!=0; $_=<T>; defined or die $!;
+
+ # quick and dirty check, will check properly later
+ m/^\[dgit[^"]* please-upload(?:\]| )/m or
+ $quit->("tag missing please-upload request $_");
+
+ m/^tagger (.*) \d+ [-+]\d+$/m or
+ $quit->("failed to fish tagger out of tag");
+ $tagger = $1;
+ };
+
+ readtag();
+ m/^($package_re) release (\S+) for ($suite_re)$/ or
+ $quit->("tag headline not for us");
+ $package = $1;
+ my $tagmversion = $2;
+ $suite = $3;
+
+
+ # This is for us. From now on, we will capture errors to
+ # be emailed to the tagger.
+ # TODO: failures to git fetch from salsa will burn a version
+
+ open H, ">>dgit-tmp/tagupl.email" or die $!;
+ print H <<END or die $!;
+Subject: push-to-upload failed, $package $version ($distro)
+X-Debian-Push-Distro: $distro
+X-Debian-Push-Package: $package
+END
+ printf H "To: %s", $tagger or die $!; # no newline
+ flush H or die $!;
+
+ open L, ">>dgit-tmp/tagupl.log" or die $!;
+
+ my $child = fork() // die $!;
+ if ($child) {
+ # we are the parent
+ # if child exits 0, it has called $quit->()
+ $!=0; waitpid $child, 0 == $child or die $!;
+ printdebug "child $child ?=$?\n";
+ exit 0 unless $?;
+ print L "execution child: ", waitstatusmsg(), "\n" or die $!;
+ close L or die $!;
+ print H <<END or die $!;
+
+
+Processing of tag $tagval
+From url $url
+Was not successful:
+
+END
+ $ENV{DGIT_DRS_SENDMAIL} //= '/usr/lib/sendmail';
+
+ close H or die $!;
+ runcmd qw(sh -ec), <<"END";
+ cd dgit-tmp
+ cat tagupl.log >>tagupl.email
+ $ENV{DGIT_DRS_SENDMAIL} -oee -odb -oi -t \\
+ -f$ENV{DGIT_DRS_EMAIL_NOREPLY} \\
+ <tagupl.email
+END
+ exit 0;
+ }
+
+ open STDERR, ">&L" or die $!;
+ open STDOUT, ">&STDERR" or die $!;
+ open DEBUG, ">&STDERR" if $debuglevel;
+
+ die "$tagmversion != $version " unless $tagmversion eq $version;
+
+ my %need = map { $_ => 1 } qw(please-upload split);
+ my ($upstreamc, $upstreamt);
+ my $quilt;
+ my $distro_ok;
+
+ parsetag_general sub {
+ if (m/^(\S+) / && exists $need{$1}) {
+ $_ = $';
+ delete $need{$1};
+ } elsif (s/^upstream=(\w+) //) {
+ $upstreamc = $1;
+ } elsif (s/^upstream-tag=(\S+) //) {
+ $upstreamt = $1;
+ } elsif (s/^quilt=([-+0-9a-z]+) //) {
+ $quilt = $1;
+ } else {
+ return 0;
+ }
+ return 1;
+ }, sub {
+ my ($gotdistro) = @_;
+ $distro_ok ||= $gotdistro eq $distro;
+ };
+
+ $quit->("other distro") unless $distro_ok;
+
+ reject "missing \"$_\"" foreach keys %need;
+
+ reject "upstream tag and not commitish, or v-v"
+ unless defined $upstreamt == defined $upstreamc;
+
+ verifytag();
+
+ my @dgit;
+ push @dgit, $ENV{DGIT_DRS_DGIT} // 'dgit';
+ push @dgit, '-wn';
+ push @dgit, "-p$package";
+
+ changedir "..";
+ runcmd (@dgit, qw(setup-gitattributes));
+
+ my @fetch = qw(git fetch origin --unshallow);
+ if (defined $upstreamt) {
+ runcmd qw(git check-ref-format), "refs/tags/$upstreamt";
+ runcmd qw(git check-ref-format), "refs/tags/$upstreamt";
+ my $utagref = "refs/tags/$upstreamt";
+ push @fetch, "$utagref:$utagref";
+ }
+ runcmd @fetch;
+
+ $upstreamc eq git_rev_parse "refs/tags/$upstreamt" or die;
+
+ runcmd qw(git checkout -q), "refs/tags/$tagval";
+
+ @fetch = (@dgit, qw(fetch), $suite);
+ debugcmd "+",@_;
+ $!=0; $?=-1;
+ if (system @fetch) {
+ failedcmd @fetch unless $? == 4*256;
+ }
+ # this is just to get the orig, so we don't really care about the ref
+ runcmd qw(git deborig), "$upstreamc";
+
+ my @dgitcmd;
+ push @dgitcmd, @dgit;
+ push @dgitcmd, qw(--force-uploading-source-only);
+ if (defined $quilt) {
+ push @dgitcmd, "--quilt=$quilt";
+ if ($quilt =~ m/baredebian/) {
+ die "needed upstream commmitish with --quilt=baredebian";
+ push @dgitcmd, "--upstream-commitish=$upstreamc";
+ }
+ }
+ push @dgitcmd, qw(push-source --new --overwrite), $suite;
+ # xxx what about the key to use?
+
+ runcmd @dgitcmd;
+
+ $quit->('done');
+}
+
sub mode_ssh () {
die if @ARGV;
$ENV{"DGIT_DRS_\U$_"} = ${ $main::{$_} } foreach @hookenvs;
- die unless @ARGV==1;
+ die unless @ARGV>=1;
my $mode = shift @ARGV;
die unless $mode =~ m/^--(\w+)$/;