chiark / gitweb /
tests: run-all: allow arg(s) to override test list
[dgit.git] / dgit
diff --git a/dgit b/dgit
index 94454d33170b15546c42f0d42c77f27d0453ba7f..2d2c0f19fe7dc60ca90c0c50f6b8d55f77d38cb1 100755 (executable)
--- a/dgit
+++ b/dgit
@@ -40,6 +40,7 @@ our @ropts;
 our $sign = 1;
 our $dryrun_level = 0;
 our $changesfile;
+our $buildproductsdir = '..';
 our $new_package = 0;
 our $ignoredirty = 0;
 our $noquilt = 0;
@@ -51,6 +52,8 @@ our $initiator_tempdir;
 
 our %format_ok = map { $_=>1 } ("1.0","3.0 (native)","3.0 (quilt)");
 
+our $suite_re = '[-+.0-9a-z]+';
+
 our (@git) = qw(git);
 our (@dget) = qw(dget);
 our (@dput) = qw(dput);
@@ -331,7 +334,7 @@ sub printcmd {
 }
 
 sub failedcmd {
-    { local ($!); printcmd \*STDERR, "$_[0]: failed command:", @_ or die $!; };
+    { local ($!); printcmd \*STDERR, "$us: failed command:", @_ or die $!; };
     if ($!) {
        fail "failed to fork/exec: $!";
     } elsif (!($? & 0xff)) {
@@ -447,15 +450,18 @@ our %defcfg = ('dgit.default.distro' => 'debian',
               'dgit.default.username' => '',
               'dgit.default.archive-query-default-component' => 'main',
               'dgit.default.ssh' => 'ssh',
+              'dgit-distro.debian.git-user' => 'dgit',
               'dgit-distro.debian.git-host' => 'git.debian.org',
               'dgit-distro.debian.git-proto' => 'git+ssh://',
-              'dgit-distro.debian.git-path' => '/git/dgit-repos/repos',
-              'dgit-distro.debian.git-check' => 'ssh-cmd',
-              'dgit-distro.debian.git-create' => 'ssh-cmd',
+              'dgit-distro.debian.git-path' => '/dgit-repos/',
+              'dgit-distro.debian.git-check' => 'true',
+              'dgit-distro.debian.git-create' => 'true',
               'dgit-distro.debian.sshpsql-host' => 'coccia.debian.org',
               'dgit-distro.debian.sshpsql-dbname' => 'service=projectb',
               'dgit-distro.debian.upload-host' => 'ftp-master', # for dput
               'dgit-distro.debian.mirror' => 'http://ftp.debian.org/debian/',
+ 'dgit-distro.debian.backports-quirk' => '%-backports*',
+ 'dgit-distro.debian-backports.mirror' => 'http://backports.debian.org/debian-backports/',
               'dgit-distro.test-dummy.ssh' => "$td/ssh",
               'dgit-distro.test-dummy.username' => "alice",
               'dgit-distro.test-dummy.git-check' => "ssh-cmd",
@@ -488,16 +494,41 @@ sub cfg {
     badcfg "need value for one of: @_";
 }
 
-sub access_distro () {
+sub access_basedistro () {
     return cfg("dgit-suite.$isuite.distro",
               "dgit.default.distro");
 }
 
+sub access_quirk () {
+    # returns (quirk name, distro to use instead, quirk-specific info)
+    my $basedistro = access_basedistro();
+    my $backports_quirk = cfg("dgit-distro.$basedistro.backports-quirk",
+                             'RETURN-UNDEF');
+    if (defined $backports_quirk) {
+       my $re = $backports_quirk;
+       $re =~ s/[^-0-9a-z_\%*]/\\$&/ig;
+       $re =~ s/\*/.*/g;
+       $re =~ s/\%/([-0-9a-z_]+)/ or badcfg "backports-quirk needs \%";
+       if ($isuite =~ m/^$re$/) {
+           return ('backports',"$basedistro-backports",$1);
+       }
+    }
+    return ('none',$basedistro);
+}
+
+sub access_distro () {
+    return (access_quirk())[1];
+}
+
 sub access_cfg (@) {
     my (@keys) = @_;
+    my $basedistro = access_basedistro();
     my $distro = $idistro || access_distro();
-    my $value = cfg(map { ("dgit-distro.$distro.$_",
-                          "dgit.default.$_") } @keys);
+    my $value = cfg(map {
+       ("dgit-distro.$distro.$_",
+        "dgit-distro.$basedistro.$_",
+        "dgit.default.$_")
+                   } @keys);
     return $value;
 }
 
@@ -599,7 +630,7 @@ sub archive_query ($) {
     my ($method) = @_;
     my $query = access_cfg('archive-query','RETURN-UNDEF');
     if (!defined $query) {
-       my $distro = access_distro();
+       my $distro = access_basedistro();
        if ($distro eq 'debian') {
            $query = "sshpsql:".
                access_someuserhost('sshpsql').':'.
@@ -636,7 +667,7 @@ sub madison_parse ($) {
        $l =~ m{^ \s*( [^ \t|]+ )\s* \|
                   \s*( [^ \t|]+ )\s* \|
                   \s*( [^ \t|/]+ )(?:/([^ \t|/]+))? \s* \|
-                  \s*( [^ \t|]+ )\s* }x or die "$rmad $?";
+                  \s*( [^ \t|]+ )\s* }x or die "$rmad ?";
        $1 eq $package or die "$rmad $package ?";
        my $vsn = $2;
        my $newsuite = $3;
@@ -809,6 +840,8 @@ sub check_for_git () {
        my $r= cmdoutput @cmd;
        failedcmd @cmd unless $r =~ m/^[01]$/;
        return $r+0;
+    } elsif ($how eq 'true') {
+       return 1;
     } else {
        badcfg "unknown git-check \`$how'";
     }
@@ -821,6 +854,8 @@ sub create_remote_git_repo () {
            (access_cfg_ssh, access_gituserhost(),
             "set -e; cd ".access_cfg('git-path').";".
             " cp -a _template $package.git");
+    } elsif ($how eq 'true') {
+       # nothing to do
     } else {
        badcfg "unknown git-create \`$how'";
     }
@@ -1272,7 +1307,7 @@ type commit
 tag $tag
 tagger $authline
 
-$package release $cversion for $clogsuite [dgit]
+$package release $cversion for $clogsuite ($csuite) [dgit]
 END
     close TO or die $!;
 
@@ -1319,13 +1354,14 @@ sub dopush () {
     my ($clogp, $cversion, $tag, $dscfn) =
        push_parse_changelog("$clogpfn");
 
-    stat "../$dscfn" or
+    my $dscpath = "$buildproductsdir/$dscfn";
+    stat $dscpath or
        fail "looked for .dsc $dscfn, but $!;".
            " maybe you forgot to build";
 
-    responder_send_file('dsc', "../$dscfn");
+    responder_send_file('dsc', $dscpath);
 
-    push_parse_dsc("../$dscfn", $dscfn, $cversion);
+    push_parse_dsc($dscpath, $dscfn, $cversion);
 
     my $format = getfield $dsc, 'Format';
     printdebug "format $format\n";
@@ -1335,7 +1371,8 @@ sub dopush () {
     check_not_dirty();
     changedir $ud;
     progress "checking that $dscfn corresponds to HEAD";
-    runcmd qw(dpkg-source -x --), "../../../../$dscfn";
+    runcmd qw(dpkg-source -x --),
+        $dscpath =~ m#^/# ? $dscpath : "../../../$dscpath";
     my ($tree,$dir) = mktree_in_ud_from_only_subdir();
     changedir '../../../..';
     my @diffcmd = (@git, qw(diff --exit-code), $tree);
@@ -1357,23 +1394,27 @@ sub dopush () {
 #        (uploadbranch());
     my $head = rev_parse('HEAD');
     if (!$changesfile) {
-       my $multi = "../${package}_".(stripepoch $cversion)."_multi.changes";
+       my $multi = "$buildproductsdir/".
+           "${package}_".(stripepoch $cversion)."_multi.changes";
        if (stat "$multi") {
            $changesfile = $multi;
        } else {
            $!==&ENOENT or die "$multi: $!";
            my $pat = "${package}_".(stripepoch $cversion)."_*.changes";
-           my @cs = glob "../$pat";
+           my @cs = glob "$buildproductsdir/$pat";
            fail "failed to find unique changes file".
-               " (looked for $pat in .., or $multi);".
+               " (looked for $pat in $buildproductsdir, or $multi);".
                " perhaps you need to use dgit -C"
                unless @cs==1;
            ($changesfile) = @cs;
        }
+    } else {
+       $changesfile = "$buildproductsdir/$changesfile";
     }
 
     responder_send_file('changes',$changesfile);
     responder_send_command("param head $head");
+    responder_send_command("param csuite $csuite");
 
     my $tfn = sub { ".git/dgit/tag$_[0]"; };
     my $tagobjfn;
@@ -1384,7 +1425,7 @@ sub dopush () {
     } else {
        $tagobjfn =
            push_mktag($head,$clogp,$tag,
-                      "../$dscfn",
+                      $dscpath,
                       $changesfile,$changesfile,
                       $tfn);
     }
@@ -1397,27 +1438,24 @@ sub dopush () {
     if (!check_for_git()) {
        create_remote_git_repo();
     }
-    runcmd_ordryrun @git, qw(push),access_giturl(),"HEAD:".rrref();
+    runcmd_ordryrun @git, qw(push),access_giturl(),
+        "HEAD:".rrref(), "refs/tags/$tag";
     runcmd_ordryrun @git, qw(update-ref -m), 'dgit push', lrref(), 'HEAD';
 
-    if (!$we_are_responder) {
-       if (act_local()) {
-           rename "../$dscfn.tmp","../$dscfn" or die "$dscfn $!";
-       } else {
-           progress "[new .dsc left in $dscfn.tmp]";
-       }
-    }
-
     if ($we_are_responder) {
        my $dryrunsuffix = act_local() ? "" : ".tmp";
        responder_receive_files('signed-dsc-changes',
-                               "../$dscfn$dryrunsuffix",
+                               "$dscpath$dryrunsuffix",
                                "$changesfile$dryrunsuffix");
     } else {
+       if (act_local()) {
+           rename "$dscpath.tmp",$dscpath or die "$dscfn $!";
+       } else {
+           progress "[new .dsc left in $dscpath.tmp]";
+       }
        sign_changes $changesfile;
     }
 
-    runcmd_ordryrun @git, qw(push),access_giturl(),"refs/tags/$tag";
     my $host = access_cfg('upload-host','RETURN-UNDEF');
     my @hostarg = defined($host) ? ($host,) : ();
     runcmd_ordryrun @dput, @hostarg, $changesfile;
@@ -1689,10 +1727,13 @@ sub i_file_changes { }
 sub i_want_signed_tag {
     printdebug Dumper(\%i_param, $i_dscfn);
     defined $i_param{'head'} && defined $i_dscfn && defined $i_clogp
+       && defined $i_param{'csuite'}
        or badproto \*RO, "premature desire for signed-tag";
     my $head = $i_param{'head'};
     die if $head =~ m/[^0-9a-f]/ || $head !~ m/^../;
 
+    die unless $i_param{'csuite'} =~ m/^$suite_re$/;
+    $csuite = $&;
     push_parse_dsc $i_dscfn, 'remote dsc', $i_version;
 
     my $tagobjfn =
@@ -1728,6 +1769,17 @@ sub build_maybe_quilt_fixup () {
     chomp;
     return unless madformat($_);
     # sigh
+    
+    my @cmd = (@git, qw(ls-files --exclude-standard -iodm));
+    my $problems = cmdoutput @cmd;
+    if (length $problems) {
+       print STDERR "problematic files:\n";
+       print STDERR "  $_\n" foreach split /\n/, $problems;
+       fail "Cannot do quilt fixup in tree containing ignored files.  ".
+           "Perhaps your package's clean target is broken, in which".
+           " case -wg (which says to use git-clean -xdf) may help.";
+    }
+
     my $clogp = parsechangelog();
     my $version = getfield $clogp, 'Version';
     my $author = getfield $clogp, 'Maintainer';
@@ -1787,9 +1839,21 @@ sub quilt_fixup_editor () {
     exit 0;
 }
 
+sub clean_tree () {
+    if ($cleanmode eq 'dpkg-source') {
+       runcmd_ordryrun_local @dpkgbuildpackage, qw(-T clean);
+    } elsif ($cleanmode eq 'git') {
+       runcmd_ordryrun_local @git, qw(clean -xdf);
+    } elsif ($cleanmode eq 'none') {
+    } else {
+       die "$cleanmode ?";
+    }
+}
+
 sub build_prep () {
     badusage "-p is not allowed when building" if defined $package;
     check_not_dirty();
+    clean_tree();
     my $clogp = parsechangelog();
     $isuite = getfield $clogp, 'Distribution';
     $package = getfield $clogp, 'Source';
@@ -1801,6 +1865,13 @@ sub changesopts () {
     my @opts =@changesopts[1..$#changesopts];
     if (!defined $changes_since_version) {
        my @vsns = archive_query('archive_query');
+       my @quirk = access_quirk();
+       if ($quirk[0] eq 'backports') {
+           local $isuite = $quirk[2];
+           local $csuite;
+           canonicalise_suite();
+           push @vsns, archive_query('archive_query');
+       }
        if (@vsns) {
            @vsns = map { $_->[0] } @vsns;
            @vsns = sort { -version_compare_string($a, $b) } @vsns;
@@ -1818,16 +1889,12 @@ sub changesopts () {
 }
 
 sub cmd_build {
-    badusage "dgit build implies --clean=dpkg-source"
-       if $cleanmode ne 'dpkg-source';
     build_prep();
     runcmd_ordryrun_local @dpkgbuildpackage, qw(-us -uc), changesopts(), @ARGV;
     printdone "build successful\n";
 }
 
 sub cmd_git_build {
-    badusage "dgit git-build implies --clean=dpkg-source"
-       if $cleanmode ne 'dpkg-source';
     build_prep();
     my @cmd =
        (qw(git-buildpackage -us -uc --git-no-sign-tags),
@@ -1849,12 +1916,6 @@ sub build_source {
        runcmd_ordryrun_local (@dpkgbuildpackage, qw(-us -uc -S)),
            changesopts();
     } else {
-       if ($cleanmode eq 'git') {
-           runcmd_ordryrun_local @git, qw(clean -xdf);
-       } elsif ($cleanmode eq 'none') {
-       } else {
-           die "$cleanmode ?";
-       }
        my $pwd = cmdoutput qw(env - pwd);
        my $leafdir = basename $pwd;
        changedir "..";
@@ -1969,6 +2030,9 @@ sub parseopts () {
            } elsif (m/^--distro=(.*)/s) {
                push @ropts, $_;
                $idistro = $1;
+           } elsif (m/^--build-products-dir=(.*)/s) {
+               push @ropts, $_;
+               $buildproductsdir = $1;
            } elsif (m/^--clean=(dpkg-source|git|none)$/s) {
                push @ropts, $_;
                $cleanmode = $1;
@@ -2017,6 +2081,9 @@ sub parseopts () {
                } elsif (s/^-C(.*)//s) {
                    push @ropts, $&;
                    $changesfile = $1;
+                   if ($changesfile =~ s#^(.*)/##) {
+                       $buildproductsdir = $1;
+                   }
                } elsif (s/^-k(.*)//s) {
                    $keyid=$1;
                } elsif (s/^-wn//s) {