chiark / gitweb /
Option parsing: Redo .opts-CMD parsing to use insertpos
[dgit.git] / dgit
diff --git a/dgit b/dgit
index 324a65f8a7c415702b62d11e15253ad6934b0ac9..7d2023e5c91ab266d3c94a81b9cd8f8ad5497c44 100755 (executable)
--- a/dgit
+++ b/dgit
@@ -39,7 +39,8 @@ use Debian::Dgit;
 
 our $our_version = 'UNRELEASED'; ###substituted###
 
-our $rpushprotovsn = 2;
+our @rpushprotovsn_support = qw(3 2);
+our $protovsn;
 
 our $isuite = 'unstable';
 our $idistro;
@@ -97,11 +98,24 @@ our %opts_opt_map = ('dget' => \@dget, # accept for compatibility
                      'mergechanges' => \@mergechanges);
 
 our %opts_opt_cmdonly = ('gpg' => 1);
+our %opts_cfg_insertpos = map {
+    $_,
+    scalar @{ $opts_opt_map{$_} }
+} keys %opts_opt_map;
+
+sub finalise_opts_opts();
 
 our $keyid;
 
 autoflush STDOUT 1;
 
+our $supplementary_message = '';
+
+END {
+    local ($@, $?);
+    print STDERR "! $_\n" foreach $supplementary_message =~ m/^.+$/mg;
+}
+
 our $remotename = 'dgit';
 our @ourdscfield = qw(Dgit Vcs-Dgit-Master);
 our $csuite;
