chiark / gitweb /
Dgit.pm: Move $playground global to dgit.
[dgit.git] / dgit
diff --git a/dgit b/dgit
index 0ef27f050976e9b550221135f649da527a8e7c7f..9ce797200102205a4dc2f566cdd6faf281ab00ae 100755 (executable)
--- a/dgit
+++ b/dgit
@@ -2,7 +2,8 @@
 # dgit
 # Integration between git and Debian-style archives
 #
-# Copyright (C)2013-2016 Ian Jackson
+# Copyright (C)2013-2017 Ian Jackson
+# Copyright (C)2017 Sean Whitton
 #
 # 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
@@ -30,6 +31,8 @@ use File::Path;
 use File::Temp qw(tempdir);
 use File::Basename;
 use Dpkg::Version;
+use Dpkg::Compression;
+use Dpkg::Compression::Process;
 use POSIX;
 use IPC::Open2;
 use Digest::SHA;
@@ -100,6 +103,8 @@ our $git_authline_re = '^([^<>]+) \<(\S+)\> (\d+ [-+]\d+)$';
 our $splitbraincache = 'dgit-intern/quilt-cache';
 our $rewritemap = 'dgit-rewrite/map';
 
+our @dpkg_source_ignores = qw(-i(?:^|/)\.git(?:/|$) -I.git);
+
 our (@git) = qw(git);
 our (@dget) = qw(dget);
 our (@curl) = (qw(curl --proto-redir), '-all,http,https', qw(-L));
@@ -111,8 +116,8 @@ our (@ssh) = 'ssh';
 our (@dgit) = qw(dgit);
 our (@aptget) = qw(apt-get);
 our (@aptcache) = qw(apt-cache);
-our (@dpkgbuildpackage) = qw(dpkg-buildpackage -i\.git/ -I.git);
-our (@dpkgsource) = qw(dpkg-source -i\.git/ -I.git);
+our (@dpkgbuildpackage) = (qw(dpkg-buildpackage), @dpkg_source_ignores);
+our (@dpkgsource) = (qw(dpkg-source), @dpkg_source_ignores);
 our (@dpkggenchanges) = qw(dpkg-genchanges);
 our (@mergechanges) = qw(mergechanges -f);
 our (@gbp_build) = ('');
@@ -148,6 +153,7 @@ sub parseopts_late_defaults();
 sub setup_gitattrs(;$);
 sub check_gitattrs($$);
 
+our $playground;
 our $keyid;
 
 autoflush STDOUT 1;
@@ -536,6 +542,7 @@ main usages:
   dgit [dgit-opts] build [dpkg-buildpackage-opts]
   dgit [dgit-opts] sbuild [sbuild-opts]
   dgit [dgit-opts] push [dgit-opts] [suite]
+  dgit [dgit-opts] push-source [dgit-opts] [suite]
   dgit [dgit-opts] rpush build-host:build-dir ...
 important dgit options:
   -k<keyid>           sign tag and package with <keyid> instead of default
