chiark / gitweb /
New --damp-run mode, for more convenient and fuller testing etc.
[dgit.git] / dgit
diff --git a/dgit b/dgit
index 9d1570c7a9f86fdfba869973e12cc08487cb8afb..3c13d79f66182a9579b6f56268161f6c9d9e3716 100755 (executable)
--- a/dgit
+++ b/dgit
@@ -28,6 +28,8 @@ use File::Basename;
 use Dpkg::Version;
 use POSIX;
 
+our $our_version = 'UNRELEASED'; ###substituted###
+
 our $isuite = 'unstable';
 our $idistro;
 our $package;
@@ -69,6 +71,8 @@ our $keyid;
 our $debug = 0;
 open DEBUG, ">/dev/null" or die $!;
 
+autoflush STDOUT 1;
+
 our $remotename = 'dgit';
 our @ourdscfield = qw(Dgit Vcs-Dgit-Master);
 our $branchprefix = 'dgit';
@@ -129,7 +133,7 @@ sub url_get {
     return $r->decoded_content();
 }
 
-our ($dscdata,$dscurl,$dsc);
+our ($dscdata,$dscurl,$dsc,$skew_warning_vsn);
 
 sub printcmd {
     my $fh = shift @_;
@@ -207,6 +211,19 @@ sub runcmd_ordryrun {
     }
 }
 
+sub runcmd_ordryrun_local {
+    if ($dryrun <= 1) {
+       runcmd @_;
+    } else {
+       dryrun_report @_;
+    }
+}
+
+sub shell_cmd {
+    my ($first_shell, @cmd) = @_;
+    return qw(sh -ec), $first_shell.'; exec "$@"', 'x', @cmd;
+}
+
 our $helpmsg = <<END;
 main usages:
   dgit [dgit-opts] clone [dgit-opts] package [suite] [./dir|/dir]
@@ -216,6 +233,7 @@ main usages:
 important dgit options:
   -k<keyid>           sign tag and package with <keyid> instead of default
   --dry-run -n        do not change anything, but go through the motions
+  --damp-run -L       like --dry-run but make local changes, without signing
   --new -N            allow introducing a new package
   --debug -D          increase debug level
   -c<name>=<value>    set git config option (used directly by dgit too)
@@ -230,7 +248,7 @@ sub badusage {
     exit 8;
 }
 
