X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=dgit.git;a=blobdiff_plain;f=git-debrebase;h=c89e598f599a713a9903d7dedd159d840426b7d7;hp=62fbc4d51acc4bf53ded1391c8cc906d34848d8f;hb=c7244a622fedfc203a935692a6f9e54733494dfc;hpb=c0e4a8e072c2b133c5aa27a9636ce97521ad3ebf diff --git a/git-debrebase b/git-debrebase index 62fbc4d5..c89e598f 100755 --- a/git-debrebase +++ b/git-debrebase @@ -18,6 +18,10 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +# git-debrebase new-upstreams-v0 \ +# NEW-VERSION ORIG-COMMITISH +# [EXTRA-ORIG-NAME EXTRA-ORIG-COMMITISH ...] + # usages: # git-debrebase status # git-debrebase start # like ffqrebase start + debrebase launder @@ -112,6 +116,9 @@ use Carp; use POSIX; use Data::Dumper; use Getopt::Long qw(:config posix_default gnu_compat bundling); +use Dpkg::Version; + +our ($opt_force); sub badusage ($) { my ($m) = @_; @@ -130,6 +137,13 @@ sub cfg ($) { memoize('cfg'); +sub dd ($) { + my ($v) = @_; + my $dd = new Data::Dumper [ $v ]; + Terse $dd 1; Indent $dd 0; Useqq $dd 1; + return Dump $dd; +} + sub get_commit ($) { my ($objid) = @_; my $data = git_cat_file $objid, 'commit'; @@ -248,6 +262,26 @@ sub calculate_committer_authline () { return $&; } +sub rm_subdir_cached ($) { + my ($subdir) = @_; + runcmd @git, qw(rm --quiet -rf --cached --ignore-unmatch), $subdir; +} + +sub read_tree_subdir ($$) { + my ($subdir, $new_tree_object) = @_; + rm_subdir_cached $subdir; + runcmd @git, qw(read-tree), "--prefix=$subdir/", $new_tree_object; +} + +sub make_commit ($$) { + my ($parents, $message_paras) = @_; + my $tree = cmdoutput @git, qw(write-tree); + my @cmd = (@git, qw(commit-tree), $tree); + push @cmd, qw(-p), $_ foreach @$parents; + push @cmd, qw(-m), $_ foreach @$message_paras; + return cmdoutput @cmd; +} + # classify returns an info hash like this # CommitId => $objid # Hdr => # commit headers, including 1 final newline @@ -309,7 +343,7 @@ sub parsecommit ($;$) { foreach my $ph (@ph) { push @$p_ref, { - Ix => $#$p_ref, + Ix => scalar @$p_ref, CommitId => $ph, }; } @@ -325,7 +359,7 @@ sub classify ($) { my $t = $r->{Tree}; foreach my $p (@p) { - $p->{Differs} => (get_differs $p->{CommitId}, $t), + $p->{Differs} = (get_differs $p->{CommitId}, $t), } printdebug "classify $objid \$t=$t \@p", @@ -336,9 +370,7 @@ sub classify ($) { 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"; + printdebug " = $type ".(dd $r)."\n"; } return $r; }; @@ -356,7 +388,7 @@ sub classify ($) { } elsif ($d & (D_PAT_ADD|D_PAT_OTH)) { return $unknown->("edits debian/patches"); } elsif ($d & DS_DEB and !($d & ~DS_DEB)) { - my ($ty,$dummy) = git_cat_file "$ph[0]:debian"; + my ($ty,$dummy) = git_cat_file "$p[0]{CommitId}:debian"; if ($ty eq 'tree') { if ($d == D_DEB_CLOG) { return $classify->(qw(Changelog)); @@ -438,14 +470,16 @@ sub classify ($) { if (@p == 2 && !$haspatches && !$p[$prevbrw]{IsOrigin} && # breakwater never starts with an origin - !($p[$prevbrw]{Differs} & ~DS_DEB) && - !($p[!$prevbrw]{Differs} & ~D_UPS)) { + !($p[!$prevbrw]{Differs} & ~DS_DEB) && # no non-debian changess + !($p[$prevbrw]{Differs} & ~D_UPS)) { # no non-upstream changes return $classify->(qw(BreakwaterUpstreamMerge), OrigParents => [ $p[!$prevbrw] ]); } - # xxx multi-.orig upstreams } + # multi-orig upstreams are represented with a breakwater merge + # from a single upstream commit which combines the orig tarballs + return $unknown->("complex merge"); } @@ -453,9 +487,11 @@ sub walk ($;$$); sub walk ($;$$) { my ($input, $nogenerate,$report) = @_; - # => ($tip, $breakwater_tip) + # => ($tip, $breakwater_tip, $last_upstream_merge_in_breakwater) # (or nothing, if $nogenerate) + printdebug "*** WALK $input ".($nogenerate//0)." ".($report//'-')."\n"; + # go through commits backwards # we generate two lists of commits to apply: # breakwater branch and upstream patches @@ -509,6 +545,8 @@ sub walk ($;$$) { no warnings qw(exiting); last; }; + my $last_upstream_update; + for (;;) { $cl = classify $cur; my $ty = $cl->{Type}; @@ -526,6 +564,7 @@ sub walk ($;$$) { $cur = $p0; next; } elsif ($ty eq 'BreakwaterStart') { + $last_upstream_update = $cur; $build_start->('FirstPackaging', $cur); } elsif ($ty eq 'Upstream') { push @upp_cl, $cl; @@ -550,6 +589,7 @@ sub walk ($;$$) { $cur = $contrib; next; } elsif ($ty eq 'BreakwaterUpstreamMerge') { + $last_upstream_update = $cur; $build_start->("PreviousBreakwater", $cur); } elsif ($ty eq 'DgitImportUnpatched') { my $pm = $pseudomerges[-1]; @@ -566,6 +606,7 @@ sub walk ($;$$) { printf $report " PM=%s \@Overwr:%d", $pm, (scalar @$ovwrs) if $report; if (@$ovwrs != 1) { + printdebug "*** WALK BOMB DgitImportUnpatched\n"; return $bomb->(); } my $ovwr = $ovwrs->[0]{CommitId}; @@ -603,6 +644,7 @@ sub walk ($;$$) { $prline->(" Import"); $rewrite_from_here->(); $upp_limit //= $#upp_cl; # further, deeper, patches discarded + die 'BUG $upp_limit is not used anywhere?'; $cur = $ovwr; next; } else { @@ -617,10 +659,15 @@ sub walk ($;$$) { } die "$ty ?"; } else { + printdebug "*** WALK BOMB unrecognised\n"; return $bomb->(); } } $prprdelim->(); + + printdebug "*** WALK prep done cur=$cur". + " brw $#brw_cl upp $#upp_cl proc $#processed pm $#pseudomerges\n"; + return if $nogenerate; # Now we build it back up again @@ -629,14 +676,10 @@ sub walk ($;$$) { my $rewriting = 0; - my $rm_tree_cached = sub { - my ($subdir) = @_; - runcmd @git, qw(rm --quiet -rf --cached --ignore-unmatch), $subdir; - }; my $read_tree_debian = sub { my ($treeish) = @_; - $rm_tree_cached->(qw(debian)); - runcmd @git, qw(read-tree --prefix=debian/), "$treeish:debian"; + read_tree_subdir 'debian', "$treeish:debian"; + rm_subdir_cached 'debian/patches'; }; my $read_tree_upstream = sub { my ($treeish) = @_; @@ -678,7 +721,7 @@ sub walk ($;$$) { next; } elsif ($method eq 'DgitImportDebianUpdate') { $read_tree_debian->($cltree); - $rm_tree_cached->(qw(debian/patches)); + rm_subdir_cached qw(debian/patches); } elsif ($method eq 'DgitImportUpstreamUpdate') { $read_tree_upstream->($cltree); push @parents, map { $_->{CommitId} } @{ $cl->{OrigParents} }; @@ -713,6 +756,9 @@ sub walk ($;$$) { my $newcommit = cmdoutput @cmd; confess "$ch ?" unless $rewriting or $newcommit eq $cl->{CommitId}; $build = $newcommit; + if (grep { $method eq $_ } qw(DgitImportUpstreamUpdate)) { + $last_upstream_update = $cur; + } } }; @@ -720,7 +766,9 @@ sub walk ($;$$) { die sprintf "internal error %#x %s %s", $final_check, $build, $input if $final_check & ~D_PAT_ADD; - return ($build, $breakwater); + my @r = ($build, $breakwater, $last_upstream_update); + printdebug "*** WALK RETURN @r\n"; + return @r } sub get_head () { return git_rev_parse qw(HEAD); } @@ -737,15 +785,21 @@ sub update_head_checkout ($$$) { update_head $old, $new, $mrest; } +sub update_head_postlaunder ($$$) { + my ($old, $tip, $reflogmsg) = @_; + update_head $old, $tip, $reflogmsg; + # no tree changes except debian/patches + runcmd @git, qw(rm --quiet --ignore-unmatch -rf debian/patches); +} + sub cmd_launder () { badusage "no arguments to launder allowed" if @ARGV; my $old = get_head(); - my ($tip,$breakwater) = walk $old; - update_head $old, $tip, 'launder'; - # no tree changes except debian/patches - runcmd @git, qw(rm --quiet --ignore-unmatch -rf debian/patches); + my ($tip,$breakwater,$last_upstream_merge) = walk $old; + update_head_postlaunder $old, $tip, 'launder'; printf "# breakwater tip\n%s\n", $breakwater; printf "# working tip\n%s\n", $tip; + printf "# last upstream merge\n%s\n", $last_upstream_merge; } sub cmd_analyse () { @@ -761,6 +815,194 @@ sub cmd_analyse () { STDOUT->error and die $!; } +sub cmd_new_upstream_v0 () { + # xxx would like to support more git-rebase options + badusage + "need NEW-VERSION UPS-COMMITISH [EXTRA-UPS-NAME EXTRA-UPS-COMMITISH...]" + unless @ARGV % 2 == 0 and @ARGV >= 2; + # tree should be clean and this is not checked + # automatically and unconditionally launders before rebasing + # if rebase --abort is used, laundering has still been done + + my %pieces; + + # parse args - low commitment + my $new_version = (new Dpkg::Version scalar(shift @ARGV), check => 1); + my $new_upstream_version = $new_version->version(); + + my $new_upstream = git_rev_parse shift @ARGV; + + my $piece = sub { + my ($n, @x) = @_; # may be '' + my $pc = $pieces{$n} //= { + Name => $n, + Desc => ($n ? "upstream piece \`$n'" : "upstream (main piece"), + }; + while (my $k = shift @x) { $pc->{$k} = shift @x; } + $pc; + }; + + my @newpieces; + my $newpiece = sub { + my ($n, @x) = @_; # may be '' + my $pc = $piece->($n, @x, NewIx => (scalar @newpieces)); + push @newpieces, $pc; + }; + + $newpiece->('', + OldIx => 0, + New => $new_upstream, + ); + while (@ARGV) { + my $n = shift @ARGV; + my $c = git_rev_parse shift @ARGV; + die unless $n =~ m/^$extra_orig_namepart_re$/; + $newpiece->($n, New => $c); + } + + # now we need to investigate the branch this generates the + # laundered version but we don't switch to it yet + my $old_head = get_head(); + my ($old_laundered_tip,$old_bw,$old_upstream_update) = walk $old_head; + + my $old_bw_cl = classify $old_bw; + my $old_upstream_update_cl = classify $old_upstream_update; + confess unless $old_upstream_update_cl->{OrigParents}; + my $old_upstream = parsecommit + $old_upstream_update_cl->{OrigParents}[0]{CommitId}; + + my $problems = 0; + my $problem = sub { + my ($msg) = @_; + $problems++; + print STDERR "preflight check failed: $msg\n"; + }; + + $piece->('', Old => $old_upstream->{CommitId}); + + if ($old_upstream->{Msg} =~ m{^\[git-debrebase }m) { + if ($old_upstream->{Msg} =~ + m{^\[git-debrebase new-upstream combine \.((?: $extra_orig_namepart_re)+)\]} + ) { + my @oldpieces = ('', split / /, $1); + my $parentix = -1 + scalar @{ $old_upstream->{Parents} }; + foreach my $i (0..$#oldpieces) { + my $n = $oldpieces[$i]; + $piece->($n, Old => $old_upstream->{CommitId}.'^'.$parentix); + } + } else { + $problem->("previous upstream $old_upstream->{CommitId} is from". + " git-debrebase but not a \`new-upstream combine' commit"); + } + } + + foreach my $pc (values %pieces) { + if (!$pc->{Old}) { + $problem->("introducing upstream piece \`$pc->{Name}'"); + } elsif (!$pc->{New}) { + $problem->("dropping upstream piece \`$pc->{Name}'"); + } elsif (!is_fast_fwd $pc->{Old}, $pc->{New}) { + $problem->("not fast forward: $pc->{Name} $pc->{Old}..$pc->{New}"); + } + } + + printdebug "%pieces = ", (dd \%pieces), "\n"; + printdebug "\@newpieces = ", (dd \@newpieces), "\n"; + + if ($problems) { + if ($opt_force) { + printf STDERR + "preflight check failures (%d) overriden by --force\n", + $problems; + } else { + fail sprintf + "preflight check failures (%d) (you could --force)", + $problems; + } + } + + my $new_bw; + + fresh_workarea(); + in_workarea sub { + my @upstream_merge_parents; + + if (!$problems) { + push @upstream_merge_parents, $old_upstream->{CommitId}; + } + + foreach my $pc (@newpieces) { # always has '' first + if ($pc->{Name}) { + read_tree_subdir $pc->{Name}, $pc->{New}; + } else { + runcmd @git, qw(read-tree), $pc->{New}; + } + push @upstream_merge_parents, $pc->{New}; + } + + # index now contains the new upstream + + if (@newpieces > 1) { + # need to make the upstream subtree merge commit + $new_upstream = make_commit \@upstream_merge_parents, + [ "Combine upstreams for $new_upstream_version", + ("[git-debrebase new-upstream combine . ". + (join " ", map { $_->{Name} } @newpieces[1..$#newpieces]). + "]"), + ]; + } + + # $new_upstream is either the single upstream commit, or the + # combined commit we just made. Either way it will be the + # "upstream" parent of the breakwater special merge. + + read_tree_subdir 'debian', "$old_bw:debian"; + + # index now contains the breakwater merge contents + $new_bw = make_commit [ $old_bw, $new_upstream ], + [ "Update to upstream $new_upstream_version", + "[git-debrebase new-upstream breakwater $new_upstream_version]", + ]; + + # Now we have to add a changelog stanza so the Debian version + # is right. + die if unlink "debian"; + die $! unless $!==ENOTEMPTY; + unlink "debian/changelog" or die $!; + open CN, ">", "debian/changelog" or die $!; + my $oldclog = git_cat_file ":debian/changelog"; + $oldclog =~ m/^($package_re) \(\S+\) / or + fail "cannot parse old changelog to get package name"; + my $p = $1; + print CN < \$debuglevel) or die badusage "bad options\n"; +GetOptions("D+" => \$debuglevel, + 'force!') or die badusage "bad options\n"; initdebug('git-debrebase '); enabledebug if $debuglevel;