chiark / gitweb /
git-debrebase: classify: introduce $prevbrw (nfc)
[dgit.git] / git-debrebase
index b3b5ea691e37cee8775134adefa4818ac4957996..a3733a989d62ee890af7a8c948fe54443c5846a5 100755 (executable)
@@ -108,8 +108,9 @@ use Memoize;
 use Carp;
 use POSIX;
 use Data::Dumper;
+use Getopt::Long qw(:config posix_default gnu_compat bundling);
 
-use Debian::Dgit qw(:DEFAULT $wa);
+use Debian::Dgit qw(:DEFAULT :playground);
 
 sub badusage ($) {
     my ($m) = @_;
@@ -130,9 +131,8 @@ memoize('cfg');
 
 sub get_commit ($) {
     my ($objid) = @_;
-    my ($type,$data) = git_cat_file $objid;
-    die unless $type eq 'commit';
-    $data =~ m/(?<=\n)\n/;
+    my $data = git_cat_file $objid, 'commit';
+    $data =~ m/(?<=\n)\n/ or die "$objid ($data) ?";
     return ($`,$');
 }
 
@@ -141,10 +141,94 @@ sub D_UPS ()     { return 0x2; } # upstream files
 sub D_PAT_ADD () { return 0x4; } # debian/patches/ extra patches at end
 sub D_PAT_OTH () { return 0x8; } # debian/patches other changes
 
-our $rd = ".git/git-debrebase";
-our $ud = "$rd/work";
+
+our $playprefix = 'debrebase';
+our $rd;
+our $workarea;
+
 our @git = qw(git);
 
+sub in_workarea ($) {
+    my ($sub) = @_;
+    changedir $workarea;
+    my $r = eval { $sub->(); };
+    changedir $maindir;
+}
+
+sub fresh_workarea () {
+    $workarea = fresh_playground "$playprefix/work";
+    in_workarea sub { playtree_setup };
+}
+
+sub get_differs ($$) {
+    my ($x,$y) = @_;
+    # This resembles quiltify_trees_differ, in dgit, a bit.
+    # But we don't care about modes, or dpkg-source-unrepresentable
+    # changes, and we don't need the plethora of different modes.
+    # Conversely we need to distinguish different kinds of changes to
+    # debian/ and debian/patches/.
+
+    my $differs = 0;
+
+    my $rundiff = sub {
+       my ($opts, $limits, $fn) = @_;
+       my @cmd = (@git, qw(diff-tree -z --no-renames));
+       push @cmd, @$opts;
+       push @cmd, "$_:" foreach $x, $y;
+       push @cmd, @$limits;
+       my $diffs = cmdoutput @cmd;
+       foreach (split /\0/, $diffs) { $fn->(); }
+    };
+
+    $rundiff->([qw(--name-only)], [], sub {
+        $differs |= $_ eq 'debian' ? D_DEB : D_UPS;
+    });
+
+    if ($differs & D_DEB) {
+       $differs &= ~D_DEB;
+       $rundiff->([qw(--name-only -r)], [qw(debian)], sub {
+            $differs |= $_ eq m{^debian/patches/} ? D_PAT_OTH : D_DEB;
+       });
+       die "mysterious debian changes $x..$y"
+           unless $differs & (D_PAT_OTH|D_DEB);
+    }
+
+    if ($differs & D_PAT_OTH) {
+       my $mode;
+       $differs &= ~D_PAT_OTH;
+       my $pat_oth = sub {
+           $differs |= D_PAT_OTH;
+           no warnings qw(exiting);  last;
+       };
+       $rundiff->([qw(--name-status -r)], [qw(debian/patches/)], sub {
+            no warnings qw(exiting);
+            if (!defined $mode) {
+               $mode = $_;  next;
+           }
+           die unless s{^debian/patches/}{};
+           my $ok;
+           if ($mode eq 'A' && !m/(?:^|\.)series$/s) {
+               $ok = 1;
+           } elsif ($mode eq 'M' && $_ eq 'series') {
+               my $x_s = git_cat_file "$x:debian/patches/series", 'blob';
+               my $y_s = git_cat_file "$y:debian/patches/series", 'blob';
+               chomp $x_s;  $x_s .= "\n";
+               $ok = $x_s eq substr($y_s, 0, length $x_s);
+           } else {
+               # nope
+           }
+           $mode = undef;
+           $differs |= $ok ? D_PAT_ADD : D_PAT_OTH;
+        });
+       die "mysterious debian/patches changes $x..$y"
+           unless $differs & (D_PAT_ADD|D_PAT_OTH);
+    }
+
+    printdebug sprintf "get_differs %s, %s = %#x\n", $x, $y, $differs;
+
+    return $differs;
+}
+
 sub commit_pr_info ($) {
     my ($r) = @_;
     return Data::Dumper->dump([$r], [qw(commit)]);
@@ -200,7 +284,7 @@ sub classify ($) {
     my ($h,$m) = get_commit $objid;
 
     my ($t) = $h =~ m/^tree (\w+)$/m or die $objid;
-    my (@ph) = $h =~ m/^parent (\w+)$/m;
+    my (@ph) = $h =~ m/^parent (\w+)$/mg;
     my @p;
 
     my $r = {
@@ -219,14 +303,24 @@ sub classify ($) {
         };
     }
 
+    printdebug "classify $objid \$t=$t \@p",
+       (map { sprintf " %s/%#x", $_->{CommitId}, $_->{Differs} } @p),
+       "\n";
+
     my $classify = sub {
        my ($type, @rest) = @_;
        $r = { %$r, Type => $type, @rest };
+       if ($debuglevel) {
+           my $dd = new Data::Dumper [ $r ];
+           Terse $dd 1; Indent $dd 0; Useqq $dd 1;
+           printdebug " = $type ".(Dump $dd)."\n";
+       }
        return $r;
     };
     my $unknown = sub {
        my ($why) = @_;
        $r = { %$r, Type => qw(Unknown) };
+       printdebug " ** Unknown\n";
        return $r;
     };
 
@@ -279,7 +373,7 @@ sub classify ($) {
                           Contributor => $bytime[1]);
     }
     foreach my $p (@p) {
-       my ($p_h, $p_m) = get_commit $p;
+       my ($p_h, $p_m) = get_commit $p->{CommitId};
        $p->{IsOrigin} = $p_h !~ m/^parent \w+$/m;
        ($p->{IsDgitImport},) = $p_m =~ m/^\[dgit import ([0-9a-z]+) .*\]$/m;
     }
@@ -296,19 +390,24 @@ sub classify ($) {
     my ($stype, $series) = git_cat_file "$t:debian/patches/series";
     my $haspatches = $stype ne 'missing' && $series =~ m/^\s*[^#\n\t ]/m;
 
+@p = reverse @p; #xxx
+
     # How to decide about l/r ordering of breakwater merges ?  git
     # --topo-order prefers to expand 2nd parent first.  There's
     # already an easy rune to look for debian/ history anyway (git log
     # debian/) so debian breakwater branch should be 1st parent; that
     # way also there's also an easy rune to look for the upstream
     # patches (--topo-order).
+
+    my $prevbrw = 0;
+
     if (@p == 2 &&
        !$haspatches &&
-       !$p[0]{IsOrigin} && # breakwater merge never starts with an origin
-       !($p[0]{Differs} & ~D_DEB) &&
-       !($p[1]{Differs} & ~D_UPS)) {
+       !$p[$prevbrw]{IsOrigin} && # breakwater never starts with an origin
+       !($p[$prevbrw]{Differs} & ~D_DEB) &&
+       !($p[!$prevbrw]{Differs} & ~D_UPS)) {
        return $classify->(qw(BreakwaterUpstreamMerge),
-                          OrigParents => [ $p[1] ]);
+                          OrigParents => [ $p[!$prevbrw] ]);
     }
     # xxx multi-.orig upstreams
 
@@ -316,7 +415,7 @@ sub classify ($) {
 }
 
 sub walk ($;$$);
-sub walk {
+sub walk ($;$$) {
     my ($input,
        $nogenerate,$report) = @_;
     # => ($tip, $breakwater_tip)
@@ -407,10 +506,11 @@ sub walk {
            $cur = $p0;
            next;
        } elsif ($ty eq 'Pseudomerge') {
-           print $report " Contributor=$ty->{Contributor}" if $report;
+           my $contrib = $cl->{Contributor}{CommitId};
+           print $report " Contributor=$contrib" if $report;
            push @pseudomerges, $cl;
            $rewrite_from_here->();
-           $cur = $ty->{Contributor};
+           $cur = $contrib;
            next;
        } elsif ($ty eq 'BreakwaterUpstreamMerge') {
            $build_start->("PreviousBreakwater", $cur);
@@ -488,13 +588,13 @@ sub walk {
 
     # Now we build it back up again
 
-    workarea_fresh();
+    fresh_workarea();
 
     my $rewriting = 0;
 
     my $rm_tree_cached = sub {
        my ($subdir) = @_;
-       runcmd @git, qw(rm --quiet -rf --cached), $subdir;
+       runcmd @git, qw(rm --quiet -rf --cached --ignore-unmatch), $subdir;
     };
     my $read_tree_debian = sub {
        my ($treeish) = @_;
@@ -573,13 +673,13 @@ sub walk {
 
 sub get_head () { return git_rev_parse qw(HEAD); }
 
-sub update_head ($$) {
+sub update_head ($$$) {
     my ($old, $new, $mrest) = @_;
     runcmd @git, qw(update-ref -m), "git-debrebase $mrest", $new, $old;
 }
 
 sub cmd_launder () {
-    badusage "no arguments to launder allowed";
+    badusage "no arguments to launder allowed" if @ARGV;
     my $old = get_head();
     my ($tip,$breakwater) = walk $old;
     update_head $old, $tip, 'launder';
@@ -598,13 +698,18 @@ sub cmd_analyse () {
        $old = get_head();
     }
     my ($dummy,$breakwater) = walk $old, 1,*STDOUT;
-    print "$breakwater BREAKWATER\n";
     STDOUT->error and die $!;
 }
 
-my $toplevel = runcmd @git, qw(rev-parse --show-toplevel);
+GetOptions("D+" => \$debuglevel) or die badusage "bad options\n";
+initdebug('git-debrebase ');
+enabledebug if $debuglevel;
+
+my $toplevel = cmdoutput @git, qw(rev-parse --show-toplevel);
 chdir $toplevel or die "chdir $toplevel: $!";
 
+$rd = fresh_playground "$playprefix/misc";
+
 my $cmd = shift @ARGV;
 my $cmdfn = $cmd;
 $cmdfn =~ y/-/_/;