-sub helponly () {
+sub cmd_help () {
     print $helpmsg or die $!;
     exit 0;
 }
@@ -247,6 +265,7 @@ our %defcfg = ('dgit.default.distro' => 'debian',
               'dgit-distro.debian.sshdakls-host' => 'coccia.debian.org',
               'dgit-distro.debian.sshdakls-dir' =>
                   '/srv/ftp-master.debian.org/ftp/dists',
+              'dgit-distro.debian.upload-host' => 'ftp-master', # for dput
               'dgit-distro.debian.mirror' => 'http://ftp.debian.org/debian/');
 
 sub cfg {
@@ -332,7 +351,7 @@ sub getfield ($$) {
 sub parsechangelog {
     my $c = Dpkg::Control::Hash->new();
     my $p = new IO::Handle;
-    my @cmd = (qw(dpkg-parsechangelog));
+    my @cmd = (qw(dpkg-parsechangelog), @_);
     open $p, '-|', @cmd or die $!;
     $c->parse($p);
     $?=0; $!=0; close $p or failedcmd @cmd;
@@ -442,16 +461,19 @@ sub get_archive_dsc () {
        my ($vsn,$subpath) = @$vinfo;
        $dscurl = access_cfg('mirror').$subpath;
        $dscdata = url_get($dscurl);
-       next unless defined $dscdata;
+       if (!$dscdata) {
+           $skew_warning_vsn = $vsn if !defined $skew_warning_vsn;
+           next;
+       }
        my $dscfh = new IO::File \$dscdata, '<' or die $!;
        print DEBUG Dumper($dscdata) if $debug>1;
        $dsc = parsecontrolfh($dscfh,$dscurl, allow_pgp=>1);
        print DEBUG Dumper($dsc) if $debug>1;
        my $fmt = getfield $dsc, 'Format';
        fail "unsupported source format $fmt, sorry" unless $format_ok{$fmt};
-       return $dsc;
+       return;
     }
-    return undef;
+    $dsc = undef;
 }
 
 sub check_for_git () {
@@ -482,7 +504,7 @@ sub create_remote_git_repo () {
     }
 }
 
-our ($dsc_hash,$upload_hash);
+our ($dsc_hash,$lastpush_hash);
 
 our $ud = '.git/dgit/unpack';
 
@@ -593,8 +615,8 @@ END
     my $outputhash = make_commit qw(../commit.tmp);
     my $cversion = getfield $clogp, 'Version';
     print "synthesised git commit from .dsc $cversion\n";
-    if ($upload_hash) {
-       runcmd @git, qw(reset --hard), $upload_hash;
+    if ($lastpush_hash) {
+       runcmd @git, qw(reset --hard), $lastpush_hash;
        runcmd qw(sh -ec), 'dpkg-parsechangelog >>../changelogold.tmp';
        my $oldclogp = parsecontrol('../changelogold.tmp','previous changelog');
        my $oversion = getfield $oldclogp, 'Version';
@@ -605,7 +627,7 @@ END
            open C, ">../commit2.tmp" or die $!;
            print C <<END or die $!;
 tree $tree
-parent $upload_hash
+parent $lastpush_hash
 parent $outputhash
 author $authline
 committer $authline
@@ -620,9 +642,9 @@ Version actually in archive:    $cversion (older)
 Last allegedly pushed/uploaded: $oversion (newer or same)
 $later_warning_msg
 END
-            $outputhash = $upload_hash;
+            $outputhash = $lastpush_hash;
         } else {
-           $outputhash = $upload_hash;
+           $outputhash = $lastpush_hash;
        }
     }
     chdir '../../../..' or die $!;
@@ -657,8 +679,7 @@ sub ensure_we_have_orig () {
        $origurl .= "/$f";
        die "$f ?" unless $f =~ m/^${package}_/;
        die "$f ?" if $f =~ m#/#;
-       runcmd_ordryrun qw(sh -ec),'cd ..; exec "$@"','x',
-           @dget,'--',$origurl;
+       runcmd_ordryrun_local shell_cmd 'cd ..', @dget,'--',$origurl;
     }
 }
 
@@ -679,71 +700,114 @@ sub is_fast_fwd ($$) {
 }
 
 sub git_fetch_us () {
-    badusage "cannot dry run with fetch" if $dryrun;
-    runcmd @git, qw(fetch),access_giturl(),fetchspec();
+    runcmd_ordryrun_local @git, qw(fetch),access_giturl(),fetchspec();
 }
 
 sub fetch_from_archive () {
     # ensures that lrref() is what is actually in the archive,
     #  one way or another
-    get_archive_dsc() or return 0;
-    foreach my $field (@ourdscfield) {
-       $dsc_hash = $dsc->{$field};
-       last if defined $dsc_hash;
-    }
-    if (defined $dsc_hash) {
-       $dsc_hash =~ m/\w+/ or fail "invalid hash in .dsc \`$dsc_hash'";
-       $dsc_hash = $&;
-       print "last upload to archive specified git hash\n";
+    get_archive_dsc();
+
+    if ($dsc) {
+       foreach my $field (@ourdscfield) {
+           $dsc_hash = $dsc->{$field};
+           last if defined $dsc_hash;
+       }
+       if (defined $dsc_hash) {
+           $dsc_hash =~ m/\w+/ or fail "invalid hash in .dsc \`$dsc_hash'";
+           $dsc_hash = $&;
+           print "last upload to archive specified git hash\n";
+       } else {
+           print "last upload to archive has NO git hash\n";
+       }
     } else {
-       print "last upload to archive has NO git hash\n";
+       print "no version available from the archive\n";
     }
 
     my $lrref_fn = ".git/".lrref();
     if (open H, $lrref_fn) {
-       $upload_hash = <H>;
-       chomp $upload_hash;
-       die "$lrref_fn $upload_hash ?" unless $upload_hash =~ m/^\w+$/;
+       $lastpush_hash = <H>;
+       chomp $lastpush_hash;
+       die "$lrref_fn $lastpush_hash ?" unless $lastpush_hash =~ m/^\w+$/;
     } elsif ($! == &ENOENT) {
-       $upload_hash = '';
+       $lastpush_hash = '';
     } else {
        die "$lrref_fn $!";
     }
-    print DEBUG "previous reference hash=$upload_hash\n";
+    print DEBUG "previous reference hash=$lastpush_hash\n";
     my $hash;
     if (defined $dsc_hash) {
        fail "missing git history even though dsc has hash -".
            " could not find commit $dsc_hash".
            " (should be in ".access_giturl()."#".rrref().")"
-           unless $upload_hash;
+           unless $lastpush_hash;
        $hash = $dsc_hash;
        ensure_we_have_orig();
-       if ($dsc_hash eq $upload_hash) {
-       } elsif (is_fast_fwd($dsc_hash,$upload_hash)) {
+       if ($dsc_hash eq $lastpush_hash) {
+       } elsif (is_fast_fwd($dsc_hash,$lastpush_hash)) {
            print STDERR <<END or die $!;
 
 Git commit in archive is behind the last version allegedly pushed/uploaded.
 Commit referred to by archive:  $dsc_hash
-Last allegedly pushed/uploaded: $upload_hash
+Last allegedly pushed/uploaded: $lastpush_hash
 $later_warning_msg
 END
-           $hash = $upload_hash;
+           $hash = $lastpush_hash;
        } else {
            fail "archive's .dsc refers to ".$dsc_hash.
-               " but this is an ancestor of ".$upload_hash;
+               " but this is an ancestor of ".$lastpush_hash;
        }
-    } else {
+    } elsif ($dsc) {
        $hash = generate_commit_from_dsc();
+    } elsif ($lastpush_hash) {
+       # only in git, not in the archive yet
+       $hash = $lastpush_hash;
+       print STDERR <<END or die $!;
+
+Package not found in the archive, but has allegedly been pushed using dgit.
+$later_warning_msg
+END
+    } else {
+       print DEBUG "nothing found!\n";
+       if (defined $skew_warning_vsn) {
+           print STDERR <<END or die $!;
+
+Warning: relevant archive skew detected.
+Archive allegedly contains $skew_warning_vsn
+But we were not able to obtain any version from the archive or git.
+
+END
+       }
+       return 0;
     }
     print DEBUG "current hash=$hash\n";
-    if ($upload_hash) {
+    if ($lastpush_hash) {
        fail "not fast forward on last upload branch!".
            " (archive's version left in DGIT_ARCHIVE)"
-           unless is_fast_fwd($upload_hash, $hash);
+           unless is_fast_fwd($lastpush_hash, $hash);
     }
-    if ($upload_hash ne $hash) {
+    if (defined $skew_warning_vsn) {
+       mkpath '.git/dgit';
+       print DEBUG "SKEW CHECK WANT $skew_warning_vsn\n";
+       my $clogf = ".git/dgit/changelog.tmp";
+       runcmd shell_cmd "exec >$clogf",
+           @git, qw(cat-file blob), "$hash:debian/changelog";
+       my $gotclogp = parsechangelog("-l$clogf");
+       my $got_vsn = getfield $gotclogp, 'Version';
+       print DEBUG "SKEW CHECK GOT $got_vsn\n";
+       if (version_compare_string($got_vsn, $skew_warning_vsn) < 0) {
+           print STDERR <<END or die $!;
+
+Warning: archive skew detected.  Using the available version:
+Archive allegedly contains    $skew_warning_vsn
+We were able to obtain only   $got_vsn
+
+END
+       }
+    }
+    if ($lastpush_hash ne $hash) {
        my @upd_cmd = (@git, qw(update-ref -m), 'dgit fetch', lrref(), $hash);
-       if (!$dryrun) {
+       if ($dryrun <= 1) {
            cmdoutput @upd_cmd;
        } else {
            dryrun_report @upd_cmd;
@@ -755,7 +819,7 @@ END
 sub clone ($) {
     my ($dstdir) = @_;
     canonicalise_suite();
-    badusage "dry run makes no sense with clone" if $dryrun;
+    badusage "dry run makes no sense with clone" if $dryrun > 1;
     mkdir $dstdir or die "$dstdir $!";
     chdir "$dstdir" or die "$dstdir $!";
     runcmd @git, qw(init -q);
@@ -767,7 +831,7 @@ sub clone ($) {
     if (check_for_git()) {
        print "fetching existing git history\n";
        git_fetch_us();
-       runcmd @git, qw(fetch origin);
+       runcmd_ordryrun_local @git, qw(fetch origin);
     } else {
        print "starting new git history\n";
     }
@@ -786,7 +850,7 @@ sub fetch () {
 
 sub pull () {
     fetch();
-    runcmd_ordryrun @git, qw(merge -m),"Merge from $csuite [dgit]",
+    runcmd_ordryrun_local @git, qw(merge -m),"Merge from $csuite [dgit]",
         lrref();
     printdone "fetched to ".lrref()." and merged into HEAD";
 }
@@ -822,10 +886,10 @@ sub commit_quilty_patch () {
        print "nothing quilty to commit, ok.\n";
        return;
     }
-    runcmd_ordryrun @git, qw(add), sort keys %adds;
+    runcmd_ordryrun_local @git, qw(add), sort keys %adds;
     my $m = "Commit Debian 3.0 (quilt) metadata";
     print "$m\n";
-    runcmd_ordryrun @git, qw(commit -m), $m;
+    runcmd_ordryrun_local @git, qw(commit -m), $m;
 }
 
 sub madformat ($) {
@@ -911,6 +975,7 @@ sub dopush () {
        create_remote_git_repo();
     }
     runcmd_ordryrun @git, qw(push),access_giturl(),"HEAD:".rrref();
+    runcmd_ordryrun @git, qw(update-ref -m), 'dgit push', lrref(), 'HEAD';
     if (!$dryrun) {
        rename "../$dscfn.tmp","../$dscfn" or die "$dscfn $!";
     } else {
@@ -920,11 +985,14 @@ sub dopush () {
        if (!defined $keyid) {
            $keyid = access_cfg('keyid','RETURN-UNDEF');
        }
-       my @tag_cmd = (@git, qw(tag -s -m),
-                      "Release $dversion for $csuite [dgit]");
-       push @tag_cmd, qw(-u),$keyid if defined $keyid;
+       my @tag_cmd = (@git, qw(tag -m),
+                      "$package release $dversion for $csuite [dgit]");
+       if ($dryrun != 1) {
+           push @tag_cmd, qw(-s);
+           push @tag_cmd, qw(-u),$keyid if defined $keyid;
+       }
        push @tag_cmd, $tag;
-       runcmd_ordryrun @tag_cmd;
+       runcmd_ordryrun_local @tag_cmd;
        my @debsign_cmd = @debsign;
        push @debsign_cmd, "-k$keyid" if defined $keyid;
        push @debsign_cmd, $changesfile;
@@ -1054,6 +1122,7 @@ sub build_maybe_quilt_fixup () {
     my $ncommits = 3;
     my $patchname = "auto-$version-$headref-$time";
     my $msg = cmdoutput @git, qw(log), "-n$ncommits";
+    mkpath '.git/dgit';
     my $descfn = ".git/dgit/quilt-description.tmp";
     open O, '>', $descfn or die "$descfn: $!";
     $msg =~ s/\n/\n /g;
@@ -1073,7 +1142,7 @@ END
        local $ENV{'EDITOR'} = cmdoutput qw(realpath --), $0;
        local $ENV{'VISUAL'} = $ENV{'EDITOR'};
        local $ENV{$fakeeditorenv} = cmdoutput qw(realpath --), $descfn;
-       runcmd_ordryrun @dpkgsource, qw(--commit .), $patchname;
+       runcmd_ordryrun_local @dpkgsource, qw(--commit .), $patchname;
     }
 
     if (!open P, '>>', ".pc/applied-patches") {
@@ -1118,7 +1187,7 @@ sub cmd_build {
     badusage "dgit build implies --clean=dpkg-source"
        if $cleanmode ne 'dpkg-source';
     build_prep();
-    runcmd_ordryrun @dpkgbuildpackage, qw(-us -uc), changesopts(), @ARGV;
+    runcmd_ordryrun_local @dpkgbuildpackage, qw(-us -uc), changesopts(), @ARGV;
     printdone "build successful\n";
 }
 
@@ -1134,7 +1203,7 @@ sub cmd_git_build {
        push @cmd, "--git-debian-branch=".lbranch();
     }
     push @cmd, changesopts();
-    runcmd_ordryrun @cmd, @ARGV;
+    runcmd_ordryrun_local @cmd, @ARGV;
     printdone "build successful\n";
 }
 
@@ -1143,10 +1212,11 @@ sub build_source {
     $sourcechanges = "${package}_".(stripepoch $version)."_source.changes";
     $dscfn = dscfn($version);
     if ($cleanmode eq 'dpkg-source') {
-       runcmd_ordryrun (@dpkgbuildpackage, qw(-us -uc -S)), changesopts();
+       runcmd_ordryrun_local (@dpkgbuildpackage, qw(-us -uc -S)),
+           changesopts();
     } else {
        if ($cleanmode eq 'git') {
-           runcmd_ordryrun @git, qw(clean -xdf);
+           runcmd_ordryrun_local @git, qw(clean -xdf);
        } elsif ($cleanmode eq 'none') {
        } else {
            die "$cleanmode ?";
@@ -1154,9 +1224,9 @@ sub build_source {
        my $pwd = cmdoutput qw(env - pwd);
        my $leafdir = basename $pwd;
        chdir ".." or die $!;
-       runcmd_ordryrun @dpkgsource, qw(-b --), $leafdir;
+       runcmd_ordryrun_local @dpkgsource, qw(-b --), $leafdir;
        chdir $pwd or die $!;
-       runcmd_ordryrun qw(sh -ec),
+       runcmd_ordryrun_local qw(sh -ec),
            'exec >$1; shift; exec "$@"','x',
            "../$sourcechanges",
            @dpkggenchanges, qw(-S), changesopts();
@@ -1173,7 +1243,7 @@ sub cmd_sbuild {
     build_source();
     chdir ".." or die $!;
     my $pat = "${package}_".(stripepoch $version)."_*.changes";
-    if (!$dryrun) {
+    if ($dryrun <= 1) {
        stat $dscfn or fail "$dscfn (in parent directory): $!";
        stat $sourcechanges or fail "$sourcechanges (in parent directory): $!";
        foreach my $cf (glob $pat) {
@@ -1184,7 +1254,7 @@ sub cmd_sbuild {
     runcmd_ordryrun @sbuild, @ARGV, qw(-d), $isuite, $dscfn;
     runcmd_ordryrun @mergechanges, glob $pat;
     my $multichanges = "${package}_".(stripepoch $version)."_multi.changes";
-    if (!$dryrun) {
+    if ($dryrun <= 1) {
        stat $multichanges or fail "$multichanges: $!";
     }
     printdone "build successful, results in $multichanges\n" or die $!;
@@ -1197,6 +1267,11 @@ sub cmd_quilt_fixup {
     build_maybe_quilt_fixup();
 }
 
+sub cmd_version {
+    print "dgit version $our_version\n" or die $!;
+    exit 0;
+}
+
 sub parseopts () {
     my $om;
     while (@ARGV) {
@@ -1205,11 +1280,15 @@ sub parseopts () {
        last if m/^--?$/;
        if (m/^--/) {
            if (m/^--dry-run$/) {
+               $dryrun=2;
+           } elsif (m/^--damp-run$/) {
                $dryrun=1;
            } elsif (m/^--no-sign$/) {
                $sign=0;
            } elsif (m/^--help$/) {
-               helponly();
+               cmd_help();
+           } elsif (m/^--version$/) {
+               cmd_version();
            } elsif (m/^--new$/) {
                $new_package=1;
            } elsif (m/^--(\w+)=(.*)/s &&
@@ -1237,9 +1316,11 @@ sub parseopts () {
        } else {
            while (m/^-./s) {
                if (s/^-n/-/) {
+                   $dryrun=2;
+               } elsif (s/^-L/-/) {
                    $dryrun=1;
                } elsif (s/^-h/-/) {
-                   helponly();
+                   cmd_help();
                } elsif (s/^-D/-/) {
                    open DEBUG, ">&STDERR" or die $!;
                    $debug++;
@@ -1277,7 +1358,8 @@ if ($ENV{$fakeeditorenv}) {
 delete $ENV{'DGET_UNPACK'};
 
 parseopts();
-print STDERR "DRY RUN ONLY\n" if $dryrun;
+print STDERR "DRY RUN ONLY\n" if $dryrun > 1;
+print STDERR "DAMP RUN - WILL MAKE LOCAL (UNSIGNED) CHANGES\n" if $dryrun == 1;
 if (!@ARGV) {
     print STDERR $helpmsg or die $!;
     exit 8;