summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSean Whitton <spwhitton@spwhitton.name>2018-06-28 11:52:54 +0100
committerSean Whitton <spwhitton@spwhitton.name>2018-06-28 11:52:54 +0100
commit4613f20fafd20f738291b355aced2bb4798ca2b1 (patch)
treee7d9bf11336468eda8076d71240368608cffea0e
parent36454d4896b806632a7b8b8f08e1b5ec16a1cc4e (diff)
parent152ccd540c3f7809d8235d0907eb0142aed792f4 (diff)
Merge tag 'debian/5.2' into wip/stretch-bpo
dgit release 5.2 for unstable (sid) [dgit] [dgit distro=debian] # gpg: Signature made Sun 24 Jun 2018 23:36:20 BST # gpg: using RSA key 559AE46C2D6B6D3265E7CBA1E3E3392348B50D39 # gpg: Can't check signature: No public key
-rw-r--r--.gitignore4
-rw-r--r--Debian/Dgit.pm124
-rw-r--r--Debian/Dgit/ExitStatus.pm26
-rw-r--r--Debian/Dgit/GDR.pm26
-rw-r--r--Makefile40
-rw-r--r--NOTES.git-debrebase131
-rw-r--r--debian/changelog101
-rw-r--r--debian/control9
-rwxr-xr-xdebian/rules30
-rw-r--r--debian/tests/control13
-rw-r--r--debian/tests/control.in2
-rwxr-xr-xdgit286
-rwxr-xr-xdgit-badcommit-fixup8
-rw-r--r--dgit-maint-debrebase.7.pod637
-rw-r--r--dgit-maint-merge.7.pod2
-rw-r--r--dgit-user.7.pod5
-rw-r--r--dgit.170
-rwxr-xr-xgit-debrebase1733
-rw-r--r--git-debrebase.1.pod470
-rw-r--r--git-debrebase.5.pod611
-rwxr-xr-xtests/enumerate-tests19
-rw-r--r--tests/lib23
-rw-r--r--tests/lib-core1
-rw-r--r--tests/lib-gdr290
-rw-r--r--tests/lib-restricts4
-rwxr-xr-xtests/run-all5
-rwxr-xr-xtests/setup/gdr-convert-gbp100
-rwxr-xr-xtests/setup/gdr-convert-gbp-noarchive9
-rwxr-xr-xtests/tests/gdr-diverge-nmu61
-rwxr-xr-xtests/tests/gdr-diverge-nmu-dgit55
-rwxr-xr-xtests/tests/gdr-edits40
-rwxr-xr-xtests/tests/gdr-import-dgit68
-rwxr-xr-xtests/tests/gdr-makepatches737
-rwxr-xr-xtests/tests/gdr-newupstream65
-rwxr-xr-xtests/tests/gdr-subcommands226
-rwxr-xr-xtests/tests/gdr-viagit40
-rwxr-xr-xtests/tests/gitattributes48
-rwxr-xr-xtests/tests/hint-testsuite-triggers10
-rwxr-xr-xtests/tests/quilt-singlepatch6
-rwxr-xr-xtests/tests/version-opt2
40 files changed, 5302 insertions, 135 deletions
diff --git a/.gitignore b/.gitignore
index 13e2c4b..ba7af78 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
*~
tests/tmp
debian/dgit
+debian/git-debrebase
debian/dgit-infrastructure
debian/files
debian/*.substvars
@@ -11,5 +12,8 @@ dgit-nmu-simple.7
dgit-maint-native.7
dgit-maint-merge.7
dgit-maint-gbp.7
+dgit-maint-debrebase.7
dgit-sponsorship.7
+git-debrebase.1
+git-debrebase.5
substituted
diff --git a/Debian/Dgit.pm b/Debian/Dgit.pm
index c4a61af..57b79e8 100644
--- a/Debian/Dgit.pm
+++ b/Debian/Dgit.pm
@@ -44,28 +44,33 @@ BEGIN {
server_branch server_ref
stat_exists link_ltarget
hashfile
- fail ensuredir must_getcwd executable_on_path
+ fail failmsg ensuredir must_getcwd executable_on_path
waitstatusmsg failedcmd_waitstatus
failedcmd_report_cmd failedcmd
- runcmd cmdoutput cmdoutput_errok
+ runcmd shell_cmd cmdoutput cmdoutput_errok
git_rev_parse git_cat_file
- git_get_ref git_for_each_ref
+ git_get_ref git_get_symref git_for_each_ref
git_for_each_tag_referring is_fast_fwd
+ git_check_unmodified
$package_re $component_re $deliberately_re
$distro_re $versiontag_re $series_filename_re
+ $extra_orig_namepart_re
+ $git_null_obj
$branchprefix
+ $ffq_refprefix $gdrlast_refprefix
initdebug enabledebug enabledebuglevel
printdebug debugcmd
$debugprefix *debuglevel *DEBUG
shellquote printcmd messagequote
$negate_harmful_gitattrs
changedir git_slurp_config_src
+ gdr_ffq_prev_branchinfo
playtree_setup);
# implicitly uses $main::us
%EXPORT_TAGS = ( policyflags => [qw(NOFFCHECK FRESHREPO NOCOMMITCHECK)],
playground => [qw(record_maindir $maindir $local_git_cfg
$maindir_gitdir $maindir_gitcommon
- fresh_playground $playground
+ fresh_playground
ensure_a_playground)]);
@EXPORT_OK = ( @{ $EXPORT_TAGS{policyflags} },
@{ $EXPORT_TAGS{playground} } );
@@ -80,6 +85,10 @@ our $distro_re = $component_re;
our $versiontag_re = qr{[-+.\%_0-9a-zA-Z/]+};
our $branchprefix = 'dgit';
our $series_filename_re = qr{(?:^|\.)series(?!\n)$}s;
+our $extra_orig_namepart_re = qr{[-0-9a-z]+};
+our $git_null_obj = '0' x 40;
+our $ffq_refprefix = 'ffq-prev';
+our $gdrlast_refprefix = 'debrebase-last';
# policy hook exit status bits
# see dgit-repos-server head comment for documentation
@@ -92,7 +101,9 @@ sub NOCOMMITCHECK () { return 0x8; }
our $debugprefix;
our $debuglevel = 0;
-our $negate_harmful_gitattrs = "-text -eol -crlf -ident -filter";
+our $negate_harmful_gitattrs =
+ "-text -eol -crlf -ident -filter -working-tree-encoding";
+ # ^ when updating this, alter the regexp in dgit:is_gitattrs_setup
our $forkcheck_mainprocess;
@@ -108,7 +119,7 @@ sub forkcheck_mainprocess () {
sub setup_sigwarn () {
forkcheck_setup();
$SIG{__WARN__} = sub {
- die $_[0] if forkcheck_mainprocess;
+ confess $_[0] if forkcheck_mainprocess;
};
}
@@ -213,12 +224,16 @@ sub _us () {
$::us // ($0 =~ m#[^/]*$#, $&);
}
-sub fail {
- my $s = "@_\n";
+sub failmsg {
+ my $s = "error: @_\n";
$s =~ s/\n\n$/\n/;
my $prefix = _us().": ";
$s =~ s/^/$prefix/gm;
- die $s;
+ return "\n".$s;
+}
+
+sub fail {
+ die failmsg @_;
}
sub ensuredir ($) {
@@ -300,6 +315,11 @@ sub runcmd {
failedcmd @_ if system @_;
}
+sub shell_cmd {
+ my ($first_shell, @cmd) = @_;
+ return qw(sh -ec), $first_shell.'; exec "$@"', 'x', @cmd;
+}
+
sub cmdoutput_errok {
confess Dumper(\@_)." ?" if grep { !defined } @_;
debugcmd "|",@_;
@@ -348,11 +368,21 @@ sub git_rev_parse ($) {
return cmdoutput qw(git rev-parse), "$_[0]~0";
}
-sub git_cat_file ($) {
- my ($objname) = @_;
+sub git_cat_file ($;$) {
+ my ($objname, $etype) = @_;
# => ($type, $data) or ('missing', undef)
# in scalar context, just the data
+ # if $etype defined, dies unless type is $etype or in @$etype
our ($gcf_pid, $gcf_i, $gcf_o);
+ my $chk = sub {
+ my ($gtype, $data) = @_;
+ if ($etype) {
+ $etype = [$etype] unless ref $etype;
+ confess "$objname expected @$etype but is $gtype"
+ unless grep { $gtype eq $_ } @$etype;
+ }
+ return ($gtype, $data);
+ };
if (!$gcf_pid) {
my @cmd = qw(git cat-file --batch);
debugcmd "GCF|", @cmd;
@@ -362,13 +392,26 @@ sub git_cat_file ($) {
print $gcf_i $objname, "\n" or die $!;
my $x = <$gcf_o>;
printdebug "GCF<| ", $x;
- if ($x =~ m/ (missing)$/) { return ($1, undef); }
+ if ($x =~ m/ (missing)$/) { return $chk->($1, undef); }
my ($type, $size) = $x =~ m/^.* (\w+) (\d+)\n/ or die "$objname ?";
my $data;
(read $gcf_o, $data, $size) == $size or die "$objname $!";
$x = <$gcf_o>;
$x eq "\n" or die "$objname ($_) $!";
- return ($type, $data);
+ return $chk->($type, $data);
+}
+
+sub git_get_symref (;$) {
+ my ($symref) = @_; $symref //= 'HEAD';
+ # => undef if not a symref, otherwise refs/...
+ my @cmd = (qw(git symbolic-ref -q HEAD));
+ my $branch = cmdoutput_errok @cmd;
+ if (!defined $branch) {
+ $?==256 or failedcmd @cmd;
+ } else {
+ chomp $branch;
+ }
+ return $branch;
}
sub git_for_each_ref ($$;$) {
@@ -418,6 +461,25 @@ sub git_for_each_tag_referring ($$) {
});
}
+sub git_check_unmodified () {
+ foreach my $cached (qw(0 1)) {
+ my @cmd = qw(git diff --quiet);
+ push @cmd, qw(--cached) if $cached;
+ push @cmd, qw(HEAD);
+ debugcmd "+",@cmd;
+ $!=0; $?=-1; system @cmd;
+ return if !$?;
+ if ($?==256) {
+ fail
+ $cached
+ ? "git index contains changes (does not match HEAD)"
+ : "working tree is dirty (does not match HEAD)";
+ } else {
+ failedcmd @cmd;
+ }
+ }
+}
+
sub is_fast_fwd ($$) {
my ($ancestor,$child) = @_;
my @cmd = (qw(git merge-base), $ancestor, $child);
@@ -460,12 +522,28 @@ sub git_slurp_config_src ($) {
return $r;
}
+sub gdr_ffq_prev_branchinfo ($) {
+ my ($symref) = @_;
+ # => ('status', "message", [$symref, $ffq_prev, $gdrlast])
+ # 'status' may be
+ # branch message is undef
+ # weird-symref } no $symref,
+ # notbranch } no $ffq_prev
+ return ('detached', 'detached HEAD') unless defined $symref;
+ return ('weird-symref', 'HEAD symref is not to refs/')
+ unless $symref =~ m{^refs/};
+ my $ffq_prev = "refs/$ffq_refprefix/$'";
+ my $gdrlast = "refs/$gdrlast_refprefix/$'";
+ printdebug "ffq_prev_branchinfo branch current $symref\n";
+ return ('branch', undef, $symref, $ffq_prev, $gdrlast);
+}
+
# ========== playground handling ==========
# terminology:
#
# $maindir user's git working tree
-# $playground area in .git/ where we can make files, unpack, etc. etc.
+# playground area in .git/ where we can make files, unpack, etc. etc.
# playtree git working tree sharing object store with the user's
# inside playground, or identical to it
#
@@ -485,28 +563,26 @@ sub git_slurp_config_src ($) {
#
# fresh_playground SUBDIR_PATH_COMPONENTS
# e.g fresh_playground 'dgit/unpack' ('.git/' is implied)
-# default SUBDIR_PATH_COMPONENTS is $playground_subdir
+# default SUBDIR_PATH_COMPONENTS is playground_subdir
# calls record_maindir
# sets up a new playground (destroying any old one)
-# assigns to $playground and returns the same pathname
+# returns playground pathname
# caller may call multiple times with different subdir paths
-# createing different playgrounds; but $playground global can
-# refer only to one, obv.
+# createing different playgrounds
#
# ensure_a_playground SUBDIR_PATH_COMPONENTS
# like fresh_playground except:
# merely ensures the directory exists; does not delete an existing one
-# never sets global $playground
#
# then can use
#
-# changedir $playground
+# changedir playground
# changedir $maindir
#
# playtree_setup $local_git_cfg
-# # ^ call in some (perhaps trivial) subdir of $playground
+# # ^ call in some (perhaps trivial) subdir of playground
#
-# rmtree $playground
+# rmtree playground
# ----- maindir -----
@@ -538,8 +614,6 @@ sub record_maindir () {
# ----- playgrounds -----
-our $playground;
-
sub ensure_a_playground_parent ($) {
my ($spc) = @_;
record_maindir();
@@ -562,7 +636,7 @@ sub fresh_playground ($) {
$spc = ensure_a_playground_parent $spc;
rmtree $spc;
mkdir $spc or fail "failed to mkdir the playground $spc: $!";
- return $playground = $spc;
+ return $spc;
}
# ----- playtrees -----
diff --git a/Debian/Dgit/ExitStatus.pm b/Debian/Dgit/ExitStatus.pm
new file mode 100644
index 0000000..b69d42d
--- /dev/null
+++ b/Debian/Dgit/ExitStatus.pm
@@ -0,0 +1,26 @@
+# -*- perl -*-
+
+package Debian::Dgit::ExitStatus;
+
+# To use this, at the top (before use strict, even):
+#
+# END { $? = $Debian::Dgit::ExitStatus::desired // -1; };
+# use Debian::Dgit::ExitStatus;
+#
+# and then replace every call to `exit' with `finish'.
+# Add a `finish 0' to the end of the program.
+
+BEGIN {
+ use Exporter;
+ @ISA = qw(Exporter);
+ @EXPORT = qw(finish $desired);
+}
+
+our $desired;
+
+sub finish ($) {
+ $desired = $_[0] // 0;
+ exit $desired;
+}
+
+1;
diff --git a/Debian/Dgit/GDR.pm b/Debian/Dgit/GDR.pm
new file mode 100644
index 0000000..ca7e621
--- /dev/null
+++ b/Debian/Dgit/GDR.pm
@@ -0,0 +1,26 @@
+# -*- perl -*-
+
+package Debian::Dgit::GDR;
+
+use strict;
+use warnings;
+
+# Scripts and programs which are going to `use Debian::Dgit' but which
+# live in git-debrebase (ie are installed with install-gdr)
+# should `use Debian::Dgit::GDR' first. All this module does is
+# adjust @INC so that the script gets the version of the script from
+# the git-debrebase package (which is installed in a different
+# location and may be a different version).
+
+# To use this with ExitStatus, put at the top (before use strict, even):
+#
+# END { $? = $Debian::Dgit::ExitStatus::desired // -1; };
+# use Debian::Dgit::GDR;
+# use Debian::Dgit::ExitStatus;
+#
+# and then replace every call to `exit' with `finish'.
+# Add a `finish 0' to the end of the program.
+
+# unshift @INC, q{/usr/share/dgit/gdr/perl5}; ###substituted###
+
+1;
diff --git a/Makefile b/Makefile
index d2a91d8..de28f4d 100644
--- a/Makefile
+++ b/Makefile
@@ -27,6 +27,7 @@ bindir=$(prefix)/bin
mandir=$(prefix)/share/man
perldir=$(prefix)/share/perl5
man1dir=$(mandir)/man1
+man5dir=$(mandir)/man5
man7dir=$(mandir)/man7
infraexamplesdir=$(prefix)/share/doc/dgit-infrastructure/examples
txtdocdir=$(prefix)/share/doc/dgit
@@ -39,12 +40,21 @@ MAN7PAGES=dgit.7 \
dgit-user.7 dgit-nmu-simple.7 \
dgit-maint-native.7 \
dgit-maint-merge.7 dgit-maint-gbp.7 \
+ dgit-maint-debrebase.7 \
dgit-sponsorship.7
TXTDOCS=README.dsc-import
-PERLMODULES=Debian/Dgit.pm
+PERLMODULES=Debian/Dgit.pm Debian/Dgit/ExitStatus.pm
ABSURDITIES=git
+GDR_PROGRAMS=git-debrebase
+GDR_PERLMODULES= \
+ Debian/Dgit.pm \
+ Debian/Dgit/GDR.pm \
+ Debian/Dgit/ExitStatus.pm
+GDR_MAN1PAGES=git-debrebase.1
+GDR_MAN5PAGES=git-debrebase.5
+
INFRA_PROGRAMS=dgit-repos-server dgit-ssh-dispatch \
dgit-repos-policy-debian dgit-repos-admin-debian \
dgit-repos-policy-trusting dgit-mirror-rsync
@@ -54,7 +64,10 @@ INFRA_PERLMODULES= \
Debian/Dgit/Infra.pm \
Debian/Dgit/Policy/Debian.pm
-all: $(MAN7PAGES) $(addprefix substituted/,$(PROGRAMS))
+MANPAGES=$(MAN1PAGES) $(MAN5PAGES) $(MAN7PAGES) \
+ $(GDR_MAN1PAGES) $(GDR_MAN5PAGES)
+
+all: $(MANPAGES) $(addprefix substituted/,$(PROGRAMS))
substituted/%: %
mkdir -p substituted
@@ -75,10 +88,19 @@ install: installdirs all
installdirs:
$(INSTALL_DIR) $(DESTDIR)$(bindir) \
- $(DESTDIR)$(man1dir) $(DESTDIR)$(man7dir) \
+ $(DESTDIR)$(man1dir) $(DESTDIR)$(man5dir) \
+ $(DESTDIR)$(man7dir) \
$(DESTDIR)$(txtdocdir) $(DESTDIR)$(absurddir) \
$(addprefix $(DESTDIR)$(perldir)/, $(dir $(PERLMODULES)))
+install-gdr: installdirs-gdr
+ $(INSTALL_PROGRAM) $(GDR_PROGRAMS) $(DESTDIR)$(bindir)
+ $(INSTALL_DATA) $(GDR_MAN1PAGES) $(DESTDIR)$(man1dir)
+ $(INSTALL_DATA) $(GDR_MAN5PAGES) $(DESTDIR)$(man5dir)
+ set -e; for m in $(GDR_PERLMODULES); do \
+ $(INSTALL_DATA) $$m $(DESTDIR)$(perldir)/$${m%/*}; \
+ done
+
install-infra: installdirs-infra
$(INSTALL_PROGRAM) $(addprefix infra/, $(INFRA_PROGRAMS)) \
$(DESTDIR)$(bindir)
@@ -88,6 +110,11 @@ install-infra: installdirs-infra
$(INSTALL_DATA) $$m $(DESTDIR)$(perldir)/$${m%/*}; \
done
+installdirs-gdr:
+ $(INSTALL_DIR) $(DESTDIR)$(bindir) \
+ $(DESTDIR)$(man1dir) $(DESTDIR)$(man5dir) \
+ $(addprefix $(DESTDIR)$(perldir)/, $(dir $(GDR_PERLMODULES)))
+
installdirs-infra:
$(INSTALL_DIR) $(DESTDIR)$(bindir) $(DESTDIR)$(infraexamplesdir) \
$(addprefix $(DESTDIR)$(perldir)/, $(dir $(INFRA_PERLMODULES)))
@@ -96,7 +123,7 @@ check installcheck:
clean distclean mostlyclean maintainer-clean:
rm -rf tests/tmp substituted
- set -e; for m in $(MAN7PAGES); do \
+ set -e; for m in $(MANPAGES); do \
test -e $$m.pod && rm -f $$m; \
done
@@ -105,5 +132,10 @@ clean distclean mostlyclean maintainer-clean:
--name=$(subst .7,,$@) \
$^ $@
+git-debrebase.%: git-debrebase.%.pod
+ pod2man --section=$* --date="Debian Project" --center="git-debrebase" \
+ --name=$(subst .$*,,$@) \
+ $^ $@
+
%.view: %
man -l $*
diff --git a/NOTES.git-debrebase b/NOTES.git-debrebase
new file mode 100644
index 0000000..bd6e715
--- /dev/null
+++ b/NOTES.git-debrebase
@@ -0,0 +1,131 @@
+# problems / outstanding questions:
+#
+# * new-upstream has an awkward UI for multiple upstream pieces.
+# You end up with giant runic command lines. Does this matter /
+# One consequence of the lack of richness it can need -f in
+# fairly sensible situations.
+#
+# * There should be a good convention for the version number,
+# and unfinalised or not changelog, after new-upstream.
+#
+# * Handing of multi-orig dgit new-upstream .dsc imports is known to
+# be broken. They may be not recognised, improperly converted, or
+# their conversion may be unrecognised.
+#
+# * We need to develop a plausible model that works for derivatives,
+# who probably want to maintain their stack on top of Debian's.
+# downstream-rebase-launder-v0 may be a starting point?
+# maybe the hypothetical git-ffqrebase is part of it too.
+
+
+# undocumented usages:
+#
+# git-debrebase [<options>] downstream-rebase-launder-v0 # experimental
+
+
+========================================
+
+Theory for ffq-prev
+
+ refs/ffq-prev/REF relates to refs/REF
+
+When we strip a pm, we need to maybe record it (or something) as the
+new start point.
+
+When we do a thing
+
+ with no recorded ffq-prev
+
+ ffq-prev is our current tip
+
+ obviously it is safe to say we will overwrite this
+ we do check whether there are not-included changes in the remotes
+ because if the new ffq-prev is not ff from the remotes
+ the later pushes will fail
+
+ this model tends to keep ad-hoc commits made on our
+ tip branch before we did rebase start, in the
+ `interchange view' and also in the rebase stack.
+
+ also we can explicitly preserve with
+ git-debrebase stitch
+
+ It is always safe to rewind ffq-prev: all
+ that does is overwrite _less_ stuff.
+
+ in any case putative ffq-prev must be ff from remote.
+ Otherwise when we push it will not be ff, even though we have
+ made pseudomerge to overwrite ffq-prev. So if we spot
+ this, report an error. see above
+
+ with a recorded ffq-prev
+
+ we may need to advance ffq-prev, to allow us to generate
+ future pseudomerges that will be pushable
+
+ advancing ffq-prev is dangerous, since it might
+ effectively cancel the commits that will-ovewrite is advanced
+ over.
+
+ ??? advance it to merge-base( current remote, current tip )
+ if possible (see above), - ie to current remote, subject
+ to the condition that that is an ancestor of current tip
+
+ currently this is not implemented
+
+ better maybe to detect divergence ? but it is rather late
+ by then!
+
+We check we are ff from remotes before recording new ffq-prev
+
+========================================
+
+how to handle divergence and merges (if not detected soon enough)
+
+same problem
+ if merge, look at branches before merge
+ generate new combined branch
+ pseudomerge to overwrite merge
+
+current avaiable strategies:
+
+ maybe launder foreign branch
+
+ if foreign branch is nmuish, can rebase it onto ours
+
+ could merge breakwaters (use analyse to find them)
+ merge breakwaters (assuming same upstream)
+ manually construct new patch queue by inspection of
+ the other two patch queues
+
+ instead of manually constructing patch queue, could use
+ gbp pq export and git merge the patch queues
+ (ie work with interdiffs)
+
+ if upstreams are different and one is ahead
+ simply treat that as "ours" and
+ do the work to import changes from the other
+
+ if upstreams have diverged, can
+ resolve somehow to make new upstream
+ do new-upstream on each branch separately
+ now reduced to previously "solved" problem
+
+ in future, auto patch queue merge algorithm
+ determine next patch to apply
+ there are three versions o..O, l..L, r..R
+ we have already constructed m (previous patch or merged breakwater)
+ try using vector calculus in the implied cube and compute
+ multiple ways to check consistency ?
+
+========================================
+
+For downstreams of Debian, sketch of git-ffqrebase
+
+# git-ffqrebase start [BASE]
+# # records previous HEAD so it can be overwritten
+# # records base for future git-ffqrebase
+# git-ffqrebase set-base BASE
+# git-ffqrebase <git-rebase options>
+# git-ffqrebase finish
+# git-ffqrebase status [BRANCH]
diff --git a/debian/changelog b/debian/changelog
index 507d29f..a67e8db 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,104 @@
+dgit (5.2) unstable; urgency=medium
+
+ dgit bugfixes:
+ * When all Debian changes vanish with single-debian-patch,
+ do not fail to commit the patch queue removal. Closes:#877036.
+ * When build fails because the network is offline, mention
+ that this is because --since-version was not specified.
+ Closes:#883340.
+ * When quilt fixup fails because of discrepancies, print a
+ git diff rune which will show them. Closes:#865446.
+ * When fetch or push wants git fetch (other than in a situation where it
+ happes to be a noop) but --dry-run was specified, fail with an
+ explanation, rather than looping with a false coplaint about git
+ fetch. Closes:#871317.
+ * --overwrite now no longer crashes if there is nothing to overwrite
+ (eg, when used with --new). Instead, it is simply ignored, as it is
+ ignored in situations where the push is fast forward. Closes:#863576.
+
+ dgit/git-debrebase interop bugfixes:
+ * git-debrebase interop: Add a missing debugcmd debugging print.
+ * git-debrebase interop: Actually tolerate git-debrebase make-patches
+ exiting with status 7.
+
+ dgit vcs-git handling improvements:
+ * Provide `update-vcs-git' subcommand, for creating and adjusting the
+ vcs-git remote url. Useful for transition from alioth to salsa.
+ Closes:#902006.
+ * Print a warning to stderr on `dgit fetch sid', if your vcs-git
+ remote url disagrees with what's in sid's .dsc.
+
+ documentation:
+ * dgit(1): Mention under `dgit build' that it uses the network.
+ * dgit(1): Clarify that --overwrite does nothing if not needed.
+ Closes:#863578.
+ * dgit-user(7): Recommend sbuild-debian-developer-setup.
+ [ Sean Whitton. ] Closes:895779.
+
+ test suite:
+ * Use nproc(1) rather than Sys::CPU. This is more portable and does not
+ depend on libsys-cpu-perl being installed. Closes:888496.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk> Sun, 24 Jun 2018 23:33:28 +0100
+
+dgit (5.1) unstable; urgency=medium
+
+ dgit gitattributes handling:
+ * Squash the working-tree-encoding attribute too.
+ * Update an existing `dgit-defuse-attrs' macro in .git/info/attributes.
+ * Test the working-tree-encoding attribute squashing properly.
+ Closes:#901900.
+
+ git-debrebase fixes:
+ * new-upstream: fix (this time for sure) ff check handling
+ of multi-piece upstreams.
+ * Suppress gbp pq export output, except in case of error.
+ Closes:#901809.
+ * Manpages: Fix typos and etc.
+ * Fix a typo in the package description.
+
+ Test suite:
+ * Triger ci.debian.net autopkgtests on: gnupg diffutils patch.
+ (A dummy test is used to add to Testsuite-Triggers.)
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk> Wed, 20 Jun 2018 23:20:57 +0100
+
+dgit (5.0) unstable; urgency=low
+
+ Major new facility:
+ * git-debrebase, a new git workflow tool, in its own package.
+ * dgit will now, when appropriate, check if it should call
+ git-debrebase.
+
+ [ Thanks to Sean Whitton for much useful input, and for
+ dgit-maint-debrebase(7). ]
+
+ dgit bugfixes:
+ * Fix the exit status of programs in dgit.deb, to avoid the Perl
+ misfeature which sometimes copies $! to the exit status.
+ * When checking that the tree is clean, check the git index too.
+ * In quilt_fixup_multipatch, work around git checkout paths
+ not deleting files. (Hypothetical bug AFAIAA.)
+ * Respect --quilt=nofix even if single-debian-patch.
+
+ dgit minor fixes:
+ * "confess" when we die due to a warning, rather than symply dieing.
+
+ Internal changes:
+ * Move $playground global to dgit.
+ * Break git_get_symref and $extra_orig_namepart_re out into Dgit.pm.
+ * Changes to support git-debrebase.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk> Mon, 18 Jun 2018 00:29:44 +0100
+
+dgit (4.4) unstable; urgency=high
+
+ Test suite bugfix:
+ * Use full key hash rather than short keyid. Closes:#896653.
+ [ report: Paul Gevers; fix: Chris Lamb ]
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk> Mon, 23 Apr 2018 13:18:51 +0100
+
dgit (4.3~bpo9+1) stretch-backports; urgency=medium
* Rebuild for stretch-backports.
diff --git a/debian/control b/debian/control
index 4405e14..42f68f6 100644
--- a/debian/control
+++ b/debian/control
@@ -26,6 +26,15 @@ Description: git interoperability with the Debian archive
.
dgit clone and dgit fetch construct git commits from uploads.
+Package: git-debrebase
+Depends: perl, git-core, libdpkg-perl, libfile-fnmatch-perl
+ ${misc:Depends}
+Recommends: dgit, git-buildpackage
+Architecture: all
+Description: rebasing git workflow tool for Debian packaging
+ git-debrebase is a tool for representing in git, and manipulating,
+ Debian packages based on upstream source code.
+
Package: dgit-infrastructure
Depends: ${misc:Depends}, perl, git-core, gpgv, chiark-utils-bin,
libjson-perl, libdigest-sha-perl, libdbd-sqlite3-perl, sqlite3,
diff --git a/debian/rules b/debian/rules
index 9249f88..baff8f8 100755
--- a/debian/rules
+++ b/debian/rules
@@ -31,25 +31,33 @@ override_dh_gencontrol:
perl -i -pe "s/UNRELEASED/$$v/g if m/###substituted###/" usr/bin/dgit
globalperl=/usr/share/perl5
-infraperl=/usr/share/dgit/infra/perl5
-override_dh_auto_install:
+override_dh_auto_install: specpkg_install_gdr specpkg_install_infra
make install prefix=/usr DESTDIR=debian/dgit
- make install-infra prefix=/usr DESTDIR=debian/dgit-infrastructure \
- perldir=$(infraperl)
-# # Most of the Perl modules in dgit-infrastructure live in
-# # $(infraperl). The exception is Debian::Dgit::Infra, which
-# # lives in $(globalperl) and adds $(infraperl) to @INC.
+
+specpkg_install_gdr: p=git-debrebase
+specpkg_install_gdr: pm=GDR
+
+specpkg_install_infra: p=dgit-infrastructure
+specpkg_install_infra: pm=Infra
+
+specpkg_install_%: tok=$*
+specpkg_install_%: specperl=/usr/share/dgit/$(tok)/perl5
+specpkg_install_%:
+ make install-$(tok) prefix=/usr DESTDIR=debian/$(p) perldir=$(specperl)
+# # Most of the Perl modules in this package live in
+# # $(specperl). The exception is Debian::Dgit::Infra, which
+# # lives in $(globalperl) and adds $(specperl) to @INC.
set -ex; \
- base=debian/dgit-infrastructure; \
- mod=Debian/Dgit/Infra.pm; \
- src=$${base}$(infraperl)/$${mod}; \
+ base=debian/$(p); \
+ mod=Debian/Dgit/$(pm).pm; \
+ src=$${base}$(specperl)/$${mod}; \
dst=$${base}$(globalperl)/$${mod}; \
mkdir -p $${dst%/*}; \
mv -f $$src $$dst; \
perl -i -p -e 'next unless m/###substituted###/;' \
-e 'next unless s/^# (?=unshift \@INC,)//;' \
- -e 'die unless s{q\{\S+\}}{q{$(infraperl)}};' \
+ -e 'die unless s{q\{\S+\}}{q{$(specperl)}};' \
$$dst
debian/tests/control: tests/enumerate-tests debian/tests/control.in
diff --git a/debian/tests/control b/debian/tests/control
index f3d20f1..378cf4c 100644
--- a/debian/tests/control
+++ b/debian/tests/control
@@ -16,10 +16,23 @@ Tests-Directory: tests/tests
Depends: dgit, dgit-infrastructure, devscripts, debhelper (>=8), fakeroot, build-essential, chiark-utils-bin
Restrictions: x-dgit-intree-only x-dgit-git-only
+Tests: gdr-diverge-nmu gdr-diverge-nmu-dgit gdr-edits gdr-import-dgit gdr-makepatches7 gdr-subcommands
+Tests-Directory: tests/tests
+Depends: dgit, dgit-infrastructure, devscripts, debhelper (>=8), fakeroot, build-essential, chiark-utils-bin, git-debrebase, git-buildpackage, faketime
+
+Tests: gdr-newupstream gdr-viagit
+Tests-Directory: tests/tests
+Depends: chiark-utils-bin, git-debrebase, git-buildpackage, faketime
+
Tests: gitattributes
Tests-Directory: tests/tests
Depends: dgit, dgit-infrastructure, devscripts, debhelper (>=8), fakeroot, build-essential, chiark-utils-bin, bsdgames, man-db, git-man
+Tests: hint-testsuite-triggers
+Tests-Directory: tests/tests
+Depends: gnupg, patch, diffutils
+Restrictions: hint-testsuite-triggers
+
Tests: defdistro-mirror mirror mirror-debnewgit mirror-private
Tests-Directory: tests/tests
Depends: dgit, dgit-infrastructure, devscripts, debhelper (>=8), fakeroot, build-essential, chiark-utils-bin, rsync
diff --git a/debian/tests/control.in b/debian/tests/control.in
index 960d3ef..b558a25 100644
--- a/debian/tests/control.in
+++ b/debian/tests/control.in
@@ -1,2 +1,2 @@
Tests-Directory: tests/tests
-Depends: dgit, dgit-infrastructure, devscripts, debhelper (>=8), fakeroot, build-essential, chiark-utils-bin
+Depends:
diff --git a/dgit b/dgit
index 27dcf1c..5c9cdc3 100755
--- 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,
@@ -153,6 +158,7 @@ sub parseopts_late_defaults();
sub setup_gitattrs(;$);
sub check_gitattrs($$);
+our $playground;
our $keyid;
autoflush STDOUT 1;
@@ -235,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) = @_;
@@ -253,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 ($) {
@@ -286,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:
@@ -529,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]
@@ -558,7 +585,7 @@ END
sub badusage {
print STDERR "$us: @_\n", $helpmsg or die $!;
- exit 8;
+ finish 8;
}
sub nextarg {
@@ -571,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";
@@ -590,6 +617,7 @@ our %defcfg = ('dgit.default.distro' => 'debian',
'dgit.dsc-url-proto-ok.http' => 'true',
'dgit.dsc-url-proto-ok.https' => 'true',
'dgit.dsc-url-proto-ok.git' => 'true',
+ 'dgit.vcs-git.suites', => 'sid', # ;-separated
'dgit.default.dsc-url-proto-ok' => 'false',
# old means "repo server accepts pushes with old dgit tags"
# new means "repo server accepts pushes with new dgit tags"
@@ -1683,7 +1711,7 @@ our ($dsc_distro, $dsc_hint_tag, $dsc_hint_url);
sub prep_ud () {
dgit_privdir(); # ensures that $dgit_privdir_made is based on $maindir
- fresh_playground 'dgit/unpack';
+ $playground = fresh_playground 'dgit/unpack';
}
sub mktree_in_ud_here () {
@@ -2730,6 +2758,11 @@ END
my $want = $wantr{$rrefname};
next if $got eq $want;
if (!defined $objgot{$want}) {
+ fail <<END unless act_local();
+--dry-run specified but we actually wanted the results of git fetch,
+so this is not going to work. Try running dgit fetch first,
+or using --damp-run instead of --dry-run.
+END
print STDERR <<END;
warning: git ls-remote suggests we want $lrefname
warning: and it should refer to $want
@@ -3361,38 +3394,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 $!;
}
@@ -3427,7 +3479,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;
@@ -3507,7 +3559,7 @@ sub fork_for_multisuite ($) {
sub {
@end = ();
fetch();
- exit 0;
+ finish 0;
});
# xxx collecte the ref here
@@ -3670,6 +3722,20 @@ sub fetch () {
git_fetch_us();
}
fetch_from_archive() or no_such_package();
+
+ my $vcsgiturl = $dsc && $dsc->{'Vcs-Git'};
+ if (length $vcsgiturl and
+ (grep { $csuite eq $_ }
+ split /\;/,
+ cfg 'dgit.vcs-git.suites')) {
+ my $current = cfg 'remote.vcs-git.url', 'RETURN-UNDEF';
+ if (defined $current && $current ne $vcsgiturl) {
+ print STDERR <<END;
+FYI: Vcs-Git in $csuite has different url to your vcs-git remote.
+ Your vcs-git remote url may be out of date. Use dgit update-vcs-git ?
+END
+ }
+ }
printdone "fetched into ".lrref();
}
@@ -3691,15 +3757,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 ($) {
@@ -3708,12 +3766,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}++;
}
}
@@ -3722,6 +3789,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
@@ -3882,6 +3950,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 $!;
@@ -4210,7 +4280,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;
@@ -4239,7 +4316,8 @@ END
}
}
- if (defined $overwrite_version && !defined $maintviewhead) {
+ if (defined $overwrite_version && !defined $maintviewhead
+ && $archive_hash) {
$dgithead = plain_overwrite_pseudomerge($clogp,
$dgithead,
$archive_hash);
@@ -4510,13 +4588,8 @@ sub cmd_clone {
}
sub branchsuite () {
- my @cmd = (@git, qw(symbolic-ref -q HEAD));
- my $branch = cmdoutput_errok @cmd;
- if (!defined $branch) {
- $?==256 or failedcmd @cmd;
- return undef;
- }
- if ($branch =~ m#$lbranch_re#o) {
+ my $branch = git_get_symref();
+ if (defined $branch && $branch =~ m#$lbranch_re#o) {
return $1;
} else {
return undef;
@@ -4547,7 +4620,7 @@ sub cmd_fetch {
parseopts();
fetchpullargs();
my $multi_fetched = fork_for_multisuite(sub { });
- exit 0 if $multi_fetched;
+ finish 0 if $multi_fetched;
fetch();
}
@@ -4563,6 +4636,53 @@ END
pull();
}
+sub cmd_update_vcs_git () {
+ my $specsuite;
+ if (@ARGV==0 || $ARGV[0] =~ m/^-/) {
+ ($specsuite,) = split /\;/, cfg 'dgit.vcs-git.suites';
+ } else {
+ ($specsuite) = (@ARGV);
+ shift @ARGV;
+ }
+ my $dofetch=1;
+ if (@ARGV) {
+ if ($ARGV[0] eq '-') {
+ $dofetch = 0;
+ } elsif ($ARGV[0] eq '-') {
+ shift;
+ }
+ }
+
+ my $sourcep = parsecontrol 'debian/control', 'debian/control';
+ $package = getfield $sourcep, 'Source';
+ my $ctrl;
+ if ($specsuite eq '.') {
+ $ctrl = $sourcep;
+ } else {
+ $isuite = $specsuite;
+ get_archive_dsc();
+ $ctrl = $dsc;
+ }
+ my $url = getfield $ctrl, 'Vcs-Git';
+
+ my @cmd;
+ my $orgurl = cfg 'remote.vcs-git.url', 'RETURN-UNDEF';
+ if (!defined $orgurl) {
+ print STDERR "setting up vcs-git: $url\n";
+ @cmd = (@git, qw(remote add vcs-git), $url);
+ } elsif ($orgurl eq $url) {
+ print STDERR "vcs git already configured: $url\n";
+ } else {
+ print STDERR "changing vcs-git url to: $url\n";
+ @cmd = (@git, qw(remote set-url vcs-git), $url);
+ }
+ runcmd_ordryrun_local @cmd;
+ if ($dofetch) {
+ print "fetching (@ARGV)\n";
+ runcmd_ordryrun_local @git, qw(fetch vcs-git), @ARGV;
+ }
+}
+
sub prep_push () {
parseopts();
build_or_push_prep_early();
@@ -4756,7 +4876,7 @@ sub i_resp_complete {
i_cleanup();
printdebug "all done\n";
- exit 0;
+ finish 0;
}
sub i_resp_file ($) {
@@ -5010,13 +5130,15 @@ sub quiltify_splitbrain_needed () {
}
}
-sub quiltify_splitbrain ($$$$$$) {
- my ($clogp, $unapplied, $headref, $diffbits,
+sub quiltify_splitbrain ($$$$$$$) {
+ my ($clogp, $unapplied, $headref, $oldtiptree, $diffbits,
$editedignores, $cachekey) = @_;
+ my $gitignore_special = 1;
if ($quilt_mode !~ m/gbp|dpm/) {
# treat .gitignore just like any other upstream file
$diffbits = { %$diffbits };
$_ = !!$_ foreach values %$diffbits;
+ $gitignore_special = 0;
}
# We would like any commits we generate to be reproducible
my @authline = clogp_authline($clogp);
@@ -5027,11 +5149,19 @@ sub quiltify_splitbrain ($$$$$$) {
local $ENV{GIT_AUTHOR_EMAIL} = $authline[1];
local $ENV{GIT_AUTHOR_DATE} = $authline[2];
+ my $fulldiffhint = sub {
+ my ($x,$y) = @_;
+ my $cmd = "git diff $x $y -- :/ ':!debian'";
+ $cmd .= " ':!/.gitignore' ':!*/.gitignore'" if $gitignore_special;
+ return "\nFor full diff showing the problem(s), type:\n $cmd\n";
+ };
+
if ($quilt_mode =~ m/gbp|unapplied/ &&
($diffbits->{O2H} & 01)) {
my $msg =
"--quilt=$quilt_mode specified, implying patches-unapplied git tree\n".
" but git tree differs from orig in upstream files.";
+ $msg .= $fulldiffhint->($unapplied, 'HEAD');
if (!stat_exists "debian/patches") {
$msg .=
"\n ... debian/patches is missing; perhaps this is a patch queue branch?";
@@ -5040,7 +5170,7 @@ sub quiltify_splitbrain ($$$$$$) {
}
if ($quilt_mode =~ m/dpm/ &&
($diffbits->{H2A} & 01)) {
- fail <<END;
+ fail <<END. $fulldiffhint->($oldtiptree,'HEAD');
--quilt=$quilt_mode specified, implying patches-applied git tree
but git tree differs from result of applying debian/patches to upstream
END
@@ -5056,7 +5186,7 @@ END
}
if ($quilt_mode =~ m/gbp|dpm/ &&
($diffbits->{O2A} & 02)) {
- fail <<END
+ fail <<END;
--quilt=$quilt_mode specified, implying that HEAD is for use with a
tool which does not create patches for changes to upstream
.gitignores: but, such patches exist in debian/patches.
@@ -5206,11 +5336,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;
@@ -5268,7 +5394,7 @@ sub quiltify ($$$$) {
return $s;
};
if ($quilt_mode eq 'linear') {
- print STDERR "$us: quilt fixup cannot be linear. Stopped at:\n";
+ print STDERR "\n$us: error: quilt fixup cannot be linear. Stopped at:\n";
foreach my $notp (@nots) {
print STDERR "$us: ", $reportnot->($notp), "\n";
}
@@ -5424,6 +5550,33 @@ 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()) {
+ debugcmd "+",@cmd;
+ $!=0; $?=-1;
+ failedcmd @cmd if system @cmd and $?!=7*256;
+ } else {
+ dryrun_report @cmd;
+ }
+ $headref = git_rev_parse('HEAD');
+ }
prep_ud();
changedir $playground;
@@ -5587,7 +5740,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 $!;
}
@@ -5713,6 +5866,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";
@@ -5801,7 +5955,7 @@ END
" --[quilt=]gbp --[quilt=]dpm --quilt=unapplied ?";
if (quiltmode_splitbrain()) {
- quiltify_splitbrain($clogp, $unapplied, $headref,
+ quiltify_splitbrain($clogp, $unapplied, $headref, $oldtiptree,
$diffbits, \%editedignores,
$splitbrain_cachekey);
return;
@@ -5839,7 +5993,7 @@ sub quilt_fixup_editor () {
}
I2->error and die $!;
close O or die $1;
- exit 0;
+ finish 0;
}
sub maybe_apply_patches_dirtily () {
@@ -5943,13 +6097,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;
@@ -6529,7 +6691,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);
@@ -6881,7 +7043,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/-/_/;
@@ -6895,3 +7057,5 @@ git_slurp_config();
my $fn = ${*::}{"cmd_$cmd"};
$fn or badusage "unknown operation $cmd";
$fn->();
+
+finish 0;
diff --git a/dgit-badcommit-fixup b/dgit-badcommit-fixup
index 3995ceb..3e4a718 100755
--- a/dgit-badcommit-fixup
+++ b/dgit-badcommit-fixup
@@ -19,6 +19,8 @@
#
# 4. Run the mirror script to push changes, if necessary.
+END { $? = $Debian::Dgit::ExitStatus::desired // -1; };
+use Debian::Dgit::ExitStatus;
use strict;
@@ -283,7 +285,7 @@ filter_updates();
if (!@updates) {
print Dumper(\%count), "all is well - nothing to do\n";
- exit 0;
+ finish 0;
}
#print Dumper(\@updates);
@@ -325,5 +327,7 @@ if ($real >= 0) {
print "testing output saved in refs/dgit-badfixuptest/\n" or die $!;
} else {
print STDERR "found work to do, exiting status 2\n";
- exit 2;
+ finish 2;
}
+
+finish 0;
diff --git a/dgit-maint-debrebase.7.pod b/dgit-maint-debrebase.7.pod
new file mode 100644
index 0000000..e492768
--- /dev/null
+++ b/dgit-maint-debrebase.7.pod
@@ -0,0 +1,637 @@
+=head1 NAME
+
+dgit - tutorial for package maintainers, using a workflow centered around git-debrebase(1)
+
+=head1 INTRODUCTION
+
+This document describes elements of a workflow for maintaining a
+non-native Debian package using B<dgit>. We maintain the Debian delta
+as a series of git commits on our master branch. We use
+git-debrebase(1) to shuffle our branch such that this series of git
+commits appears at the end of the branch. All the public git history
+is fast-forwarding, i.e., we do not rewrite and force-push.
+
+Some advantages of this workflow:
+
+=over 4
+
+=item
+
+Manipulate the delta queue using the full power of git-rebase(1),
+instead of relying on quilt(1), and without having to switch back and
+forth between patches-applied and patches-unapplied branches when
+committing changes and trying to build, as with gbp-pq(1).
+
+=item
+
+If you are using 3.0 (quilt), provide your delta queue as a properly
+separated series of quilt patches in the source package that you
+upload to the archive (unlike with dgit-maint-merge(7)).
+
+=item
+
+Avoid the git tree being dirtied by the application or unapplication
+of patches, as they are always applied.
+
+=item
+
+Benefit from dgit's safety catches. In particular, ensure that your
+upload always matches exactly your git HEAD.
+
+=item
+
+Provide your full git history in a standard format on B<dgit-repos>,
+where it can benefit downstream dgit users, such as people using dgit
+to do an NMU (see dgit-nmu-simple(7) and dgit-user(7)).
+
+=item
+
+Minimise the amount you need to know about 3.0 (quilt) in order to
+maintain Debian source packages which use that format.
+
+=back
+
+This workflow is appropriate for packages where the Debian delta
+contains multiple pieces which interact, or which you don't expect to
+be able to upstream soon. For packages with simple and/or short-lived
+Debian deltas, use of git-debrebase(1) introduces unneeded complexity.
+For such packages, consider the workflow described in
+dgit-maint-merge(7).
+
+=head1 INITIAL DEBIANISATION
+
+This section explains how to start using this workflow with a new
+package. It should be skipped when converting an existing package to
+this workflow.
+
+=head2 When upstream tags releases in git
+
+Suppose that the latest stable upstream release is 1.2.2, and this has
+been tagged '1.2.2' by upstream.
+
+=over 4
+
+ % git clone -oupstream https://some.upstream/foo.git
+ % cd foo
+ % git verify-tag 1.2.2
+ % git reset --hard 1.2.2
+ % git branch --unset-upstream
+
+=back
+
+The final command detaches your master branch from the upstream
+remote, so that git doesn't try to push anything there, or merge
+unreleased upstream commits. To maintain a copy of your packaging
+branch on B<salsa.debian.org> in addition to B<dgit-repos>, you can do
+something like this:
+
+=over 4
+
+ % git remote add -f origin salsa.debian.org:Debian/foo.git
+ % git push --follow-tags -u origin master
+
+=back
+
+Now go ahead and Debianise your package. Make commits on the master
+branch, adding things in the I<debian/> directory, or patching the
+upstream source. For technical reasons, B<it is essential that your
+first commit introduces the debian/ directory containing at least one
+file, and does nothing else.> In other words, make a commit
+introducing I<debian/> before patching the upstream source.
+
+Finally, you need an orig tarball:
+
+=over 4
+
+ % git deborig
+
+=back
+
+See git-deborig(1) if this fails.
+
+This tarball is ephemeral and easily regenerated, so we don't commit
+it anywhere (e.g. with tools like pristine-tar(1)).
+
+=head3 Comparing upstream's tarball releases
+
+=over 4
+
+The above assumes that you know how to build the package from git and
+that doing so is straightforward.
+
+If, as a user of the upstream source, you usually build from upstream
+tarball releases, rather than upstream git tags, you will sometimes
+find that the git tree doesn't contain everything that is in the
+tarball.
+
+Additional build steps may be needed. For example, you may need your
+I<debian/rules> to run autotools.
+
+You can compare the upstream tarball release, and upstream git tag,
+within git, by importing the tarball into git as described in the
+next section, using a different value for 'upstream-tag', and then
+using git-diff(1) to compare the imported tarball to the release tag.
+
+=back
+
+=head2 When upstream releases only tarballs
+
+Because we want to work in git, we need a virtual upstream branch with
+virtual release tags. gbp-import-orig(1) can manage this for us. To
+begin
+
+=over 4
+
+ % mkdir foo
+ % cd foo
+ % git init
+
+=back
+
+Now create I<debian/gbp.conf>:
+
+=over 4
+
+ [DEFAULT]
+ upstream-branch = upstream
+ debian-branch = master
+ upstream-tag = %(version)s
+
+ sign-tags = True
+ pristine-tar = False
+ pristine-tar-commit = False
+
+ [import-orig]
+ merge-mode = merge
+
+=back
+
+gbp-import-orig(1) requires a pre-existing upstream branch:
+
+=over 4
+
+ % git add debian/gbp.conf && git commit -m "create gbp.conf"
+ % git checkout --orphan upstream
+ % git rm -rf .
+ % git commit --allow-empty -m "initial, empty branch for upstream source"
+ % git checkout -f master
+
+=back
+
+Then we can import the upstream version:
+
+=over 4
+
+ % gbp import-orig --merge-mode=replace ../foo_1.2.2.orig.tar.xz
+
+=back
+
+Our upstream branch cannot be pushed to B<dgit-repos>, but since we
+will need it whenever we import a new upstream version, we must push
+it somewhere. The usual choice is B<salsa.debian.org>:
+
+=over 4
+
+ % git remote add -f origin salsa.debian.org:Debian/foo.git
+ % git push --follow-tags -u origin master upstream
+
+=back
+
+You are now ready to proceed as above, making commits to the
+I<debian/> directory and to the upstream source. As above, for
+technical reasons, B<it is essential that your first commit introduces
+the debian/ directory containing at least one file, and does nothing
+else.> In other words, make a commit introducing I<debian/> before
+patching the upstream source.
+
+=head1 CONVERTING AN EXISTING PACKAGE
+
+This section explains how to convert an existing Debian package to
+this workflow. It should be skipped when debianising a new package.
+
+=head2 No existing git history
+
+=over 4
+
+ % dgit clone foo
+ % cd foo
+ % git remote add -f upstream https://some.upstream/foo.git
+
+=back
+
+=head2 Existing git history using another workflow
+
+First, if you don't already have the git history locally, clone it,
+and obtain the corresponding orig.tar from the archive:
+
+=over 4
+
+ % git clone salsa.debian.org:Debian/foo
+ % cd foo
+ % origtargz
+
+=back
+
+If your tree is patches-unapplied, some conversion work is needed.
+You can use
+
+=over 4
+
+ git debrebase convert-from-gbp
+
+=back
+
+Then make new upstream tags available:
+
+=over 4
+
+ % git remote add -f upstream https://some.upstream/foo.git
+
+=back
+
+Now you simply need to ensure that your git HEAD is dgit-compatible,
+i.e., it is exactly what you would get if you deleted .git, invoked
+B<dpkg-buildpackage -S>, and then unpacked the resultant source
+package.
+
+To achieve this, you might need to delete
+I<debian/source/local-options>. One way to have dgit check your
+progress is to run B<dgit build-source>.
+
+The first dgit push will require I<--overwrite>.
+
+=head1 GIT CONFIGURATION
+
+git-debrebase(1) does not yet support using B<git merge> to merge
+divergent branches of development (see "OTHER MERGES" in
+git-debrebase(5)). You should configure git such that B<git pull>
+does not try to merge:
+
+=over 4
+
+ % git config --local pull.rebase true
+
+=back
+
+Now when you pull work from other Debian contributors, git will rebase
+your work on top of theirs.
+
+If you use this clone for upstream development in addition to
+Debian packaging work, you may not want to set this global setting.
+Instead, see the B<branch.autoSetupRebase> and
+B<branch.E<lt>nameE<gt>.rebase> settings in git-config(5).
+
+=head1 IMPORTING NEW UPSTREAM RELEASES
+
+There are two steps: obtaining git refs that correspond to the new
+release, and importing that release using git-debrebase(1).
+
+=head2 Obtaining the release
+
+=head3 When upstream tags releases in git
+
+=over 4
+
+ % git remote update
+
+=back
+
+=head3 When upstream releases only tarballs
+
+You will need the I<debian/gbp.conf> from "When upstream releases only
+tarballs", above. You will also need your upstream branch. Above, we
+pushed this to B<salsa.debian.org>. You will need to clone or fetch
+from there, instead of relying on B<dgit clone>/B<dgit fetch> alone.
+
+Then, either
+
+=over 4
+
+ % gbp import-orig --no-merge ../foo_1.2.3.orig.tar.xz
+
+=back
+
+or if you have a working watch file
+
+=over 4
+
+ % gbp import-orig --no-merge --uscan
+
+=back
+
+=head2 Importing the release
+
+=over 4
+
+ % git debrebase new-upstream 1.2.3
+
+=back
+
+This invocation of git-debrebase(1) involves a git rebase. You may
+need to resolve conflicts if the Debian delta queue does not apply
+cleanly to the new upstream source.
+
+If all went well, you can now review the merge of the new upstream
+release:
+
+=over 4
+
+ git diff debian/1.2.2-1..HEAD -- . ':!debian'
+
+=back
+
+Pass I<--stat> just to see the list of changed files, which is useful
+to determine whether there are any new or deleted files to may need
+accounting for in your copyright file.
+
+If you obtained a tarball from upstream, you are ready to try a build.
+If you merged a git tag from upstream, you will first need to generate
+a tarball:
+
+=over 4
+
+ % git deborig
+
+=back
+
+=head1 EDITING THE DEBIAN PACKAGING
+
+Just make commits on master that change the contents of I<debian/>.
+
+=head1 EDITING THE DELTA QUEUE
+
+=head2 Adding new patches
+
+Adding new patches is straightforward: just make commits touching only
+files outside of the I<debian/> directory. You can also use tools
+like git-revert(1), git-am(1) and git-cherry-pick(1).
+
+=head2 Editing patches: starting a debrebase
+
+git-debrebase(1) is a wrapper around git-rebase(1) which allows us to
+edit, re-order and delete patches. Run
+
+=over 4
+
+ % git debrebase -i
+
+=back
+
+to start an interactive rebase. You can edit, re-order and delete
+commits just as you would during B<git rebase -i>.
+
+=head2 Editing patches: finishing a debrebase
+
+After completing the git rebase, your branch will not be a
+fast-forward of the git HEAD you had before the rebase. This means
+that we cannot push the branch anywhere. If you are ready to upload,
+B<dgit push> or B<dgit push-source> will take care of fixing this up
+for you.
+
+If you are not yet ready to upload, and want to push your branch to a
+git remote such as B<salsa.debian.org>,
+
+=over 4
+
+ % git debrebase conclude
+
+=back
+
+Note that each time you conclude a debrebase you introduce a
+pseudomerge into your git history, which may make it harder to read.
+Try to do all of the editing of the delta queue that you think will be
+needed for this editing session in a single debrebase, so that there
+is a single debrebase stitch.
+
+=head1 BUILDING AND UPLOADING
+
+You can use dpkg-buildpackage(1) for test builds. When you are ready
+to build for an upload, use B<dgit sbuild>.
+
+Upload with B<dgit push> or B<dgit push-source>. Remember to pass
+I<--new> if the package is new in the target suite.
+
+Right before uploading, if you did not just already do so, you might
+want to have git-debrebase(1) shuffle your branch such that the Debian
+delta queue appears right at the tip of the branch you will push:
+
+=over 4
+
+ % git debrebase
+ % dgit push-source
+
+=back
+
+Note that this will introduce a new pseudomerge.
+
+After dgit pushing, be sure to git push to B<salsa.debian.org>, if
+you're using that.
+
+=head1 HANDLING DFSG-NON-FREE MATERIAL
+
+=head2 Illegal material
+
+Here we explain how to handle material that is merely DFSG-non-free.
+Material which is legally dangerous (for example, files which are
+actually illegal) cannot be handled this way.
+
+If you encounter possibly-legally-dangerous material in the upstream
+source code you should seek advice. It is often best not to make a
+fuss on a public mailing list (at least, not at first). Instead,
+email your archive administrators. For Debian that is
+ To: dgit-owner@debian.org, ftpmaster@ftp-master.debian.org
+
+=head2 DFSG-non-free: When upstream tags releases in git
+
+Our approach is to maintain a DFSG-clean upstream branch, and create
+tags on this branch for each release that we want to import. We then
+import those tags per "Importing the release", above.
+
+For the first upstream release that requires DFSG filtering:
+
+=over 4
+
+ % git checkout -b upstream-dfsg 1.2.3
+ % git rm evil.bin
+ % git commit -m "upstream version 1.2.3 DFSG-cleaned"
+ % git tag -s 1.2.3+dfsg
+ % git checkout master
+ % # proceed with "Importing the release" on 1.2.3+dfsg tag
+
+=back
+
+And for subsequent releases (whether or not they require filtering):
+
+=over 4
+
+ % git checkout upstream-dfsg
+ % git merge 1.2.4
+ % git rm further-evil.bin # if needed
+ % git commit -m "upstream version 1.2.4 DFSG-cleaned" # if needed
+ % git tag -s 1.2.4+dfsg
+ % git checkout master
+ % # proceed with "Importing the release" on 1.2.4+dfsg tag
+
+=back
+
+Our upstream-dfsg branch cannot be pushed to B<dgit-repos>, but since
+we will need it whenever we import a new upstream version, we must
+push it somewhere. Assuming that you have already set up an origin
+remote per the above,
+
+=over 4
+
+ % git push --follow-tags -u origin master upstream-dfsg
+
+=back
+
+=head2 DFSG-non-free: When upstream releases only tarballs
+
+The easiest way to handle this is to add a B<Files-Excluded> field to
+I<debian/copyright>, and a B<uversionmangle> setting in
+I<debian/watch>. See uscan(1). Alternatively, see the I<--filter>
+option detailed in gbp-import-orig(1).
+
+=head1 INCORPORATING NMUS
+
+In the simplest case,
+
+=over 4
+
+ % dgit fetch
+ % git merge --ff-only dgit/dgit/sid
+
+=back
+
+If that fails, because your branch and the NMUers work represent
+divergent branches of development, you have a number of options. Here
+we describe the two simplest.
+
+Note that you should not try to resolve the divergent branches by
+editing files in I<debian/patches>. Changes there would either cause
+trouble, or be overwritten by git-debrebase(1).
+
+=head2 Rebasing your work onto the NMU
+
+=over 4
+
+ % git rebase dgit/dgit/sid
+
+=back
+
+If the NMUer added new commits modifying the upstream source, you will
+probably want to debrebase before your next upload to tidy those up.
+
+For example, the NMUer might have used git-revert(1) to unapply one of
+your patches. A debrebase can be used to strip both the patch and the
+reversion from the delta queue.
+
+=head2 Manually applying the debdiff
+
+If you cannot rebase because you have already pushed to
+B<salsa.debian.org>, say, you can manually apply the NMU debdiff,
+commit and debrebase. The next B<dgit push> will require
+I<--overwrite>.
+
+=head1 HINTS AND TIPS
+
+=head2 Minimising pseudomerges
+
+Above we noted that each time you conclude a debrebase, you introduce
+a pseudomerge into your git history, which may make it harder to read.
+
+A simple convention you can use to minimise the number of pseudomerges
+is to B<git debrebase conclude> only right before you upload or push
+to B<salsa.debian.org>.
+
+It is possible, though much less convenient, to reduce the number of
+pseudomerges yet further. We debrebase only (i) when importing a new
+release, and (ii) right before uploading. Instead of editing the
+existing delta queue, you append fixup commits (and reversions of
+commits) that alter the upstream source to the required state. You
+can push and pull to and from B<salsa.debian.org> during this. Just
+before uploading, you debrebase, once, to tidy everything up.
+
+=head2 The debian/patches directory
+
+In this workflow, I<debian/patches> is purely an output of
+git-debrebase(1). You should not make changes there. They will
+either cause trouble, or be ignored and overwritten by
+git-debrebase(1).
+
+I<debian/patches> will often be out-of-date because git-debrebase(1)
+will only regenerate it when it needs to. So you should not rely on
+the information in that directory. When preparing patches to forward
+upstream, you should use git-format-patch(1) on git commits, rather
+than sending files from I<debian/patches>.
+
+=head2 Upstream branches
+
+In this workflow, we specify upstream tags rather than any branches.
+
+Except when (i) upstream releases only tarballs, (ii) we require DFSG
+filtering, or (iii) you also happen to be involved in upstream
+development, we do not maintain any local branch corresponding to
+upstream, except temporary branches used to prepare patches for
+forwarding, and the like.
+
+The idea here is that from Debian's point of view, upstream releases
+are immutable points in history. We want to make sure that we are
+basing our Debian package on a properly identified upstream version,
+rather than some arbitrary commit on some branch. Tags are more
+useful for this.
+
+Upstream's branches remain available as the git remote tracking
+branches for your upstream remote, e.g. I<remotes/upstream/master>.
+
+=head2 The first ever dgit push
+
+If this is the first ever dgit push of the package, consider passing
+I<--deliberately-not-fast-forward> instead of I<--overwrite>. This
+avoids introducing a new origin commit into your git history. (This
+origin commit would represent the most recent non-dgit upload of the
+package, but this should already be represented in your git history.)
+
+=head2 Alternative ways to start a debrebase
+
+Above we started an interactive debrebase by invoking git-debrebase(1)
+like this:
+
+=over 4
+
+ % git debrebase -i
+
+=back
+
+It is also possible to perform a non-interactive rebase, like this:
+
+=over 4
+
+ % git debrebase -- [git-rebase options...]
+
+=back
+
+
+A third alternative is to have git-debrebase(1) shuffle all the Debian
+changes to the end of your branch, and then manipulate them yourself
+using git-rebase(1) directly. For example,
+
+=over 4
+
+ % git debrebase launder
+ % git rebase -i HEAD~5 # there are 4 Debian patches
+
+=back
+
+If you take this approach, you should be very careful not to start the
+rebase too early.
+
+=head1 SEE ALSO
+
+dgit(1), dgit(7), git-debrebase(1), git-debrebase(5)
+
+=head1 AUTHOR
+
+This tutorial was written and is maintained by Sean Whitton
+<spwhitton@spwhitton.name>. It contains contributions from other dgit
+contributors too - see the dgit copyright file.
diff --git a/dgit-maint-merge.7.pod b/dgit-maint-merge.7.pod
index 2674d66..a02e1fd 100644
--- a/dgit-maint-merge.7.pod
+++ b/dgit-maint-merge.7.pod
@@ -42,7 +42,7 @@ or which you aren't going to be able to upstream soon,
it might be preferable to
maintain the delta as a rebasing patch series.
For such a workflow see for example
-dgit-maint-gbp(7).
+dgit-maint-debrebase(7) and dgit-maint-gbp(7).
=head1 INITIAL DEBIANISATION
diff --git a/dgit-user.7.pod b/dgit-user.7.pod
index c74396a..5713064 100644
--- a/dgit-user.7.pod
+++ b/dgit-user.7.pod
@@ -367,8 +367,9 @@ have enabled.
You'll need a chroot for each of the secondary architectures.
This is somewhat tiresome,
even though Debian has excellent tools for managing chroots.
-C<sbuild-createchroot> from the sbuild package is a
-good starting point.
+C<sbuild-debian-developer-setup> from the package of the same name
+and C<sbuild-createchroot> from the C<sbuild> package are
+good starting points.
Otherwise you could deinstall the packages of interest
for those other architectures
diff --git a/dgit.1 b/dgit.1
index 6d46b20..8ddcbe3 100644
--- a/dgit.1
+++ b/dgit.1
@@ -39,10 +39,11 @@ This is the command line reference.
Please read the tutorial(s):
.TS
lb l.
-dgit-user(7) for users: editing, building and sharing packages
-dgit-nmu-simple(7) for DDs: doing a straightforward NMU
+dgit-user(7) for users: edit, build and share packages
+dgit-nmu-simple(7) for DDs: do a straightforward NMU
dgit-maint-native(7) for maintainers of Debian-native packages
-dgit-maint-merge(7) for maintainers who want a pure git workflow
+dgit-maint-debrebase(7) for maintainers: a pure-git rebasish workflow
+dgit-maint-merge(7) for maintainers: a pure-git merging workflow
dgit-maint-gbp(7) for maintainers already using git-buildpackage
dgit-sponsorship(7) for sponsors and sponsored contributors
.TE
@@ -87,6 +88,7 @@ For your convenience, the
remote will be set up from the package's Vcs-Git field, if there is
one - but note that in the general case the history found there may be
different to or even disjoint from dgit's view.
+(See also dgit update-vcs-git.)
.TP
\fBdgit fetch\fR [\fIsuite\fP]
Consults the archive and git-repos to update the git view of
@@ -117,6 +119,10 @@ that the generated source package corresponds to the relevant git
commit.
Tagging, signing and actually uploading should be left to dgit push.
+
+dgit's build operations access the the network,
+to get the -v option right.
+See -v, below.
.TP
\fBdgit build-source\fR ...
Builds the source package, and a changes file for a prospective
@@ -134,6 +140,20 @@ push-source, or dgit push.
Cleans the current working tree (according to the --clean= option in
force).
.TP
+\fBdgit update-vcs-git\fR [\fIsuite\fP|\fB.\fR] [\fB--\fR] [\fIgit fetch options\fR]
+.TQ
+\fBdgit update-vcs-git\fR [\fIsuite|\fP\fB.\fR] \fB-\fR
+Sets up, or updates the url of, the vcs-git remote, and
+(unless \fB-\fR was specified)
+runs git fetch on it.
+
+By default, the Vcs-Git field of the .dsc from Debian sid is used,
+as that is probably most up to date.
+Another suite may be specified, or
+.B .
+to indicate that the Vcs-Git of the cwd's debian/control should
+be used instead.
+.TP
.B dgit help
Print a usage summary.
.TP
@@ -185,14 +205,19 @@ archive.
dgit push always uses the package, suite and version specified in the
debian/changelog and the .dsc, which must agree. If the command line
specifies a suite then that must match too.
+
+With \fB-C\fR, performs a dgit push, additionally ensuring that no
+binary packages are uploaded.
+
+When used on a git-debrebase branch,
+dgit calls git-debrebase
+to prepare the branch
+for source package upload and push.
.TP
\fBdgit push-source\fR [\fIsuite\fP]
Without \fB-C\fR, builds a source package and dgit pushes it. Saying
\fBdgit push-source\fR is like saying "update the source code in the
archive to match my git HEAD, and let the autobuilders do the rest."
-
-With \fB-C\fR, performs a dgit push, additionally ensuring that no
-binary packages are uploaded.
.TP
\fBdgit rpush\fR \fIbuild-host\fR\fB:\fR\fIbuild-dir\fR [\fIpush args...\fR]
Pushes the contents of the specified directory on a remote machine.
@@ -236,6 +261,11 @@ and
(but only does each thing if dgit is configured to do it automatically).
You can use these in any git repository, not just ones used with
the other dgit operations.
+Does
+.B not
+run
+.B update-vcs-git
+(as that requires Debian packaging information).
.TP
.B dgit setup-useremail
Set the working tree's user.name and user.email from the
@@ -262,10 +292,19 @@ For why, see
in
.BR dgit(7) .
+If there is an existing macro attribute line
+.B [attr]dgit-defuse-attrs
+in .git/info/attributes,
+but it is insufficient,
+because it was made by an earlier version of dgit
+and git has since introduced new transforming attributes,
+modifies the macro to disable the newer transformations.
+
(If there is already a macro attribute line
.B [attr]dgit-defuse-attrs
in .git/info/attributes
-(whatever its effects),
+which does what dgit requires
+(whatever files it effects),
this operation does nothing further.
This fact can be used to defeat or partially defeat
dgit setup-gitattributes
@@ -286,6 +325,15 @@ new quilt patch. dgit cannot convert nontrivial merges, or certain
other kinds of more exotic history. If dgit can't find a suitable
linearisation of your history, by default it will fail, but you can
ask it to generate a single squashed patch instead.
+
+When used with a git-debrebase branch,
+dgit will ask git-debrebase to prepare patches.
+However,
+dgit can make patches in some situations where git-debrebase fails,
+so dgit quilt-fixup can be useful in its own right.
+To always use dgit's own patch generator
+instead of git-debrebase make-patches,
+pass --git-debrebase=true to dgit.
.TP
\fBdgit import-dsc\fR [\fIsub-options\fR] \fI../path/to/.dsc\fR [\fB+\fR|\fB..\fR]branch
Import a Debian-format source package,
@@ -461,7 +509,7 @@ push will still ensure that the .dsc you upload and the git tree
you push are identical, so this option won't make broken pushes.)
.TP
.BR --overwrite [=\fIprevious-version\fR]
-Declare that even though your git branch is not a descendant
+Declare that even though your git branch may not be a descendant
of the version in the archive
according to the revision history,
it really does contain
@@ -482,7 +530,7 @@ changes unless someone committed to git a finalised changelog
entry, and then made later changes to that version.)
dgit push --overwrite
-will make a
+will, if necessary, make a
pseudo-merge (that is, something that looks like the result
of git merge -s ours) to stitch the archive's version into your own
git history, so that your push is a fast forward from the archive.
@@ -825,6 +873,7 @@ Specifies a single additional option to pass to
.BR sbuild ,
.BR ssh ,
.BR dgit ,
+.BR git-debrebase ,
.BR apt-get ,
.BR apt-cache ,
.BR gbp-pq ,
@@ -872,6 +921,7 @@ Specifies alternative programs to use instead of
.BR gpg ,
.BR ssh ,
.BR dgit ,
+.BR git-debrebase ,
.BR apt-get ,
.BR apt-cache ,
.BR git ,
@@ -1143,6 +1193,8 @@ or when pushing and
.BI dgit.dsc-url-proto-ok.bad-syntax
.TP
.BI dgit.default.dsc-url-proto-ok
+.TP
+.BR dgit.vcs-git.suites " \fIsuite\fR[" ; ...]
.SH ENVIRONMENT VARIABLES
.TP
.BR DGIT_SSH ", " GIT_SSH
diff --git a/git-debrebase b/git-debrebase
new file mode 100755
index 0000000..fd05c6b
--- /dev/null
+++ b/git-debrebase
@@ -0,0 +1,1733 @@
+#!/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,2018 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 <http://www.gnu.org/licenses/>.
+
+END { $? = $Debian::Dgit::ExitStatus::desired // -1; };
+use Debian::Dgit::GDR;
+use Debian::Dgit::ExitStatus;
+
+use strict;
+
+use Debian::Dgit qw(:DEFAULT :playground);
+setup_sigwarn();
+
+use Memoize;
+use Carp;
+use POSIX;
+use Data::Dumper;
+use Getopt::Long qw(:config posix_default gnu_compat bundling);
+use Dpkg::Version;
+use File::FnMatch qw(:fnmatch);
+use File::Copy;
+
+our ($opt_force, $opt_noop_ok, @opt_anchors);
+our ($opt_defaultcmd_interactive);
+
+our $us = qw(git-debrebase);
+
+sub badusage ($) {
+ my ($m) = @_;
+ print STDERR "bad usage: $m\n";
+ finish 8;
+}
+
+sub cfg ($;$) {
+ my ($k, $optional) = @_;
+ local $/ = "\0";
+ my @cmd = qw(git config -z);
+ push @cmd, qw(--get-all) if wantarray;
+ push @cmd, $k;
+ my $out = cmdoutput_errok @cmd;
+ if (!defined $out) {
+ fail "missing required git config $k" unless $optional;
+ return ();
+ }
+ my @l = split /\0/, $out;
+ return wantarray ? @l : $l[0];
+}
+
+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');
+ $data =~ m/(?<=\n)\n/ or die "$objid ($data) ?";
+ return ($`,$');
+}
+
+sub D_UPS () { 0x02; } # upstream files
+sub D_PAT_ADD () { 0x04; } # debian/patches/ extra patches at end
+sub D_PAT_OTH () { 0x08; } # debian/patches other changes
+sub D_DEB_CLOG () { 0x10; } # debian/ (not patches/ or changelog)
+sub D_DEB_OTH () { 0x20; } # debian/changelog
+sub DS_DEB () { D_DEB_CLOG | D_DEB_OTH; } # debian/ (not patches/)
+
+our $playprefix = 'debrebase';
+our $rd;
+our $workarea;
+
+our @git = qw(git);
+
+sub in_workarea ($) {
+ my ($sub) = @_;
+ changedir $workarea;
+ my $r = eval { $sub->(); };
+ { local $@; changedir $maindir; }
+ die $@ if $@;
+}
+
+sub fresh_workarea () {
+ $workarea = fresh_playground "$playprefix/work";
+ in_workarea sub { playtree_setup };
+}
+
+our $snags_forced = 0;
+our $snags_tripped = 0;
+our $snags_summarised = 0;
+our @deferred_updates;
+our @deferred_update_messages;
+
+sub all_snags_summarised () {
+ $snags_forced + $snags_tripped == $snags_summarised;
+}
+sub run_deferred_updates ($) {
+ my ($mrest) = @_;
+
+ confess 'dangerous internal error' unless all_snags_summarised();
+
+ my @upd_cmd = (@git, qw(update-ref --stdin -m), "debrebase: $mrest");
+ debugcmd '>|', @upd_cmd;
+ open U, "|-", @upd_cmd or die $!;
+ foreach (@deferred_updates) {
+ printdebug ">= ", $_, "\n";
+ print U $_, "\n" or die $!;
+ }
+ printdebug ">\$\n";
+ close U or failedcmd @upd_cmd;
+
+ print $_, "\n" foreach @deferred_update_messages;
+
+ @deferred_updates = ();
+ @deferred_update_messages = ();
+}
+
+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' ? DS_DEB : D_UPS;
+ });
+
+ if ($differs & DS_DEB) {
+ $differs &= ~DS_DEB;
+ $rundiff->([qw(--name-only -r)], [qw(debian)], sub {
+ $differs |=
+ m{^debian/patches/} ? D_PAT_OTH :
+ $_ eq 'debian/changelog' ? D_DEB_CLOG :
+ D_DEB_OTH;
+ });
+ die "mysterious debian changes $x..$y"
+ unless $differs & (D_PAT_OTH|DS_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)]);
+}
+
+sub calculate_committer_authline () {
+ my $c = cmdoutput @git, qw(commit-tree --no-gpg-sign -m),
+ 'DUMMY COMMIT (git-debrebase)', "HEAD:";
+ my ($h,$m) = get_commit $c;
+ $h =~ m/^committer .*$/m or confess "($h) ?";
+ 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;
+}
+
+our @snag_force_opts;
+sub snag ($$;@) {
+ my ($tag,$msg) = @_; # ignores extra args, for benefit of keycommits
+ if (grep { $_ eq $tag } @snag_force_opts) {
+ $snags_forced++;
+ print STDERR "git-debrebase: snag ignored (-f$tag): $msg\n";
+ } else {
+ $snags_tripped++;
+ print STDERR "git-debrebase: snag detected (-f$tag): $msg\n";
+ }
+}
+
+# Important: all mainline code must call snags_maybe_bail after
+# any point where snag might be called, but before making changes
+# (eg before any call to run_deferred_updates). snags_maybe_bail
+# may be called more than once if necessary (but this is not ideal
+# because then the messages about number of snags may be confusing).
+sub snags_maybe_bail () {
+ return if all_snags_summarised();
+ if ($snags_forced) {
+ printf STDERR
+ "%s: snags: %d overriden by individual -f options\n",
+ $us, $snags_forced;
+ }
+ if ($snags_tripped) {
+ if ($opt_force) {
+ printf STDERR
+ "%s: snags: %d overriden by global --force\n",
+ $us, $snags_tripped;
+ } else {
+ fail sprintf
+ "%s: snags: %d blockers (you could -f<tag>, or --force)",
+ $us, $snags_tripped;
+ }
+ }
+ $snags_summarised = $snags_forced + $snags_tripped;
+}
+sub any_snags () {
+ return $snags_forced || $snags_tripped;
+}
+
+# classify returns an info hash like this
+# CommitId => $objid
+# Hdr => # commit headers, including 1 final newline
+# Msg => # commit message (so one newline is dropped)
+# Tree => $treeobjid
+# Type => (see below)
+# Parents = [ {
+# Ix => $index # ie 0, 1, 2, ...
+# CommitId
+# Differs => return value from get_differs
+# IsOrigin
+# IsDggitImport => 'orig' 'tarball' 'unpatched' 'package' (as from dgit)
+# } ...]
+# NewMsg => # commit message, but with any [dgit import ...] edited
+# # to say "[was: ...]"
+#
+# Types:
+# Packaging
+# Changelog
+# Upstream
+# AddPatches
+# Mixed
+#
+# Pseudomerge
+# has additional entres in classification result
+# Overwritten = [ subset of Parents ]
+# Contributor = $the_remaining_Parent
+#
+# DgitImportUnpatched
+# has additional entry in classification result
+# OrigParents = [ subset of Parents ]
+#
+# Anchor
+# has additional entry in classification result
+# OrigParents = [ subset of Parents ] # singleton list
+#
+# TreatAsAnchor
+#
+# BreakwaterStart
+#
+# Unknown
+# has additional entry in classification result
+# Why => "prose"
+
+sub parsecommit ($;$) {
+ my ($objid, $p_ref) = @_;
+ # => hash with CommitId Hdr Msg Tree Parents
+ # Parents entries have only Ix CommitId
+ # $p_ref, if provided, must be [] and is used as a base for Parents
+
+ $p_ref //= [];
+ die if @$p_ref;
+
+ my ($h,$m) = get_commit $objid;
+
+ my ($t) = $h =~ m/^tree (\w+)$/m or die $objid;
+ my (@ph) = $h =~ m/^parent (\w+)$/mg;
+
+ my $r = {
+ CommitId => $objid,
+ Hdr => $h,
+ Msg => $m,
+ Tree => $t,
+ Parents => $p_ref,
+ };
+
+ foreach my $ph (@ph) {
+ push @$p_ref, {
+ Ix => scalar @$p_ref,
+ CommitId => $ph,
+ };
+ }
+
+ return $r;
+}
+
+sub classify ($) {
+ my ($objid) = @_;
+
+ my @p;
+ my $r = parsecommit($objid, \@p);
+ my $t = $r->{Tree};
+
+ foreach my $p (@p) {
+ $p->{Differs} = (get_differs $p->{CommitId}, $t),
+ }
+
+ 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) {
+ printdebug " = $type ".(dd $r)."\n";
+ }
+ return $r;
+ };
+ my $unknown = sub {
+ my ($why) = @_;
+ $r = { %$r, Type => qw(Unknown), Why => $why };
+ printdebug " ** Unknown\n";
+ return $r;
+ };
+
+ if (grep { $_ eq $objid } @opt_anchors) {
+ return $classify->('TreatAsAnchor');
+ }
+
+ my @identical = grep { !$_->{Differs} } @p;
+ my ($stype, $series) = git_cat_file "$t:debian/patches/series";
+ my $haspatches = $stype ne 'missing' && $series =~ m/^\s*[^#\n\t ]/m;
+
+ if ($r->{Msg} =~ m{^\[git-debrebase anchor.*\]$}m) {
+ # multi-orig upstreams are represented with an anchor merge
+ # from a single upstream commit which combines the orig tarballs
+
+ # Every anchor tagged this way must be a merge.
+ # We are relying on the
+ # [git-debrebase anchor: ...]
+ # commit message annotation in "declare" anchor merges (which
+ # do not have any upstream changes), to distinguish those
+ # anchor merges from ordinary pseudomerges (which we might
+ # just try to strip).
+ #
+ # However, the user is going to be doing git-rebase a lot. We
+ # really don't want them to rewrite an anchor commit.
+ # git-rebase trips up on merges, so that is a useful safety
+ # catch.
+ #
+ # BreakwaterStart commits are also anchors in the terminology
+ # of git-debrebase(5), but they are untagged (and always
+ # manually generated).
+ #
+ # We cannot not tolerate any tagged linear commit (ie,
+ # BreakwaterStart commits tagged `[anchor:') because such a
+ # thing could result from an erroneous linearising raw git
+ # rebase of a merge anchor. That would represent a corruption
+ # of the branch. and we want to detect and reject the results
+ # of such corruption before it makes it out anywhere. If we
+ # reject it here then we avoid making the pseudomerge which
+ # would be needed to push it.
+
+ my $badanchor = sub { $unknown->("git-debrebase \`anchor' but @_"); };
+ @p == 2 or return $badanchor->("has other than two parents");
+ $haspatches and return $badanchor->("contains debian/patches");
+
+ # How to decide about l/r ordering of anchors ? 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).
+
+ # Also this makes --first-parent be slightly more likely to
+ # be useful - it makes it provide a linearised breakwater history.
+
+ # Of course one can say somthing like
+ # gitk -- ':/' ':!/debian'
+ # to get _just_ the commits touching upstream files, and by
+ # the TREESAME logic in git-rev-list this will leave the
+ # breakwater into upstream at the first anchor. But that
+ # doesn't report debian/ changes at all.
+
+ # Other observations about gitk: by default, gitk seems to
+ # produce output in a different order to git-rev-list. I
+ # can't seem to find this documented anywhere. gitk
+ # --date-order DTRT. But, gitk always seems to put the
+ # parents from left to right, in order, so it's easy to see
+ # which way round a pseudomerge is.
+
+ $p[0]{IsOrigin} and $badanchor->("is an origin commit");
+ $p[1]{Differs} & ~DS_DEB and
+ $badanchor->("upstream files differ from left parent");
+ $p[0]{Differs} & ~D_UPS and
+ $badanchor->("debian/ differs from right parent");
+
+ return $classify->(qw(Anchor),
+ OrigParents => [ $p[1] ]);
+ }
+
+ if (@p == 1) {
+ my $d = $r->{Parents}[0]{Differs};
+ if ($d == D_PAT_ADD) {
+ return $classify->(qw(AddPatches));
+ } 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 "$p[0]{CommitId}:debian";
+ if ($ty eq 'tree') {
+ if ($d == D_DEB_CLOG) {
+ return $classify->(qw(Changelog));
+ } else {
+ return $classify->(qw(Packaging));
+ }
+ } elsif ($ty eq 'missing') {
+ return $classify->(qw(BreakwaterStart));
+ } else {
+ return $unknown->("parent's debian is not a directory");
+ }
+ } elsif ($d == D_UPS) {
+ return $classify->(qw(Upstream));
+ } elsif ($d & DS_DEB and $d & D_UPS and !($d & ~(DS_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");
+ }
+
+ 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 $get_t = sub {
+ my ($ph,$pm) = get_commit $_[0]{CommitId};
+ $ph =~ m/^committer .* (\d+) [-+]\d+$/m or die "$_->{CommitId} ?";
+ $1;
+ };
+ my @bytime = @p;
+ my $order = $get_t->($bytime[0]) <=> $get_t->($bytime[1]);
+ if ($order > 0) { # newer first
+ } elsif ($order < 0) {
+ @bytime = reverse @bytime;
+ } else {
+ # same age, default to order made by -s ours
+ # that is, commit was made by someone who preferred L
+ }
+ return $classify->(qw(Pseudomerge),
+ SubType => qw(Ambiguous),
+ Contributor => $bytime[0],
+ Overwritten => [ $bytime[1] ]);
+ }
+ foreach my $p (@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;
+ }
+ my @orig_ps = grep { ($_->{IsDgitImport}//'X') eq 'orig' } @p;
+ my $m2 = $r->{Msg};
+ if (!(grep { !$_->{IsOrigin} } @p) and
+ (@orig_ps >= @p - 1) and
+ $m2 =~ s{^\[(dgit import unpatched .*)\]$}{[was: $1]}m) {
+ $r->{NewMsg} = $m2;
+ return $classify->(qw(DgitImportUnpatched),
+ OrigParents => \@orig_ps);
+ }
+
+ return $unknown->("complex merge");
+}
+
+sub keycommits ($;$$$$) {
+ my ($head, $furniture, $unclean, $trouble, $fatal) = @_;
+ # => ($anchor, $breakwater)
+
+ # $unclean->("unclean-$tagsfx", $msg, $cl)
+ # $furniture->("unclean-$tagsfx", $msg, $cl)
+ # $dgitimport->("unclean-$tagsfx", $msg, $cl))
+ # is callled for each situation or commit that
+ # wouldn't be found in a laundered branch
+ # $furniture is for furniture commits such as might be found on an
+ # interchange branch (pseudomerge, d/patches, changelog)
+ # $trouble is for things whnich prevent the return of
+ # anchor and breakwater information; if that is ignored,
+ # then keycommits returns (undef, undef) instead.
+ # $fatal is for unprocessable commits, and should normally cause
+ # a failure. If ignored, agaion, (undef, undef) is returned.
+ #
+ # If a callback is undef, fail is called instead.
+ # If a callback is defined but false, the situation is ignored.
+ # Callbacks may say:
+ # no warnings qw(exiting); last;
+ # if the answer is no longer wanted.
+
+ my ($anchor, $breakwater);
+ my $clogonly;
+ my $cl;
+ $fatal //= sub { fail $_[2]; };
+ my $x = sub {
+ my ($cb, $tagsfx, $why) = @_;
+ my $m = "branch needs laundering (run git-debrebase): $why";
+ fail $m unless defined $cb;
+ return unless $cb;
+ $cb->("unclean-$tagsfx", $why, $cl);
+ };
+ for (;;) {
+ $cl = classify $head;
+ my $ty = $cl->{Type};
+ if ($ty eq 'Packaging') {
+ $breakwater //= $clogonly;
+ $breakwater //= $head;
+ } elsif ($ty eq 'Changelog') {
+ # this is going to count as the tip of the breakwater
+ # only if it has no upstream stuff before it
+ $clogonly //= $head;
+ } elsif ($ty eq 'Anchor' or
+ $ty eq 'TreatAsAnchor' or
+ $ty eq 'BreakwaterStart') {
+ $anchor = $head;
+ $breakwater //= $clogonly;
+ $breakwater //= $head;
+ last;
+ } elsif ($ty eq 'Upstream') {
+ $x->($unclean, 'ordering',
+ "packaging change ($breakwater) follows upstream change (eg $head)")
+ if defined $breakwater;
+ $clogonly = undef;
+ $breakwater = undef;
+ } elsif ($ty eq 'Mixed') {
+ $x->($unclean, 'mixed',
+ "found mixed upstream/packaging commit ($head)");
+ $clogonly = undef;
+ $breakwater = undef;
+ } elsif ($ty eq 'Pseudomerge' or
+ $ty eq 'AddPatches') {
+ $x->($furniture, (lc $ty),
+ "found interchange bureaucracy commit ($ty, $head)");
+ } elsif ($ty eq 'DgitImportUnpatched') {
+ $x->($trouble, 'dgitimport',
+ "found dgit dsc import ($head)");
+ return (undef,undef);
+ } else {
+ $x->($fatal, 'unprocessable',
+ "found unprocessable commit, cannot cope: $head; $cl->{Why}"
+ );
+ return (undef,undef);
+ }
+ $head = $cl->{Parents}[0]{CommitId};
+ }
+ return ($anchor, $breakwater);
+}
+
+sub walk ($;$$);
+sub walk ($;$$) {
+ my ($input,
+ $nogenerate,$report) = @_;
+ # => ($tip, $breakwater_tip, $last_anchor)
+ # (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
+ my (@brw_cl, @upp_cl, @processed);
+ my %found;
+ my $upp_limit;
+ my @pseudomerges;
+
+ my $cl;
+ my $xmsg = sub {
+ my ($prose, $info) = @_;
+ my $ms = $cl->{Msg};
+ chomp $ms;
+ $info //= '';
+ $ms .= "\n\n[git-debrebase$info: $prose]\n";
+ return (Msg => $ms);
+ };
+ my $rewrite_from_here = sub {
+ my ($cl) = @_;
+ my $sp_cl = { SpecialMethod => 'StartRewrite' };
+ push @$cl, $sp_cl;
+ push @processed, $sp_cl;
+ };
+ my $cur = $input;
+
+ my $prdelim = "";
+ my $prprdelim = sub { print $report $prdelim if $report; $prdelim=""; };
+
+ my $prline = sub {
+ return unless $report;
+ print $report $prdelim, @_;
+ $prdelim = "\n";
+ };
+
+ my $bomb = sub { # usage: return $bomb->();
+ print $report " Unprocessable" if $report;
+ print $report " ($cl->{Why})" if $report && defined $cl->{Why};
+ $prprdelim->();
+ if ($nogenerate) {
+ return (undef,undef);
+ }
+ die "commit $cur: Cannot cope with this commit (d.".
+ (join ' ', map { sprintf "%#x", $_->{Differs} }
+ @{ $cl->{Parents} }).
+ (defined $cl->{Why} ? "; $cl->{Why}": '').
+ ")";
+ };
+
+ my $build;
+ my $breakwater;
+
+ my $build_start = sub {
+ my ($msg, $parent) = @_;
+ $prline->(" $msg");
+ $build = $parent;
+ no warnings qw(exiting); last;
+ };
+
+ my $last_anchor;
+
+ for (;;) {
+ $cl = classify $cur;
+ my $ty = $cl->{Type};
+ my $st = $cl->{SubType};
+ $prline->("$cl->{CommitId} $cl->{Type}");
+ $found{$ty. ( defined($st) ? "-$st" : '' )}++;
+ push @processed, $cl;
+ my $p0 = @{ $cl->{Parents} }==1 ? $cl->{Parents}[0]{CommitId} : undef;
+ if ($ty eq 'AddPatches') {
+ $cur = $p0;
+ $rewrite_from_here->(\@upp_cl);
+ next;
+ } elsif ($ty eq 'Packaging' or $ty eq 'Changelog') {
+ push @brw_cl, $cl;
+ $cur = $p0;
+ next;
+ } elsif ($ty eq 'BreakwaterStart') {
+ $last_anchor = $cur;
+ $build_start->('FirstPackaging', $cur);
+ } elsif ($ty eq 'Upstream') {
+ push @upp_cl, $cl;
+ $cur = $p0;
+ next;
+ } elsif ($ty eq 'Mixed') {
+ my $queue = sub {
+ my ($q, $wh) = @_;
+ my $cls = { %$cl, $xmsg->("split mixed commit: $wh part") };
+ push @$q, $cls;
+ };
+ $queue->(\@brw_cl, "debian");
+ $queue->(\@upp_cl, "upstream");
+ $rewrite_from_here->(\@brw_cl);
+ $cur = $p0;
+ next;
+ } elsif ($ty eq 'Pseudomerge') {
+ my $contrib = $cl->{Contributor}{CommitId};
+ print $report " Contributor=$contrib" if $report;
+ push @pseudomerges, $cl;
+ $rewrite_from_here->(\@upp_cl);
+ $cur = $contrib;
+ next;
+ } elsif ($ty eq 'Anchor' or $ty eq 'TreatAsAnchor') {
+ $last_anchor = $cur;
+ $build_start->("Anchor", $cur);
+ } elsif ($ty eq 'DgitImportUnpatched') {
+ my $pm = $pseudomerges[-1];
+ if (defined $pm) {
+ # To an extent, this is heuristic. Imports don't have
+ # a useful history of the debian/ branch. We assume
+ # that the first pseudomerge after an import has a
+ # useful history of debian/, and ignore the histories
+ # from later pseudomerges. Often the first pseudomerge
+ # will be the dgit import of the upload to the actual
+ # suite intended by the non-dgit NMUer, and later
+ # pseudomerges may represent in-archive copies.
+ my $ovwrs = $pm->{Overwritten};
+ printf $report " PM=%s \@Overwr:%d",
+ $pm->{CommitId}, (scalar @$ovwrs)
+ if $report;
+ if (@$ovwrs != 1) {
+ printdebug "*** WALK BOMB DgitImportUnpatched\n";
+ return $bomb->();
+ }
+ my $ovwr = $ovwrs->[0]{CommitId};
+ printf $report " Overwr=%s", $ovwr if $report;
+ # This import has a tree which is just like a
+ # breakwater tree, but it has the wrong history. It
+ # ought to have the previous breakwater (which the
+ # pseudomerge overwrote) as an ancestor. That will
+ # make the history of the debian/ files correct. As
+ # for the upstream version: either it's the same as
+ # was ovewritten (ie, same as the previous
+ # breakwater), in which case that history is precisely
+ # right; or, otherwise, it was a non-gitish upload of a
+ # new upstream version. We can tell these apart by
+ # looking at the tree of the supposed upstream.
+ push @brw_cl, {
+ %$cl,
+ SpecialMethod => 'DgitImportDebianUpdate',
+ $xmsg->("convert dgit import: debian changes")
+ }, {
+ %$cl,
+ SpecialMethod => 'DgitImportUpstreamUpdate',
+ $xmsg->("convert dgit import: upstream update",
+ " anchor")
+ };
+ $prline->(" Import");
+ $rewrite_from_here->(\@brw_cl);
+ $upp_limit //= $#upp_cl; # further, deeper, patches discarded
+ $cur = $ovwr;
+ next;
+ } else {
+ # Everything is from this import. This kind of import
+ # is already in valid breakwater format, with the
+ # patches as commits.
+ printf $report " NoPM" if $report;
+ # last thing we processed will have been the first patch,
+ # if there is one; which is fine, so no need to rewrite
+ # on account of this import
+ $build_start->("ImportOrigin", $cur);
+ }
+ 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
+
+ fresh_workarea();
+
+ my $rewriting = 0;
+
+ my $read_tree_debian = sub {
+ my ($treeish) = @_;
+ read_tree_subdir 'debian', "$treeish:debian";
+ rm_subdir_cached 'debian/patches';
+ };
+ my $read_tree_upstream = sub {
+ my ($treeish) = @_;
+ runcmd @git, qw(read-tree), $treeish;
+ $read_tree_debian->($build);
+ };
+
+ $#upp_cl = $upp_limit if defined $upp_limit;
+
+ my $committer_authline = calculate_committer_authline();
+
+ printdebug "WALK REBUILD $build ".(scalar @processed)."\n";
+
+ confess "internal error" unless $build eq (pop @processed)->{CommitId};
+
+ in_workarea sub {
+ mkdir $rd or $!==EEXIST or die $!;
+ my $current_method;
+ runcmd @git, qw(read-tree), $build;
+ foreach my $cl (qw(Debian), (reverse @brw_cl),
+ { SpecialMethod => 'RecordBreakwaterTip' },
+ qw(Upstream), (reverse @upp_cl)) {
+ if (!ref $cl) {
+ $current_method = $cl;
+ next;
+ }
+ my $method = $cl->{SpecialMethod} // $current_method;
+ my @parents = ($build);
+ my $cltree = $cl->{CommitId};
+ printdebug "WALK BUILD ".($cltree//'undef').
+ " $method (rewriting=$rewriting)\n";
+ if ($method eq 'Debian') {
+ $read_tree_debian->($cltree);
+ } elsif ($method eq 'Upstream') {
+ $read_tree_upstream->($cltree);
+ } elsif ($method eq 'StartRewrite') {
+ $rewriting = 1;
+ next;
+ } elsif ($method eq 'RecordBreakwaterTip') {
+ $breakwater = $build;
+ next;
+ } elsif ($method eq 'DgitImportDebianUpdate') {
+ $read_tree_debian->($cltree);
+ } elsif ($method eq 'DgitImportUpstreamUpdate') {
+ confess unless $rewriting;
+ my $differs = (get_differs $build, $cltree);
+ next unless $differs & D_UPS;
+ $read_tree_upstream->($cltree);
+ push @parents, map { $_->{CommitId} } @{ $cl->{OrigParents} };
+ } else {
+ confess "$method ?";
+ }
+ if (!$rewriting) {
+ my $procd = (pop @processed) // 'UNDEF';
+ if ($cl ne $procd) {
+ $rewriting = 1;
+ printdebug "WALK REWRITING NOW cl=$cl procd=$procd\n";
+ }
+ }
+ my $newtree = cmdoutput @git, qw(write-tree);
+ my $ch = $cl->{Hdr};
+ $ch =~ s{^tree .*}{tree $newtree}m or confess "$ch ?";
+ $ch =~ s{^parent .*\n}{}mg;
+ $ch =~ s{(?=^author)}{
+ join '', map { "parent $_\n" } @parents
+ }me or confess "$ch ?";
+ if ($rewriting) {
+ $ch =~ s{^committer .*$}{$committer_authline}m
+ or confess "$ch ?";
+ }
+ my $cf = "$rd/m$rewriting";
+ open CD, ">", $cf or die $!;
+ print CD $ch, "\n", $cl->{Msg} or die $!;
+ close CD or die $!;
+ my @cmd = (@git, qw(hash-object));
+ push @cmd, qw(-w) if $rewriting;
+ push @cmd, qw(-t commit), $cf;
+ my $newcommit = cmdoutput @cmd;
+ confess "$ch ?" unless $rewriting or $newcommit eq $cl->{CommitId};
+ $build = $newcommit;
+ if (grep { $method eq $_ } qw(DgitImportUpstreamUpdate)) {
+ $last_anchor = $cur;
+ }
+ }
+ };
+
+ my $final_check = get_differs $build, $input;
+ die sprintf "internal error %#x %s %s", $final_check, $build, $input
+ if $final_check & ~D_PAT_ADD;
+
+ my @r = ($build, $breakwater, $last_anchor);
+ printdebug "*** WALK RETURN @r\n";
+ return @r
+}
+
+sub get_head () {
+ git_check_unmodified();
+ return git_rev_parse qw(HEAD);
+}
+
+sub update_head ($$$) {
+ my ($old, $new, $mrest) = @_;
+ push @deferred_updates, "update HEAD $new $old";
+ run_deferred_updates $mrest;
+}
+
+sub update_head_checkout ($$$) {
+ my ($old, $new, $mrest) = @_;
+ update_head $old, $new, $mrest;
+ runcmd @git, qw(reset --hard);
+}
+
+sub update_head_postlaunder ($$$) {
+ my ($old, $tip, $reflogmsg) = @_;
+ return if $tip eq $old;
+ print "git-debrebase: laundered (head was $old)\n";
+ update_head $old, $tip, $reflogmsg;
+ # no tree changes except debian/patches
+ runcmd @git, qw(rm --quiet --ignore-unmatch -rf debian/patches);
+}
+
+sub do_launder_head ($) {
+ my ($reflogmsg) = @_;
+ my $old = get_head();
+ record_ffq_auto();
+ my ($tip,$breakwater) = walk $old;
+ snags_maybe_bail();
+ update_head_postlaunder $old, $tip, $reflogmsg;
+ return ($tip,$breakwater);
+}
+
+sub cmd_launder_v0 () {
+ badusage "no arguments to launder-v0 allowed" if @ARGV;
+ my $old = get_head();
+ my ($tip,$breakwater,$last_anchor) = walk $old;
+ update_head_postlaunder $old, $tip, 'launder';
+ printf "# breakwater tip\n%s\n", $breakwater;
+ printf "# working tip\n%s\n", $tip;
+ printf "# last anchor\n%s\n", $last_anchor;
+}
+
+sub defaultcmd_rebase () {
+ push @ARGV, @{ $opt_defaultcmd_interactive // [] };
+ my ($tip,$breakwater) = do_launder_head 'launder for rebase';
+ runcmd @git, qw(rebase), @ARGV, $breakwater if @ARGV;
+}
+
+sub cmd_analyse () {
+ die if ($ARGV[0]//'') =~ m/^-/;
+ badusage "too many arguments to analyse" if @ARGV>1;
+ my ($old) = @ARGV;
+ if (defined $old) {
+ $old = git_rev_parse $old;
+ } else {
+ $old = git_rev_parse 'HEAD';
+ }
+ my ($dummy,$breakwater) = walk $old, 1,*STDOUT;
+ STDOUT->error and die $!;
+}
+
+sub ffq_prev_branchinfo () {
+ my $current = git_get_symref();
+ return gdr_ffq_prev_branchinfo($current);
+}
+
+sub record_ffq_prev_deferred () {
+ # => ('status', "message")
+ # 'status' may be
+ # deferred message is undef
+ # exists
+ # detached
+ # weird-symref
+ # notbranch
+ # if not ff from some branch we should be ff from, is an snag
+ # if "deferred", will have added something about that to
+ # @deferred_update_messages, and also maybe printed (already)
+ # some messages about ff checks
+ my ($status, $message, $current, $ffq_prev, $gdrlast)
+ = ffq_prev_branchinfo();
+ return ($status, $message) unless $status eq 'branch';
+
+ my $currentval = get_head();
+
+ my $exists = git_get_ref $ffq_prev;
+ return ('exists',"$ffq_prev already exists") if $exists;
+
+ return ('not-branch', 'HEAD symref is not to refs/heads/')
+ unless $current =~ m{^refs/heads/};
+ my $branch = $';
+
+ my @check_specs = split /\;/, (cfg "branch.$branch.ffq-ffrefs",1) // '*';
+ my %checked;
+
+ printdebug "ffq check_specs @check_specs\n";
+
+ my $check = sub {
+ my ($lrref, $desc) = @_;
+ printdebug "ffq might check $lrref ($desc)\n";
+ my $invert;
+ for my $chk (@check_specs) {
+ my $glob = $chk;
+ $invert = $glob =~ s{^[!^]}{};
+ last if fnmatch $glob, $lrref;
+ }
+ return if $invert;
+ my $lrval = git_get_ref $lrref;
+ return unless length $lrval;
+
+ if (is_fast_fwd $lrval, $currentval) {
+ print "OK, you are ahead of $lrref\n" or die $!;
+ $checked{$lrref} = 1;
+ } elsif (is_fast_fwd $currentval, $lrval) {
+ $checked{$lrref} = -1;
+ snag 'behind', "you are behind $lrref, divergence risk";
+ } else {
+ $checked{$lrref} = -1;
+ snag 'diverged', "you have diverged from $lrref";
+ }
+ };
+
+ my $merge = cfg "branch.$branch.merge",1;
+ if (defined $merge and $merge =~ m{^refs/heads/}) {
+ my $rhs = $';
+ printdebug "ffq merge $rhs\n";
+ my $check_remote = sub {
+ my ($remote, $desc) = @_;
+ printdebug "ffq check_remote ".($remote//'undef')." $desc\n";
+ return unless defined $remote;
+ $check->("refs/remotes/$remote/$rhs", $desc);
+ };
+ $check_remote->((scalar cfg "branch.$branch.remote",1),
+ 'remote fetch/merge branch');
+ $check_remote->((scalar cfg "branch.$branch.pushRemote",1) //
+ (scalar cfg "branch.$branch.pushDefault",1),
+ 'remote push branch');
+ }
+ if ($branch =~ m{^dgit/}) {
+ $check->("refs/remotes/dgit/$branch", 'remote dgit branch');
+ } elsif ($branch =~ m{^master$}) {
+ $check->("refs/remotes/dgit/dgit/sid", 'remote dgit branch for sid');
+ }
+
+ snags_maybe_bail();
+
+ push @deferred_updates, "update $ffq_prev $currentval $git_null_obj";
+ push @deferred_updates, "delete $gdrlast";
+ push @deferred_update_messages, "Recorded current head for preservation";
+ return ('deferred', undef);
+}
+
+sub record_ffq_auto () {
+ my ($status, $message) = record_ffq_prev_deferred();
+ if ($status eq 'deferred' || $status eq 'exists') {
+ } else {
+ snag $status, "could not record ffq-prev: $message";
+ snags_maybe_bail();
+ }
+}
+
+sub ffq_prev_info () {
+ # => ($ffq_prev, $gdrlast, $ffq_prev_commitish)
+ my ($status, $message, $current, $ffq_prev, $gdrlast)
+ = ffq_prev_branchinfo();
+ if ($status ne 'branch') {
+ snag $status, "could not check ffq-prev: $message";
+ snags_maybe_bail();
+ }
+ my $ffq_prev_commitish = $ffq_prev && git_get_ref $ffq_prev;
+ return ($ffq_prev, $gdrlast, $ffq_prev_commitish);
+}
+
+sub stitch ($$$$$) {
+ my ($old_head, $ffq_prev, $gdrlast, $ffq_prev_commitish, $prose) = @_;
+
+ push @deferred_updates, "delete $ffq_prev $ffq_prev_commitish";
+
+ if (is_fast_fwd $old_head, $ffq_prev_commitish) {
+ my $differs = get_differs $old_head, $ffq_prev_commitish;
+ unless ($differs & ~D_PAT_ADD) {
+ # ffq-prev is ahead of us, and the only tree changes it has
+ # are possibly addition of things in debian/patches/.
+ # Just wind forwards rather than making a pointless pseudomerge.
+ push @deferred_updates,
+ "update $gdrlast $ffq_prev_commitish $git_null_obj";
+ update_head_checkout $old_head, $ffq_prev_commitish,
+ "stitch (fast forward)";
+ return;
+ }
+ }
+ fresh_workarea();
+ # We make pseudomerges with L as the contributing parent.
+ # This makes git rev-list --first-parent work properly.
+ my $new_head = make_commit [ $old_head, $ffq_prev ], [
+ 'Declare fast forward / record previous work',
+ "[git-debrebase pseudomerge: $prose]",
+ ];
+ push @deferred_updates, "update $gdrlast $new_head $git_null_obj";
+ update_head $old_head, $new_head, "stitch: $prose";
+}
+
+sub do_stitch ($;$) {
+ my ($prose, $unclean) = @_;
+
+ my ($ffq_prev, $gdrlast, $ffq_prev_commitish) = ffq_prev_info();
+ if (!$ffq_prev_commitish) {
+ fail "No ffq-prev to stitch." unless $opt_noop_ok;
+ return;
+ }
+ my $dangling_head = get_head();
+
+ keycommits $dangling_head, $unclean,$unclean,$unclean;
+ snags_maybe_bail();
+
+ stitch($dangling_head, $ffq_prev, $gdrlast, $ffq_prev_commitish, $prose);
+}
+
+sub cmd_new_upstream () {
+ # automatically and unconditionally launders before rebasing
+ # if rebase --abort is used, laundering has still been done
+
+ my %pieces;
+
+ badusage "need NEW-VERSION [UPS-COMMITTISH]" unless @ARGV >= 1;
+
+ # 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 = shift @ARGV;
+ if (!defined $new_upstream) {
+ my @tried;
+ # todo: at some point maybe use git-deborig to do this
+ foreach my $tagpfx ('', 'v', 'upstream/') {
+ my $tag = $tagpfx.(dep14_version_mangle $new_upstream_version);
+ $new_upstream = git_get_ref "refs/tags/$tag";
+ last if length $new_upstream;
+ push @tried, $tag;
+ }
+ if (!length $new_upstream) {
+ fail "Could not determine appropriate upstream commitish.\n".
+ " (Tried these tags: @tried)\n".
+ " Check version, and specify upstream commitish explicitly.";
+ }
+ }
+ $new_upstream = git_rev_parse $new_upstream;
+
+ record_ffq_auto();
+
+ 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 && $ARGV[0] !~ m{^-}) {
+ my $n = shift @ARGV;
+
+ badusage "for each EXTRA-UPS-NAME need EXTRA-UPS-COMMITISH"
+ unless @ARGV && $ARGV[0] !~ m{^-};
+
+ 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_anchor) = walk $old_head;
+
+ my $old_bw_cl = classify $old_bw;
+ my $old_anchor_cl = classify $old_anchor;
+ my $old_upstream;
+ if (!$old_anchor_cl->{OrigParents}) {
+ snag 'anchor-treated',
+ 'old anchor is recognised due to --anchor, cannot check upstream';
+ } else {
+ $old_upstream = parsecommit
+ $old_anchor_cl->{OrigParents}[0]{CommitId};
+ $piece->('', Old => $old_upstream->{CommitId});
+ }
+
+ if ($old_upstream && $old_upstream->{Msg} =~ m{^\[git-debrebase }m) {
+ if ($old_upstream->{Msg} =~
+ m{^\[git-debrebase upstream-combine (\.(?: $extra_orig_namepart_re)+)\:.*\]$}m
+ ) {
+ my @oldpieces = (split / /, $1);
+ my $old_n_parents = scalar @{ $old_upstream->{Parents} };
+ if ($old_n_parents != @oldpieces &&
+ $old_n_parents != @oldpieces + 1) {
+ snag 'upstream-confusing', sprintf
+ "previous upstream combine %s".
+ " mentions %d pieces (each implying one parent)".
+ " but has %d parents".
+ " (one per piece plus maybe a previous combine)",
+ $old_upstream->{CommitId},
+ (scalar @oldpieces),
+ $old_n_parents;
+ } elsif ($oldpieces[0] ne '.') {
+ snag 'upstream-confusing', sprintf
+ "previous upstream combine %s".
+ " first piece is not \`.'",
+ $oldpieces[0];
+ } else {
+ $oldpieces[0] = '';
+ foreach my $i (0..$#oldpieces) {
+ my $n = $oldpieces[$i];
+ my $hat = 1 + $i + ($old_n_parents - @oldpieces);
+ $piece->($n, Old => $old_upstream->{CommitId}.'^'.$hat);
+ }
+ }
+ } else {
+ snag 'upstream-confusing',
+ "previous upstream $old_upstream->{CommitId} is from".
+ " git-debrebase but not an \`upstream-combine' commit";
+ }
+ }
+
+ foreach my $pc (values %pieces) {
+ if (!$old_upstream) {
+ # we have complained already
+ } elsif (!$pc->{Old}) {
+ snag 'upstream-new-piece',
+ "introducing upstream piece \`$pc->{Name}'";
+ } elsif (!$pc->{New}) {
+ snag 'upstream-rm-piece',
+ "dropping upstream piece \`$pc->{Name}'";
+ } elsif (!is_fast_fwd $pc->{Old}, $pc->{New}) {
+ snag 'upstream-not-ff',
+ "not fast forward: $pc->{Name} $pc->{Old}..$pc->{New}";
+ }
+ }
+
+ printdebug "%pieces = ", (dd \%pieces), "\n";
+ printdebug "\@newpieces = ", (dd \@newpieces), "\n";
+
+ snags_maybe_bail();
+
+ my $new_bw;
+
+ fresh_workarea();
+ in_workarea sub {
+ my @upstream_merge_parents;
+
+ if (!any_snags()) {
+ 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 upstream-combine . ".
+ (join " ", map { $_->{Name} } @newpieces[1..$#newpieces]).
+ ": new upstream]"),
+ ];
+ }
+
+ # $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 anchor merge.
+
+ read_tree_subdir 'debian', "$old_bw:debian";
+
+ # index now contains the anchor merge contents
+ $new_bw = make_commit [ $old_bw, $new_upstream ],
+ [ "Update to upstream $new_upstream_version",
+ "[git-debrebase anchor: new upstream $new_upstream_version, merge]",
+ ];
+
+ my $clogsignoff = cmdoutput qw(git show),
+ '--pretty=format:%an <%ae> %aD',
+ $new_bw;
+
+ # Now we have to add a changelog stanza so the Debian version
+ # is right.
+ die if unlink "debian";
+ die $! unless $!==ENOENT or $!==ENOTEMPTY;
+ unlink "debian/changelog" or $!==ENOENT or die $!;
+ mkdir "debian" 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 <<END, $oldclog or die $!;
+$p ($new_version) UNRELEASED; urgency=medium
+
+ * Update to new upstream version $new_upstream_version.
+
+ -- $clogsignoff
+
+END
+ close CN or die $!;
+ runcmd @git, qw(update-index --add --replace), 'debian/changelog';
+
+ # Now we have the final new breakwater branch in the index
+ $new_bw = make_commit [ $new_bw ],
+ [ "Update changelog for new upstream $new_upstream_version",
+ "[git-debrebase: new upstream $new_upstream_version, changelog]",
+ ];
+ };
+
+ # we have constructed the new breakwater. we now need to commit to
+ # the laundering output, because git-rebase can't easily be made
+ # to make a replay list which is based on some other branch
+
+ update_head_postlaunder $old_head, $old_laundered_tip,
+ 'launder for new upstream';
+
+ my @cmd = (@git, qw(rebase --onto), $new_bw, $old_bw, @ARGV);
+ runcmd @cmd;
+ # now it's for the user to sort out
+}
+
+sub cmd_record_ffq_prev () {
+ badusage "no arguments allowed" if @ARGV;
+ my ($status, $msg) = record_ffq_prev_deferred();
+ if ($status eq 'exists' && $opt_noop_ok) {
+ print "Previous head already recorded\n" or die $!;
+ } elsif ($status eq 'deferred') {
+ run_deferred_updates 'record-ffq-prev';
+ } else {
+ fail "Could not preserve: $msg";
+ }
+}
+
+sub cmd_anchor () {
+ badusage "no arguments allowed" if @ARGV;
+ my ($anchor, $bw) = keycommits +(git_rev_parse 'HEAD'), 0,0;
+ print "$bw\n" or die $!;
+}
+
+sub cmd_breakwater () {
+ badusage "no arguments allowed" if @ARGV;
+ my ($anchor, $bw) = keycommits +(git_rev_parse 'HEAD'), 0,0;
+ print "$bw\n" or die $!;
+}
+
+sub cmd_status () {
+ badusage "no arguments allowed" if @ARGV;
+
+ # todo: gdr status should print divergence info
+ # todo: gdr status should print upstream component(s) info
+ # todo: gdr should leave/maintain some refs with this kind of info ?
+
+ my $oldest = [ 0 ];
+ my $newest;
+ my $note = sub {
+ my ($badness, $ourmsg, $snagname, $kcmsg, $cl) = @_;
+ if ($oldest->[0] < $badness) {
+ $oldest = $newest = undef;
+ }
+ $oldest = \@_; # we're walking backwards
+ $newest //= \@_;
+ };
+ my ($anchor, $bw) = keycommits +(git_rev_parse 'HEAD'),
+ sub { $note->(1, 'branch contains furniture (not laundered)', @_); },
+ sub { $note->(2, 'branch is unlaundered', @_); },
+ sub { $note->(3, 'branch needs laundering', @_); },
+ sub { $note->(4, 'branch not in git-debrebase form', @_); };
+
+ my $prcommitinfo = sub {
+ my ($cid) = @_;
+ flush STDOUT or die $!;
+ runcmd @git, qw(--no-pager log -n1),
+ '--pretty=format: %h %s%n',
+ $cid;
+ };
+
+ print "current branch contents, in git-debrebase terms:\n";
+ if (!$oldest->[0]) {
+ print " branch is laundered\n";
+ } else {
+ print " $oldest->[1]\n";
+ my $printed = '';
+ foreach my $info ($oldest, $newest) {
+ my $cid = $info->[4]{CommitId};
+ next if $cid eq $printed;
+ $printed = $cid;
+ print " $info->[3]\n";
+ $prcommitinfo->($cid);
+ }
+ }
+
+ my $prab = sub {
+ my ($cid, $what) = @_;
+ if (!defined $cid) {
+ print " $what is not well-defined\n";
+ } else {
+ print " $what\n";
+ $prcommitinfo->($cid);
+ }
+ };
+ print "key git-debrebase commits:\n";
+ $prab->($anchor, 'anchor');
+ $prab->($bw, 'breakwater');
+
+ my ($ffqstatus, $ffq_msg, $current, $ffq_prev, $gdrlast) =
+ ffq_prev_branchinfo();
+
+ print "branch and ref status, in git-debrebase terms:\n";
+ if ($ffq_msg) {
+ print " $ffq_msg\n";
+ } else {
+ $ffq_prev = git_get_ref $ffq_prev;
+ $gdrlast = git_get_ref $gdrlast;
+ if ($ffq_prev) {
+ print " unstitched; previous tip was:\n";
+ $prcommitinfo->($ffq_prev);
+ } elsif (!$gdrlast) {
+ print " stitched? (no record of git-debrebase work)\n";
+ } elsif (is_fast_fwd $gdrlast, 'HEAD') {
+ print " stitched\n";
+ } else {
+ print " not git-debrebase (diverged since last stitch)\n"
+ }
+ }
+}
+
+sub cmd_stitch () {
+ my $prose = 'stitch';
+ GetOptions('prose=s', \$prose) or die badusage("bad options to stitch");
+ badusage "no arguments allowed" if @ARGV;
+ do_stitch $prose, 0;
+}
+sub cmd_prepush () { cmd_stitch(); }
+
+sub cmd_quick () {
+ badusage "no arguments allowed" if @ARGV;
+ do_launder_head 'launder for git-debrebase quick';
+ do_stitch 'quick';
+}
+
+sub cmd_conclude () {
+ my ($ffq_prev, $gdrlast, $ffq_prev_commitish) = ffq_prev_info();
+ if (!$ffq_prev_commitish) {
+ fail "No ongoing git-debrebase session." unless $opt_noop_ok;
+ return;
+ }
+ my $dangling_head = get_head();
+
+ badusage "no arguments allowed" if @ARGV;
+ do_launder_head 'launder for git-debrebase quick';
+ do_stitch 'quick';
+}
+
+sub make_patches_staged ($) {
+ my ($head) = @_;
+ # Produces the patches that would result from $head if it were
+ # laundered.
+ my ($secret_head, $secret_bw, $last_anchor) = walk $head;
+ fresh_workarea();
+ in_workarea sub {
+ runcmd @git, qw(checkout -q -b bw), $secret_bw;
+ runcmd @git, qw(checkout -q -b patch-queue/bw), $secret_head;
+ my @gbp_cmd = (qw(gbp pq export));
+ my $r = system shell_cmd 'exec >../gbp-pq-err 2>&1', @gbp_cmd;
+ if ($r) {
+ { local ($!,$?); copy('../gbp-pq-err', \*STDERR); }
+ failedcmd @gbp_cmd;
+ }
+ runcmd @git, qw(add debian/patches);
+ };
+}
+
+sub make_patches ($) {
+ my ($head) = @_;
+ keycommits $head, 0, \&snag;
+ make_patches_staged $head;
+ my $out;
+ in_workarea sub {
+ my $ptree = cmdoutput @git, qw(write-tree --prefix=debian/patches/);
+ runcmd @git, qw(read-tree), $head;
+ read_tree_subdir 'debian/patches', $ptree;
+ $out = make_commit [$head], [
+ 'Commit patch queue (exported by git-debrebase)',
+ '[git-debrebase: export and commit patches]',
+ ];
+ };
+ return $out;
+}
+
+sub cmd_make_patches () {
+ my $opt_quiet_would_amend;
+ GetOptions('quiet-would-amend!', \$opt_quiet_would_amend)
+ or die badusage("bad options to make-patches");
+ badusage "no arguments allowed" if @ARGV;
+ my $old_head = get_head();
+ my $new = make_patches $old_head;
+ my $d = get_differs $old_head, $new;
+ if ($d == 0) {
+ fail "No (more) patches to export." unless $opt_noop_ok;
+ return;
+ } elsif ($d == D_PAT_ADD) {
+ snags_maybe_bail();
+ update_head_checkout $old_head, $new, 'make-patches';
+ } else {
+ print STDERR failmsg
+ "Patch export produced patch amendments".
+ " (abandoned output commit $new).".
+ " Try laundering first."
+ unless $opt_quiet_would_amend;
+ finish 7;
+ }
+}
+
+sub cmd_convert_from_gbp () {
+ badusage "needs 1 optional argument, the upstream git rev"
+ unless @ARGV<=1;
+ my ($upstream_spec) = @ARGV;
+ $upstream_spec //= 'refs/heads/upstream';
+ my $upstream = git_rev_parse $upstream_spec;
+ my $old_head = get_head();
+
+ my $upsdiff = get_differs $upstream, $old_head;
+ if ($upsdiff & D_UPS) {
+ runcmd @git, qw(--no-pager diff),
+ $upstream, $old_head,
+ qw( -- :!/debian :/);
+ fail "upstream ($upstream_spec) and HEAD are not identical in upstream files";
+ }
+
+ if (!is_fast_fwd $upstream, $old_head) {
+ snag 'upstream-not-ancestor',
+ "upstream ($upstream) is not an ancestor of HEAD";
+ } else {
+ my $wrong = cmdoutput
+ (@git, qw(rev-list --ancestry-path), "$upstream..HEAD",
+ qw(-- :/ :!/debian));
+ if (length $wrong) {
+ snag 'unexpected-upstream-changes',
+ "history between upstream ($upstream) and HEAD contains direct changes to upstream files - are you sure this is a gbp (patches-unapplied) branch?";
+ print STDERR "list expected changes with: git log --stat --ancestry-path $upstream_spec..HEAD -- :/ ':!/debian'\n";
+ }
+ }
+
+ if ((git_cat_file "$upstream:debian")[0] ne 'missing') {
+ snag 'upstream-has-debian',
+ "upstream ($upstream) contains debian/ directory";
+ }
+
+ snags_maybe_bail();
+
+ my $work;
+
+ fresh_workarea();
+ in_workarea sub {
+ runcmd @git, qw(checkout -q -b gdr-internal), $old_head;
+ # make a branch out of the patch queue - we'll want this in a mo
+ runcmd qw(gbp pq import);
+ # strip the patches out
+ runcmd @git, qw(checkout -q gdr-internal~0);
+ rm_subdir_cached 'debian/patches';
+ $work = make_commit ['HEAD'], [
+ 'git-debrebase convert-from-gbp: drop patches from tree',
+ 'Delete debian/patches, as part of converting to git-debrebase format.',
+ '[git-debrebase convert-from-gbp: drop patches from tree]'
+ ];
+ # make the anchor merge
+ # the tree is already exactly right
+ $work = make_commit [$work, $upstream], [
+ 'git-debrebase import: declare upstream',
+ 'First breakwater merge.',
+ '[git-debrebase anchor: declare upstream]'
+ ];
+
+ # rebase the patch queue onto the new breakwater
+ runcmd @git, qw(reset --quiet --hard patch-queue/gdr-internal);
+ runcmd @git, qw(rebase --quiet --onto), $work, qw(gdr-internal);
+ $work = git_rev_parse 'HEAD';
+ };
+
+ update_head_checkout $old_head, $work, 'convert-from-gbp';
+}
+
+sub cmd_convert_to_gbp () {
+ badusage "no arguments allowed" if @ARGV;
+ my $head = get_head();
+ my (undef, undef, undef, $ffq, $gdrlast) = ffq_prev_branchinfo();
+ keycommits $head, 0;
+ my $out;
+ make_patches_staged $head;
+ in_workarea sub {
+ $out = make_commit ['HEAD'], [
+ 'Commit patch queue (converted from git-debrebase format)',
+ '[git-debrebase convert-to-gbp: commit patches]',
+ ];
+ };
+ if (defined $ffq) {
+ push @deferred_updates, "delete $ffq";
+ push @deferred_updates, "delete $gdrlast";
+ }
+ snags_maybe_bail();
+ update_head_checkout $head, $out, "convert to gbp (v0)";
+ print <<END or die $!;
+git-debrebase: converted to git-buildpackage branch format
+git-debrebase: WARNING: do not now run "git-debrebase" any more
+git-debrebase: WARNING: doing so would drop all upstream patches!
+END
+}
+
+sub cmd_downstream_rebase_launder_v0 () {
+ badusage "needs 1 argument, the baseline" unless @ARGV==1;
+ my ($base) = @ARGV;
+ $base = git_rev_parse $base;
+ my $old_head = get_head();
+ my $current = $old_head;
+ my $topmost_keep;
+ for (;;) {
+ if ($current eq $base) {
+ $topmost_keep //= $current;
+ print " $current BASE stop\n";
+ last;
+ }
+ my $cl = classify $current;
+ print " $current $cl->{Type}";
+ my $keep = 0;
+ my $p0 = $cl->{Parents}[0]{CommitId};
+ my $next;
+ if ($cl->{Type} eq 'Pseudomerge') {
+ print " ^".($cl->{Contributor}{Ix}+1);
+ $next = $cl->{Contributor}{CommitId};
+ } elsif ($cl->{Type} eq 'AddPatches' or
+ $cl->{Type} eq 'Changelog') {
+ print " strip";
+ $next = $p0;
+ } else {
+ print " keep";
+ $next = $p0;
+ $keep = 1;
+ }
+ print "\n";
+ if ($keep) {
+ $topmost_keep //= $current;
+ } else {
+ die "to-be stripped changes not on top of the branch\n"
+ if $topmost_keep;
+ }
+ $current = $next;
+ }
+ if ($topmost_keep eq $old_head) {
+ print "unchanged\n";
+ } else {
+ print "updating to $topmost_keep\n";
+ update_head_checkout
+ $old_head, $topmost_keep,
+ 'downstream-rebase-launder-v0';
+ }
+}
+
+GetOptions("D+" => \$debuglevel,
+ 'noop-ok', => \$opt_noop_ok,
+ 'f=s' => \@snag_force_opts,
+ 'anchor=s' => \@opt_anchors,
+ 'force!',
+ '-i:s' => sub {
+ my ($opt,$val) = @_;
+ badusage "git-debrebase: no cuddling to -i for git-rebase"
+ if length $val;
+ die if $opt_defaultcmd_interactive; # should not happen
+ $opt_defaultcmd_interactive = [ qw(-i) ];
+ # This access to @ARGV is excessive familiarity with
+ # Getopt::Long, but there isn't another sensible
+ # approach. '-i=s{0,}' does not work with bundling.
+ push @$opt_defaultcmd_interactive, @ARGV;
+ @ARGV=();
+ }) 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";
+
+@opt_anchors = map { git_rev_parse $_ } @opt_anchors;
+
+if (!@ARGV || $opt_defaultcmd_interactive || $ARGV[0] =~ m{^-}) {
+ defaultcmd_rebase();
+} else {
+ my $cmd = shift @ARGV;
+ my $cmdfn = $cmd;
+ $cmdfn =~ y/-/_/;
+ $cmdfn = ${*::}{"cmd_$cmdfn"};
+
+ $cmdfn or badusage "unknown git-debrebase sub-operation $cmd";
+ $cmdfn->();
+}
+
+finish 0;
diff --git a/git-debrebase.1.pod b/git-debrebase.1.pod
new file mode 100644
index 0000000..1c3e681
--- /dev/null
+++ b/git-debrebase.1.pod
@@ -0,0 +1,470 @@
+=head1 NAME
+
+git-debrebase - delta queue rebase tool for Debian packaging
+
+=head1 SYNOPSYS
+
+ git-debrebase [<options...>] [-- <git-rebase options...>]
+ git-debrebase [<options...>] <operation> [<operation options...>
+
+=head1 INTRODUCTION
+
+git-debrebase is a tool for representing in git,
+and manpulating,
+Debian packages based on upstream source code.
+
+This is the command line reference.
+Please read the tutorial
+L<dgit-maint-debrebase(7)>.
+For background, theory of operation,
+and definitions see L<git-debrebase(5)>.
+
+You should read this manpage in cojnunction with
+L<git-debrebase(5)/TERMINOLOGY>,
+which defines many important terms used here.
+
+=head1 PRINCIPAL OPERATIONS
+
+=over
+
+=item git-debrebase [-- <git-rebase options...>]
+
+=item git-debrebase [-i <further git-rebase options...>]
+
+Unstitches and launders the branch.
+(See L</UNSTITCHING AND LAUNDERING> below.)
+
+Then, if any git-rebase options were supplied,
+edits the Debian delta queue,
+using git-rebase, by running
+
+ git rebase <git-rebase options> <breakwater-tip>
+
+Do not pass a base branch argument:
+git-debrebase will supply that.
+Do not use --onto, or --fork-point.
+Useful git-rebase options include -i and --autosquash.
+
+If git-rebase stops for any reason,
+you may git-rebase --abort, --continue, or --skip, as usual.
+If you abort the git-rebase,
+the branch will still have been laundered,
+but everything in the rebase will be undone.
+
+The options for git-rebase must either start with C<-i>,
+or be prececded by C<-->,
+to distinguish them from options for git-debrebase.
+
+=item git-debrebase status
+
+Analyses the current branch,
+both in terms of its contents,
+and the refs which are relevant to git-debrebase,
+and prints a human-readable summary.
+
+Please do not attempt to parse the output;
+it may be reformatted or reorganised in the future.
+Instead,
+use one of the L<UNDERLYING AND SUPPLEMENTARY OPERATIONS>
+described below.
+
+=item git-debrebase conclude
+
+Finishes a git-debrebase session,
+tidying up the branch and making it fast forward again.
+
+Specifically: if the branch is unstitched,
+launders and restitches it,
+making a new pseudomerge.
+Otherwise, it is an error,
+unless --noop-ok.
+
+=item git-debrebase quick
+
+Unconditionally launders and restitches the branch,
+consuming any ffq-prev
+and making a new pseudomerge.
+
+If the branch is already laundered and stitched, does nothing.
+
+=item git-debrebase prepush [--prose=<for commit message>]
+
+=item git-debrebase stitch [--prose=<for commit message>]
+
+Stitches the branch,
+consuming ffq-prev.
+This is a good command to run before pushing to a git server.
+
+If there is no ffq-prev, it is an error, unless --noop-ok.
+
+You should consider using B<conclude> instead,
+because that launders the branch too.
+
+=item git-debrebase new-upstream <new-version> [<upstream-details>...]
+
+Rebases the delta queue
+onto a new upstream version. In detail:
+
+Firstly, checks that the proposed rebase seems to make sense:
+It is a snag unless the new upstream(s)
+are fast forward from the previous upstream(s)
+as found in the current breakwater anchor.
+And, in the case of a multi-piece upstream
+(a multi-component upstream, in dpkg-source terminology),
+if the pieces are not in the same order, with the same names.
+
+If all seems well, unstitches and launders the branch.
+
+Then,
+generates
+(in a private working area)
+a new anchor merge commit,
+on top of the breakwater tip,
+and on top of that a commit to
+update the version number in debian/changelog.
+
+Finally,
+starts a git-rebase
+of the delta queue onto these new commits.
+
+That git-rebase may complete successfully,
+or it may require your assistance,
+just like a normal git-rebase.
+
+If you git-rebase --abort,
+the whole new upstream operation is aborted,
+except for the laundering.
+
+The <upstream-details> are, optionally, in order:
+
+=over
+
+=item <upstream-commit-ish>
+
+The new upstream branch (or commit-ish).
+The default is to look for one of these tags, in this order:
+U vU upstream/U;
+where U is the new upstream version.
+(This is the same algorithm as L<git-deborig(1)>.)
+
+It is a snag if the upstream contains a debian/ directory;
+if forced to proceed,
+git-debrebase will disregard the upstream's debian/ and
+take (only) the packaging from the current breakwater.
+
+=item <piece-name> <piece-upstream-commit-ish>
+
+Specifies that this is a multi-piece upstream.
+May be repeated.
+
+When such a pair is specified,
+git-debrebase will first combine the pieces of the upstream
+together,
+and then use the result as the combined new upstream.
+
+For each <piece-name>,
+the tree of the <piece-upstream-commit-ish>
+becomes the subdirectory <piece-name>
+in the combined new upstream
+(supplanting any subdirectory that might be there in
+the main upstream branch).
+
+<piece-name> has a restricted syntax:
+it may contain only ASCII alphanumerics and hyphens.
+
+The combined upstream is itself recorded as a commit,
+with each of the upstream pieces' commits as parents.
+The combined commit contains an annotation
+to allow a future git-debrebase new upstream operation
+to make the coherency checks described above.
+
+=item <git-rebase options>
+
+These will be passed to git rebase.
+
+If the upstream rebase is troublesome, -i may be helpful.
+As with plain git-debrebase,
+do not specify a base, or --onto, or --fork-point.
+
+=back
+
+If you are planning to generate a .dsc,
+you will also need to have, or generate,
+actual orig tarball(s),
+which must be identical to the rev-spec(s)
+passed to git-debrebase.
+git-debrebase does not concern itself with source packages
+so neither helps with this, nor checks it.
+L<git-deborig(1)>,
+L<git-archive(1)>, L<dgit(1)> and
+L<gbp-import-orig(1)> may be able to help.
+
+=item git-debrebase make-patches [--quiet-would-amend]
+
+Generate patches in debian/patches/
+representing the changes made to upstream files.
+
+It is not normally necessary to run this command explicitly.
+When uploading to Debian,
+dgit and git-debrebase
+will cooperate to regenerate patches as necessary.
+When working with pure git remotes,
+the patches are not needed.
+
+Normally git-debrebase make-patches will
+require a laundered branch.
+(A laundered branch does not contain any patches.)
+But if there are already some patches made by
+git-debrebase make-patches,
+and all that has happened is that more
+changes to upstream files have been committed,
+running it again can add the missing patches.
+
+If the patches implied by the current branch
+are not a simple superset of those already in debian/patches,
+make-patches will fail with exit status 7,
+and an error message.
+(The message can be suppress with --quiet-would-amend.)
+If the problem is simply that
+the existing patches were not made by git-debrebase,
+using dgit quilt-fixup instead should succeed.
+
+=item git-debrebase convert-from-gbp [<upstream-commit-ish>]
+
+Cnnverts a gbp patches-unapplied branch
+(not a gbp pq patch queue branch)
+into a git-debrebase interchange branch.
+
+This is done by generating a new anchor merge,
+converting the quilt patches as a delta queue,
+and dropping the patches from the tree.
+
+The upstream commit-ish should correspond to
+the gbp upstream branch, if there is one.
+It is a snag if it is not an ancestor of HEAD,
+or if the history between the upstream and HEAD
+contains commits which make changes to upstream files.
+
+It is also a snag if the specified upstream
+has a debian/ subdirectory.
+This check exists to detect certain likely user errors,
+but if this situation is true and expected,
+forcing it is fine.
+
+The result is a well-formed git-debrebase interchange branch.
+The result is also fast-forward from the gbp branch.
+
+Note that it is dangerous not to know whether you are
+dealing with a gbp patches-unappled branch containing quilt patches,
+or a git-debrebase interchange branch.
+At worst,
+using the wrong tool for the branch format might result in
+a dropped patch queue!
+
+=back
+
+=head1 UNDERLYING AND SUPPLEMENTARY OPERATIONS
+
+=over
+
+=item git-debrebase breakwater
+
+Prints the breakwater tip commitid.
+If your HEAD branch is not fully laundered,
+prints the tip of the so-far-laundered breakwater.
+
+=item git-debrebase anchor
+
+Prints the breakwater anchor commitid.
+
+=item git-debrebase analyse
+
+Walks the history of the current branch,
+most recent commit first,
+back until the most recent anchor,
+printing the commit object id,
+and commit type and info
+(ie the semantics in the git-debrebase model)
+for each commit.
+
+=item git-debrebase record-ffq-prev
+
+Establishes the current branch's ffq-prev,
+as discussed in L</UNSTITCHING AND LAUNDERING>,
+but does not launder the branch or move HEAD.
+
+It is an error if the ffq-prev could not be recorded.
+It is also an error if an ffq-prev has already been recorded,
+unless --noop-ok.
+
+=item git-debrebase launder-v0
+
+Launders the branch without recording anything in ffq-prev.
+Then prints some information about the current branch.
+Do not use this operation;
+it will be withdrawn soon.
+
+=item git-debrebase convert-to-gbp
+
+Converts a laundered branch into a
+gbp patches-unapplied branch containing quilt patches.
+The result is not fast forward from the interchange branch,
+and any ffq-prev is deleted.
+
+This is provided mostly for the test suite
+and for unusual situations.
+It should only be used with a care and
+with a proper understanding of the underlying theory.
+
+Be sure to not accidentally treat the result as
+a git-debrebase branch,
+or you will drop all the patches!
+
+=back
+
+=head1 OPTIONS
+
+This section documents the general options
+to git-debrebase
+(ie, the ones which immediately follow
+git-debrebase
+or
+git debrebase
+on the command line).
+Individual operations may have their own options which are
+docuented under each operation.
+
+=over
+
+=item -f<snag-id>
+
+Turns snag(s) with id <snag-id> into warnings.
+
+Some troublesome things which git-debrebase encounters
+are B<snag>s.
+(The specific instances are discussed
+in the text for the relevant operation.)
+
+When a snag is detected,
+a message is printed to stderr containing the snag id
+(in the form C<-f<snag-idE<gt>>),
+along with some prose.
+
+If snags are detected, git-debrebase does not continue,
+unless the relevant -f<snag-id> is specified,
+or --force is specified.
+
+=item --force
+
+Turns all snags into warnings.
+See the -f<snag-id> option.
+
+Do not invoke git-debrebase --force in scripts and aliases;
+instead, specify the particular -f<snag-id> for expected snags.
+
+=item --noop-ok
+
+Suppresses the error in
+some situations where git-debrebase does nothing,
+because there is nothing to do.
+
+The specific instances are discussed
+in the text for the relvant operation.
+
+=item --anchor=<commit-ish>
+
+Treats <commit-ish> as an anchor.
+This overrides the usual logic which automatically classifies
+commits as anchors, pseudomerges, delta queue commits, etc.
+
+It also disables some coherency checks
+which depend on metadata extracted from its commit message,
+so
+it is a snag if <commit-ish> is the anchor
+for the previous upstream version in
+git-debrebase new-upstream operations.
+
+=item -D
+
+Requests (more) debugging. May be repeated.
+
+=back
+
+=head1 UNSTITCHING AND LAUNDERING
+
+Several operations unstitch and launder the branch first.
+In detail this means:
+
+=head2 Establish the current branch's ffq-prev
+
+If ffq-prev is not yet recorded,
+git-debrebase checks that the current branch is ahead of relevant
+remote tracking branches.
+The relevant branches depend on
+the current branch (and its
+git configuration)
+and are as follows:
+
+=over
+
+=item
+
+The branch that git would merge from
+(remote.<branch>.merge, remote.<branch>.remote);
+
+=item
+
+The branch git would push to, if different
+(remote.<branch>.pushRemote etc.);
+
+=item
+
+For local dgit suite branches,
+the corresponding tracking remote;
+
+=item
+
+If you are on C<master>,
+remotes/dgit/dgit/sid.
+
+=back
+
+The apparently relevant ref names to check are filtered through
+branch.<branch>.ffq-ffrefs,
+which is a semicolon-separated list of glob patterns,
+each optionally preceded by !; first match wins.
+
+In each case it is a snag if
+the local HEAD is behind the checked remote,
+or if local HEAD has diverged from it.
+All the checks are done locally using the remote tracking refs:
+git-debrebase does not fetch anything from anywhere.
+
+If these checks pass,
+or are forced,
+git-debrebse then records the current tip as ffq-prev.
+
+=head2 Examine the branch
+
+git-debrebase
+analyses the current HEAD's history to find the anchor
+in its breakwater,
+and the most recent breakwater tip.
+
+=head2 Rewrite the commits into laundered form
+
+Mixed debian+upstream commits are split into two commits each.
+Delta queue (upstream files) commits bubble to the top.
+Pseudomerges,
+and quilt patch additions,
+are dropped.
+
+This rewrite will always succeed, by construction.
+The result is the laundered branch.
+
+=head1 SEE ALSO
+
+git-debrebase(1),
+dgit-maint-rebase(7),
+dgit(1),
+gitglossary(7)
diff --git a/git-debrebase.5.pod b/git-debrebase.5.pod
new file mode 100644
index 0000000..e445c0e
--- /dev/null
+++ b/git-debrebase.5.pod
@@ -0,0 +1,611 @@
+=head1 NAME
+
+git-debrebase - git data model for Debian packaging
+
+=head1 INTRODUCTION
+
+git-debrebase is a tool for representing in git,
+and manpulating,
+Debian packages based on upstream source code.
+
+The Debian packaging
+has a fast forwarding history.
+The delta queue (changes to upstream files) is represented
+as a series of individual git commits,
+which can worked on with rebase,
+and also shared.
+
+git-debrebase is designed to work well with dgit.
+git-debrebase can also be used in workflows without source packages,
+for example to work on Debian-format packages outside or alongside Debian.
+
+git-debrebase
+itself is not very suitable for use by Debian derivatives,
+to work on packages inherited from Debian,
+because it assumes that you want to throw away any packaging
+provided by your upstream.
+However, use of git-debrebase in Debian does not make anything harder for
+derivatives, and it can make some things easier.
+
+=head1 TERMINOLOGY
+
+=over
+
+=item Pseudomerge
+
+A merge which does not actually merge the trees;
+instead, it is constructed by taking the tree
+from one of the parents
+(ignoring the contents of the other parents).
+These are used to make a rewritten history fast forward
+from a previous tip,
+so that it can be pushed and pulled normally.
+Manual construction of pseudomerges can be done with
+C<git merge -s ours>
+but is not normally needed when using git-debrebase.
+
+=item Packaging files
+
+Files in the source tree within B<debian/>,
+excluding anything in B<debian/patches/>.
+
+=item Upstream
+
+The version of the package without Debian's packaging.
+Typically provided by the actual upstream project,
+and sometimes tracked by Debian contributors in a branch C<upstream>.
+
+Upstream contains upstream files,
+but some upstreams also contain packaging files in B<debian/>.
+Any such non-upstream files found in upstream
+are thrown away by git-debrebase
+each time a new upstream version is incorporated.
+
+=item Upstream files
+
+Files in the source tree outside B<debian/>.
+These may include unmodified source from upstream,
+but also files which have been modified or created for Debian.
+
+=item Delta queue
+
+Debian's changes to upstream files:
+a series of git commits.
+
+=item Quilt patches
+
+Files in B<debian/patches/> generated for the benefit of
+dpkg-source's 3.0 (quilt) .dsc source package format.
+Not used, often deleted, and regenerated when needed
+(such as when uploading to Debian),
+by git-debrebase.
+
+=item Interchange branch; breakwater; stitched; laundered
+
+See L</BRANCHES AND BRANCH STATES - OVERVIEW>.
+
+=item Anchor; Packaging
+
+See L</BRANCH CONTENTS - DETAILED SPECIFICATION>.
+
+=item ffq-prev; debrebase-last
+
+See L</STITCHING, PSEUDO-MERGES, FFQ RECORD>.
+
+=back
+
+=head1 DIAGRAM
+
+ ------/--A!----/--B3!--%--/--> interchange view
+ / / / with debian/ directory
+ % % % entire delta queue applied
+ / / / 3.0 (quilt) has debian/patches
+ / / 3* "master" on Debian git servers
+ / / /
+ 2* 2* 2
+ / / /
+ 1 1 1 breakwater branch, merging baseline
+ / / / unmodified upstream code
+ ---@-----@--A----@--B--C plus debian/ (but no debian/patches)
+ / / / no ref refers to this: we
+ --#-----#-------#-----> upstream reconstruct its identity by
+ inspecting interchange branch
+ Key:
+
+ 1,2,3 commits touching upstream files only
+ A,B,C commits touching debian/ only
+ B3 mixed commit (eg made by an NMUer)
+ # upstream releases
+
+ -@- anchor merge, takes contents of debian/ from the
+ / previous `breakwater' commit and rest from upstream
+
+ -/- pseudomerge; contents are identical to
+ / parent lower on diagram.
+
+ % dgit- or git-debrebase- generated commit of debian/patches.
+ `3.0 (quilt)' only; generally dropped by git-debrebase.
+
+ * Maintainer's HEAD was here while they were editing,
+ before they said they were done, at which point their
+ tools made -/- (and maybe %) to convert to
+ the fast-forwarding interchange branch.
+
+ ! NMUer's HEAD was here when they said `dgit push'.
+ Rebase branch launderer turns each ! into an
+ equivalent *.
+
+=head1 BRANCHES AND BRANCH STATES - OVERVIEW
+
+git-debrebase has one primary branch,
+the B<interchange branch>.
+This branch is found on Debian contributor's workstations
+(typically, a maintainer would call it B<master>),
+in the Debian dgit git server as the suite branch (B<dgit/dgit/sid>)
+and on other git servers which support Debian work
+(eg B<master> on salsa).
+
+The interchange branch is fast-forwarding
+(by virtue of pseudomerges, where necessary).
+
+It is possible to have multiple different interchange branches
+for the same package,
+stored as different local and remote git branches.
+However, divergence should be avoided where possible -
+see L</OTHER MERGES>.
+
+A suitable interchange branch can be used directly with dgit.
+In this case each dgit archive suite branch is a separate
+interchange branch.
+
+Within the ancestry of the interchange branch,
+there is another important, implicit branch, the
+B<breakwater>.
+The breakwater contains unmodified upstream source,
+but with Debian's packaging superimposed
+(replacing any C<debian/> directory that may be in
+the upstream commits).
+The breakwater does not contain any representation of
+the delta queue (not even debian/patches).
+The part of the breakwater processed by git-debrebase
+is the part since the most reecent B<anchor>,
+which is usually a special merge generated by git-debrebase.
+
+When working, locally,
+the user's branch can be in a rebasing state,
+known as B<unstitched>.
+While a branch is unstitched,
+it is not in interchange format.
+The previous interchange branch tip
+is recorded,
+so that the previous history
+and the user's work
+can later be
+stitched into the fast-forwarding interchange form.
+
+An unstitched branch may be in
+B<laundered>
+state,
+which means it has a more particular special form
+convenient for manipulating the delta queue.
+
+=head1 BRANCH CONTENTS - DETAILED SPECIFICATION
+
+It is most convenient to describe the
+B<breakwater>
+branch first.
+A breakwater is B<fast-forwarding>,
+but is not usually named by a ref.
+It contains B<in this order> (ancestors first):
+
+=over
+
+=item Anchor
+
+An B<anchor> commit,
+which is usually a special two-parent merge:
+
+The first parent
+contains the most recent version, at that point,
+of the Debian packaging (in debian/);
+it also often contains upstream files,
+but they are to be ignored.
+Often the first parent is a previous breakwater tip.
+
+The second parent
+is an upstream source commit.
+It may sometimes contain a debian/ subdirectory,
+but if so that is to be ignored.
+The second parent's upstream files
+are identical to the anchor's.
+Anchor merges always contain
+C<[git-debrebase anchor: ...]>
+as a line in the commit message.
+
+Alternatively,
+an anchor may be a single-parent commit which introduces
+the C<debian/> directory and makes no other changes:
+ie, the start of Debian packaging.
+
+=item Packaging
+
+Zero or more single-parent commits
+containing only packaging changes.
+(And no quilt patch changes.)
+
+=back
+
+The
+B<laundered>
+branch state is B<rebasing>.
+A laundered branch is based on a breakwater
+but also contains, additionally,
+B<after> the breakwater,
+a representation of the delta queue:
+
+=over
+
+=item Delta queue commits
+
+Zero or more single-parent commits
+contaioning only changes to upstream files.
+
+=back
+
+The merely
+B<unstitched>
+(ie, unstitched but unlaundered)
+branch state is also B<rebasing>.
+It has the same contents as the laundered state,
+except that it may contain,
+additionally,
+in B<in any order but after the breakwater>:
+
+=over
+
+=item Linear commits to the source
+
+Further commit(s) containing changes to
+to upstream files
+and/or
+to packaging,
+possibly mixed within a single commit.
+(But not quilt patch changes.)
+
+=item Quilt patch addition for `3.0 (quilt)'
+
+Commit(s) which add patches to B<debian/patches/>,
+and add those patches to the end of B<series>.
+
+These are only necessary when working with
+packages in C<.dsc 3.0 (quilt)> format.
+For git-debrebase they are purely an output;
+they are deleted when branches are laundered.
+git-debrebase takes care to make a proper patch
+series out of the delta queue,
+so that any resulting source packages are nice.
+
+=back
+
+Finally, an
+B<interchange>
+branch is B<fast forwarding>.
+It has the same contents as an
+unlaundered branch state,
+but may (and usually will) additionally contain
+(in some order,
+possibly intermixed with the extra commits
+which may be found on an unstitched unlaundered branch):
+
+=over
+
+=item Pseudomerge to make fast forward
+
+A pseudomerge making the branch fast forward from
+previous history.
+The contributing parent is itself in interchange format.
+Normally the overwritten parent is
+a previous tip of an interchange branch,
+but this is not necessary as the overwritten
+parent is not examined.
+
+If the two parents have identical trees,
+the one with the later commit date
+(or, if the commit dates are the same,
+the first parent)
+is treated as
+the contributing parent.
+
+=item dgit dsc import pseudomerge
+
+Debian .dsc source package import(s)
+made by dgit
+(during dgit fetch of a package most recently
+uploaded to Debian without dgit,
+or during dgit import-dsc).
+
+git-debrebase requires that
+each such import is in the fast-forwarding
+format produced by dgit:
+a two-parent pseudomerge,
+whose contributing parent is in the
+non-fast-forwarding
+dgit dsc import format (not described further here),
+and whose overwritten parent is
+the previous interchange tip
+(eg, the previous tip of the dgit suite branch).
+
+=back
+
+=head1 STITCHING, PSEUDO-MERGES, FFQ RECORD
+
+Whenever the branch C<refs/B> is unstitched,
+the previous head is recorded in the git ref C<refs/ffq-prev/B>.
+
+Unstiched branches are not fast forward from the published
+interchange branches [1].
+So before a branch can be pushed,
+the right pseudomerge must be reestablished.
+This is the stitch operation,
+which consumes the ffq-prev ref.
+
+When the user has an unstitched branch,
+they may rewrite it freely,
+from the breakwater tip onwards.
+Such a git rebase is the default operation for git-debrebase.
+Rebases should not go back before the breakwater tip,
+and certainly not before the most recent anchor.
+
+Unstitched branches must not be pushed to interchange branch refs
+(by the use of C<git push -f> or equivalent).
+It is OK to share an unstitched branch
+in similar circumstances and with similar warnings
+to sharing any other rebasing git branch.
+
+[1] Strictly, for a package
+which has never had a Debian delta queue,
+the interchange and breakwater branches may be identical,
+in which case the unstitched branch is fast forward
+from the interchange branch and no pseudomerge is needed.
+
+When ffq-prev is not present,
+C<refs/debrebase-last/B> records some ancestor of refs/B,
+(usually, the result of last stitch).
+This can be used to quickly determine whether refs/B
+is being maintained in git-debrebase form.
+
+=head1 OTHER MERGES
+
+Note that the representation described here does not permit
+general merges on any of the relevant branches.
+For this reason the tools will try to help the user
+avoid divergence of the interchange branch.
+
+See dgit-maint-rebase(7)
+for a discussion of what kinds of behaviours
+should be be avoided
+because
+they might generate such merges.
+
+Automatic resolution of divergent interchange branches
+(or laundering of merges on the interchange branch)
+is thought to be possible,
+but there is no tooling for this yet:
+
+Nonlinear (merging) history in the interchange branch is awkward
+because it (obviously) does not preserve
+the linearity of the delta queue.
+Easy merging of divergent delta queues is a research problem.
+
+Nonlinear (merging) history in the breakwater branch is
+in principle tolerable,
+but each of the parents would have to be, in turn,
+a breakwater,
+and difficult questions arise if they don't have the same anchor.
+
+We use the commit message annotation to
+distinguish the special anchor merges from other general merges,
+so we can at least detect unsupported merges.
+
+=head1 LEGAL OPERATIONS
+
+The following basic operations follows from this model
+(refer to the diagram above):
+
+=over
+
+=item Append linear commits
+
+No matter the branch state,
+it is always fine to simply git commit
+(or cherry-pick etc.)
+commits containing upstream file changes, packaging changes,
+or both.
+
+(This may make the branch unlaundered.)
+
+=item Launder branch
+
+Record the previous head in ffq-prev,
+if we were stitched before
+(and delete debrebase-last).
+
+Reorganise the current branch so that the packaging
+changes come first,
+followed by the delta queue,
+turning C<-@-A-1-2-B3> into C<...@-A-B-1-2-3>.
+
+Drop pseudomerges and any quilt patch additions.
+
+=item Interactive rebase
+
+With a laundered branch,
+one can do an interactive git rebase of the delta queue.
+
+=item New upstream rebase
+
+Start rebasing onto a new upstream version,
+turning C<...#..@-A-B-1-2-3> into C<(...#..@-A-B-, ...#'-)@'-1-2>.
+
+This has to be a wrapper around git-rebase,
+which prepares @' and then tries to rebase 1 2 onto @'.
+If the user asks for an interactive rebase,
+@' doesn't appear in the commit list, since
+@' is the newbase of the rebase (see git-rebase(1)).
+
+Note that the construction of @' cannot fail
+because @' simply copies debian/ from B and and everything else from #'.
+(Rebasing A and B is undesirable.
+We want the debian/ files to be non-rebasing
+so that git log shows the packaging history.)
+
+=item Stitch
+
+Make a pseudomerge,
+whose contributing parent to is the unstitched branch
+and
+whose overwritten parent is ffq-prev,
+consuming ffq-prev in the process
+(and writing debrebase-last instead).
+Ideally the contributing parent would be a laundered branch,
+or perhaps a laundered branch with a quilt patch addition commit.
+
+=item Commit quilt patches
+
+To generate a tree which can be represented as a
+3.0 (quilt) .dsc source packages,
+the delta queue must be reified inside the git tree
+in B<debian/patches/>.
+These patch files can be stripped out and/or regenerated as needed.
+
+=back
+
+=head1 COMMIT MESSAGE ANNOTATIONS
+
+git-debrebase makes annotations
+in the messages of commits it generates.
+
+The general form is
+
+ [git-debrebase[ COMMIT-TYPE [ ARGS...]]: PROSE, MORE PROSE]
+
+git-debrebase treats anything after the colon as a comment,
+paying no attention to PROSE.
+
+The full set of annotations is:
+ [git-debrebase: split mixed commit, debian part]
+ [git-debrebase: split mixed commit, upstream-part]
+ [git-debrebase: convert dgit import, debian changes]
+ [git-debrebase anchor: convert dgit import, upstream changes]
+
+ [git-debrebase upstream-combine . PIECE[ PIECE...]: new upstream]
+ [git-debrebase anchor: new upstream NEW-UPSTREAM-VERSION, merge]
+ [git-debrebase: new upstream NEW-UPSTREAM-VERSION, changelog]
+ [git-debrebase: export and commit patches]
+
+ [git-debrebase convert-from-gbp: drop patches]
+ [git-debrebase anchor: declare upstream]
+ [git-debrebase pseudomerge: stitch]
+
+ [git-debrebase convert-to-gbp: commit patches]
+
+Only anchor merges have the C<[git-debrebase anchor: ...]> tag.
+Single-parent anchors are not generated by git-debrebase,
+and when made manually should not
+contain any C<[git-debrebase ...]> annotation.
+
+The C<split mixed commit> and C<convert dgit import>
+tags are added to the pre-existing commit message,
+when git-debrebase rewrites the commit.
+
+=head1 APPENDIX - DGIT IMPORT HANDLING
+
+The dgit .dsc import format is not documented or specified
+(so some of the following terms are not defined anywhere).
+The dgit import format it is defined by the implementation in dgit,
+of which git-debrebase has special knowledge.
+
+Consider a non-dgit NMU followed by a dgit NMU:
+
+ interchange --/--B3!--%--//----D*-->
+ / /
+ % 4
+ / 3
+ / 2
+ / 1
+ 2 &_
+ / /| \
+ 1 0 00 =XBC%
+ /
+ /
+ --@--A breakwater
+ /
+ --#--------> upstream
+
+
+ Supplementary key:
+
+ =XBC% dgit tarball import of .debian.tar.gz containing
+ Debian packaging including changes B C and quilt patches
+ 0 dgit tarball import of upstream tarball
+ 00 dgit tarball import of supplementary upstream piece
+ &_ dgit import nearly-breakwater-anchor
+ // dgit fetch / import-dsc pseudomerge to make fast forward
+
+ &' git-debrebase converted import (upstream files only)
+ C' git-debrebase converted packaging change import
+
+ * ** before and after HEAD
+
+We want to transform this into:
+
+=over
+
+=item I. No new upstream version
+
+ (0 + 00 eq #)
+ --/--B3!--%--//-----D*-------------/-->
+ / / /
+ % 4 4**
+ / 3 3
+ / 2 2
+ / 1 1
+ 2 &_ /
+ / /| \ /
+ 1 0 00 =XBC% /
+ / /
+ / /
+ --@--A-----B---------------------C'---D
+ /
+ --#----------------------------------------->
+
+=item II. New upstream
+
+ (0 + 00 neq #)
+
+ --/--B3!--%--//-----D*-------------/-->
+ / / /
+ % 4 4**
+ / 3 3
+ / 2 2
+ / 1 1
+ 2 &_ /
+ / /| \ /
+ 1 0 00 =XBC% /
+ / /
+ / /
+ --@--A-----B-----------------@---C'---D
+ / /
+ --#--------------------- - - / - - --------->
+ /
+ &'
+ /|
+ 0 00
+
+=back
+
+=head1 SEE ALSO
+
+git-debrebase(1),
+dgit-maint-rebase(7),
+dgit(1)
diff --git a/tests/enumerate-tests b/tests/enumerate-tests
index 2c00f97..0599b69 100755
--- a/tests/enumerate-tests
+++ b/tests/enumerate-tests
@@ -42,15 +42,30 @@ finish- () {
test-begin-gencontrol () {
restrictions=''
- dependencies=''
+ dependencies='dgit, dgit-infrastructure, devscripts, debhelper (>=8), fakeroot, build-essential, chiark-utils-bin'
}
restriction-gencontrol () {
restrictions+=" $r"
}
+gencontrol-add-deps () {
+ for dep in "$@"; do
+ dependencies+="${dependencies:+, }$dep"
+ done
+}
+
dependencies-gencontrol () {
- dependencies+=", $deps"
+ for dep in "$deps"; do
+ case "$dep" in
+ NO-DGIT) dependencies='chiark-utils-bin' ;;
+ NO-DEFAULT) dependencies='' ;;
+ GDR) gencontrol-add-deps \
+ git-debrebase git-buildpackage faketime
+ ;;
+ *) gencontrol-add-deps "$dep" ;;
+ esac
+ done
}
test-done-gencontrol () {
diff --git a/tests/lib b/tests/lib
index e4554e3..bd06d20 100644
--- a/tests/lib
+++ b/tests/lib
@@ -31,8 +31,8 @@ export DGIT_TEST_DEBUG
: ${DGIT_TEST_DISTRO+ ${distro=${DGIT_TEST_DISTRO}}}
-export GIT_COMMITTER_DATE='1440253867 +0100'
-export GIT_AUTHOR_DATE='1440253867 +0100'
+export GIT_COMMITTER_DATE='1515000000 +0100'
+export GIT_AUTHOR_DATE='1515000000 +0100'
root=`pwd`
troot=$root/tests
@@ -189,6 +189,13 @@ t-git-none () {
(set -e; cd $tmp/git; tar xf $troot/git-template.tar)
}
+t-salsa-add-remote () {
+ local d=$tmp/salsa/$p
+ mkdir -p $d
+ (set -e; cd $d; git init --bare)
+ git remote add ${1-origin} $d
+}
+
t-git-merge-base () {
git merge-base $1 $2 || test $? = 1
}
@@ -415,9 +422,10 @@ t-dgit () {
{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{'
$dgit --dgit=$dgit --dget:-u --dput:--config=$tmp/dput.cf \
${dgit_config_debian_alias-"--config-lookup-explode=dgit-distro.debian.alias-canon"} \
+ ${DGIT_GITDEBREBASE_TEST+--git-debrebase=}${DGIT_GITDEBREBASE_TEST} \
${distro+${distro:+-d}}${distro--dtest-dummy} \
$DGIT_TEST_OPTS $DGIT_TEST_DEBUG \
- -k39B13D8A $t_dgit_xopts "$@"
+ -kBCD22CD83243B79D3DFAC33EA3DBCBC039B13D8A $t_dgit_xopts "$@"
: '}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
'
}
@@ -472,12 +480,12 @@ t-setup-done () {
local savedirs=$2
local importeval=$3
- local import=IMPORT.${0##*/}
+ local import=IMPORT.${DGIT_TEST_TESTNAME-${0##*/}}
exec 4>$tmp/$import.new
local vn
for vn in $savevars; do
- perl >&4 -I. -MDebian::Dgit -e '
+ perl >&4 -"I$root" -MDebian::Dgit -e '
printf "%s=%s\n", $ARGV[0], shellquote $ARGV[1]
' $vn "$(eval "printf '%s\n' \"\$$vn\"")"
done
@@ -997,6 +1005,11 @@ t-commit () {
revision=$(( ${revision-0} + 1 ))
}
+t-dch-commit () {
+ faketime @"${GIT_AUTHOR_DATE% *}" dch "$@"
+ git commit -m "dch $*" debian/changelog
+}
+
t-git-config () {
git config --global "$@"
}
diff --git a/tests/lib-core b/tests/lib-core
index d65a1ff..c3a04cb 100644
--- a/tests/lib-core
+++ b/tests/lib-core
@@ -12,6 +12,7 @@ t-set-intree () {
: ${DGIT_REPOS_SERVER_TEST:=$DGIT_TEST_INTREE/infra/dgit-repos-server}
: ${DGIT_SSH_DISPATCH_TEST:=$DGIT_TEST_INTREE/infra/dgit-ssh-dispatch}
: ${DGIT_INFRA_PFX:=$DGIT_TEST_INTREE${DGIT_TEST_INTREE:+/infra/}}
+ : ${DGIT_GITDEBREBASE_TEST:=$DGIT_TEST_INTREE/git-debrebase}
export DGIT_TEST DGIT_BADCOMMIT_FIXUP
export DGIT_REPOS_SERVER_TEST DGIT_SSH_DISPATCH_TEST
export PERLLIB="$DGIT_TEST_INTREE${PERLLIB:+:}${PERLLIB}"
diff --git a/tests/lib-gdr b/tests/lib-gdr
new file mode 100644
index 0000000..7b4f085
--- /dev/null
+++ b/tests/lib-gdr
@@ -0,0 +1,290 @@
+#
+
+: ${GDR_TEST_DEBUG=-D}
+export GDR_TEST_DEBUG
+
+t-git-debrebase () {
+ local gdr=${DGIT_GITDEBREBASE_TEST-git-debrebase}
+ : '
+{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{'
+ $gdr $GDR_TEST_OPTS $GDR_TEST_DEBUG $t_gdr_xopts "$@"
+ : '}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
+'
+}
+
+t-gdr-good () {
+ local state=$1
+ local beforetag=$2 # say HEAD to skip this check
+ # state should be one of
+ # laundered
+ # stitched
+ # pushed
+
+ git diff --quiet ${beforetag-t.before} -- ':.' ':!debian/patches'
+
+ local etypes bwtip
+
+ LC_MESSAGES=C t-git-debrebase status >../status.check
+ case $state in
+ laundered)
+ egrep '^ *branch is laundered' ../status.check
+ ;;
+ stitched|pushed)
+ egrep \
+ '^ *branch contains furniture|^ *branch is unlaundered|^ *branch needs laundering' ../status.check
+ egrep '^ stitched$' ../status.check
+ ;;
+ esac
+
+ # etypes is either a type,
+ # or PseudoMerge-<more etypes>
+ # or AddPatches-<more etypes>
+
+ case $state in
+ laundered)
+ etypes=Upstream
+ bwtip=Y:`t-git-debrebase breakwater`
+ ;;
+ stitched) etypes=Pseudomerge-Upstream ;;
+ pushed) etypes=AddPatches-Pseudomerge-Upstream ;;
+ pushed-interop) etypes=Pseudomerge-AddPatchesInterop-Upstream ;;
+ esac
+
+ t-git-debrebase analyse >../anal.check
+ expect=`git rev-parse HEAD`
+ exec <../anal.check
+ local cid ctype info nparents
+ while read cid ctype info; do
+ : ===== $cid $ctype $info =====
+ test $cid = $expect
+ local cetype=${etypes%%-*}
+ if [ "x$ctype" = "x$cetype" ]; then cetype=SAME; fi
+ local parents="`git log -n1 --pretty=format:%P $cid`"
+ expect="$parents"
+ enparents=1
+ : "$ctype/$cetype" "$parents"
+
+ case "$ctype/$cetype" in
+ Pseudomerge/SAME) ;;
+ Packaging/SAME) ;;
+ Packaging/Upstream) ;;
+ AddPatches/SAME) ;;
+ AddPatches/AddPatchesInterop) ;;
+ Changelog/Packaging) ;;
+ Changelog/Upstream) ;;
+ Upstream/SAME) ;;
+ Anchor/Upstream) ;;
+ Anchor/Packaging) ;;
+ *)
+ fail "etypes=$etypes ctype=$ctype cetype=$cetype $cid"
+ ;;
+ esac
+
+ case "$ctype/$etypes" in
+ Packaging/Upstream|\
+ Changelog/Upstream)
+ if [ "x$bwtip" != x ]; then
+ test "$bwtip" = "Y:$cid"
+ bwtip=''
+ fi
+ esac
+
+ case "$cetype" in
+ AddPatchesInterop)
+ git log -n1 --pretty=format:%B \
+ | grep '^\[git-debrebase[ :]'
+ ;;
+ esac
+
+ case "$ctype" in
+ Pseudomerge)
+ expect=${info#Contributor=}
+ expect=${expect%% *}
+ enparents=2
+ git diff --quiet $expect..$cid
+ etypes=${etypes#*-}
+
+ : 'reject pointless pseudomerges'
+ local overwritten=${parents/$expect/}
+ overwritten=${overwritten// /}
+ t-git-debrebase analyse $overwritten >../anal.overwr
+ local ocid otype oinfo
+ read <../anal.overwr ocid otype oinfo
+ case "$otype" in
+ Pseudomerge) test "x$info" != "x$oinfo" ;;
+ esac
+ ;;
+ Packaging)
+ git diff --quiet $expect..$cid -- ':.' ':!debian'
+ git diff --quiet $expect..$cid -- ':debian/patches'
+ etypes=Packaging
+ ;;
+ AddPatches)
+ git diff --quiet $expect..$cid -- \
+ ':.' ':!debian/patches'
+ etypes=${etypes#*-}
+ ;;
+ Changelog)
+ git diff --quiet $expect..$cid -- \
+ ':.' ':!debian/changelog'
+ etypes=Packaging
+ ;;
+ Upstream/SAME)
+ git diff --quiet $expect..$cid -- ':debian'
+ ;;
+ Anchor)
+ break
+ ;;
+ esac
+
+ local cnparents=`printf "%s" "$parents" | wc -w`
+ test $cnparents = $enparents
+
+ local cndparents=`
+ for f in $parents; do echo $f; done | sort -u | wc -w
+ `
+ test $cndparents = $cnparents
+
+ case "$parents" in
+ *"$expect"*) ;;
+ *) fail 'unexpected parent' ;;
+ esac
+
+ done
+}
+
+t-some-changes () {
+ local token=$1
+ local which=${2-dum}
+
+ t-git-next-date
+
+ case "$which" in
+ *d*)
+ echo >>debian/zorkmid "// debian $token"
+ git add debian/zorkmid
+ git commit -m "DEBIAN add zorkmid ($token)"
+ ;;
+ esac
+
+ case "$which" in
+ *u*)
+ echo >>src.c "// upstream $token"
+ git commit -a -m "UPSTREAM edit src.c ($token)"
+ ;;
+ esac
+
+ case "$which" in
+ *m*)
+ for f in debian/zorkmid src.c; do
+ echo "// both! $token" >>$f
+ git add $f
+ done
+ git commit -m "MIXED add both ($token)"
+ ;;
+ esac
+
+ t-git-next-date
+}
+
+t-make-new-upstream-tarball () {
+ local uv=$1
+ git checkout make-upstream
+ # leaves ust set to filename of orig tarball
+ echo "upstream $uv" >>docs/README
+ git commit -a -m "upstream $uv tarball"
+ ust=example_$uv.orig.tar.gz
+ git archive -o ../$ust --prefix=example-2.0/ make-upstream
+}
+
+t-nmu-upload-1 () {
+ # usage:
+ # v=<full version>
+ # nmu-upload-1 <nmubranch>
+ # gbp pq import or perhaps other similar things
+ # nmu-upload-2
+ # maybe make some dgit-covertible commits
+ # nmu-upload-3
+
+ t-git-next-date
+ nmubranch=$1
+ git checkout -f -b $nmubranch
+ t-git-debrebase
+ t-git-debrebase convert-to-gbp
+ t-git-next-date
+ # now we are on a gbp patched-unapplied branch
+}
+
+
+t-nmu-upload-2 () {
+ t-git-next-date
+ t-dch-commit -v $v -m "nmu $nmubranch $v"
+}
+
+t-nmu-upload-3 () {
+ t-dch-commit -r sid
+
+ t-dgit -wgf build-source
+
+ cd ..
+ c=${p}_${v}_source.changes
+ debsign -kBCD22CD83243B79D3DFAC33EA3DBCBC039B13D8A $c
+ dput -c $tmp/dput.cf test-dummy $c
+
+ t-archive-process-incoming sid
+ t-git-next-date
+ cd $p
+ git checkout master
+}
+
+t-nmu-commit-an-upstream-change () {
+ echo >>newsrc.c "// more upstream"
+ git add newsrc.c
+ git commit -m 'UPSTREAM NMU'
+}
+
+t-maintainer-commit-some-changes () {
+ t-dch-commit -v$v -m "start $v"
+
+ t-some-changes "maintainer $v"
+ t-git-debrebase
+ t-git-debrebase stitch
+
+ git branch did.maintainer
+
+ t-git-next-date
+}
+
+t-nmu-causes-ff-fail () {
+ t-dgit fetch
+
+ t-expect-fail E:'Not.*fast-forward' \
+ git merge --ff-only dgit/dgit/sid
+
+ t-expect-fail E:'-fdiverged.*refs/remotes/dgit/dgit/sid' \
+ t-git-debrebase
+}
+
+t-nmu-reconciled-good () {
+ local nmutree=$1
+
+ : 'check that what we have is what is expected'
+
+ git checkout -b compare.nmu origin/master~0
+ git checkout $nmutree .
+ git rm -rf debian/patches
+ git commit -m 'rm patches nmu'
+
+ git checkout -b compare.maintainer origin/master~0
+ git checkout did.maintainer .
+ git rm -rf --ignore-unmatch debian/patches
+ git commit --allow-empty -m 'rm patches maintainer'
+
+ git merge compare.nmu
+ git diff --quiet master
+
+ : 'check that dgit still likes it'
+
+ git checkout master
+ t-dgit -wgf quilt-fixup
+}
diff --git a/tests/lib-restricts b/tests/lib-restricts
index bffe13a..bb695c4 100644
--- a/tests/lib-restricts
+++ b/tests/lib-restricts
@@ -20,3 +20,7 @@ t-restriction-x-dgit-unfinished () {
echo 'unfinished test, or unfinished feature'
return 1
}
+
+t-restriction-hint-testsuite-triggers () {
+ :
+}
diff --git a/tests/run-all b/tests/run-all
index cfa5ce2..f72b9fa 100755
--- a/tests/run-all
+++ b/tests/run-all
@@ -5,9 +5,8 @@ set -e
set -o pipefail
-set +e
-jcpus=`perl -MSys::CPU -we 'printf "-j%d\n", 1.34 * Sys::CPU::cpu_count()'`
-set -e
+ncpus=$(nproc || echo 1)
+jcpus=-j$(( ncpus * 134 / 100 ))
if [ $# != 0 ]; then
set TESTSCRIPTS="$*"
diff --git a/tests/setup/gdr-convert-gbp b/tests/setup/gdr-convert-gbp
new file mode 100755
index 0000000..0b525c8
--- /dev/null
+++ b/tests/setup/gdr-convert-gbp
@@ -0,0 +1,100 @@
+#!/bin/bash
+set -e
+. tests/lib
+. $troot/lib-gdr
+
+t-dependencies GDR
+
+t-tstunt-parsechangelog
+
+not-gdr-processable () {
+ t-git-debrebase analyse | grep 'Unknown Unprocessable'
+}
+
+p=example
+t-worktree 1.1
+
+cd example
+
+: 'fake up some kind of upstream'
+git checkout -b upstream quilt-tip
+rm -rf debian
+mkdir debian
+echo junk >debian/rules
+git add debian
+git commit -m "an upstream retcon ($0)"
+
+: 'fake up that our quilt-tip was descended from upstream'
+git checkout quilt-tip
+git merge --no-edit -s ours upstream
+
+: 'fake up that our quilt-tip had the patch queue in it'
+git checkout patch-queue/quilt-tip
+gbp pq export
+git add debian/patches
+git commit -m "patch queue update ($0)"
+
+not-gdr-processable
+
+: 'fake up an upstream 2.0'
+git branch make-upstream upstream
+t-make-new-upstream-tarball 2.0
+
+: 'make branch names more conventional'
+git branch -D master
+git branch -m quilt-tip master
+
+for b in \
+ quilt-tip-2 \
+ gitish-only \
+ quilt-tip-1.1 \
+ patch-queue/quilt-tip \
+ indep-arch \
+; do
+ git branch -D $b
+done
+
+: 'see what gbp import-orig does'
+git checkout master
+gbp import-orig --upstream-version=2.0 ../$ust
+
+not-gdr-processable
+
+t-dch-commit -v 2.0-1 -m 'new upstream (did gbp import-orig)'
+t-dch-commit -r sid
+
+$ifarchive t-archive-none $p
+$ifarchive t-git-none
+$ifarchive t-dgit -wgf --gbp push-source --new
+
+t-salsa-add-remote
+git push --set-upstream origin master
+
+# OK now this looks like something more normal.
+# We have:
+# maintainer (gbp) view dgit view
+# master
+# debian/2.0-1 archive/debian/2.0-1
+# remotes/origin/master remotes/dgit/dgit/sid
+
+t-git-debrebase -fupstream-has-debian convert-from-gbp
+
+v=2.0-2
+t-dch-commit -v $v -m 'switch to git-debrebase, no other changes'
+t-dch-commit -r sid
+
+$ifarchive t-dgit -wgf push-source --new --overwrite
+git push
+
+cd ..
+
+$ifarchive t-archive-process-incoming sid
+
+t-setup-done '' "$(echo $p*) salsa $($ifarchive echo git mirror aq)" '
+ . $troot/lib-gdr
+ t-tstunt-parsechangelog
+ p=example
+ t-git-next-date
+'
+
+t-ok
diff --git a/tests/setup/gdr-convert-gbp-noarchive b/tests/setup/gdr-convert-gbp-noarchive
new file mode 100755
index 0000000..dfeea3b
--- /dev/null
+++ b/tests/setup/gdr-convert-gbp-noarchive
@@ -0,0 +1,9 @@
+#!/bin/bash
+set -e
+. tests/lib
+. $troot/lib-gdr
+
+t-dependencies GDR
+
+export ifarchive=:
+t-chain-test gdr-convert-gbp
diff --git a/tests/tests/gdr-diverge-nmu b/tests/tests/gdr-diverge-nmu
new file mode 100755
index 0000000..15bf901
--- /dev/null
+++ b/tests/tests/gdr-diverge-nmu
@@ -0,0 +1,61 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+t-dependencies GDR
+
+t-setup-import gdr-convert-gbp
+
+cd $p
+
+t-dgit setup-mergechangelogs
+
+: 'maintainer'
+
+v=2.0-3
+t-maintainer-commit-some-changes
+
+t-git-next-date
+
+: 'non-dgit upload (but we prepare it with dgit anyway)'
+
+t-git-next-date
+git checkout origin/master
+
+v=2.0-2+nmu1
+t-nmu-upload-1 nmu
+gbp pq import
+t-nmu-upload-2
+t-nmu-commit-an-upstream-change
+t-nmu-upload-3
+
+: 'ad hocery'
+
+t-git-next-date
+git checkout master
+t-nmu-causes-ff-fail
+
+git cherry-pick 'dgit/dgit/sid^{/UPSTREAM NMU}'
+
+t-expect-fail 'Automatic merge failed; fix conflicts' \
+git merge --squash -m 'Incorporate NMU' dgit/dgit/sid
+
+git rm -rf debian/patches
+git commit -m 'Incorporate NMU'
+
+git merge -s ours -m 'Declare incorporate NMU' dgit/dgit/sid
+
+: 'right, how are we'
+
+t-git-next-date
+
+t-git-debrebase
+t-gdr-good laundered
+
+t-git-debrebase stitch
+t-gdr-good stitched
+
+
+t-nmu-reconciled-good patch-queue/nmu
+
+t-ok
diff --git a/tests/tests/gdr-diverge-nmu-dgit b/tests/tests/gdr-diverge-nmu-dgit
new file mode 100755
index 0000000..4b5907a
--- /dev/null
+++ b/tests/tests/gdr-diverge-nmu-dgit
@@ -0,0 +1,55 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+t-dependencies GDR
+
+t-setup-import gdr-convert-gbp
+
+cd $p
+
+t-dgit setup-mergechangelogs
+
+: 'maintainer'
+
+git checkout master
+
+v=2.0-3
+t-maintainer-commit-some-changes
+
+t-git-next-date
+
+: 'nmu'
+
+git checkout -b nmu origin/master~0
+
+t-git-next-date
+
+v=2.0-2+nmu1
+t-nmu-commit-an-upstream-change
+t-dch-commit -v$v -m finalise
+t-dch-commit -r sid
+
+t-dgit -wgf push-source
+
+t-archive-process-incoming sid
+
+: 'rebase nmu onto our branch'
+
+t-git-next-date
+git checkout master
+t-nmu-causes-ff-fail
+
+git checkout dgit/dgit/sid # detach
+
+t-expect-fail 'E:CONFLICT.*Commit Debian 3\.0 \(quilt\) metadata' \
+git rebase master
+git rebase --skip
+
+git push . HEAD:master
+git checkout master
+
+
+t-nmu-reconciled-good nmu
+
+t-ok
diff --git a/tests/tests/gdr-edits b/tests/tests/gdr-edits
new file mode 100755
index 0000000..6c77184
--- /dev/null
+++ b/tests/tests/gdr-edits
@@ -0,0 +1,40 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+t-dependencies GDR
+
+t-setup-import gdr-convert-gbp
+
+cd $p
+
+v=2.0-3
+t-dch-commit -v $v -m testing
+
+t-git-debrebase analyse |tee ../anal.1
+cat ../anal.1
+
+t-some-changes edits
+
+t-dch-commit -r sid
+
+git tag t.before
+
+t-git-debrebase
+t-gdr-good laundered
+
+t-dgit push-source
+t-gdr-good pushed-interop
+
+git branch before-noop
+
+t-git-next-date
+t-git-debrebase
+t-git-debrebase stitch
+t-gdr-good pushed-interop
+
+t-refs-same-start
+t-ref-same refs/heads/before-noop
+t-ref-head
+
+t-ok
diff --git a/tests/tests/gdr-import-dgit b/tests/tests/gdr-import-dgit
new file mode 100755
index 0000000..19918d8
--- /dev/null
+++ b/tests/tests/gdr-import-dgit
@@ -0,0 +1,68 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+t-dependencies GDR
+
+t-setup-import gdr-convert-gbp
+
+cd $p
+
+: 'non-dgit upload (but we prepare it with dgit anyway)'
+
+v=2.0-2+nmu1
+t-nmu-upload-1 nmu
+gbp pq import
+t-nmu-upload-2
+t-some-changes $numbranch
+t-nmu-upload-3
+
+: 'done the nmu, switching back to the maintainer hat'
+
+nmu-fold () {
+ t-git-next-date
+ t-dgit fetch
+ t-git-next-date
+ git merge --ff-only dgit/dgit/sid
+
+ git diff --exit-code patch-queue/$nmubranch
+
+ git branch unlaundered.$nmubranch
+
+ t-git-debrebase
+ t-gdr-good laundered
+
+ t-git-debrebase stitch
+ t-gdr-good stitched
+}
+
+nmu-fold
+
+v=2.0-3
+t-dch-commit -v $v -m "incorporate nmu"
+t-dch-commit -r sid
+t-dgit -wgf push-source
+
+: 'now test a new upstream'
+
+t-make-new-upstream-tarball 2.1
+
+git checkout master
+v=2.1-0+nmu1
+t-nmu-upload-1 nmu2
+
+gbp import-orig --upstream-version=2.1 --debian-branch=nmu2 ../$ust
+t-dch-commit -v $v -m "new upstream $v"
+gbp pq import
+
+#t-dgit -wgf build-source
+
+t-nmu-upload-2
+t-some-changes $numbranch
+t-nmu-upload-3
+
+: 'done the nmu, back to the maintainer'
+
+nmu-fold
+
+t-ok
diff --git a/tests/tests/gdr-makepatches7 b/tests/tests/gdr-makepatches7
new file mode 100755
index 0000000..9c39710
--- /dev/null
+++ b/tests/tests/gdr-makepatches7
@@ -0,0 +1,37 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+t-dependencies GDR
+
+t-setup-import gdr-convert-gbp
+
+cd $p
+
+t-some-changes for-rebase-fixup m
+
+t-git-debrebase
+t-git-debrebase make-patches
+
+t-some-changes for-dgit-fixup m
+
+t-git-debrebase
+
+t-expect-fail 'dgit: failed command: test-suite-break-git-debrebase' \
+t-dgit -wgf --git-debrebase=test-suite-break-git-debrebase quilt-fixup
+
+t-dgit -wgf --git-debrebase=true quilt-fixup
+
+t-some-changes for-make-patches-fails-then-dgit-fixup m
+
+t-expect-fail 'Patch export produced patch amendments' \
+t-git-debrebase make-patches
+
+t-dgit -wgf quilt-fixup
+
+t-refs-same-start
+t-ref-head
+t-dgit -wg quilt-fixup
+t-ref-head
+
+t-ok
diff --git a/tests/tests/gdr-newupstream b/tests/tests/gdr-newupstream
new file mode 100755
index 0000000..536f49c
--- /dev/null
+++ b/tests/tests/gdr-newupstream
@@ -0,0 +1,65 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+t-dependencies NO-DGIT GDR
+
+t-setup-import gdr-convert-gbp-noarchive
+
+cd $p
+
+: 'upstream hat'
+
+new-upstream () {
+ uv=$1
+ t-git-next-date
+ git checkout make-upstream
+ git reset --hard upstream
+ t-make-new-upstream-tarball $uv
+ git push . make-upstream:upstream
+ git checkout master
+ t-git-next-date
+}
+
+new-upstream 2.1
+
+: 'maintainer hat'
+
+git branch startpoint
+v=2.1-1
+
+git checkout master
+
+t-expect-fail F:'Could not determine appropriate upstream commitish' \
+t-git-debrebase new-upstream $v
+
+git tag v2.1 upstream
+
+t-git-debrebase new-upstream $v
+t-gdr-good laundered
+
+t-git-debrebase stitch
+t-gdr-good stitched
+
+git branch ordinary
+
+: 'with --anchor'
+
+git reset --hard startpoint
+
+t-git-debrebase analyse >../anal.anch
+anchor=$(perl <../anal.anch -ne '
+ next unless m/^(\w+) Anchor\s/;
+ print $1,"\n";
+ exit;
+')
+
+t-git-debrebase --anchor=$anchor -fanchor-treated new-upstream $v upstream
+t-gdr-good laundered
+
+t-git-debrebase stitch
+t-gdr-good stitched
+
+git diff --quiet ordinary
+
+t-ok
diff --git a/tests/tests/gdr-subcommands b/tests/tests/gdr-subcommands
new file mode 100755
index 0000000..e59fc07
--- /dev/null
+++ b/tests/tests/gdr-subcommands
@@ -0,0 +1,226 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+t-dependencies GDR
+
+t-setup-import gdr-convert-gbp
+
+cd $p
+
+t-dgit setup-mergechangelogs
+
+mix-it () {
+ t-git-next-date
+
+ local m=$(git symbolic-ref HEAD)
+ t-some-changes "subcommands $m 1"
+
+ # we want patches mde by dgit, not gdr, for our test cases
+ t-dgit --git-debrebase=true -wgf quilt-fixup
+ t-git-next-date
+
+ t-some-changes "subcommands $m 2"
+ t-git-next-date
+}
+
+git checkout -b stitched-laundered master
+mix-it
+t-git-debrebase quick
+t-gdr-good stitched HEAD
+
+git checkout -b stitched-mixed master
+mix-it
+
+git checkout -b unstitched-laundered master
+mix-it
+t-git-debrebase
+t-gdr-good laundered
+
+git checkout -b unstitched-mixed master
+t-git-debrebase
+mix-it
+
+t-git-next-date
+
+git show-ref
+
+subcmd () {
+ local subcmd=$1
+ shift
+ for startbranch in {stitched,unstitched}-{laundered,mixed}; do
+ work="work-$subcmd-$startbranch"
+
+ : "---------- $subcmd $startbranch ----------"
+
+ git for-each-ref "**/$startbranch"{,/**} \
+ --format='create %(refname) %(objectname)' \
+ | sed "s/$startbranch/$work/" \
+ | git update-ref --stdin
+
+ git checkout $work
+ checkletters=$1; shift
+
+ before=before-$work
+ git branch $before
+
+ local xopts=''
+
+ case "$checkletters" in
+ XX*)
+ fail "$checkletters" # for debugging
+ ;;
+ esac
+
+ case "$checkletters" in
+ X*)
+ t-expect-fail E:'snags: [0-9]* blockers' \
+ t-git-debrebase $xopts $subcmd
+ xopts+=' --force'
+ next_checkletter
+ ;;
+ esac
+
+ case "$checkletters" in
+ N*)
+ t-expect-fail E:. \
+ t-git-debrebase $xopts $subcmd
+ xopts+=' --noop-ok'
+ next_checkletter
+ ;;
+ esac
+
+ case "$checkletters" in
+ [EF]:*)
+ t-expect-fail "$checkletters" \
+ t-git-debrebase $xopts $subcmd
+ continue
+ ;;
+ *)
+ t-git-debrebase $xopts $subcmd
+ ;;
+ esac
+
+ peel=peel-$subcmd-$startbranch
+ git checkout -b $peel
+ t-clean-on-branch $peel
+
+ : "---------- $subcmd $startbranch $checkletters ----------"
+
+ while [ "x$checkletters" != x ]; do
+ : "---- $subcmd $startbranch ...$checkletters ----"
+ make_check "$checkletters"
+ checkletters="${checkletters#?}"
+ done
+ done
+
+}
+
+next_checkletter () {
+ checkletters="${checkletters#?}"
+}
+
+make_check () {
+ case "$1" in
+ [Nn]*)
+ t-refs-same-start
+ t-refs-same refs/heads/$before refs/heads/$work
+ ;;
+ U*)
+ t-refs-same-start
+ t-refs-same refs/heads/$before refs/ffq-prev/heads/$work
+ make_check u
+ ;;
+ u*)
+ t-git-get-ref refs/ffq-prev/heads/$work
+ t-refs-notexist refs/debrebase-last/heads/$work
+ ;;
+ V*)
+ t-refs-same-start
+ t-refs-same refs/ffq-prev/heads/$work \
+ refs/ffq-prev/heads/$startbranch
+ t-refs-notexist refs/debrebase-last/heads/$work
+ ;;
+ s*)
+ t-refs-notexist refs/ffq-prev/heads/$work
+ t-refs-same-start
+ t-refs-same refs/debrebase-last/heads/$work \
+ refs/debrebase-last/heads/$startbranch
+ t-has-ancestor HEAD refs/debrebase-last/heads/$work
+ ;;
+ S*)
+ t-refs-notexist refs/ffq-prev/heads/$work
+ t-refs-same-start refs/debrebase-last/heads/$work
+ t-ref-head
+ git diff --quiet HEAD^1
+ git diff HEAD^2 | grep $startbranch
+ git reset --hard HEAD^1
+ ;;
+ P*)
+ t-dgit -wgf --quilt=nofix quilt-fixup
+ git diff HEAD~ debian/patches | egrep .
+ git diff --quiet HEAD~ -- ':.' ':!debian/patches'
+ git reset --hard HEAD~
+ ;;
+ l*)
+ git diff --quiet HEAD refs/heads/$before -- ':.' ':!debian/patches'
+ t-gdr-good laundered
+ ;;
+ t*)
+ git diff --quiet HEAD refs/heads/$before
+ ;;
+ f*)
+ t-has-ancestor HEAD refs/heads/$before
+ ;;
+ *)
+ fail "$1"
+ ;;
+ esac
+}
+
+Ec="F:No ongoing git-debrebase session"
+Ep="F:Patch export produced patch amendments"
+
+# input state:
+# stitched? st'd st'd unst'd unst'd
+# laundered? laund'd mixed laund'd mixed
+#
+# "mixed" means an out of order branch
+# containing mixed commits and patch additions,
+# but which needs even more patches
+#
+subcmd '' Ult Ull Vlt Vl
+subcmd stitch Ns Nu Sltf Stf
+subcmd prepush Ns Nu Sltf Stf
+subcmd quick ns Sl Sltf Sl
+subcmd conclude "$Ec" "$Ec" Sltf Sl
+subcmd make-patches sPft "$Ep" uPft "$Ep"
+#subcmd dgit-upload-hook Psft "$Ep" SPft "$Ep"
+#
+# result codes, each one is a check:
+# E:$pat } this is an error (must come first)
+# F:$pat } arg is passed to expect-fail
+#
+# X should fail due to snags, but succeed when forced
+# XX crash out of script for manual debugging
+#
+# N this is a noop, error unless --noop-ok
+# n this is a silent noop
+# both of these imply tf; but, specify also one of u s
+#
+# should normally specify one of these:
+# U just unstiched: ffq-prev is exactly previous HEAD; implies u
+# u result is unstitched
+# V ffq-prev remains unchanged; implies also u
+# s result is stitched, debrebase-last exists and is unchanged
+# S result is stitch just made, remaining letters apply to result~
+#
+# P result is add-patches, remaining letters apply to result~
+#
+# should normally specify one or both of these:
+# l result is laundered, tree is same as before minus d/patches
+# t tree is exactly same as before
+#
+# f result is ff from previous HEAD
+
+t-ok
diff --git a/tests/tests/gdr-viagit b/tests/tests/gdr-viagit
new file mode 100755
index 0000000..644d2d4
--- /dev/null
+++ b/tests/tests/gdr-viagit
@@ -0,0 +1,40 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+t-dependencies NO-DGIT GDR
+
+t-setup-import gdr-convert-gbp-noarchive
+
+: 'set up so t-git-debrebase runs gdr via git'
+
+case "$DGIT_GITDEBREBASE_TEST" in
+''|git-debrebase) ;;
+*)
+ t-tstunt
+ st=$tmp/tstunt/git-debrebase
+ export DGIT_GITDEBREBASE_TEST_REAL="$DGIT_GITDEBREBASE_TEST"
+ cat <<'END' >$st
+#!/bin/sh
+set -x
+exec "$DGIT_GITDEBREBASE_TEST_REAL" "$@"
+END
+ chmod +x $st
+ ;;
+esac
+
+DGIT_GITDEBREBASE_TEST='git debrebase'
+
+: 'do a simple test'
+
+cd $p
+
+t-some-changes
+
+t-git-debrebase
+t-gdr-good laundered
+
+t-git-debrebase stitch --prose=wombat
+t-gdr-good stitched
+
+t-ok
diff --git a/tests/tests/gitattributes b/tests/tests/gitattributes
index 9e1c246..bdc61f8 100755
--- a/tests/tests/gitattributes
+++ b/tests/tests/gitattributes
@@ -22,12 +22,20 @@ badattr1 () {
printf >>af/$filename 'crlf: \r\n'
echo >>af/$filename 'id $Id: $'
echo >>af/$filename 'id $Id: SPLARK $'
+ printf >>af/$filename '\xEF\xBB\xBF <- UTF-8 BOM\n'
echo >>gitattrs "af/$filename" "$attrspec"
}
badattr () {
attrname=$1; shift
- badattr1 $attrname-set $attrname
+
+ case "$attrname" in
+ working-tree-encoding) ;;
+ *)
+ badattr1 $attrname-set $attrname
+ ;;
+ esac
+
badattr1 $attrname-unset -$attrname
badattr1 $attrname-unspec \!$attrname
local val
@@ -40,6 +48,9 @@ badattr () {
t-git-config core.eol crlf
+printf >>.dotfile-attr 'dotfiles too?\n'
+echo >>gitattrs '.dotfile-attr filter=dgit-test-crazy-f'
+
badattr text auto
badattr eol lf crlf
badattr ident
@@ -57,6 +68,7 @@ badattr export-ignore
badattr export-subst
badattr delta
badattr encoding no-such-encoding
+badattr working-tree-encoding ISO-8859-1 UTF-16
man gitattributes \
| perl -ne 'print $1,"\n" if m/^ *(\w[-a-z]*)$/' \
@@ -67,7 +79,7 @@ while read attr; do
badattr $attr
done
-sha256sum af/* >sums
+sha256sum .dotfile-attr af/* >sums
# ----- common to source formats -----
@@ -92,7 +104,7 @@ sfmt_setup () {
cp ../gitattrs .gitattributes
$addpatch gitattrs
- cp -a ../af .
+ cp -a ../af ../.dotfile-attr .
$addpatch files
cp ../sums .
@@ -108,10 +120,10 @@ sums_check () {
# $sums $branch
# and check out $branch
- sha256sum af/* >../$sums.checkout
+ sha256sum .dotfile-attr af/* >../$sums.checkout
diff -U0 ../sums ../$sums.checkout
- for f in af/*; do
+ for f in .dotfile-attr af/*; do
git cat-file blob "refs/heads/$branch:$f" \
| sha256sum \
| sed -e 's#-$#'$f'#' \
@@ -126,7 +138,7 @@ sums_check_broken () {
# $sums
# and check out the broken branch
- sha256sum af/* >../$sums.broken
+ sha256sum .dotfile-attr af/* >../$sums.broken
for s in ../sums ../$sums.broken; do
sed 's/[0-9a-f]* //' $s >$s.nosums
@@ -178,7 +190,7 @@ sfmt_import () {
t-dgit-warn-check 1 $dgitargs import-dsc ../$dscf +$branch
git checkout $branch
- touch af/*
+ touch .dotfile-attr af/*
git reset --hard
sums_check
@@ -239,7 +251,7 @@ t-dgit-warn-check 0 -cdgit.default.setup-gitattributes=false \
clone $p sid $p.clone.broken
cd $p.clone.broken
-sums=$p.clone.broken
+sums=sums.broken
sums_check_broken
cd ..
@@ -253,4 +265,24 @@ sums_check
cd ..
+t-dgit-warn-check 0 -cdgit.default.setup-gitattributes=false \
+ clone $p sid $p.clone.old
+
+cd $p.clone.old
+
+mkdir -p .git/info
+cat >.git/info/attributes <<'END'
+* dgit-defuse-attrs
+[attr]dgit-defuse-attrs -text -eol -crlf -ident -filter
+# ^ see GITATTRIBUTES in dgit(7) and dgit setup-new-tree in dgit(1)
+END
+
+t-dgit setup-gitattributes
+git reset --hard
+
+sums=sums.old
+sums_check
+
+cd ..
+
t-ok
diff --git a/tests/tests/hint-testsuite-triggers b/tests/tests/hint-testsuite-triggers
new file mode 100755
index 0000000..f35dd43
--- /dev/null
+++ b/tests/tests/hint-testsuite-triggers
@@ -0,0 +1,10 @@
+#!/bin/bash
+set -e
+. tests/lib
+
+: "exists only to trigger ci.debian.net retests for some indirect deps"
+
+t-restrict hint-testsuite-triggers
+t-dependencies NO-DEFAULT gnupg patch diffutils
+
+t-ok
diff --git a/tests/tests/quilt-singlepatch b/tests/tests/quilt-singlepatch
index 3b6e228..320a31c 100755
--- a/tests/tests/quilt-singlepatch
+++ b/tests/tests/quilt-singlepatch
@@ -36,4 +36,10 @@ debian-changes
series
END
+git apply --reverse debian/patches/debian-changes
+git commit -a -m 'go back to plain upstream'
+
+t-dgit -wgf build-source
+t-dgit --damp-run push
+
t-ok
diff --git a/tests/tests/version-opt b/tests/tests/version-opt
index 22c35e7..2933912 100755
--- a/tests/tests/version-opt
+++ b/tests/tests/version-opt
@@ -14,7 +14,7 @@ git tag start
t-dgit setup-mergechangelogs
t-dgit build-source
-t-dgit push --new
+t-dgit push --new --overwrite
t-archive-process-incoming sid