chiark / gitweb /
changelog message for "dgit help"
[dgit.git] / dgit
diff --git a/dgit b/dgit
index 88f018d36a6b750e5c81b047b006e648d63bdce8..7be8e30846a6f2d927f1db52154baf67e7168c7e 100755 (executable)
--- a/dgit
+++ b/dgit
@@ -36,6 +36,8 @@ our $sign = 1;
 our $dryrun = 0;
 our $changesfile;
 our $new_package = 0;
+our $ignoredirty = 0;
+our $noquilt = 0;
 our $existing_package = 'dpkg';
 our $cleanmode = 'dpkg-source';
 
@@ -83,7 +85,16 @@ sub debiantag ($) {
     return "debian/$v";
 }
 
-sub dscfn ($) { return "${package}_$_[0].dsc"; }
+sub stripepoch ($) {
+    my ($vsn) = @_;
+    $vsn =~ s/^\d+\://;
+    return $vsn;
+}
+
+sub dscfn ($) {
+    my ($vsn) = @_;
+    return "${package}_".(stripepoch $vsn).".dsc";
+}
 
 sub changesopts () { return @changesopts[1..$#changesopts]; }
 
@@ -219,7 +230,7 @@ sub badusage {
     exit 8;
 }
 
-sub helponly () {
+sub cmd_help () {
     print $helpmsg or die $!;
     exit 0;
 }
@@ -230,13 +241,14 @@ our %defcfg = ('dgit.default.distro' => 'debian',
               'dgit.default.ssh' => 'ssh',
               'dgit-distro.debian.git-host' => 'git.debian.org',
               'dgit-distro.debian.git-proto' => 'git+ssh://',
-              'dgit-distro.debian.git-path' => '/git/dgit-repos',
+              '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.sshdakls-host' => 'coccia.debian.org',
               'dgit-distro.debian.sshdakls-dir' =>
                   '/srv/ftp-master.debian.org/ftp/dists',
-              'dgit-distro.debian.mirror' => 'http://http.debian.net/debian/');
+              'dgit-distro.debian.upload-host' => 'ftp-master', # for dput
+              'dgit-distro.debian.mirror' => 'http://ftp.debian.org/debian/');
 
 sub cfg {
     foreach my $c (@_) {
@@ -399,7 +411,7 @@ sub madison_parse ($) {
        }
        $5 eq 'source' or die "$rmad ?";
        my $prefix = substr($package, 0, $package =~ m/^l/ ? 4 : 1);
-       my $subpath = "/pool/$component/$prefix/$package/${package}_$vsn.dsc";
+       my $subpath = "/pool/$component/$prefix/$package/".dscfn($vsn);
        push @out, [$vsn,$subpath,$newsuite];
     }
     return sort { -version_compare_string($a->[0],$b->[0]); } @out;
@@ -415,6 +427,8 @@ sub canonicalise_suite_madison ($$) {
 }
 
 sub canonicalise_suite () {
+    return if defined $csuite;
+    fail "cannot operate on $isuite suite" if $isuite eq 'UNRELEASED';
     $csuite = archive_query('canonicalise_suite');
     if ($isuite ne $csuite) {
        # madison canonicalises for us
@@ -430,8 +444,6 @@ sub get_archive_dsc () {
        $dscurl = access_cfg('mirror').$subpath;
        $dscdata = url_get($dscurl);
        next unless defined $dscdata;
-       $dscurl = access_cfg('mirror').$subpath;
-       $dscdata = url_get($dscurl);
        my $dscfh = new IO::File \$dscdata, '<' or die $!;
        print DEBUG Dumper($dscdata) if $debug>1;
        $dsc = parsecontrolfh($dscfh,$dscurl, allow_pgp=>1);
@@ -499,16 +511,35 @@ sub mktree_in_ud_from_only_subdir () {
     return ($tree,$dir);
 }
 
-sub dsc_files () {
-    my $field = $dsc->{'Checksums-Sha256'} || $dsc->{Files};
-    defined $field or
-       fail "missing both Checksums-Sha256 and Files in ".
+sub dsc_files_info () {
+    foreach my $csumi (['Checksums-Sha256','Digest::SHA', 'new(256)'],
+                      ['Checksums-Sha1',  'Digest::SHA', 'new(1)'],
+                      ['Files',           'Digest::MD5', 'new()']) {
+       my ($fname, $module, $method) = @$csumi;
+       my $field = $dsc->{$fname};
+       next unless defined $field;
+       eval "use $module; 1;" or die $@;
+       my @out;
+       foreach (split /\n/, $field) {
+           next unless m/\S/;
+           m/^(\w+) (\d+) (\S+)$/ or
+               fail "could not parse .dsc $fname line \`$_'";
+           my $digester = eval "$module"."->$method;" or die $@;
+           push @out, {
+               Hash => $1,
+               Bytes => $2,
+               Filename => $3,
+               Digester => $digester,
+           };
+       }
+       return @out;
+    }
+    fail "missing any supported Checksums-* or Files field in ".
        $dsc->get_option('name');
-    map {
-       m/^\w+ \d+ (\S+)$/ or
-           fail "could not parse .dsc Files/Checksums line \`$_'";
-       $1;
-    } grep m/\S/, split /\n/, $field;
+}
+
+sub dsc_files () {
+    map { $_->{Filename} } dsc_files_info();
 }
 
 sub is_orig_file ($) {
@@ -591,12 +622,9 @@ Last allegedly pushed/uploaded: $oversion (newer or same)
 $later_warning_msg
 END
             $outputhash = $upload_hash;
-       } elsif ($outputhash ne $upload_hash) {
-           fail "version in archive ($cversion)".
-               " is same as version in git".
-               " to-be-uploaded (upload/) branch ($oversion)".
-               " but archive version hash no commit hash?!";
-        }
+        } else {
+           $outputhash = $upload_hash;
+       }
     }
     chdir '../../../..' or die $!;
     runcmd @git, qw(update-ref -m),"dgit fetch import $cversion",
@@ -608,10 +636,20 @@ END
 }
 
 sub ensure_we_have_orig () {
-    foreach my $f (dsc_files()) {
+    foreach my $fi (dsc_files_info()) {
+       my $f = $fi->{Filename};
        next unless is_orig_file($f);
-       if (stat "../$f") {
-           die "$f ?" unless -f _;
+       if (open F, "<", "../$f") {
+           $fi->{Digester}->reset();
+           $fi->{Digester}->addfile(*F);
+           F->error and die $!;
+           my $got = $fi->{Digester}->hexdigest();
+           $got eq $fi->{Hash} or
+               fail "existing file $f has hash $got but .dsc".
+                   " demands hash $fi->{Hash}".
+                   " (perhaps you should delete this file?)";
+           print "using existing $f\n";
+           next;
        } else {
            die "$f $!" unless $!==&ENOENT;
        }
@@ -631,13 +669,18 @@ sub rev_parse ($) {
 
 sub is_fast_fwd ($$) {
     my ($ancestor,$child) = @_;
-    my $mb = cmdoutput @git, qw(merge-base), $ancestor, $child;
-    return rev_parse($mb) eq rev_parse($ancestor);
+    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 () {
-    badusage "cannot dry run with fetch" if $dryrun;
-    runcmd @git, qw(fetch),access_giturl(),fetchspec();
+    runcmd_ordryrun @git, qw(fetch),access_giturl(),fetchspec();
 }
 
 sub fetch_from_archive () {
@@ -724,7 +767,7 @@ sub clone ($) {
     if (check_for_git()) {
        print "fetching existing git history\n";
        git_fetch_us();
-       runcmd @git, qw(fetch origin);
+       runcmd_ordryrun @git, qw(fetch origin);
     } else {
        print "starting new git history\n";
     }
@@ -749,6 +792,7 @@ sub pull () {
 }
 
 sub check_not_dirty () {
+    return if $ignoredirty;
     my @cmd = (@git, qw(diff --quiet HEAD));
     printcmd(\*DEBUG,"+",@cmd) if $debug>0;
     $!=0; $?=0; system @cmd;
@@ -766,7 +810,7 @@ sub commit_quilty_patch () {
     my $bad=0;
     foreach my $l (split /\n/, $output) {
        next unless $l =~ m/\S/;
-       if ($l =~ m{^\?\? (.pc|debian/patches)}) {
+       if ($l =~ m{^(?:\?\?| M) (.pc|debian/patches)}) {
            $adds{$1}++;
        } else {
            print STDERR "git status: $l\n";
@@ -788,6 +832,10 @@ sub madformat ($) {
     my ($format) = @_;
     return 0 unless $format eq '3.0 (quilt)';
     print "Format \`$format', urgh\n";
+    if ($noquilt) {
+       print "Not doing any fixup of \`$format' due to --no-quilt-fixup";
+       return 0;
+    }
     return 1;
 }
 
@@ -838,12 +886,12 @@ sub dopush () {
     $dsc->{$ourdscfield[0]} = rev_parse('HEAD');
     $dsc->save("../$dscfn.tmp") or die $!;
     if (!$changesfile) {
-       my $multi = "../${package}_${cversion}_multi.changes";
+       my $multi = "../${package}_".(stripepoch $cversion)."_multi.changes";
        if (stat "$multi") {
            $changesfile = $multi;
        } else {
            $!==&ENOENT or die "$multi: $!";
-           my $pat = "${package}_${cversion}_*.changes";
+           my $pat = "${package}_".(stripepoch $cversion)."_*.changes";
            my @cs = glob "../$pat";
            fail "failed to find unique changes file".
                " (looked for $pat in .., or $multi);".
@@ -863,6 +911,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 {
@@ -873,7 +922,7 @@ sub dopush () {
            $keyid = access_cfg('keyid','RETURN-UNDEF');
        }
        my @tag_cmd = (@git, qw(tag -s -m),
-                      "Release $dversion for $csuite [dgit]");
+                      "$package release $dversion for $csuite [dgit]");
        push @tag_cmd, qw(-u),$keyid if defined $keyid;
        push @tag_cmd, $tag;
        runcmd_ordryrun @tag_cmd;
@@ -954,7 +1003,7 @@ sub cmd_pull {
 sub cmd_push {
     parseopts();
     badusage "-p is not allowed with dgit push" if defined $package;
-    runcmd @git, qw(diff --quiet HEAD);
+    check_not_dirty();
     my $clogp = parsechangelog();
     $package = getfield $clogp, 'Source';
     if (@ARGV==0) {
@@ -970,7 +1019,10 @@ sub cmd_push {
        git_fetch_us();
     }
     if (fetch_from_archive()) {
-       is_fast_fwd(lrref(), 'HEAD') or die;
+       is_fast_fwd(lrref(), 'HEAD') or
+           fail "dgit push: HEAD is not a descendant".
+               " of the archive's version.\n".
+               "$us: To overwrite it, use git-merge -s ours ".lrref().".";
     } else {
        $new_package or
            fail "package appears to be new in this suite;".
@@ -995,18 +1047,24 @@ sub build_maybe_quilt_fixup () {
     chomp;
     return unless madformat($_);
     # sigh
+    my $clogp = parsechangelog();
+    my $version = getfield $clogp, 'Version';
+    my $author = getfield $clogp, 'Maintainer';
     my $headref = rev_parse('HEAD');
     my $time = time;
+    my $ncommits = 3;
     my $patchname = "auto-$version-$headref-$time";
-    my $author = cmdoutput @git, qw(log -n1), '--pretty=format:%an <%ae>';
-    my $msg = cmdoutput @git, qw(log -n1), "--pretty=format:%s\n%b";
+    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;
     $msg =~ s/^\s+$/ ./mg;
     print O <<END or die $!;
-Description: $msg
- [generated from git commit $headref]
+Description: Automatically generated patch ($clogp->{Version})
+ Last (up to) $ncommits git changes, FYI:
+ .
+ $msg
 Author: $author
 
 ---
@@ -1019,6 +1077,13 @@ END
        local $ENV{$fakeeditorenv} = cmdoutput qw(realpath --), $descfn;
        runcmd_ordryrun @dpkgsource, qw(--commit .), $patchname;
     }
+
+    if (!open P, '>>', ".pc/applied-patches") {
+       $!==&ENOENT or die $!;
+    } else {
+       close P;
+    }
+
     commit_quilty_patch();
 }
 
@@ -1041,19 +1106,32 @@ sub quilt_fixup_editor () {
     exit 0;
 }
 
-sub cmd_build {
-    # we pass further options and args to git-buildpackage
-    badusage "-p is not allowed with dgit build" if defined $package;
-    badusage "dgit build implies --clean=dpkg-source" if defined $package;
+sub build_prep () {
+    badusage "-p is not allowed when building" if defined $package;
+    check_not_dirty();
     my $clogp = parsechangelog();
     $isuite = getfield $clogp, 'Distribution';
     $package = getfield $clogp, 'Source';
     $version = getfield $clogp, 'Version';
     build_maybe_quilt_fixup();
+}
+
+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;
+    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),
         "--git-builder=@dpkgbuildpackage");
-    unless (grep { m/^--git-debian-branch/ } @ARGV) {
+    unless (grep { m/^--git-debian-branch|^--git-ignore-branch/ } @ARGV) {
        canonicalise_suite();
        push @cmd, "--git-debian-branch=".lbranch();
     }
@@ -1063,15 +1141,9 @@ sub cmd_build {
 }
 
 sub build_source {
-    badusage "-p is not allowed with this action" if defined $package;
-    check_not_dirty();
-    my $clogp = parsechangelog();
-    $package = getfield $clogp, 'Source';
-    $isuite = getfield $clogp, 'Distribution';
-    $version = getfield $clogp, 'Version';
-    $sourcechanges = "${package}_${version}_source.changes";
+    build_prep();
+    $sourcechanges = "${package}_".(stripepoch $version)."_source.changes";
     $dscfn = dscfn($version);
-    build_maybe_quilt_fixup();
     if ($cleanmode eq 'dpkg-source') {
        runcmd_ordryrun (@dpkgbuildpackage, qw(-us -uc -S)), changesopts();
     } else {
@@ -1102,7 +1174,7 @@ sub cmd_build_source {
 sub cmd_sbuild {
     build_source();
     chdir ".." or die $!;
-    my $pat = "${package}_${version}_*.changes";
+    my $pat = "${package}_".(stripepoch $version)."_*.changes";
     if (!$dryrun) {
        stat $dscfn or fail "$dscfn (in parent directory): $!";
        stat $sourcechanges or fail "$sourcechanges (in parent directory): $!";
@@ -1113,7 +1185,7 @@ sub cmd_sbuild {
     }
     runcmd_ordryrun @sbuild, @ARGV, qw(-d), $isuite, $dscfn;
     runcmd_ordryrun @mergechanges, glob $pat;
-    my $multichanges = "${package}_${version}_multi.changes";
+    my $multichanges = "${package}_".(stripepoch $version)."_multi.changes";
     if (!$dryrun) {
        stat $multichanges or fail "$multichanges: $!";
     }
@@ -1139,7 +1211,7 @@ sub parseopts () {
            } elsif (m/^--no-sign$/) {
                $sign=0;
            } elsif (m/^--help$/) {
-               helponly();
+               cmd_help();
            } elsif (m/^--new$/) {
                $new_package=1;
            } elsif (m/^--(\w+)=(.*)/s &&
@@ -1157,6 +1229,10 @@ sub parseopts () {
                $cleanmode = $1;
            } elsif (m/^--clean=(.*)$/s) {
                badusage "unknown cleaning mode \`$1'";
+           } elsif (m/^--ignore-dirty$/s) {
+               $ignoredirty = 1;
+           } elsif (m/^--no-quilt-fixup$/s) {
+               $noquilt = 1;
            } else {
                badusage "unknown long option \`$_'";
            }
@@ -1165,7 +1241,7 @@ sub parseopts () {
                if (s/^-n/-/) {
                    $dryrun=1;
                } elsif (s/^-h/-/) {
-                   helponly();
+                   cmd_help();
                } elsif (s/^-D/-/) {
                    open DEBUG, ">&STDERR" or die $!;
                    $debug++;
@@ -1200,6 +1276,8 @@ if ($ENV{$fakeeditorenv}) {
     quilt_fixup_editor();
 }
 
+delete $ENV{'DGET_UNPACK'};
+
 parseopts();
 print STDERR "DRY RUN ONLY\n" if $dryrun;
 if (!@ARGV) {