chiark / gitweb /
Infra: Add get-suites
[dgit.git] / dgit
diff --git a/dgit b/dgit
index 68d8b42d1da15ee921157f67f70aa7b793308f6a..31b5bf4c9359842c421709f6c2c20f50a1b5d406 100755 (executable)
--- 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 <http://www.gnu.org/licenses/>.
 
 use strict;
+$SIG{__WARN__} = sub { die $_[0]; };
 
 use IO::Handle;
 use Data::Dumper;
@@ -31,7 +32,6 @@ use POSIX;
 use IPC::Open2;
 use Digest::SHA;
 use Digest::MD5;
-use Config;
 
 use Debian::Dgit;
 
@@ -52,7 +52,7 @@ our $new_package = 0;
 our $ignoredirty = 0;
 our $rmonerror = 1;
 our @deliberatelies;
-our %supersedes;
+our %previously;
 our $existing_package = 'dpkg';
 our $cleanmode = 'dpkg-source';
 our $changes_since_version;
@@ -98,9 +98,6 @@ our %opts_opt_cmdonly = ('gpg' => 1);
 
 our $keyid;
 
-our $debug = 0;
-open DEBUG, ">/dev/null" or die $!;
-
 autoflush STDOUT 1;
 
 our $remotename = 'dgit';
@@ -114,6 +111,8 @@ sub lref () { return "refs/heads/".lbranch(); }
 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) = @_;
     $vsn =~ s/^\d+\://;
@@ -131,7 +130,7 @@ sub dscfn ($) {
 }
 
 our $us = 'dgit';
-our $debugprefix = '';
+initdebug('');
 
 our @end;
 END { 
@@ -142,32 +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 { 
-    my $s = "@_\n";
-    my $prefix = $us.($we_are_responder ? " (build host)" : "").": ";
-    $s =~ s/^/$prefix/gm;
-    die $s;
-}
-
 sub badcfg { print STDERR "$us: invalid configuration: @_\n"; exit 12; }
 
 sub no_such_package () {
@@ -187,7 +160,14 @@ sub changedir ($) {
 }
 
 sub deliberately ($) {
-    return !!grep { $_[0] eq $_ } @deliberatelies;
+    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 ----------
@@ -378,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 @_;
 }
@@ -429,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 = <P>; }
-    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."#",@_);
 }
@@ -525,8 +450,15 @@ our %defcfg = ('dgit.default.distro' => 'debian',
               '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.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' => '',
@@ -558,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) {
@@ -750,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: $!";
@@ -798,16 +713,25 @@ sub archive_api_query_cmd ($) {
     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');
+       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;
            }
-           push @cmd, "--ca-certificate=$key", "--ca-directory=/dev/enoent";
+           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;
@@ -937,7 +861,7 @@ sub sshpsql ($$$) {
               access_runeinfo("ssh-psql $runeinfo").
               " 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;
@@ -1069,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;
@@ -1354,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 () {
@@ -1529,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) {
@@ -1645,9 +1558,9 @@ tagger $authline
 $package release $cversion for $clogsuite ($csuite) [dgit]
 [dgit distro=$declaredistro$delibs]
 END
-    foreach my $ref (sort keys %supersedes) {
+    foreach my $ref (sort keys %previously) {
                    print TO <<END or die $!;
-[dgit supersede:$ref=$supersedes{$ref}]
+[dgit previously:$ref=$previously{$ref}]
 END
     }
 
@@ -1684,7 +1597,8 @@ sub sign_changes ($) {
     }
 }
 
-sub dopush () {
+sub dopush ($) {
+    my ($forceflag) = @_;
     printdebug "actually entering push\n";
     prep_ud();
 
@@ -1719,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) {
@@ -1740,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";
@@ -1763,12 +1677,12 @@ sub dopush () {
     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;
+    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;
        });
     }
 
@@ -1795,7 +1709,7 @@ sub dopush () {
        create_remote_git_repo();
     }
     runcmd_ordryrun @git, qw(push),access_giturl(),
-        $forceflag."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) {
@@ -1928,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 ----------
@@ -1954,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 $!;
@@ -2019,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;
@@ -2076,12 +2000,12 @@ sub i_resp_param ($) {
     $i_param{$1} = $2;
 }
 
-sub i_resp_supersedes ($) {
+sub i_resp_previously ($) {
     $_[0] =~ m#^(refs/tags/\S+)=(\w+)$#
-       or badproto \*RO, "bad supersedes spec";
+       or badproto \*RO, "bad previously spec";
     my $r = system qw(git check-ref-format), $1;
-    die "bad supersedes ref spec ($r)" if $r;
-    $supersedes{$1} = $2;
+    die "bad previously ref spec ($r)" if $r;
+    $previously{$1} = $2;
 }
 
 our %i_wanted;
@@ -2475,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;
@@ -2724,9 +2648,19 @@ 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 {
@@ -2810,7 +2744,7 @@ sub parseopts () {
            } elsif (m/^--no-rm-on-error$/s) {
                push @ropts, $_;
                $rmonerror = 0;
-           } elsif (m/^--deliberately-($suite_re)$/s) {
+           } elsif (m/^--deliberately-($deliberately_re)$/s) {
                push @ropts, $_;
                push @deliberatelies, $&;
            } else {
@@ -2828,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;