@@ -175,7 +189,9 @@ sub deliberately_not_fast_forward () {
 #---------- remote protocol support, common ----------
 
 # remote push initiator/responder protocol:
-#  < dgit-remote-push-ready [optional extra info ignored by old initiators]
+#  $ dgit remote-push-build-host <n-rargs> <rargs>... <push-args>...
+#  where <rargs> is <push-host-dir> <supported-proto-vsn>,... ...
+#  < dgit-remote-push-ready <actual-proto-vsn>
 #
 #  > file parsed-changelog
 #  [indicates that output of dpkg-parsechangelog follows]
@@ -270,7 +286,7 @@ sub protocol_send_file ($$) {
 
 sub protocol_read_bytes ($$) {
     my ($fh, $nbytes) = @_;
-    $nbytes =~ m/^[1-9]\d{0,5}$/ or badproto \*RO, "bad byte count";
+    $nbytes =~ m/^[1-9]\d{0,5}$|^0$/ or badproto \*RO, "bad byte count";
     my $d;
     my $got = read $fh, $d, $nbytes;
     $got==$nbytes or badproto_badread $fh, "data block";
@@ -589,6 +605,27 @@ sub pushing () {
     badcfg "pushing but distro is configured readonly"
        if access_forpush_config() eq '0';
     $access_forpush = 1;
+    $supplementary_message = <<'END' unless $we_are_responder;
+Push failed, before we got started.
+You can retry the push, after fixing the problem, if you like.
+END
+    finalise_opts_opts();
+}
+
+sub notpushing () {
+    finalise_opts_opts();
+}
+
+sub supplementary_message ($) {
+    my ($msg) = @_;
+    if (!$we_are_responder) {
+       $supplementary_message = $msg;
+       return;
+    } elsif ($protovsn >= 3) {
+       responder_send_command "supplementary-message ".length($msg)
+           or die $!;
+       print PO $msg or die $!;
+    }
 }
 
 sub access_distros () {
@@ -612,7 +649,7 @@ sub access_distros () {
     @l;
 }
 
-sub access_cfg (@) {
+sub access_cfg_cfgs (@) {
     my (@keys) = @_;
     my @cfgs;
     # The nesting of these loops determines the search order.  We put
@@ -639,6 +676,12 @@ sub access_cfg (@) {
     }
     push @cfgs, map { "dgit.default.$_" } @realkeys;
     push @cfgs, @rundef;
+    return @cfgs;
+}
+
+sub access_cfg (@) {
+    my (@keys) = @_;
+    my (@cfgs) = access_cfg_cfgs(@keys);
     my $value = cfg(@cfgs);
     return $value;
 }
@@ -1819,6 +1862,10 @@ sub sign_changes ($) {
 sub dopush ($) {
     my ($forceflag) = @_;
     printdebug "actually entering push\n";
+    supplementary_message(<<'END');
+Push failed, while preparing your push.
+You can retry the push, after fixing the problem, if you like.
+END
     prep_ud();
 
     access_giturl(); # check that success is vaguely likely
@@ -1903,6 +1950,11 @@ sub dopush ($) {
     my $tfn = sub { ".git/dgit/tag$_[0]"; };
     my $tagobjfn;
 
+    supplementary_message(<<'END');
+Push failed, while signing the tag.
+You can retry the push, after fixing the problem, if you like.
+END
+    # If we manage to sign but fail to record it anywhere, it's fine.
     if ($we_are_responder) {
        $tagobjfn = $tfn->('.signed.tmp');
        responder_receive_files('signed-tag', $tagobjfn);
@@ -1913,11 +1965,19 @@ sub dopush ($) {
                       $changesfile,$changesfile,
                       $tfn);
     }
+    supplementary_message(<<'END');
+Push failed, *after* signing the tag.
+If you want to try again, you should use a new version number.
+END
 
     my $tag_obj_hash = cmdoutput @git, qw(hash-object -w -t tag), $tagobjfn;
     runcmd_ordryrun @git, qw(verify-tag), $tag_obj_hash;
     runcmd_ordryrun_local @git, qw(update-ref), "refs/tags/$tag", $tag_obj_hash;
 
+    supplementary_message(<<'END');
+Push failed, while updating the remote git repository - see messages above.
+If you want to try again, you should use a new version number.
+END
     if (!check_for_git()) {
        create_remote_git_repo();
     }
@@ -1925,6 +1985,10 @@ sub dopush ($) {
         $forceflag."HEAD:".rrref(), $forceflag."refs/tags/$tag";
     runcmd_ordryrun @git, qw(update-ref -m), 'dgit push', lrref(), 'HEAD';
 
+    supplementary_message(<<'END');
+Push failed, after updating the remote git repository.
+If you want to try again, you must use a new version number.
+END
     if ($we_are_responder) {
        my $dryrunsuffix = act_local() ? "" : ".tmp";
        responder_receive_files('signed-dsc-changes',
@@ -1939,16 +2003,25 @@ sub dopush ($) {
        sign_changes $changesfile;
     }
 
+    supplementary_message(<<'END');
+Push failed, while uploading package(s) to the archive server.
+You can retry the upload of exactly these same files with dput of:
+  $changesfile
+If that .changes file is broken, you will need to use a new version
+number for your next attempt at the upload.
+END
     my $host = access_cfg('upload-host','RETURN-UNDEF');
     my @hostarg = defined($host) ? ($host,) : ();
     runcmd_ordryrun @dput, @hostarg, $changesfile;
     printdone "pushed and uploaded $cversion";
 
+    supplementary_message('');
     responder_send_command("complete");
 }
 
 sub cmd_clone {
     parseopts();
+    notpushing();
     my $dstdir;
     badusage "-p is not allowed with clone; specify as argument instead"
        if defined $package;
@@ -1996,6 +2069,7 @@ sub branchsuite () {
 }
 
 sub fetchpullargs () {
+    notpushing();
     if (!defined $package) {
        my $sourcep = parsecontrol('debian/control','debian/control');
        $package = getfield $sourcep, 'Source';
@@ -2029,8 +2103,8 @@ sub cmd_pull {
 }
 
 sub cmd_push {
-    pushing();
     parseopts();
+    pushing();
     badusage "-p is not allowed with dgit push" if defined $package;
     check_not_dirty();
     my $clogp = parsechangelog();
@@ -2055,6 +2129,10 @@ sub cmd_push {
            fail "dgit push: changelog specifies $isuite ($csuite)".
                " but command line specifies $specsuite";
     }
+    supplementary_message(<<'END');
+Push failed, while checking state of the archive.
+You can retry the push, after fixing the problem, if you like.
+END
     if (check_for_git()) {
        git_fetch_us();
     }
@@ -2083,7 +2161,6 @@ sub cmd_push {
 #---------- remote commands' implementation ----------
 
 sub cmd_remote_push_build_host {
-    pushing();
     my ($nrargs) = shift @ARGV;
     my (@rargs) = @ARGV[0..$nrargs-1];
     @ARGV = @ARGV[$nrargs..$#ARGV];
@@ -2096,6 +2173,8 @@ sub cmd_remote_push_build_host {
     $we_are_responder = 1;
     $us .= " (build host)";
 
+    pushing();
+
     open PI, "<&STDIN" or die $!;
     open STDIN, "/dev/null" or die $!;
     open PO, ">&STDOUT" or die $!;
@@ -2104,11 +2183,16 @@ sub cmd_remote_push_build_host {
     autoflush STDOUT 1;
 
     $vsnwant //= 1;
-    fail "build host has dgit rpush protocol version".
-       " $rpushprotovsn but invocation host has $vsnwant"
-       unless grep { $rpushprotovsn eq $_ } split /,/, $vsnwant;
+    ($protovsn) = grep {
+       $vsnwant =~ m{^(?:.*,)?$_(?:,.*)?$}
+    } @rpushprotovsn_support;
+
+    fail "build host has dgit rpush protocol versions ".
+       (join ",", @rpushprotovsn_support).
+        " but invocation host has $vsnwant"
+       unless defined $protovsn;
 
-    responder_send_command("dgit-remote-push-ready $rpushprotovsn");
+    responder_send_command("dgit-remote-push-ready $protovsn");
 
     changedir $dir;
     &cmd_push;
@@ -2154,7 +2238,8 @@ sub cmd_rpush {
        $dir = nextarg;
     }
     $dir =~ s{^-}{./-};
-    my @rargs = ($dir,$rpushprotovsn);
+    my @rargs = ($dir);
+    push @rargs, join ",", @rpushprotovsn_support;
     my @rdgit;
     push @rdgit, @dgit;
     push @rdgit, @ropts;
@@ -2172,7 +2257,9 @@ sub cmd_rpush {
     }
     $i_child_pid = open2(\*RO, \*RI, @cmd);
     changedir $i_tmp;
-    initiator_expect { m/^dgit-remote-push-ready/ };
+    ($protovsn) = initiator_expect { m/^dgit-remote-push-ready (\S+)/ };
+    die "$protovsn ?" unless grep { $_ eq $protovsn } @rpushprotovsn_support;
+    $supplementary_message = '' unless $protovsn >= 3;
     for (;;) {
        my ($icmd,$iargs) = initiator_expect {
            m/^(\S+)(?: (.*))?$/;
@@ -2188,6 +2275,11 @@ sub i_resp_progress ($) {
     progress $msg;
 }
 
+sub i_resp_supplementary_message ($) {
+    my ($rhs) = @_;
+    $supplementary_message = protocol_read_bytes \*RO, $rhs;
+}
+
 sub i_resp_complete {
     my $pid = $i_child_pid;
     $i_child_pid = undef; # prevents killing some other process with same pid
@@ -2750,10 +2842,12 @@ sub clean_tree () {
 
 sub cmd_clean () {
     badusage "clean takes no additional arguments" if @ARGV;
+    notpushing();
     clean_tree();
 }
 
 sub build_prep () {
+    notpushing();
     badusage "-p is not allowed when building" if defined $package;
     check_not_dirty();
     clean_tree();
@@ -2957,7 +3051,7 @@ sub parseopts () {
            } elsif (m/^--since-version=([^_]+|_)$/) {
                push @ropts, $_;
                $changes_since_version = $1;
-           } elsif (m/^--([-0-9a-z]+)=(.*)/s &&
+           } elsif (m/^--([-0-9a-z]+)=(.+)/s &&
                     ($om = $opts_opt_map{$1}) &&
                     length $om->[0]) {
                push @ropts, $_;
@@ -3073,6 +3167,37 @@ sub parseopts () {
     }
 }
 
+sub finalise_opts_opts () {
+    foreach my $k (keys %opts_opt_map) {
+       my $om = $opts_opt_map{$k};
+
+       my $v = access_cfg("cmd-$k", 'RETURN-UNDEF');
+       if (defined $v) {
+           badcfg "cannot set command for $k"
+               unless length $om->[0];
+           $om->[0] = $v;
+       }
+
+       foreach my $c (access_cfg_cfgs("opts-$k")) {
+           local ($debuglevel) = $debuglevel-2;
+           my @cmd = (@git, qw(config -z --get-all), $c);
+           my $vs = cmdoutput_errok @cmd;
+           if ($?==0) {
+               badcfg "cannot configure options for $k"
+                   if $opts_opt_cmdonly{$k};
+               my $insertpos = $opts_cfg_insertpos{$k};
+               @$om = ( @$om[0..$insertpos-1],
+                        (split /\0/, $vs),
+                        @$om[$insertpos..$#$om] );
+           } elsif ($?==256) {
+               die "$k $c ?" if length $vs;
+           } else {
+               failedcmd @cmd;
+           }
+       }
+    }
+}
+
 if ($ENV{$fakeeditorenv}) {
     quilt_fixup_editor();
 }