chiark / gitweb /
changelog: start 9.14
[dgit.git] / infra / dgit-repos-server
index 7b8849a85cfb3162731efc0c469a6f9496da1643..bbf1aa215a34e054b5b4532254865365c7f6e3b4 100755 (executable)
@@ -3,7 +3,7 @@
 #
 # git protocol proxy to check dgit pushes etc.
 #
-# Copyright (C) 2014-2016  Ian Jackson
+# Copyright (C) 2014-2017,2019  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
@@ -21,6 +21,8 @@
 # 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
@@ -50,6 +52,8 @@
 # (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);
@@ -552,9 +556,9 @@ sub readupdates () {
     printdebug " updates ok.\n";
 }
 
-sub parsetag_general ($$) {
-    my ($mainfn, $dgititemfn) = @_;
-    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 $!;
@@ -570,8 +574,11 @@ sub parsetag_general ($$) {
        }
     }
     $!=0; $_=<T>; defined or die $!;
+}
 
-    $mainfn->();
+sub parsetag_general ($$) {
+    my ($dgititemfn, $distrofn) = @_;
+    printdebug " parsetag...\n";
 
     my $copyl = $_;
     for (;;) {
@@ -583,8 +590,9 @@ sub parsetag_general ($$) {
            while (length) {
                if ($dgititemfn->()) {
                } elsif (s/^distro\=(\S+) //) {
-                   die "$1 != $distro" unless $1 eq $distro;
-               } elsif (s/^[-+.=0-9a-z]\S* //) {
+                   $distrofn->($1);
+               } elsif (s/^([-+.=0-9a-z]\S*) //) {
+                   printdebug " parsetag ignoring unrecognised \`$1'\n";
                } else {
                    die "unknown dgit info in tag ($_)";
                }
@@ -593,6 +601,7 @@ sub parsetag_general ($$) {
        }
        last if m/^-----BEGIN PGP/;
     }
+
     $_ = $copyl;
     for (;;) {
        print DS or die $!;
@@ -606,13 +615,14 @@ sub parsetag_general ($$) {
 }
 
 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 {
-       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 {
        if (s/^(--deliberately-$deliberately_re) //) {
            push @deliberatelies, $1;
        } elsif (s/^previously:(\S+)=(\w+) //) {
@@ -622,6 +632,9 @@ sub parsetag () {
            return 0;
        }
        return 1;
+    }, sub {
+       my ($gotdistro) = @_;
+       die "$gotdistro != $distro" unless $gotdistro eq $distro;
     };
 }
 
@@ -771,7 +784,7 @@ sub checktagnoreplay () {
     #     current head for the suite (there must be at least one).
     #
     #     This prevents any tag implying a NOFFCHECK push being
-    #     replayed to rewind from a different head.
+    #     replayed to overwrite a different head.
     #
     #     The possibility of an earlier ff-only push being replayed is
     #     eliminated as follows: the tag from such a push would still
@@ -1060,6 +1073,228 @@ our @hookenvs = qw(distro suitesfile suitesformasterfile policyhook
 
 # workrepo and destrepo handled ad-hoc
 
+sub mode_tag2upload () {
+    # 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
+    # Uses whatever ambient gpg key is available
+    @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;
+    rmtree 'bpd';
+    mkdir $work or die $!;
+    mkdir 'bpd' or die $!;
+    unlink <*.orig*>;
+    dif $! if <*.orig*>;
+    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.
+
+    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
+       $quit->("failed, emailed");
+    }
+
+    open STDERR, ">&L" or die $!;
+    open STDOUT, ">&STDERR" or die $!;
+    open DEBUG, ">&STDERR" if $debuglevel;
+
+    reject "version mismatch $tagmversion != $version "
+       unless $tagmversion eq $version;
+
+    my %need = map { $_ => 1 } qw(please-upload split);
+    my ($upstreamc, $upstreamt);
+    my $quilt;
+    my $distro_ok;
+
+    confess if defined $upstreamt;
+
+    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->("not for this distro") unless $distro_ok;
+
+    reject "missing \"$_\"" foreach keys %need;
+
+    verifytag();
+
+    reject "upstream tag and not commitish, or v-v"
+       unless defined $upstreamt == defined $upstreamc;
+
+    my @dgit;
+    push @dgit, $ENV{DGIT_DRS_DGIT} // 'dgit';
+    push @dgit, '-wn';
+    push @dgit, "-p$package";
+    push @dgit, '--build-products-dir=../bpd';
+
+    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";
+       my $utagref = "refs/tags/$upstreamt";
+       push @fetch, "$utagref:$utagref";
+    }
+    runcmd @fetch;
+
+    runcmd qw(git checkout -q), "refs/tags/$tagval";
+
+    my $clogp = parsechangelog();
+    my $clogf = sub {
+       my ($f, $exp) = @_;
+       my $got = getfield $clogp, $f;
+       return if $got eq $exp;
+       reject "mismatch: changelog $f $got != $exp";
+    };
+    $clogf->('Version', $version);
+    $clogf->('Source',  $package);
+
+    @fetch = (@dgit, qw(--for-push 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
+    if (defined $upstreamc) {
+       my $need_upstreamc = git_rev_parse "refs/tags/$upstreamt";
+       $upstreamc eq $need_upstreamc or reject
+           "upstream-commitish=$upstreamc but tag refers to $need_upstreamc";
+       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"
+               unless defined $upstreamc;
+           push @dgitcmd, "--upstream-commitish=refs/tags/$upstreamt";
+       }
+    }
+    push @dgitcmd, qw(push-source --new --overwrite), $suite;
+    
+    runcmd @dgitcmd;
+
+    $quit->('done');
+}
+
 sub mode_ssh () {
     die if @ARGV;