chiark / gitweb /
dgit: commit_quilty_patch: Handle other file statuses too
[dgit.git] / dgit
diff --git a/dgit b/dgit
index aaafefb736a05d88258e7c5d4f0023c174f37cf3..1f065dd3bbc9436804f53a52bfa50fb117553dc0 100755 (executable)
--- a/dgit
+++ b/dgit
@@ -18,6 +18,9 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+END { $? = $Debian::Dgit::ExitStatus::desired // -1; };
+use Debian::Dgit::ExitStatus;
+
 use strict;
 
 use Debian::Dgit qw(:DEFAULT :playground);
@@ -95,7 +98,7 @@ our %format_ok = map { $_=>1 } ("1.0","3.0 (native)","3.0 (quilt)");
 
 our $suite_re = '[-+.0-9a-z]+';
 our $cleanmode_re = 'dpkg-source(?:-d)?|git|git-ff|check|none';
-our $orig_f_comp_re = 'orig(?:-[-0-9a-z]+)?';
+our $orig_f_comp_re = qr{orig(?:-$extra_orig_namepart_re)?};
 our $orig_f_sig_re = '\\.(?:asc|gpg|pgp)';
 our $orig_f_tail_re = "$orig_f_comp_re\\.tar(?:\\.\\w+)?(?:$orig_f_sig_re)?";
 
@@ -114,6 +117,7 @@ our (@gpg) = qw(gpg);
 our (@sbuild) = qw(sbuild);
 our (@ssh) = 'ssh';
 our (@dgit) = qw(dgit);
+our (@git_debrebase) = qw(git-debrebase);
 our (@aptget) = qw(apt-get);
 our (@aptcache) = qw(apt-cache);
 our (@dpkgbuildpackage) = (qw(dpkg-buildpackage), @dpkg_source_ignores);
@@ -133,6 +137,7 @@ our %opts_opt_map = ('dget' => \@dget, # accept for compatibility
                      'ssh' => \@ssh,
                      'dgit' => \@dgit,
                      'git' => \@git,
+                    'git-debrebase' => \@git_debrebase,
                      'apt-get' => \@aptget,
                      'apt-cache' => \@aptcache,
                      'dpkg-source' => \@dpkgsource,
@@ -236,7 +241,7 @@ END {
     }
 };
 
-sub badcfg { print STDERR "$us: invalid configuration: @_\n"; exit 12; }
+sub badcfg { print STDERR "$us: invalid configuration: @_\n"; finish 12; }
 
 sub forceable_fail ($$) {
     my ($forceoptsl, $msg) = @_;
@@ -254,7 +259,7 @@ sub forceing ($) {
 
 sub no_such_package () {
     print STDERR "$us: package $package does not exist in suite $isuite\n";
-    exit 4;
+    finish 4;
 }
 
 sub deliberately ($) {
@@ -287,6 +292,32 @@ sub dgit_privdir () {
     our $dgit_privdir_made //= ensure_a_playground 'dgit';
 }
 
+sub branch_gdr_info ($$) {
+    my ($symref, $head) = @_;
+    my ($status, $msg, $current, $ffq_prev, $gdrlast) =
+       gdr_ffq_prev_branchinfo($symref);
+    return () unless $status eq 'branch';
+    $ffq_prev = git_get_ref $ffq_prev;
+    $gdrlast  = git_get_ref $gdrlast;
+    $gdrlast &&= is_fast_fwd $gdrlast, $head;
+    return ($ffq_prev, $gdrlast);
+}
+
+sub branch_is_gdr ($$) {
+    my ($symref, $head) = @_;
+    my ($ffq_prev, $gdrlast) = branch_gdr_info($symref, $head);
+    return 0 unless $ffq_prev || $gdrlast;
+    return 1;
+}
+
+sub branch_is_gdr_unstitched_ff ($$$) {
+    my ($symref, $head, $ancestor) = @_;
+    my ($ffq_prev, $gdrlast) = branch_gdr_info($symref, $head);
+    return 0 unless $ffq_prev;
+    return 0 unless is_fast_fwd $ancestor, $ffq_prev;
+    return 1;
+}
+
 #---------- remote protocol support, common ----------
 
 # remote push initiator/responder protocol:
@@ -530,11 +561,6 @@ sub runcmd_ordryrun_local {
     }
 }
 
-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]
@@ -559,7 +585,7 @@ END
 
 sub badusage {
     print STDERR "$us: @_\n", $helpmsg or die $!;
-    exit 8;
+    finish 8;
 }
 
 sub nextarg {
@@ -572,7 +598,7 @@ sub pre_help () {
 }
 sub cmd_help () {
     print $helpmsg or die $!;
-    exit 0;
+    finish 0;
 }
 
 our $td = $ENV{DGIT_TEST_DUMMY_DIR} || "DGIT_TEST_DUMMY_DIR-unset";
@@ -3362,38 +3388,57 @@ sub open_main_gitattrs () {
     return $gai;
 }
 
+our $gitattrs_ourmacro_re = qr{^\[attr\]dgit-defuse-attrs\s};
+
 sub is_gitattrs_setup () {
+    # return values:
+    #  trueish
+    #     1: gitattributes set up and should be left alone
+    #  falseish
+    #     0: there is a dgit-defuse-attrs but it needs fixing
+    #     undef: there is none
     my $gai = open_main_gitattrs();
     return 0 unless $gai;
     while (<$gai>) {
-       return 1 if m{^\[attr\]dgit-defuse-attrs\s};
+       next unless m{$gitattrs_ourmacro_re};
+       return 1 if m{\s-working-tree-encoding\s};
+       printdebug "is_gitattrs_setup: found old macro\n";
+       return 0;
     }
     $gai->error and die $!;
-    return 0;
+    printdebug "is_gitattrs_setup: found nothing\n";
+    return undef;
 }    
 
 sub setup_gitattrs (;$) {
     my ($always) = @_;
     return unless $always || access_cfg_bool(1, 'setup-gitattributes');
 
-    if (is_gitattrs_setup()) {
+    my $already = is_gitattrs_setup();
+    if ($already) {
        progress <<END;
-[attr]dgit-defuse-attrs already found in .git/info/attributes
+[attr]dgit-defuse-attrs already found, and proper, in .git/info/attributes
  not doing further gitattributes setup
 END
        return;
     }
+    my $new = "[attr]dgit-defuse-attrs $negate_harmful_gitattrs";
     my $af = "$maindir_gitcommon/info/attributes";
     ensuredir "$maindir_gitcommon/info";
+
     open GAO, "> $af.new" or die $!;
-    print GAO <<END or die $!;
+    print GAO <<END or die $! unless defined $already;
 *      dgit-defuse-attrs
-[attr]dgit-defuse-attrs        $negate_harmful_gitattrs
+$new
 # ^ see GITATTRIBUTES in dgit(7) and dgit setup-new-tree in dgit(1)
 END
     my $gai = open_main_gitattrs();
     if ($gai) {
        while (<$gai>) {
+           if (m{$gitattrs_ourmacro_re}) {
+               die unless defined $already;
+               $_ = $new;
+           }
            chomp;
            print GAO $_, "\n" or die $!;
        }
@@ -3428,7 +3473,7 @@ sub check_gitattrs ($$) {
        # oh dear, found one
        print STDERR <<END;
 dgit: warning: $what contains .gitattributes
-dgit: .gitattributes have not been defused.  Recommended: dgit setup-new-tree.
+dgit: .gitattributes not (fully) defused.  Recommended: dgit setup-new-tree.
 END
        close $gafl;
        return;
@@ -3508,7 +3553,7 @@ sub fork_for_multisuite ($) {
                                               sub {
             @end = ();
             fetch();
-           exit 0;
+           finish 0;
        });
        # xxx collecte the ref here
 
@@ -3692,15 +3737,7 @@ sub check_not_dirty () {
 
     return if $ignoredirty;
 
-    my @cmd = (@git, qw(diff --quiet HEAD));
-    debugcmd "+",@cmd;
-    $!=0; $?=-1; system @cmd;
-    return if !$?;
-    if ($?==256) {
-       fail "working tree is dirty (does not match HEAD)";
-    } else {
-       failedcmd @cmd;
-    }
+    git_check_unmodified();
 }
 
 sub commit_admin ($) {
@@ -3709,12 +3746,21 @@ sub commit_admin ($) {
     runcmd_ordryrun_local @git, qw(commit -m), $m;
 }
 
+sub quiltify_nofix_bail ($$) {
+    my ($headinfo, $xinfo) = @_;
+    if ($quilt_mode eq 'nofix') {
+       fail "quilt fixup required but quilt mode is \`nofix'\n".
+           "HEAD commit".$headinfo." differs from tree implied by ".
+           " debian/patches".$xinfo;
+    }
+}
+
 sub commit_quilty_patch () {
     my $output = cmdoutput @git, qw(status --porcelain);
     my %adds;
     foreach my $l (split /\n/, $output) {
        next unless $l =~ m/\S/;
-       if ($l =~ m{^(?:\?\?| M) (.pc|debian/patches)}) {
+       if ($l =~ m{^(?:\?\?| [MADRC]) (.pc|debian/patches)}) {
            $adds{$1}++;
        }
     }
@@ -3723,6 +3769,7 @@ sub commit_quilty_patch () {
        progress "nothing quilty to commit, ok.";
        return;
     }
+    quiltify_nofix_bail "", " (wanted to commit patch update)";
     my @adds = map { s/[][*?\\]/\\$&/g; $_; } sort keys %adds;
     runcmd_ordryrun_local @git, qw(add -f), @adds;
     commit_admin <<END
@@ -3883,6 +3930,8 @@ sub pseudomerge_make_commit ($$$$ $$) {
        : !length  $overwrite_version ? " --overwrite"
        : " --overwrite=".$overwrite_version;
 
+    # Contributing parent is the first parent - that makes
+    # git rev-list --first-parent DTRT.
     my $pmf = dgit_privdir()."/pseudomerge";
     open MC, ">", $pmf or die "$pmf $!";
     print MC <<END or die $!;
@@ -4211,7 +4260,14 @@ END
     my $format = getfield $dsc, 'Format';
     printdebug "format $format\n";
 
+    my $symref = git_get_symref();
     my $actualhead = git_rev_parse('HEAD');
+
+    if (branch_is_gdr_unstitched_ff($symref, $actualhead, $archive_hash)) {
+       runcmd_ordryrun_local @git_debrebase, 'stitch';
+       $actualhead = git_rev_parse('HEAD');
+    }
+
     my $dgithead = $actualhead;
     my $maintviewhead = undef;
 
@@ -4543,7 +4599,7 @@ sub cmd_fetch {
     parseopts();
     fetchpullargs();
     my $multi_fetched = fork_for_multisuite(sub { });
-    exit 0 if $multi_fetched;
+    finish 0 if $multi_fetched;
     fetch();
 }
 
@@ -4752,7 +4808,7 @@ sub i_resp_complete {
 
     i_cleanup();
     printdebug "all done\n";
-    exit 0;
+    finish 0;
 }
 
 sub i_resp_file ($) {
@@ -5202,11 +5258,7 @@ sub quiltify ($$$$) {
            last;
        }
 
-       if ($quilt_mode eq 'nofix') {
-           fail "quilt fixup required but quilt mode is \`nofix'\n".
-               "HEAD commit $c->{Commit} differs from tree implied by ".
-               " debian/patches (tree object $oldtiptree)";
-       }
+       quiltify_nofix_bail " $c->{Commit}", " (tree object $oldtiptree)";
        if ($quilt_mode eq 'smash') {
            printdebug " search quitting smash\n";
            last;
@@ -5420,6 +5472,32 @@ END
 
     my $clogp = parsechangelog();
     my $headref = git_rev_parse('HEAD');
+    my $symref = git_get_symref();
+
+    if ($quilt_mode eq 'linear'
+       && !$fopts->{'single-debian-patch'}
+       && branch_is_gdr($symref, $headref)) {
+       # This is much faster.  It also makes patches that gdr
+       # likes better for future updates without laundering.
+       #
+       # However, it can fail in some casses where we would
+       # succeed: if there are existing patches, which correspond
+       # to a prefix of the branch, but are not in gbp/gdr
+       # format, gdr will fail (exiting status 7), but we might
+       # be able to figure out where to start linearising.  That
+       # will be slower so hopefully there's not much to do.
+       my @cmd = (@git_debrebase,
+                  qw(--noop-ok -funclean-mixed -funclean-ordering
+                     make-patches --quiet-would-amend));
+       # We tolerate soe snags that gdr wouldn't, by default.
+       if (act_local()) {
+           $!=0; $?=-1;
+           failedcmd @cmd if system @cmd and $?!=7;
+       } else {
+           dryrun_report @cmd;
+       }
+       $headref = git_rev_parse('HEAD');
+    }
 
     prep_ud();
     changedir $playground;
@@ -5583,7 +5661,7 @@ sub quilt_check_splitbrain_cache ($$) {
        if (!stat "$maindir_gitcommon/logs/refs/$splitbraincache") {
            $! == ENOENT or die $!;
            printdebug ">(no reflog)\n";
-           exit 0;
+           finish 0;
        }
        exec @cmd; die $!;
     }
@@ -5709,6 +5787,7 @@ sub quilt_fixup_multipatch ($$$) {
 
     rmtree '.pc';
 
+    rmtree 'debian'; # git checkout commitish paths does not delete!
     runcmd @git, qw(checkout -f), $headref, qw(-- debian);
     my $unapplied=git_add_write_tree();
     printdebug "fake orig tree object $unapplied\n";
@@ -5835,7 +5914,7 @@ sub quilt_fixup_editor () {
     }
     I2->error and die $!;
     close O or die $1;
-    exit 0;
+    finish 0;
 }
 
 sub maybe_apply_patches_dirtily () {
@@ -5939,13 +6018,21 @@ sub changesopts_initial () {
 
 sub changesopts_version () {
     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');
+       my @vsns;
+       unless (eval {
+           @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');
+           }
+           1;
+       }) {
+           print STDERR $@;
+           fail
+ "archive query failed (queried because --since-version not specified)";
        }
        if (@vsns) {
            @vsns = map { $_->[0] } @vsns;
@@ -6525,7 +6612,7 @@ sub cmd_setup_new_tree {
 
 sub cmd_version {
     print "dgit version $our_version\n" or die $!;
-    exit 0;
+    finish 0;
 }
 
 our (%valopts_long, %valopts_short);
@@ -6877,7 +6964,7 @@ print STDERR "DAMP RUN - WILL MAKE LOCAL (UNSIGNED) CHANGES\n"
     if $dryrun_level == 1;
 if (!@ARGV) {
     print STDERR $helpmsg or die $!;
-    exit 8;
+    finish 8;
 }
 $cmd = $subcommand = shift @ARGV;
 $cmd =~ y/-/_/;
@@ -6891,3 +6978,5 @@ git_slurp_config();
 my $fn = ${*::}{"cmd_$cmd"};
 $fn or badusage "unknown operation $cmd";
 $fn->();
+
+finish 0;