3 # Script helping make fast-forwarding histories while still rebasing
4 # upstream deltas when working on Debian packaging
6 # Copyright (C)2017 Ian Jackson
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
31 my @cmd = qw(git config -z);
32 push @cmd, qw(--get-all) if wantarray;
34 my $out = cmdoutput @cmd;
35 return split /\0/, $out;
41 # git debrebase launder
45 my ($type,$data) = git_cat_file $objid;
46 die unless $type eq 'commit';
47 $data =~ m/(?<=\n)\n/;
51 sub D_DEB () { return 0x1; }
52 sub D_UPS () { return 0x2; }
53 sub D_PAT_ADD () { return 0x4; }
54 sub D_PAT_OTH () { return 0x8; }
56 sub commit_pr_info ($) {
58 return Data::Dumper->dump([$r], [qw(commit)]);
64 my ($h,$m) = get_commit $objid;
66 my ($t) = $h =~ m/^tree (\w+)$/m or die $cur;
67 my (@ph) = $h =~ m/^parent (\w+)$/m;
78 foreach my $ph (@ph) {
82 Differs => (get_differs $t, $ph),
87 my ($type, @rest) = @_;
88 $r = { %r, Type => $type, @rest };
93 $r = { %r, Type => Unknown };
98 my $d = $r->{Parents}[0]{Differs};
99 if ($d == D_DPAT_ADD) {
100 return $classify->(qw(AddPatches));
101 } elsif ($d & (D_DPAT_ADD|D_DPAT_OTH)) {
102 return $unknown->("edits debian/patches");
103 } elsif ($d == D_DEB) {
104 return $classify->(qw(Packaging));
105 } elsif ($d == D_UPS) {
106 return $classify->(qw(Upstream));
107 } elsif ($d == D_DEB|D_UPS) {
108 return $classify->(qw(Mixed));
110 return $unknown->("no changes");
112 confess "internal error $objid ?";
116 return $unknown->("origin commit");
119 my @identical = grep { !$_->{Differs} } @p;
120 if (@p == 2 && @identical == 1) {
121 my @overwritten = grep { $_->{Differs} } @p;
122 confess "internal error $objid ?" unless @overwritten==1;
123 return $classify->(qw(Pseudomerge),
124 Overwritten => $overwritten[0],
125 Contributor => $identical[0]);
127 if (@p == 2 && @identical == 2) {
128 my @bytime = nsort_by {
129 my ($ph,$pm) = get_commit $_->{CommitId};
130 $ph =~ m/^committer .* (\d+) [-+]\d+$/m or die "$_->{CommitId} ?";
133 return $classify->(qw(Pseudomerge),
134 SubType => qw(Ambiguous),
135 Overwritten => $bytime[0],
136 Contributor => $bytime[1]);
139 my ($p_h, $p_m) = get_commit $p;
140 $p->{IsOrigin} = $p_h !~ m/^parent \w+$/m;
141 $p->{IsDgitImport} = $p_m =~ m/^\[dgit import .*\]$/m;
142 $p->{IsDgitImportOrig} = $p_m =~ m/^\[dgit import orig .*\]$/m;
145 if (!grep { !($_->{IsOrigin} && $_->{isDgitImport}) } @p and
146 $m2 =~ s{^\[(dgit import unpatched .*)\]$}{[was: $1]}m) {
148 xxx check exactly one IsDgitImport that is tarball
150 return $classify->(qw(DgitImportUnpatched));
153 my ($stype, $series) = git_cat_file "$t:debian/patches/series";
154 my $haspatches = $stype ne 'missing' && $series =~ m/^\s*[^#\n\t ]/m;
156 # how to decide about l/r ordering of breakwater merges
157 # git --topo-order prefers to expand 2nd parent first
158 # easy rune to look for debian/ history so that should
162 !$p[0]{IsOrigin} && # breakwater merge never starts with an origin
163 !($p[0]{Differs} & ~D_DEB) &&
164 !($p[1]{Differs} & ~D_UPS)) {
165 return $classify->(qw(BreakwaterUpstreamMerge),
169 return $unknown->("complex merge");
174 # go through commits backwards
175 # we generate two lists of commits to apply
176 my (@phases, @deb_cl, @ups_cl);
180 my $cl = classify $cur;
181 my $ty = $cl->{Type};
182 my $st = $cl->{SubType};
183 $found{$ty. ( defined($st) ? "-$st" : '' )}++;
184 my $p0 = $cl->{Parents}[0]{CommitId};
185 if ($ty eq 'AddPatches') {
188 } elsif ($ty eq 'Packaging') {
192 } elsif ($ty eq 'Upstream') {
196 } elsif ($ty eq 'Mixed') {
201 $ms .= "\n[git-debrebase split mixed commit: $wh part]\n";
202 my $cls = { $cl, Msg => $ms };
205 $queue->(\@deb_cl, "debian");
206 $queue->(\@ups_cl, "upstream");
208 } elsif ($ty eq 'Pseudomerge') {
209 push @pseudomerges, $cl;
210 $cur = $ty->{Contributor};
212 } elsif ($ty eq 'DgitImportUnpatched' &&
213 @pseudomerges == 1) {
214 # This import has a tree which is just like a breakwater
215 # tree, but it has the wrong history. Its ought to have
216 # the previous breakwater (which dgit ought to have
217 # generated a pseudomerge to overwrite) as an ancestor.
218 # That will make the history of the debian/ files correct.
219 # As for the upstream version: either it's the same upstream
220 # as the previous breakwater, in which case that history is
221 # precisely right. Otherwise, it was a non-gitish upload
222 # of a new upstream version. We can tell these apart
223 # by looking at the tree of the supposed upstream.
224 my $previous_breakwater = launder $pseudomerges[0]{Overwritten};
225 my $differs = get_differs $previous_breakwater, $cl->{Tree};
226 if ($differs & D_UPS) {
227 $r->{FixupUpstreamMerge} = $
231 Type => NonGitNewUpstreamDgitImport,
232 PreviousBreakwaterMerge =>
235 push @phases, [ @deb_cl, @ups_cl ];
237 if (@pseudomerges != 1) {
240 if ($ARGV[0] eq 'launder') {
245 print Dumper(cfg('wombat.foo.bar'));
248 ((git_cat_file "$t:debian/patches/series"
259 # changes on debian/patches, discard it
274 when starting must record original start (for ff)