@@ -984,8 +991,8 @@ sub commit_getclogp ($) {
     our %commit_getclogp_memo;
     my $memo = $commit_getclogp_memo{$objid};
     return $memo if $memo;
-    
-    my $mclog = dgit_privdir()."clog-$objid";
+
+    my $mclog = dgit_privdir()."clog";
     runcmd shell_cmd "exec >$mclog", @git, qw(cat-file blob),
        "$objid:debian/changelog";
     $commit_getclogp_memo{$objid} = parsechangelog("-l$mclog");
@@ -1677,7 +1684,7 @@ our ($dsc_distro, $dsc_hint_tag, $dsc_hint_url);
 
 sub prep_ud () {
     dgit_privdir(); # ensures that $dgit_privdir_made is based on $maindir
-    fresh_playground 'dgit/unpack';
+    $playground = fresh_playground 'dgit/unpack';
 }
 
 sub mktree_in_ud_here () {
@@ -1844,6 +1851,40 @@ sub is_orig_file_of_vsn ($$) {
     return 1;
 }
 
+# This function determines whether a .changes file is source-only from
+# the point of view of dak.  Thus, it permits *_source.buildinfo
+# files.
+#
+# It does not, however, permit any other buildinfo files.  After a
+# source-only upload, the buildds will try to upload files like
+# foo_1.2.3_amd64.buildinfo.  If the package maintainer included files
+# named like this in their (otherwise) source-only upload, the uploads
+# of the buildd can be rejected by dak.  Fixing the resultant
+# situation can require manual intervention.  So we block such
+# .buildinfo files when the user tells us to perform a source-only
+# upload (such as when using the push-source subcommand with the -C
+# option, which calls this function).
+#
+# Note, though, that when dgit is told to prepare a source-only
+# upload, such as when subcommands like build-source and push-source
+# without -C are used, dgit has a more restrictive notion of
+# source-only .changes than dak: such uploads will never include
+# *_source.buildinfo files.  This is because there is no use for such
+# files when using a tool like dgit to produce the source package, as
+# dgit ensures the source is identical to git HEAD.
+sub test_source_only_changes ($) {
+    my ($changes) = @_;
+    foreach my $l (split /\n/, getfield $changes, 'Files') {
+        $l =~ m/\S+$/ or next;
+        # \.tar\.[a-z0-9]+ covers orig.tar and the tarballs in native packages
+        unless ($& =~ m/(?:\.dsc|\.diff\.gz|\.tar\.[a-z0-9]+|_source\.buildinfo)$/) {
+            print "purportedly source-only changes polluted by $&\n";
+            return 0;
+        }
+    }
+    return 1;
+}
+
 sub changes_update_origs_from_dsc ($$$$) {
     my ($dsc, $changes, $upstreamvsn, $changesfile) = @_;
     my %changes_f;
@@ -2381,7 +2422,7 @@ END
        local $ENV{GIT_AUTHOR_DATE} =  $authline[2];
 
        my $path = $ENV{PATH} or die;
-       
+
        # we use ../../gbp-pq-output, which (given that we are in
        # $playground/PLAYTREE, and $playground is .git/dgit/unpack,
        # is .git/dgit.
@@ -3266,8 +3307,9 @@ sub setup_mergechangelogs (;$) {
 
     my $driver = 'dpkg-mergechangelogs';
     my $cb = "merge.$driver";
-    my $attrs = '.git/info/attributes';
-    ensuredir '.git/info';
+    confess unless defined $maindir;
+    my $attrs = "$maindir_gitcommon/info/attributes";
+    ensuredir "$maindir_gitcommon/info";
 
     open NATTRS, ">", "$attrs.new" or die "$attrs.new $!";
     if (!open ATTRS, "<", $attrs) {
@@ -3312,15 +3354,16 @@ sub ensure_setup_existing_tree () {
     set_local_git_config $k, 'true';
 }
 
-sub open_gitattrs () {
-    my $gai = new IO::File ".git/info/attributes"
+sub open_main_gitattrs () {
+    confess 'internal error no maindir' unless defined $maindir;
+    my $gai = new IO::File "$maindir_gitcommon/info/attributes"
        or $!==ENOENT
-       or die "open .git/info/attributes: $!";
+       or die "open $maindir_gitcommon/info/attributes: $!";
     return $gai;
 }
 
 sub is_gitattrs_setup () {
-    my $gai = open_gitattrs();
+    my $gai = open_main_gitattrs();
     return 0 unless $gai;
     while (<$gai>) {
        return 1 if m{^\[attr\]dgit-defuse-attrs\s};
@@ -3340,15 +3383,15 @@ sub setup_gitattrs (;$) {
 END
        return;
     }
-    my $af = ".git/info/attributes";
-    ensuredir '.git/info';
+    my $af = "$maindir_gitcommon/info/attributes";
+    ensuredir "$maindir_gitcommon/info";
     open GAO, "> $af.new" or die $!;
     print GAO <<END or die $!;
 *      dgit-defuse-attrs
 [attr]dgit-defuse-attrs        $negate_harmful_gitattrs
 # ^ see GITATTRIBUTES in dgit(7) and dgit setup-new-tree in dgit(1)
 END
-    my $gai = open_gitattrs();
+    my $gai = open_main_gitattrs();
     if ($gai) {
        while (<$gai>) {
            chomp;
@@ -4184,7 +4227,7 @@ END
                quilt_check_splitbrain_cache($actualhead, $upstreamversion);
            $dgithead or fail
  "--quilt=$quilt_mode but no cached dgit view:
- perhaps tree changed since dgit build[-source] ?";
+ perhaps HEAD changed since dgit build[-source] ?";
            $split_brain = 1;
            $dgithead = splitbrain_pseudomerge($clogp,
                                               $actualhead, $dgithead,
@@ -4234,13 +4277,42 @@ END
     my $r = system @diffcmd;
     if ($r) {
        if ($r==256) {
+           my $referent = $split_brain ? $dgithead : 'HEAD';
            my $diffs = cmdoutput @git, qw(diff --stat), $tree, $dgithead;
-           fail <<END
+
+           my @mode_changes;
+           my $raw = cmdoutput @git,
+               qw(diff --no-renames -z -r --raw), $tree, $dgithead;
+           my $changed;
+           foreach (split /\0/, $raw) {
+               if (defined $changed) {
+                   push @mode_changes, "$changed: $_\n" if $changed;
+                   $changed = undef;
+                   next;
+               } elsif (m/^:0+ 0+ /) {
+                   $changed = '';
+               } elsif (m/^:(?:10*)?(\d+) (?:10*)?(\d+) /) {
+                   $changed = "Mode change from $1 to $2"
+               } else {
+                   die "$_ ?";
+               }
+           }
+           if (@mode_changes) {
+               fail <<END.(join '', @mode_changes).<<END;
+HEAD specifies a different tree to $dscfn:
+$diffs
+END
+There is a problem with your source tree (see dgit(7) for some hints).
+To see a full diff, run git diff $tree $referent
+END
+           }
+
+           fail <<END;
 HEAD specifies a different tree to $dscfn:
 $diffs
 Perhaps you forgot to build.  Or perhaps there is a problem with your
  source tree (see dgit(7) for some hints).  To see a full diff, run
-   git diff $tree HEAD
+   git diff $tree $referent
 END
        } else {
            failedcmd @diffcmd;
@@ -4523,6 +4595,21 @@ sub cmd_push {
     dopush();
 }
 
+sub cmd_push_source {
+    prep_push();
+    if ($changesfile) {
+        my $changes = parsecontrol("$buildproductsdir/$changesfile",
+                                   "source changes file");
+        unless (test_source_only_changes($changes)) {
+            fail "user-specified changes file is not source-only";
+        }
+    } else {
+        # Building a source package is very fast, so just do it
+        build_source_for_push();
+    }
+    dopush();
+}
+
 #---------- remote commands' implementation ----------
 
 sub pre_remote_push_build_host {
@@ -5019,8 +5106,8 @@ END
     changedir $maindir;
     # When we no longer need to support squeeze, use --create-reflog
     # instead of this:
-    ensuredir ".git/logs/refs/dgit-intern";
-    my $makelogfh = new IO::File ".git/logs/refs/$splitbraincache", '>>'
+    ensuredir "$maindir_gitcommon/logs/refs/dgit-intern";
+    my $makelogfh = new IO::File "$maindir_gitcommon/logs/refs/$splitbraincache", '>>'
       or die $!;
 
     my $oldcache = git_get_ref "refs/$splitbraincache";
@@ -5498,7 +5585,7 @@ sub quilt_check_splitbrain_cache ($$) {
     my $child = open GC, "-|";  defined $child or die $!;
     if (!$child) {
        chdir $maindir or die $!;
-       if (!stat ".git/logs/refs/$splitbraincache") {
+       if (!stat "$maindir_gitcommon/logs/refs/$splitbraincache") {
            $! == ENOENT or die $!;
            printdebug ">(no reflog)\n";
            exit 0;
@@ -6068,7 +6155,8 @@ sub cmd_gbp_build {
     }
     my @cmd = opts_opt_multi_cmd @gbp_build;
 
-    push @cmd, (qw(-us -uc --git-no-sign-tags), "--git-builder=@dbp");
+    push @cmd, (qw(-us -uc --git-no-sign-tags),
+               "--git-builder=".(shellquote @dbp));
 
     if ($gbp_make_orig) {
        my $priv = dgit_privdir();
@@ -6107,21 +6195,14 @@ sub cmd_gbp_build {
 }
 sub cmd_git_build { cmd_gbp_build(); } # compatibility with <= 1.0
 
+sub build_source_for_push {
+    build_source();
+    maybe_unapply_patches_again();
+    $changesfile = $sourcechanges;
+}
+
 sub build_source {
     build_prep_early();
-    my $our_cleanmode = $cleanmode;
-    if ($need_split_build_invocation) {
-       # Pretend that clean is being done some other way.  This
-       # forces us not to try to use dpkg-buildpackage to clean and
-       # build source all in one go; and instead we run dpkg-source
-       # (and build_prep() will do the clean since $clean_using_builder
-       # is false).
-       $our_cleanmode = 'ELSEWHERE';
-    }
-    if ($our_cleanmode =~ m/^dpkg-source/) {
-       # dpkg-source invocation (below) will clean, so build_prep shouldn't
-       $clean_using_builder = 1;
-    }
     build_prep();
     $sourcechanges = changespat $version,'source';
     if (act_local()) {
@@ -6129,43 +6210,33 @@ sub build_source {
            or fail "remove $sourcechanges: $!";
     }
     $dscfn = dscfn($version);
-    if ($our_cleanmode eq 'dpkg-source') {
-       maybe_apply_patches_dirtily();
-       runcmd_ordryrun_local @dpkgbuildpackage, qw(-us -uc -S),
-           changesopts();
-    } elsif ($our_cleanmode eq 'dpkg-source-d') {
-       maybe_apply_patches_dirtily();
-       runcmd_ordryrun_local @dpkgbuildpackage, qw(-us -uc -S -d),
-           changesopts();
+    my @cmd = (@dpkgsource, qw(-b --));
+    if ($split_brain) {
+        changedir $playground;
+        runcmd_ordryrun_local @cmd, "work";
+        my @udfiles = <${package}_*>;
+        changedir $maindir;
+        foreach my $f (@udfiles) {
+            printdebug "source copy, found $f\n";
+            next unless
+              $f eq $dscfn or
+              ($f =~ m/\.debian\.tar(?:\.\w+)$/ &&
+               $f eq srcfn($version, $&));
+            printdebug "source copy, found $f - renaming\n";
+            rename "$playground/$f", "../$f" or $!==ENOENT
+              or fail "put in place new source file ($f): $!";
+        }
     } else {
-       my @cmd = (@dpkgsource, qw(-b --));
-       if ($split_brain) {
-           changedir $playground;
-           runcmd_ordryrun_local @cmd, "work";
-           my @udfiles = <${package}_*>;
-           changedir $maindir;
-           foreach my $f (@udfiles) {
-               printdebug "source copy, found $f\n";
-               next unless
-                   $f eq $dscfn or
-                   ($f =~ m/\.debian\.tar(?:\.\w+)$/ &&
-                    $f eq srcfn($version, $&));
-               printdebug "source copy, found $f - renaming\n";
-               rename "$playground/$f", "../$f" or $!==ENOENT
-                   or fail "put in place new source file ($f): $!";
-           }
-       } else {
-           my $pwd = must_getcwd();
-           my $leafdir = basename $pwd;
-           changedir "..";
-           runcmd_ordryrun_local @cmd, $leafdir;
-           changedir $pwd;
-       }
-       runcmd_ordryrun_local qw(sh -ec),
-           'exec >$1; shift; exec "$@"','x',
-           "../$sourcechanges",
-           @dpkggenchanges, qw(-S), changesopts();
+        my $pwd = must_getcwd();
+        my $leafdir = basename $pwd;
+        changedir "..";
+        runcmd_ordryrun_local @cmd, $leafdir;
+        changedir $pwd;
     }
+    runcmd_ordryrun_local qw(sh -ec),
+      'exec >$1; shift; exec "$@"','x',
+      "../$sourcechanges",
+      @dpkggenchanges, qw(-S), changesopts();
 }
 
 sub cmd_build_source {
@@ -6386,6 +6457,7 @@ sub pre_archive_api_query () {
 sub cmd_archive_api_query {
     badusage "need only 1 subpath argument" unless @ARGV==1;
     my ($subpath) = @ARGV;
+    local $isuite = 'DGIT-API-QUERY-CMD';
     my @cmd = archive_api_query_cmd($subpath);
     push @cmd, qw(-f);
     debugcmd ">",@cmd;
@@ -6421,6 +6493,15 @@ sub cmd_print_dgit_repos_server_source_url {
     print $url, "\n" or die $!;
 }
 
+sub pre_print_dpkg_source_ignores {
+    not_necessarily_a_tree();
+}
+sub cmd_print_dpkg_source_ignores {
+    badusage "no arguments allowed to dgit print-dpkg-source-ignores"
+       if @ARGV;
+    print "@dpkg_source_ignores\n" or die $!;
+}
+
 sub cmd_setup_mergechangelogs {
     badusage "no arguments allowed to dgit setup-mergechangelogs" if @ARGV;
     local $isuite = 'DGIT-SETUP-TREE';