#!/usr/bin/perl -w # git-debrebase # Script helping make fast-forwarding histories while still rebasing # upstream deltas when working on Debian packaging # # Copyright (C)2017 Ian Jackson # # 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 # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . use strict; use Memoize; use Debian::Dgit; sub cfg ($) { my ($k) = @_; $/ = "\0"; my @cmd = qw(git config -z); push @cmd, qw(--get-all) if wantarray; push @cmd, $k; my $out = cmdoutput @cmd; return split /\0/, $out; } memoize('cfg'); # usage # git debrebase launder sub get_commit ($) { my ($objid) = @_; my ($type,$data) = git_cat_file $objid; die unless $type eq 'commit'; $data =~ m/(?<=\n)\n/; return ($`,$'); } sub D_DEB () { return 0x1; } sub D_UPS () { return 0x2; } sub D_PAT_ADD () { return 0x4; } sub D_PAT_OTH () { return 0x8; } sub classify ($) { my ($objid) = @_; my ($h,$m) = get_commit $objid; my ($t) = $h =~ m/^tree (\w+)$/m or die $cur; my (@ph) = $h =~ m/^parent (\w+)$/m; my @p; my $r = { CommitId => $objid, Hdr => $hdr, Msg => $m, Parents => \@p, }; foreach my $ph (@ph) { push @p, { Ix => $#p, CommitId => $ph, Differs => (get_differs $t, $ph), }; } if (@p == 1) { my $d = $r->{Parents}[0]{Differs}; if ($d == D_DPAT_ADD) { return $classify->(qw(AddPatches)); } elsif ($d & (D_DPAT_ADD|D_DPAT_OTH)) { return $unknown->("edits debian/patches"); } elsif ($d == D_DEB) { return $classify->(qw(Packaging)); } elsif ($d == D_UPS) { return $classify->(qw(Upstream)); } elsif ($d == D_DEB|D_UPS) { return $classify->(qw(Mixed)); } elsif ($d == 0) { return $unknown->("no changes"); } else { confess "internal error $objid ?"; } } if (!@p) { return $unknown->("origin commit"); } my @identical = grep { !$_->{Differs} } @p; if (@p == 2 && @identical == 1) { my @overwritten = grep { $_->{Differs} } @p; confess "internal error $objid ?" unless @overwritten==1; return $classify->(qw(Pseudomerge), Overwritten => $overwritten[0], Contributor => $identical[0]); } if (@p == 2 && @identical == 2) { my @bytime = nsort_by { my ($ph,$pm) = get_commit $_->{CommitId}; $ph =~ m/^committer .* (\d+) [-+]\d+$/m or die "$_->{CommitId} ?"; $1; } @p; return $classify->(qw(Pseudomerge), Xtype => qw(Ambiguous), Overwritten => $bytime[0], Contributyor => $bytime[1]); } foreach my $p (@p) { my ($type) = git_cat_file "$p^1"; $p->{IsOrigin} = $type eq 'missing'; } if (!grep { !$_->{IsOrigin} } @p && $m =~ m{^\[dgit import unpatched .*\]$}m) { return $classify->(qw(DgitImport)); } my ($stype, $series) = git_cat_file "$t:debian/patches/series"; my $haspatches = $stype ne 'missing' && $series =~ m/^\s*[^#\n\t ]/m; # how to decide about l/r ordering of breakwater merges # git --topo-order prefers to expand 2nd parent first # easy rune to look for debian/ history so that should # be 1st parent. if (@p == 2 && !$haspatches && !$p[0]{IsOrigin} && # breakwater merge never starts with an origin !($p[0]{Differs} & ~D_DEB) && !($p[1]{Differs} & ~D_UPS)) { return $classify->(qw(BreakwaterUpstreamMerge), Upstream => $p[1]); } return $unknown->("complex merge"); } ((git_cat_file "$t:debian/patches/series" my @ return $r; $r->{Type} = ' $r->{Type} = ' return # changes on debian/patches, discard it $cur = $p[0]; next; } if ($d & DPAT) { ($r->{Tree},) = sub launder () { # go through commits backwards # we generate two lists of commits to apply my (@deb_cl, @ups_cl); my $cur = git_rev_parse('HEAD'); for (;;) { } if ($ARGV[0] eq 'launder') { launder(); } use Data::Dumper; print Dumper(cfg('wombat.foo.bar')); when starting must record original start (for ff) and new rebase basis