chiark / gitweb /
e125922ec11ad91cd4590ac6ffbb44faa902fa5d
[dgit.git] / dgit
1 #!/usr/bin/perl -w
2 # dgit
3 # Integration between git and Debian-style archives
4 #
5 # Copyright (C)2013-2015 Ian Jackson
6 #
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
20 use strict;
21
22 use Debian::Dgit;
23 setup_sigwarn();
24
25 use IO::Handle;
26 use Data::Dumper;
27 use LWP::UserAgent;
28 use Dpkg::Control::Hash;
29 use File::Path;
30 use File::Temp qw(tempdir);
31 use File::Basename;
32 use Dpkg::Version;
33 use POSIX;
34 use IPC::Open2;
35 use Digest::SHA;
36 use Digest::MD5;
37 use List::Util qw(any);
38 use List::MoreUtils qw(pairwise);
39 use Carp;
40
41 use Debian::Dgit;
42
43 our $our_version = 'UNRELEASED'; ###substituted###
44
45 our @rpushprotovsn_support = qw(4 3 2); # 4 is new tag format
46 our $protovsn;
47
48 our $isuite = 'unstable';
49 our $idistro;
50 our $package;
51 our @ropts;
52
53 our $sign = 1;
54 our $dryrun_level = 0;
55 our $changesfile;
56 our $buildproductsdir = '..';
57 our $new_package = 0;
58 our $ignoredirty = 0;
59 our $rmonerror = 1;
60 our @deliberatelies;
61 our %previously;
62 our $existing_package = 'dpkg';
63 our $cleanmode;
64 our $changes_since_version;
65 our $rmchanges;
66 our $quilt_mode;
67 our $quilt_modes_re = 'linear|smash|auto|nofix|nocheck|gbp|unapplied';
68 our $we_are_responder;
69 our $initiator_tempdir;
70 our $patches_applied_dirtily = 00;
71 our $tagformat_want;
72 our $tagformat;
73 our $tagformatfn;
74
75 our %format_ok = map { $_=>1 } ("1.0","3.0 (native)","3.0 (quilt)");
76
77 our $suite_re = '[-+.0-9a-z]+';
78 our $cleanmode_re = 'dpkg-source(?:-d)?|git|git-ff|check|none';
79
80 our $git_authline_re = '^([^<>]+) \<(\S+)\> (\d+ [-+]\d+)$';
81 our $splitbraincache = 'dgit-intern/quilt-cache';
82
83 our (@git) = qw(git);
84 our (@dget) = qw(dget);
85 our (@curl) = qw(curl -f);
86 our (@dput) = qw(dput);
87 our (@debsign) = qw(debsign);
88 our (@gpg) = qw(gpg);
89 our (@sbuild) = qw(sbuild);
90 our (@ssh) = 'ssh';
91 our (@dgit) = qw(dgit);
92 our (@dpkgbuildpackage) = qw(dpkg-buildpackage -i\.git/ -I.git);
93 our (@dpkgsource) = qw(dpkg-source -i\.git/ -I.git);
94 our (@dpkggenchanges) = qw(dpkg-genchanges);
95 our (@mergechanges) = qw(mergechanges -f);
96 our (@gbp) = qw(gbp);
97 our (@changesopts) = ('');
98
99 our %opts_opt_map = ('dget' => \@dget, # accept for compatibility
100                      'curl' => \@curl,
101                      'dput' => \@dput,
102                      'debsign' => \@debsign,
103                      'gpg' => \@gpg,
104                      'sbuild' => \@sbuild,
105                      'ssh' => \@ssh,
106                      'dgit' => \@dgit,
107                      'git' => \@git,
108                      'dpkg-source' => \@dpkgsource,
109                      'dpkg-buildpackage' => \@dpkgbuildpackage,
110                      'dpkg-genchanges' => \@dpkggenchanges,
111                      'gbp' => \@gbp,
112                      'ch' => \@changesopts,
113                      'mergechanges' => \@mergechanges);
114
115 our %opts_opt_cmdonly = ('gpg' => 1, 'git' => 1);
116 our %opts_cfg_insertpos = map {
117     $_,
118     scalar @{ $opts_opt_map{$_} }
119 } keys %opts_opt_map;
120
121 sub finalise_opts_opts();
122
123 our $keyid;
124
125 autoflush STDOUT 1;
126
127 our $supplementary_message = '';
128 our $need_split_build_invocation = 0;
129 our $split_brain = 0;
130
131 END {
132     local ($@, $?);
133     print STDERR "! $_\n" foreach $supplementary_message =~ m/^.+$/mg;
134 }
135
136 our $remotename = 'dgit';
137 our @ourdscfield = qw(Dgit Vcs-Dgit-Master);
138 our $csuite;
139 our $instead_distro;
140
141 sub debiantag ($$) {
142     my ($v,$distro) = @_;
143     return $tagformatfn->($v, $distro);
144 }
145
146 sub debiantag_maintview ($$) { 
147     my ($v,$distro) = @_;
148     $v =~ y/~:/_%/;
149     return "$distro/$v";
150 }
151
152 sub lbranch () { return "$branchprefix/$csuite"; }
153 my $lbranch_re = '^refs/heads/'.$branchprefix.'/([^/.]+)$';
154 sub lref () { return "refs/heads/".lbranch(); }
155 sub lrref () { return "refs/remotes/$remotename/".server_branch($csuite); }
156 sub rrref () { return server_ref($csuite); }
157
158 sub lrfetchrefs () { return "refs/dgit-fetch/$csuite"; }
159 sub lrfetchref () { return lrfetchrefs.'/'.server_branch($csuite); }
160
161 # We fetch some parts of lrfetchrefs/*.  Ideally we delete these
162 # locally fetched refs because they have unhelpful names and clutter
163 # up gitk etc.  So we track whether we have "used up" head ref (ie,
164 # whether we have made another local ref which refers to this object).
165 #
166 # (If we deleted them unconditionally, then we might end up
167 # re-fetching the same git objects each time dgit fetch was run.)
168 #
169 # So, leach use of lrfetchrefs needs to be accompanied by arrangements
170 # in git_fetch_us to fetch the refs in question, and possibly a call
171 # to lrfetchref_used.
172
173 our (%lrfetchrefs_f, %lrfetchrefs_d);
174 # $lrfetchrefs_X{lrfetchrefs."/heads/whatever"} = $objid
175
176 sub lrfetchref_used ($) {
177     my ($fullrefname) = @_;
178     my $objid = $lrfetchrefs_f{$fullrefname};
179     $lrfetchrefs_d{$fullrefname} = $objid if defined $objid;
180 }
181
182 sub stripepoch ($) {
183     my ($vsn) = @_;
184     $vsn =~ s/^\d+\://;
185     return $vsn;
186 }
187
188 sub srcfn ($$) {
189     my ($vsn,$sfx) = @_;
190     return "${package}_".(stripepoch $vsn).$sfx
191 }
192
193 sub dscfn ($) {
194     my ($vsn) = @_;
195     return srcfn($vsn,".dsc");
196 }
197
198 sub changespat ($;$) {
199     my ($vsn, $arch) = @_;
200     return "${package}_".(stripepoch $vsn)."_".($arch//'*').".changes";
201 }
202
203 our $us = 'dgit';
204 initdebug('');
205
206 our @end;
207 END { 
208     local ($?);
209     foreach my $f (@end) {
210         eval { $f->(); };
211         print STDERR "$us: cleanup: $@" if length $@;
212     }
213 };
214
215 sub badcfg { print STDERR "$us: invalid configuration: @_\n"; exit 12; }
216
217 sub no_such_package () {
218     print STDERR "$us: package $package does not exist in suite $isuite\n";
219     exit 4;
220 }
221
222 sub changedir ($) {
223     my ($newdir) = @_;
224     printdebug "CD $newdir\n";
225     chdir $newdir or confess "chdir: $newdir: $!";
226 }
227
228 sub deliberately ($) {
229     my ($enquiry) = @_;
230     return !!grep { $_ eq "--deliberately-$enquiry" } @deliberatelies;
231 }
232
233 sub deliberately_not_fast_forward () {
234     foreach (qw(not-fast-forward fresh-repo)) {
235         return 1 if deliberately($_) || deliberately("TEST-dgit-only-$_");
236     }
237 }
238
239 sub quiltmode_splitbrain () {
240     $quilt_mode =~ m/gbp|dpm|unapplied/;
241 }
242
243 #---------- remote protocol support, common ----------
244
245 # remote push initiator/responder protocol:
246 #  $ dgit remote-push-build-host <n-rargs> <rargs>... <push-args>...
247 #  where <rargs> is <push-host-dir> <supported-proto-vsn>,... ...
248 #  < dgit-remote-push-ready <actual-proto-vsn>
249 #
250 # occasionally:
251 #
252 #  > progress NBYTES
253 #  [NBYTES message]
254 #
255 #  > supplementary-message NBYTES          # $protovsn >= 3
256 #  [NBYTES message]
257 #
258 # main sequence:
259 #
260 #  > file parsed-changelog
261 #  [indicates that output of dpkg-parsechangelog follows]
262 #  > data-block NBYTES
263 #  > [NBYTES bytes of data (no newline)]
264 #  [maybe some more blocks]
265 #  > data-end
266 #
267 #  > file dsc
268 #  [etc]
269 #
270 #  > file changes
271 #  [etc]
272 #
273 #  > param head DGIT-VIEW-HEAD
274 #  > param csuite SUITE
275 #  > param tagformat old|new
276 #  > param maint-view MAINT-VIEW-HEAD
277 #
278 #  > previously REFNAME=OBJNAME       # if --deliberately-not-fast-forward
279 #                                     # goes into tag, for replay prevention
280 #
281 #  > want signed-tag
282 #  [indicates that signed tag is wanted]
283 #  < data-block NBYTES
284 #  < [NBYTES bytes of data (no newline)]
285 #  [maybe some more blocks]
286 #  < data-end
287 #  < files-end
288 #
289 #  > want signed-dsc-changes
290 #  < data-block NBYTES    [transfer of signed dsc]
291 #  [etc]
292 #  < data-block NBYTES    [transfer of signed changes]
293 #  [etc]
294 #  < files-end
295 #
296 #  > complete
297
298 our $i_child_pid;
299
300 sub i_child_report () {
301     # Sees if our child has died, and reap it if so.  Returns a string
302     # describing how it died if it failed, or undef otherwise.
303     return undef unless $i_child_pid;
304     my $got = waitpid $i_child_pid, WNOHANG;
305     return undef if $got <= 0;
306     die unless $got == $i_child_pid;
307     $i_child_pid = undef;
308     return undef unless $?;
309     return "build host child ".waitstatusmsg();
310 }
311
312 sub badproto ($$) {
313     my ($fh, $m) = @_;
314     fail "connection lost: $!" if $fh->error;
315     fail "protocol violation; $m not expected";
316 }
317
318 sub badproto_badread ($$) {
319     my ($fh, $wh) = @_;
320     fail "connection lost: $!" if $!;
321     my $report = i_child_report();
322     fail $report if defined $report;
323     badproto $fh, "eof (reading $wh)";
324 }
325
326 sub protocol_expect (&$) {
327     my ($match, $fh) = @_;
328     local $_;
329     $_ = <$fh>;
330     defined && chomp or badproto_badread $fh, "protocol message";
331     if (wantarray) {
332         my @r = &$match;
333         return @r if @r;
334     } else {
335         my $r = &$match;
336         return $r if $r;
337     }
338     badproto $fh, "\`$_'";
339 }
340
341 sub protocol_send_file ($$) {
342     my ($fh, $ourfn) = @_;
343     open PF, "<", $ourfn or die "$ourfn: $!";
344     for (;;) {
345         my $d;
346         my $got = read PF, $d, 65536;
347         die "$ourfn: $!" unless defined $got;
348         last if !$got;
349         print $fh "data-block ".length($d)."\n" or die $!;
350         print $fh $d or die $!;
351     }
352     PF->error and die "$ourfn $!";
353     print $fh "data-end\n" or die $!;
354     close PF;
355 }
356
357 sub protocol_read_bytes ($$) {
358     my ($fh, $nbytes) = @_;
359     $nbytes =~ m/^[1-9]\d{0,5}$|^0$/ or badproto \*RO, "bad byte count";
360     my $d;
361     my $got = read $fh, $d, $nbytes;
362     $got==$nbytes or badproto_badread $fh, "data block";
363     return $d;
364 }
365
366 sub protocol_receive_file ($$) {
367     my ($fh, $ourfn) = @_;
368     printdebug "() $ourfn\n";
369     open PF, ">", $ourfn or die "$ourfn: $!";
370     for (;;) {
371         my ($y,$l) = protocol_expect {
372             m/^data-block (.*)$/ ? (1,$1) :
373             m/^data-end$/ ? (0,) :
374             ();
375         } $fh;
376         last unless $y;
377         my $d = protocol_read_bytes $fh, $l;
378         print PF $d or die $!;
379     }
380     close PF or die $!;
381 }
382
383 #---------- remote protocol support, responder ----------
384
385 sub responder_send_command ($) {
386     my ($command) = @_;
387     return unless $we_are_responder;
388     # called even without $we_are_responder
389     printdebug ">> $command\n";
390     print PO $command, "\n" or die $!;
391 }    
392
393 sub responder_send_file ($$) {
394     my ($keyword, $ourfn) = @_;
395     return unless $we_are_responder;
396     printdebug "]] $keyword $ourfn\n";
397     responder_send_command "file $keyword";
398     protocol_send_file \*PO, $ourfn;
399 }
400
401 sub responder_receive_files ($@) {
402     my ($keyword, @ourfns) = @_;
403     die unless $we_are_responder;
404     printdebug "[[ $keyword @ourfns\n";
405     responder_send_command "want $keyword";
406     foreach my $fn (@ourfns) {
407         protocol_receive_file \*PI, $fn;
408     }
409     printdebug "[[\$\n";
410     protocol_expect { m/^files-end$/ } \*PI;
411 }
412
413 #---------- remote protocol support, initiator ----------
414
415 sub initiator_expect (&) {
416     my ($match) = @_;
417     protocol_expect { &$match } \*RO;
418 }
419
420 #---------- end remote code ----------
421
422 sub progress {
423     if ($we_are_responder) {
424         my $m = join '', @_;
425         responder_send_command "progress ".length($m) or die $!;
426         print PO $m or die $!;
427     } else {
428         print @_, "\n";
429     }
430 }
431
432 our $ua;
433
434 sub url_get {
435     if (!$ua) {
436         $ua = LWP::UserAgent->new();
437         $ua->env_proxy;
438     }
439     my $what = $_[$#_];
440     progress "downloading $what...";
441     my $r = $ua->get(@_) or die $!;
442     return undef if $r->code == 404;
443     $r->is_success or fail "failed to fetch $what: ".$r->status_line;
444     return $r->decoded_content(charset => 'none');
445 }
446
447 our ($dscdata,$dscurl,$dsc,$dsc_checked,$skew_warning_vsn);
448
449 sub runcmd {
450     debugcmd "+",@_;
451     $!=0; $?=-1;
452     failedcmd @_ if system @_;
453 }
454
455 sub act_local () { return $dryrun_level <= 1; }
456 sub act_scary () { return !$dryrun_level; }
457
458 sub printdone {
459     if (!$dryrun_level) {
460         progress "dgit ok: @_";
461     } else {
462         progress "would be ok: @_ (but dry run only)";
463     }
464 }
465
466 sub dryrun_report {
467     printcmd(\*STDERR,$debugprefix."#",@_);
468 }
469
470 sub runcmd_ordryrun {
471     if (act_scary()) {
472         runcmd @_;
473     } else {
474         dryrun_report @_;
475     }
476 }
477
478 sub runcmd_ordryrun_local {
479     if (act_local()) {
480         runcmd @_;
481     } else {
482         dryrun_report @_;
483     }
484 }
485
486 sub shell_cmd {
487     my ($first_shell, @cmd) = @_;
488     return qw(sh -ec), $first_shell.'; exec "$@"', 'x', @cmd;
489 }
490
491 our $helpmsg = <<END;
492 main usages:
493   dgit [dgit-opts] clone [dgit-opts] package [suite] [./dir|/dir]
494   dgit [dgit-opts] fetch|pull [dgit-opts] [suite]
495   dgit [dgit-opts] build [dpkg-buildpackage-opts]
496   dgit [dgit-opts] sbuild [sbuild-opts]
497   dgit [dgit-opts] push [dgit-opts] [suite]
498   dgit [dgit-opts] rpush build-host:build-dir ...
499 important dgit options:
500   -k<keyid>           sign tag and package with <keyid> instead of default
501   --dry-run -n        do not change anything, but go through the motions
502   --damp-run -L       like --dry-run but make local changes, without signing
503   --new -N            allow introducing a new package
504   --debug -D          increase debug level
505   -c<name>=<value>    set git config option (used directly by dgit too)
506 END
507
508 our $later_warning_msg = <<END;
509 Perhaps the upload is stuck in incoming.  Using the version from git.
510 END
511
512 sub badusage {
513     print STDERR "$us: @_\n", $helpmsg or die $!;
514     exit 8;
515 }
516
517 sub nextarg {
518     @ARGV or badusage "too few arguments";
519     return scalar shift @ARGV;
520 }
521
522 sub cmd_help () {
523     print $helpmsg or die $!;
524     exit 0;
525 }
526
527 our $td = $ENV{DGIT_TEST_DUMMY_DIR} || "DGIT_TEST_DUMMY_DIR-unset";
528
529 our %defcfg = ('dgit.default.distro' => 'debian',
530                'dgit.default.username' => '',
531                'dgit.default.archive-query-default-component' => 'main',
532                'dgit.default.ssh' => 'ssh',
533                'dgit.default.archive-query' => 'madison:',
534                'dgit.default.sshpsql-dbname' => 'service=projectb',
535                'dgit.default.dgit-tag-format' => 'old,new,maint',
536                'dgit-distro.debian.archive-query' => 'ftpmasterapi:',
537                'dgit-distro.debian.git-check' => 'url',
538                'dgit-distro.debian.git-check-suffix' => '/info/refs',
539                'dgit-distro.debian.new-private-pushers' => 't',
540                'dgit-distro.debian.dgit-tag-format' => 'old',
541                'dgit-distro.debian/push.git-url' => '',
542                'dgit-distro.debian/push.git-host' => 'push.dgit.debian.org',
543                'dgit-distro.debian/push.git-user-force' => 'dgit',
544                'dgit-distro.debian/push.git-proto' => 'git+ssh://',
545                'dgit-distro.debian/push.git-path' => '/dgit/debian/repos',
546                'dgit-distro.debian/push.git-create' => 'true',
547                'dgit-distro.debian/push.git-check' => 'ssh-cmd',
548  'dgit-distro.debian.archive-query-url', 'https://api.ftp-master.debian.org/',
549 # 'dgit-distro.debian.archive-query-tls-key',
550 #    '/etc/ssl/certs/%HOST%.pem:/etc/dgit/%HOST%.pem',
551 # ^ this does not work because curl is broken nowadays
552 # Fixing #790093 properly will involve providing providing the key
553 # in some pacagke and maybe updating these paths.
554 #
555 # 'dgit-distro.debian.archive-query-tls-curl-args',
556 #   '--ca-path=/etc/ssl/ca-debian',
557 # ^ this is a workaround but works (only) on DSA-administered machines
558                'dgit-distro.debian.git-url' => 'https://git.dgit.debian.org',
559                'dgit-distro.debian.git-url-suffix' => '',
560                'dgit-distro.debian.upload-host' => 'ftp-master', # for dput
561                'dgit-distro.debian.mirror' => 'http://ftp.debian.org/debian/',
562  'dgit-distro.debian.backports-quirk' => '(squeeze)-backports*',
563  'dgit-distro.debian-backports.mirror' => 'http://backports.debian.org/debian-backports/',
564                'dgit-distro.ubuntu.git-check' => 'false',
565  'dgit-distro.ubuntu.mirror' => 'http://archive.ubuntu.com/ubuntu',
566                'dgit-distro.test-dummy.ssh' => "$td/ssh",
567                'dgit-distro.test-dummy.username' => "alice",
568                'dgit-distro.test-dummy.git-check' => "ssh-cmd",
569                'dgit-distro.test-dummy.git-create' => "ssh-cmd",
570                'dgit-distro.test-dummy.git-url' => "$td/git",
571                'dgit-distro.test-dummy.git-host' => "git",
572                'dgit-distro.test-dummy.git-path' => "$td/git",
573                'dgit-distro.test-dummy.archive-query' => "ftpmasterapi:",
574                'dgit-distro.test-dummy.archive-query-url' => "file://$td/aq/",
575                'dgit-distro.test-dummy.mirror' => "file://$td/mirror/",
576                'dgit-distro.test-dummy.upload-host' => 'test-dummy',
577                );
578
579 our %gitcfg;
580
581 sub git_slurp_config () {
582     local ($debuglevel) = $debuglevel-2;
583     local $/="\0";
584
585     my @cmd = (@git, qw(config -z --get-regexp .*));
586     debugcmd "|",@cmd;
587
588     open GITS, "-|", @cmd or die $!;
589     while (<GITS>) {
590         chomp or die;
591         printdebug "=> ", (messagequote $_), "\n";
592         m/\n/ or die "$_ ?";
593         push @{ $gitcfg{$`} }, $'; #';
594     }
595     $!=0; $?=0;
596     close GITS
597         or ($!==0 && $?==256)
598         or failedcmd @cmd;
599 }
600
601 sub git_get_config ($) {
602     my ($c) = @_;
603     my $l = $gitcfg{$c};
604     printdebug"C $c ".(defined $l ? messagequote "'$l'" : "undef")."\n"
605         if $debuglevel >= 4;
606     $l or return undef;
607     @$l==1 or badcfg "multiple values for $c" if @$l > 1;
608     return $l->[0];
609 }
610
611 sub cfg {
612     foreach my $c (@_) {
613         return undef if $c =~ /RETURN-UNDEF/;
614         my $v = git_get_config($c);
615         return $v if defined $v;
616         my $dv = $defcfg{$c};
617         return $dv if defined $dv;
618     }
619     badcfg "need value for one of: @_\n".
620         "$us: distro or suite appears not to be (properly) supported";
621 }
622
623 sub access_basedistro () {
624     if (defined $idistro) {
625         return $idistro;
626     } else {    
627         return cfg("dgit-suite.$isuite.distro",
628                    "dgit.default.distro");
629     }
630 }
631
632 sub access_quirk () {
633     # returns (quirk name, distro to use instead or undef, quirk-specific info)
634     my $basedistro = access_basedistro();
635     my $backports_quirk = cfg("dgit-distro.$basedistro.backports-quirk",
636                               'RETURN-UNDEF');
637     if (defined $backports_quirk) {
638         my $re = $backports_quirk;
639         $re =~ s/[^-0-9a-z_\%*()]/\\$&/ig;
640         $re =~ s/\*/.*/g;
641         $re =~ s/\%/([-0-9a-z_]+)/
642             or $re =~ m/[()]/ or badcfg "backports-quirk needs \% or ( )";
643         if ($isuite =~ m/^$re$/) {
644             return ('backports',"$basedistro-backports",$1);
645         }
646     }
647     return ('none',undef);
648 }
649
650 our $access_forpush;
651
652 sub parse_cfg_bool ($$$) {
653     my ($what,$def,$v) = @_;
654     $v //= $def;
655     return
656         $v =~ m/^[ty1]/ ? 1 :
657         $v =~ m/^[fn0]/ ? 0 :
658         badcfg "$what needs t (true, y, 1) or f (false, n, 0) not \`$v'";
659 }       
660
661 sub access_forpush_config () {
662     my $d = access_basedistro();
663
664     return 1 if
665         $new_package &&
666         parse_cfg_bool('new-private-pushers', 0,
667                        cfg("dgit-distro.$d.new-private-pushers",
668                            'RETURN-UNDEF'));
669
670     my $v = cfg("dgit-distro.$d.readonly", 'RETURN-UNDEF');
671     $v //= 'a';
672     return
673         $v =~ m/^[ty1]/ ? 0 : # force readonly,    forpush = 0
674         $v =~ m/^[fn0]/ ? 1 : # force nonreadonly, forpush = 1
675         $v =~ m/^[a]/  ? '' : # auto,              forpush = ''
676         badcfg "readonly needs t (true, y, 1) or f (false, n, 0) or a (auto)";
677 }
678
679 sub access_forpush () {
680     $access_forpush //= access_forpush_config();
681     return $access_forpush;
682 }
683
684 sub pushing () {
685     die "$access_forpush ?" if ($access_forpush // 1) ne 1;
686     badcfg "pushing but distro is configured readonly"
687         if access_forpush_config() eq '0';
688     $access_forpush = 1;
689     $supplementary_message = <<'END' unless $we_are_responder;
690 Push failed, before we got started.
691 You can retry the push, after fixing the problem, if you like.
692 END
693     finalise_opts_opts();
694 }
695
696 sub notpushing () {
697     finalise_opts_opts();
698 }
699
700 sub supplementary_message ($) {
701     my ($msg) = @_;
702     if (!$we_are_responder) {
703         $supplementary_message = $msg;
704         return;
705     } elsif ($protovsn >= 3) {
706         responder_send_command "supplementary-message ".length($msg)
707             or die $!;
708         print PO $msg or die $!;
709     }
710 }
711
712 sub access_distros () {
713     # Returns list of distros to try, in order
714     #
715     # We want to try:
716     #    0. `instead of' distro name(s) we have been pointed to
717     #    1. the access_quirk distro, if any
718     #    2a. the user's specified distro, or failing that  } basedistro
719     #    2b. the distro calculated from the suite          }
720     my @l = access_basedistro();
721
722     my (undef,$quirkdistro) = access_quirk();
723     unshift @l, $quirkdistro;
724     unshift @l, $instead_distro;
725     @l = grep { defined } @l;
726
727     if (access_forpush()) {
728         @l = map { ("$_/push", $_) } @l;
729     }
730     @l;
731 }
732
733 sub access_cfg_cfgs (@) {
734     my (@keys) = @_;
735     my @cfgs;
736     # The nesting of these loops determines the search order.  We put
737     # the key loop on the outside so that we search all the distros
738     # for each key, before going on to the next key.  That means that
739     # if access_cfg is called with a more specific, and then a less
740     # specific, key, an earlier distro can override the less specific
741     # without necessarily overriding any more specific keys.  (If the
742     # distro wants to override the more specific keys it can simply do
743     # so; whereas if we did the loop the other way around, it would be
744     # impossible to for an earlier distro to override a less specific
745     # key but not the more specific ones without restating the unknown
746     # values of the more specific keys.
747     my @realkeys;
748     my @rundef;
749     # We have to deal with RETURN-UNDEF specially, so that we don't
750     # terminate the search prematurely.
751     foreach (@keys) {
752         if (m/RETURN-UNDEF/) { push @rundef, $_; last; }
753         push @realkeys, $_
754     }
755     foreach my $d (access_distros()) {
756         push @cfgs, map { "dgit-distro.$d.$_" } @realkeys;
757     }
758     push @cfgs, map { "dgit.default.$_" } @realkeys;
759     push @cfgs, @rundef;
760     return @cfgs;
761 }
762
763 sub access_cfg (@) {
764     my (@keys) = @_;
765     my (@cfgs) = access_cfg_cfgs(@keys);
766     my $value = cfg(@cfgs);
767     return $value;
768 }
769
770 sub access_cfg_bool ($$) {
771     my ($def, @keys) = @_;
772     parse_cfg_bool($keys[0], $def, access_cfg(@keys, 'RETURN-UNDEF'));
773 }
774
775 sub string_to_ssh ($) {
776     my ($spec) = @_;
777     if ($spec =~ m/\s/) {
778         return qw(sh -ec), 'exec '.$spec.' "$@"', 'x';
779     } else {
780         return ($spec);
781     }
782 }
783
784 sub access_cfg_ssh () {
785     my $gitssh = access_cfg('ssh', 'RETURN-UNDEF');
786     if (!defined $gitssh) {
787         return @ssh;
788     } else {
789         return string_to_ssh $gitssh;
790     }
791 }
792
793 sub access_runeinfo ($) {
794     my ($info) = @_;
795     return ": dgit ".access_basedistro()." $info ;";
796 }
797
798 sub access_someuserhost ($) {
799     my ($some) = @_;
800     my $user = access_cfg("$some-user-force", 'RETURN-UNDEF');
801     defined($user) && length($user) or
802         $user = access_cfg("$some-user",'username');
803     my $host = access_cfg("$some-host");
804     return length($user) ? "$user\@$host" : $host;
805 }
806
807 sub access_gituserhost () {
808     return access_someuserhost('git');
809 }
810
811 sub access_giturl (;$) {
812     my ($optional) = @_;
813     my $url = access_cfg('git-url','RETURN-UNDEF');
814     my $suffix;
815     if (!length $url) {
816         my $proto = access_cfg('git-proto', 'RETURN-UNDEF');
817         return undef unless defined $proto;
818         $url =
819             $proto.
820             access_gituserhost().
821             access_cfg('git-path');
822     } else {
823         $suffix = access_cfg('git-url-suffix','RETURN-UNDEF');
824     }
825     $suffix //= '.git';
826     return "$url/$package$suffix";
827 }              
828
829 sub parsecontrolfh ($$;$) {
830     my ($fh, $desc, $allowsigned) = @_;
831     our $dpkgcontrolhash_noissigned;
832     my $c;
833     for (;;) {
834         my %opts = ('name' => $desc);
835         $opts{allow_pgp}= $allowsigned || !$dpkgcontrolhash_noissigned;
836         $c = Dpkg::Control::Hash->new(%opts);
837         $c->parse($fh,$desc) or die "parsing of $desc failed";
838         last if $allowsigned;
839         last if $dpkgcontrolhash_noissigned;
840         my $issigned= $c->get_option('is_pgp_signed');
841         if (!defined $issigned) {
842             $dpkgcontrolhash_noissigned= 1;
843             seek $fh, 0,0 or die "seek $desc: $!";
844         } elsif ($issigned) {
845             fail "control file $desc is (already) PGP-signed. ".
846                 " Note that dgit push needs to modify the .dsc and then".
847                 " do the signature itself";
848         } else {
849             last;
850         }
851     }
852     return $c;
853 }
854
855 sub parsecontrol {
856     my ($file, $desc) = @_;
857     my $fh = new IO::Handle;
858     open $fh, '<', $file or die "$file: $!";
859     my $c = parsecontrolfh($fh,$desc);
860     $fh->error and die $!;
861     close $fh;
862     return $c;
863 }
864
865 sub getfield ($$) {
866     my ($dctrl,$field) = @_;
867     my $v = $dctrl->{$field};
868     return $v if defined $v;
869     fail "missing field $field in ".$v->get_option('name');
870 }
871
872 sub parsechangelog {
873     my $c = Dpkg::Control::Hash->new();
874     my $p = new IO::Handle;
875     my @cmd = (qw(dpkg-parsechangelog), @_);
876     open $p, '-|', @cmd or die $!;
877     $c->parse($p);
878     $?=0; $!=0; close $p or failedcmd @cmd;
879     return $c;
880 }
881
882 sub commit_getclogp ($) {
883     # Returns the parsed changelog hashref for a particular commit
884     my ($objid) = @_;
885     our %commit_getclogp_memo;
886     my $memo = $commit_getclogp_memo{$objid};
887     return $memo if $memo;
888     mkpath '.git/dgit';
889     my $mclog = ".git/dgit/clog-$objid";
890     runcmd shell_cmd "exec >$mclog", @git, qw(cat-file blob),
891         "$objid:debian/changelog";
892     $commit_getclogp_memo{$objid} = parsechangelog("-l$mclog");
893 }
894
895 sub must_getcwd () {
896     my $d = getcwd();
897     defined $d or fail "getcwd failed: $!";
898     return $d;
899 }
900
901 our %rmad;
902
903 sub archive_query ($) {
904     my ($method) = @_;
905     my $query = access_cfg('archive-query','RETURN-UNDEF');
906     $query =~ s/^(\w+):// or badcfg "invalid archive-query method \`$query'";
907     my $proto = $1;
908     my $data = $'; #';
909     { no strict qw(refs); &{"${method}_${proto}"}($proto,$data); }
910 }
911
912 sub pool_dsc_subpath ($$) {
913     my ($vsn,$component) = @_; # $package is implict arg
914     my $prefix = substr($package, 0, $package =~ m/^l/ ? 4 : 1);
915     return "/pool/$component/$prefix/$package/".dscfn($vsn);
916 }
917
918 #---------- `ftpmasterapi' archive query method (nascent) ----------
919
920 sub archive_api_query_cmd ($) {
921     my ($subpath) = @_;
922     my @cmd = qw(curl -sS);
923     my $url = access_cfg('archive-query-url');
924     if ($url =~ m#^https://([-.0-9a-z]+)/#) {
925         my $host = $1;
926         my $keys = access_cfg('archive-query-tls-key','RETURN-UNDEF') //'';
927         foreach my $key (split /\:/, $keys) {
928             $key =~ s/\%HOST\%/$host/g;
929             if (!stat $key) {
930                 fail "for $url: stat $key: $!" unless $!==ENOENT;
931                 next;
932             }
933             fail "config requested specific TLS key but do not know".
934                 " how to get curl to use exactly that EE key ($key)";
935 #           push @cmd, "--cacert", $key, "--capath", "/dev/enoent";
936 #           # Sadly the above line does not work because of changes
937 #           # to gnutls.   The real fix for #790093 may involve
938 #           # new curl options.
939             last;
940         }
941         # Fixing #790093 properly will involve providing a value
942         # for this on clients.
943         my $kargs = access_cfg('archive-query-tls-curl-ca-args','RETURN-UNDEF');
944         push @cmd, split / /, $kargs if defined $kargs;
945     }
946     push @cmd, $url.$subpath;
947     return @cmd;
948 }
949
950 sub api_query ($$) {
951     use JSON;
952     my ($data, $subpath) = @_;
953     badcfg "ftpmasterapi archive query method takes no data part"
954         if length $data;
955     my @cmd = archive_api_query_cmd($subpath);
956     my $json = cmdoutput @cmd;
957     return decode_json($json);
958 }
959
960 sub canonicalise_suite_ftpmasterapi () {
961     my ($proto,$data) = @_;
962     my $suites = api_query($data, 'suites');
963     my @matched;
964     foreach my $entry (@$suites) {
965         next unless grep { 
966             my $v = $entry->{$_};
967             defined $v && $v eq $isuite;
968         } qw(codename name);
969         push @matched, $entry;
970     }
971     fail "unknown suite $isuite" unless @matched;
972     my $cn;
973     eval {
974         @matched==1 or die "multiple matches for suite $isuite\n";
975         $cn = "$matched[0]{codename}";
976         defined $cn or die "suite $isuite info has no codename\n";
977         $cn =~ m/^$suite_re$/ or die "suite $isuite maps to bad codename\n";
978     };
979     die "bad ftpmaster api response: $@\n".Dumper(\@matched)
980         if length $@;
981     return $cn;
982 }
983
984 sub archive_query_ftpmasterapi () {
985     my ($proto,$data) = @_;
986     my $info = api_query($data, "dsc_in_suite/$isuite/$package");
987     my @rows;
988     my $digester = Digest::SHA->new(256);
989     foreach my $entry (@$info) {
990         eval {
991             my $vsn = "$entry->{version}";
992             my ($ok,$msg) = version_check $vsn;
993             die "bad version: $msg\n" unless $ok;
994             my $component = "$entry->{component}";
995             $component =~ m/^$component_re$/ or die "bad component";
996             my $filename = "$entry->{filename}";
997             $filename && $filename !~ m#[^-+:._~0-9a-zA-Z/]|^[/.]|/[/.]#
998                 or die "bad filename";
999             my $sha256sum = "$entry->{sha256sum}";
1000             $sha256sum =~ m/^[0-9a-f]+$/ or die "bad sha256sum";
1001             push @rows, [ $vsn, "/pool/$component/$filename",
1002                           $digester, $sha256sum ];
1003         };
1004         die "bad ftpmaster api response: $@\n".Dumper($entry)
1005             if length $@;
1006     }
1007     @rows = sort { -version_compare($a->[0],$b->[0]) } @rows;
1008     return @rows;
1009 }
1010
1011 #---------- `madison' archive query method ----------
1012
1013 sub archive_query_madison {
1014     return map { [ @$_[0..1] ] } madison_get_parse(@_);
1015 }
1016
1017 sub madison_get_parse {
1018     my ($proto,$data) = @_;
1019     die unless $proto eq 'madison';
1020     if (!length $data) {
1021         $data= access_cfg('madison-distro','RETURN-UNDEF');
1022         $data //= access_basedistro();
1023     }
1024     $rmad{$proto,$data,$package} ||= cmdoutput
1025         qw(rmadison -asource),"-s$isuite","-u$data",$package;
1026     my $rmad = $rmad{$proto,$data,$package};
1027
1028     my @out;
1029     foreach my $l (split /\n/, $rmad) {
1030         $l =~ m{^ \s*( [^ \t|]+ )\s* \|
1031                   \s*( [^ \t|]+ )\s* \|
1032                   \s*( [^ \t|/]+ )(?:/([^ \t|/]+))? \s* \|
1033                   \s*( [^ \t|]+ )\s* }x or die "$rmad ?";
1034         $1 eq $package or die "$rmad $package ?";
1035         my $vsn = $2;
1036         my $newsuite = $3;
1037         my $component;
1038         if (defined $4) {
1039             $component = $4;
1040         } else {
1041             $component = access_cfg('archive-query-default-component');
1042         }
1043         $5 eq 'source' or die "$rmad ?";
1044         push @out, [$vsn,pool_dsc_subpath($vsn,$component),$newsuite];
1045     }
1046     return sort { -version_compare($a->[0],$b->[0]); } @out;
1047 }
1048
1049 sub canonicalise_suite_madison {
1050     # madison canonicalises for us
1051     my @r = madison_get_parse(@_);
1052     @r or fail
1053         "unable to canonicalise suite using package $package".
1054         " which does not appear to exist in suite $isuite;".
1055         " --existing-package may help";
1056     return $r[0][2];
1057 }
1058
1059 #---------- `sshpsql' archive query method ----------
1060
1061 sub sshpsql ($$$) {
1062     my ($data,$runeinfo,$sql) = @_;
1063     if (!length $data) {
1064         $data= access_someuserhost('sshpsql').':'.
1065             access_cfg('sshpsql-dbname');
1066     }
1067     $data =~ m/:/ or badcfg "invalid sshpsql method string \`$data'";
1068     my ($userhost,$dbname) = ($`,$'); #';
1069     my @rows;
1070     my @cmd = (access_cfg_ssh, $userhost,
1071                access_runeinfo("ssh-psql $runeinfo").
1072                " export LC_MESSAGES=C; export LC_CTYPE=C;".
1073                " ".shellquote qw(psql -A), $dbname, qw(-c), $sql);
1074     debugcmd "|",@cmd;
1075     open P, "-|", @cmd or die $!;
1076     while (<P>) {
1077         chomp or die;
1078         printdebug(">|$_|\n");
1079         push @rows, $_;
1080     }
1081     $!=0; $?=0; close P or failedcmd @cmd;
1082     @rows or die;
1083     my $nrows = pop @rows;
1084     $nrows =~ s/^\((\d+) rows?\)$/$1/ or die "$nrows ?";
1085     @rows == $nrows+1 or die "$nrows ".(scalar @rows)." ?";
1086     @rows = map { [ split /\|/, $_ ] } @rows;
1087     my $ncols = scalar @{ shift @rows };
1088     die if grep { scalar @$_ != $ncols } @rows;
1089     return @rows;
1090 }
1091
1092 sub sql_injection_check {
1093     foreach (@_) { die "$_ $& ?" if m{[^-+=:_.,/0-9a-zA-Z]}; }
1094 }
1095
1096 sub archive_query_sshpsql ($$) {
1097     my ($proto,$data) = @_;
1098     sql_injection_check $isuite, $package;
1099     my @rows = sshpsql($data, "archive-query $isuite $package", <<END);
1100         SELECT source.version, component.name, files.filename, files.sha256sum
1101           FROM source
1102           JOIN src_associations ON source.id = src_associations.source
1103           JOIN suite ON suite.id = src_associations.suite
1104           JOIN dsc_files ON dsc_files.source = source.id
1105           JOIN files_archive_map ON files_archive_map.file_id = dsc_files.file
1106           JOIN component ON component.id = files_archive_map.component_id
1107           JOIN files ON files.id = dsc_files.file
1108          WHERE ( suite.suite_name='$isuite' OR suite.codename='$isuite' )
1109            AND source.source='$package'
1110            AND files.filename LIKE '%.dsc';
1111 END
1112     @rows = sort { -version_compare($a->[0],$b->[0]) } @rows;
1113     my $digester = Digest::SHA->new(256);
1114     @rows = map {
1115         my ($vsn,$component,$filename,$sha256sum) = @$_;
1116         [ $vsn, "/pool/$component/$filename",$digester,$sha256sum ];
1117     } @rows;
1118     return @rows;
1119 }
1120
1121 sub canonicalise_suite_sshpsql ($$) {
1122     my ($proto,$data) = @_;
1123     sql_injection_check $isuite;
1124     my @rows = sshpsql($data, "canonicalise-suite $isuite", <<END);
1125         SELECT suite.codename
1126           FROM suite where suite_name='$isuite' or codename='$isuite';
1127 END
1128     @rows = map { $_->[0] } @rows;
1129     fail "unknown suite $isuite" unless @rows;
1130     die "ambiguous $isuite: @rows ?" if @rows>1;
1131     return $rows[0];
1132 }
1133
1134 #---------- `dummycat' archive query method ----------
1135
1136 sub canonicalise_suite_dummycat ($$) {
1137     my ($proto,$data) = @_;
1138     my $dpath = "$data/suite.$isuite";
1139     if (!open C, "<", $dpath) {
1140         $!==ENOENT or die "$dpath: $!";
1141         printdebug "dummycat canonicalise_suite $isuite $dpath ENOENT\n";
1142         return $isuite;
1143     }
1144     $!=0; $_ = <C>;
1145     chomp or die "$dpath: $!";
1146     close C;
1147     printdebug "dummycat canonicalise_suite $isuite $dpath = $_\n";
1148     return $_;
1149 }
1150
1151 sub archive_query_dummycat ($$) {
1152     my ($proto,$data) = @_;
1153     canonicalise_suite();
1154     my $dpath = "$data/package.$csuite.$package";
1155     if (!open C, "<", $dpath) {
1156         $!==ENOENT or die "$dpath: $!";
1157         printdebug "dummycat query $csuite $package $dpath ENOENT\n";
1158         return ();
1159     }
1160     my @rows;
1161     while (<C>) {
1162         next if m/^\#/;
1163         next unless m/\S/;
1164         die unless chomp;
1165         printdebug "dummycat query $csuite $package $dpath | $_\n";
1166         my @row = split /\s+/, $_;
1167         @row==2 or die "$dpath: $_ ?";
1168         push @rows, \@row;
1169     }
1170     C->error and die "$dpath: $!";
1171     close C;
1172     return sort { -version_compare($a->[0],$b->[0]); } @rows;
1173 }
1174
1175 #---------- tag format handling ----------
1176
1177 sub access_cfg_tagformats () {
1178     split /\,/, access_cfg('dgit-tag-format');
1179 }
1180
1181 sub need_tagformat ($$) {
1182     my ($fmt, $why) = @_;
1183     fail "need to use tag format $fmt ($why) but also need".
1184         " to use tag format $tagformat_want->[0] ($tagformat_want->[1])".
1185         " - no way to proceed"
1186         if $tagformat_want && $tagformat_want->[0] ne $fmt;
1187     $tagformat_want = [$fmt, $why, $tagformat_want->[2] // 0];
1188 }
1189
1190 sub select_tagformat () {
1191     # sets $tagformatfn
1192     return if $tagformatfn && !$tagformat_want;
1193     die 'bug' if $tagformatfn && $tagformat_want;
1194     # ... $tagformat_want assigned after previous select_tagformat
1195
1196     my (@supported) = grep { $_ ne 'maint' } access_cfg_tagformats();
1197     printdebug "select_tagformat supported @supported\n";
1198
1199     $tagformat_want //= [ $supported[0], "distro access configuration", 0 ];
1200     printdebug "select_tagformat specified @$tagformat_want\n";
1201
1202     my ($fmt,$why,$override) = @$tagformat_want;
1203
1204     fail "target distro supports tag formats @supported".
1205         " but have to use $fmt ($why)"
1206         unless $override
1207             or grep { $_ eq $fmt } @supported;
1208
1209     $tagformat_want = undef;
1210     $tagformat = $fmt;
1211     $tagformatfn = ${*::}{"debiantag_$fmt"};
1212
1213     fail "trying to use unknown tag format \`$fmt' ($why) !"
1214         unless $tagformatfn;
1215 }
1216
1217 #---------- archive query entrypoints and rest of program ----------
1218
1219 sub canonicalise_suite () {
1220     return if defined $csuite;
1221     fail "cannot operate on $isuite suite" if $isuite eq 'UNRELEASED';
1222     $csuite = archive_query('canonicalise_suite');
1223     if ($isuite ne $csuite) {
1224         progress "canonical suite name for $isuite is $csuite";
1225     }
1226 }
1227
1228 sub get_archive_dsc () {
1229     canonicalise_suite();
1230     my @vsns = archive_query('archive_query');
1231     foreach my $vinfo (@vsns) {
1232         my ($vsn,$subpath,$digester,$digest) = @$vinfo;
1233         $dscurl = access_cfg('mirror').$subpath;
1234         $dscdata = url_get($dscurl);
1235         if (!$dscdata) {
1236             $skew_warning_vsn = $vsn if !defined $skew_warning_vsn;
1237             next;
1238         }
1239         if ($digester) {
1240             $digester->reset();
1241             $digester->add($dscdata);
1242             my $got = $digester->hexdigest();
1243             $got eq $digest or
1244                 fail "$dscurl has hash $got but".
1245                     " archive told us to expect $digest";
1246         }
1247         my $dscfh = new IO::File \$dscdata, '<' or die $!;
1248         printdebug Dumper($dscdata) if $debuglevel>1;
1249         $dsc = parsecontrolfh($dscfh,$dscurl,1);
1250         printdebug Dumper($dsc) if $debuglevel>1;
1251         my $fmt = getfield $dsc, 'Format';
1252         fail "unsupported source format $fmt, sorry" unless $format_ok{$fmt};
1253         $dsc_checked = !!$digester;
1254         printdebug "get_archive_dsc: Version ".(getfield $dsc, 'Version')."\n";
1255         return;
1256     }
1257     $dsc = undef;
1258     printdebug "get_archive_dsc: nothing in archive, returning undef\n";
1259 }
1260
1261 sub check_for_git ();
1262 sub check_for_git () {
1263     # returns 0 or 1
1264     my $how = access_cfg('git-check');
1265     if ($how eq 'ssh-cmd') {
1266         my @cmd =
1267             (access_cfg_ssh, access_gituserhost(),
1268              access_runeinfo("git-check $package").
1269              " set -e; cd ".access_cfg('git-path').";".
1270              " if test -d $package.git; then echo 1; else echo 0; fi");
1271         my $r= cmdoutput @cmd;
1272         if (defined $r and $r =~ m/^divert (\w+)$/) {
1273             my $divert=$1;
1274             my ($usedistro,) = access_distros();
1275             # NB that if we are pushing, $usedistro will be $distro/push
1276             $instead_distro= cfg("dgit-distro.$usedistro.diverts.$divert");
1277             $instead_distro =~ s{^/}{ access_basedistro()."/" }e;
1278             progress "diverting to $divert (using config for $instead_distro)";
1279             return check_for_git();
1280         }
1281         failedcmd @cmd unless defined $r and $r =~ m/^[01]$/;
1282         return $r+0;
1283     } elsif ($how eq 'url') {
1284         my $prefix = access_cfg('git-check-url','git-url');
1285         my $suffix = access_cfg('git-check-suffix','git-suffix',
1286                                 'RETURN-UNDEF') // '.git';
1287         my $url = "$prefix/$package$suffix";
1288         my @cmd = (qw(curl -sS -I), $url);
1289         my $result = cmdoutput @cmd;
1290         $result =~ s/^\S+ 200 .*\n\r?\n//;
1291         # curl -sS -I with https_proxy prints
1292         # HTTP/1.0 200 Connection established
1293         $result =~ m/^\S+ (404|200) /s or
1294             fail "unexpected results from git check query - ".
1295                 Dumper($prefix, $result);
1296         my $code = $1;
1297         if ($code eq '404') {
1298             return 0;
1299         } elsif ($code eq '200') {
1300             return 1;
1301         } else {
1302             die;
1303         }
1304     } elsif ($how eq 'true') {
1305         return 1;
1306     } elsif ($how eq 'false') {
1307         return 0;
1308     } else {
1309         badcfg "unknown git-check \`$how'";
1310     }
1311 }
1312
1313 sub create_remote_git_repo () {
1314     my $how = access_cfg('git-create');
1315     if ($how eq 'ssh-cmd') {
1316         runcmd_ordryrun
1317             (access_cfg_ssh, access_gituserhost(),
1318              access_runeinfo("git-create $package").
1319              "set -e; cd ".access_cfg('git-path').";".
1320              " cp -a _template $package.git");
1321     } elsif ($how eq 'true') {
1322         # nothing to do
1323     } else {
1324         badcfg "unknown git-create \`$how'";
1325     }
1326 }
1327
1328 our ($dsc_hash,$lastpush_mergeinput);
1329
1330 our $ud = '.git/dgit/unpack';
1331
1332 sub prep_ud (;$) {
1333     my ($d) = @_;
1334     $d //= $ud;
1335     rmtree($d);
1336     mkpath '.git/dgit';
1337     mkdir $d or die $!;
1338 }
1339
1340 sub mktree_in_ud_here () {
1341     runcmd qw(git init -q);
1342     rmtree('.git/objects');
1343     symlink '../../../../objects','.git/objects' or die $!;
1344 }
1345
1346 sub git_write_tree () {
1347     my $tree = cmdoutput @git, qw(write-tree);
1348     $tree =~ m/^\w+$/ or die "$tree ?";
1349     return $tree;
1350 }
1351
1352 sub remove_stray_gits () {
1353     my @gitscmd = qw(find -name .git -prune -print0);
1354     debugcmd "|",@gitscmd;
1355     open GITS, "-|", @gitscmd or die $!;
1356     {
1357         local $/="\0";
1358         while (<GITS>) {
1359             chomp or die;
1360             print STDERR "$us: warning: removing from source package: ",
1361                 (messagequote $_), "\n";
1362             rmtree $_;
1363         }
1364     }
1365     $!=0; $?=0; close GITS or failedcmd @gitscmd;
1366 }
1367
1368 sub mktree_in_ud_from_only_subdir () {
1369     # changes into the subdir
1370     my (@dirs) = <*/.>;
1371     die "@dirs ?" unless @dirs==1;
1372     $dirs[0] =~ m#^([^/]+)/\.$# or die;
1373     my $dir = $1;
1374     changedir $dir;
1375
1376     remove_stray_gits();
1377     mktree_in_ud_here();
1378     my ($format, $fopts) = get_source_format();
1379     if (madformat($format)) {
1380         rmtree '.pc';
1381     }
1382     runcmd @git, qw(add -Af);
1383     my $tree=git_write_tree();
1384     return ($tree,$dir);
1385 }
1386
1387 sub dsc_files_info () {
1388     foreach my $csumi (['Checksums-Sha256','Digest::SHA', 'new(256)'],
1389                        ['Checksums-Sha1',  'Digest::SHA', 'new(1)'],
1390                        ['Files',           'Digest::MD5', 'new()']) {
1391         my ($fname, $module, $method) = @$csumi;
1392         my $field = $dsc->{$fname};
1393         next unless defined $field;
1394         eval "use $module; 1;" or die $@;
1395         my @out;
1396         foreach (split /\n/, $field) {
1397             next unless m/\S/;
1398             m/^(\w+) (\d+) (\S+)$/ or
1399                 fail "could not parse .dsc $fname line \`$_'";
1400             my $digester = eval "$module"."->$method;" or die $@;
1401             push @out, {
1402                 Hash => $1,
1403                 Bytes => $2,
1404                 Filename => $3,
1405                 Digester => $digester,
1406             };
1407         }
1408         return @out;
1409     }
1410     fail "missing any supported Checksums-* or Files field in ".
1411         $dsc->get_option('name');
1412 }
1413
1414 sub dsc_files () {
1415     map { $_->{Filename} } dsc_files_info();
1416 }
1417
1418 sub is_orig_file ($;$) {
1419     local ($_) = $_[0];
1420     my $base = $_[1];
1421     m/\.orig(?:-\w+)?\.tar\.\w+$/ or return 0;
1422     defined $base or return 1;
1423     return $` eq $base;
1424 }
1425
1426 sub make_commit ($) {
1427     my ($file) = @_;
1428     return cmdoutput @git, qw(hash-object -w -t commit), $file;
1429 }
1430
1431 sub clogp_authline ($) {
1432     my ($clogp) = @_;
1433     my $author = getfield $clogp, 'Maintainer';
1434     $author =~ s#,.*##ms;
1435     my $date = cmdoutput qw(date), '+%s %z', qw(-d), getfield($clogp,'Date');
1436     my $authline = "$author $date";
1437     $authline =~ m/$git_authline_re/o or
1438         fail "unexpected commit author line format \`$authline'".
1439         " (was generated from changelog Maintainer field)";
1440     return ($1,$2,$3) if wantarray;
1441     return $authline;
1442 }
1443
1444 sub vendor_patches_distro ($$) {
1445     my ($checkdistro, $what) = @_;
1446     return unless defined $checkdistro;
1447
1448     my $series = "debian/patches/\L$checkdistro\E.series";
1449     printdebug "checking for vendor-specific $series ($what)\n";
1450
1451     if (!open SERIES, "<", $series) {
1452         die "$series $!" unless $!==ENOENT;
1453         return;
1454     }
1455     while (<SERIES>) {
1456         next unless m/\S/;
1457         next if m/^\s+\#/;
1458
1459         print STDERR <<END;
1460
1461 Unfortunately, this source package uses a feature of dpkg-source where
1462 the same source package unpacks to different source code on different
1463 distros.  dgit cannot safely operate on such packages on affected
1464 distros, because the meaning of source packages is not stable.
1465
1466 Please ask the distro/maintainer to remove the distro-specific series
1467 files and use a different technique (if necessary, uploading actually
1468 different packages, if different distros are supposed to have
1469 different code).
1470
1471 END
1472         fail "Found active distro-specific series file for".
1473             " $checkdistro ($what): $series, cannot continue";
1474     }
1475     die "$series $!" if SERIES->error;
1476     close SERIES;
1477 }
1478
1479 sub check_for_vendor_patches () {
1480     # This dpkg-source feature doesn't seem to be documented anywhere!
1481     # But it can be found in the changelog (reformatted):
1482
1483     #   commit  4fa01b70df1dc4458daee306cfa1f987b69da58c
1484     #   Author: Raphael Hertzog <hertzog@debian.org>
1485     #   Date: Sun  Oct  3  09:36:48  2010 +0200
1486
1487     #   dpkg-source: correctly create .pc/.quilt_series with alternate
1488     #   series files
1489     #   
1490     #   If you have debian/patches/ubuntu.series and you were
1491     #   unpacking the source package on ubuntu, quilt was still
1492     #   directed to debian/patches/series instead of
1493     #   debian/patches/ubuntu.series.
1494     #   
1495     #   debian/changelog                        |    3 +++
1496     #   scripts/Dpkg/Source/Package/V3/quilt.pm |    4 +++-
1497     #   2 files changed, 6 insertions(+), 1 deletion(-)
1498
1499     use Dpkg::Vendor;
1500     vendor_patches_distro($ENV{DEB_VENDOR}, "DEB_VENDOR");
1501     vendor_patches_distro(Dpkg::Vendor::get_current_vendor(),
1502                          "Dpkg::Vendor \`current vendor'");
1503     vendor_patches_distro(access_basedistro(),
1504                           "distro being accessed");
1505 }
1506
1507 sub generate_commits_from_dsc () {
1508     # See big comment in fetch_from_archive, below.
1509     prep_ud();
1510     changedir $ud;
1511
1512     foreach my $fi (dsc_files_info()) {
1513         my $f = $fi->{Filename};
1514         die "$f ?" if $f =~ m#/|^\.|\.dsc$|\.tmp$#;
1515
1516         link_ltarget "../../../$f", $f
1517             or $!==&ENOENT
1518             or die "$f $!";
1519
1520         complete_file_from_dsc('.', $fi)
1521             or next;
1522
1523         if (is_orig_file($f)) {
1524             link $f, "../../../../$f"
1525                 or $!==&EEXIST
1526                 or die "$f $!";
1527         }
1528     }
1529
1530     my $dscfn = "$package.dsc";
1531
1532     open D, ">", $dscfn or die "$dscfn: $!";
1533     print D $dscdata or die "$dscfn: $!";
1534     close D or die "$dscfn: $!";
1535     my @cmd = qw(dpkg-source);
1536     push @cmd, '--no-check' if $dsc_checked;
1537     push @cmd, qw(-x --), $dscfn;
1538     runcmd @cmd;
1539
1540     my ($tree,$dir) = mktree_in_ud_from_only_subdir();
1541     check_for_vendor_patches() if madformat($dsc->{format});
1542     runcmd qw(sh -ec), 'dpkg-parsechangelog >../changelog.tmp';
1543     my $clogp = parsecontrol('../changelog.tmp',"commit's changelog");
1544     my $authline = clogp_authline $clogp;
1545     my $changes = getfield $clogp, 'Changes';
1546     open C, ">../commit.tmp" or die $!;
1547     print C <<END or die $!;
1548 tree $tree
1549 author $authline
1550 committer $authline
1551
1552 $changes
1553
1554 # imported from the archive
1555 END
1556     close C or die $!;
1557     my $rawimport_hash = make_commit qw(../commit.tmp);
1558     my $cversion = getfield $clogp, 'Version';
1559     my $rawimport_mergeinput = {
1560         Commit => $rawimport_hash,
1561         Info => "Import of source package",
1562     };
1563     my @output = ($rawimport_mergeinput);
1564     progress "synthesised git commit from .dsc $cversion";
1565     if ($lastpush_mergeinput) {
1566         my $oldclogp = mergeinfo_getclogp($lastpush_mergeinput);
1567         my $oversion = getfield $oldclogp, 'Version';
1568         my $vcmp =
1569             version_compare($oversion, $cversion);
1570         if ($vcmp < 0) {
1571             @output = ($rawimport_mergeinput, $lastpush_mergeinput,
1572                 { Message => <<END, ReverseParents => 1 });
1573 Record $package ($cversion) in archive suite $csuite
1574 END
1575         } elsif ($vcmp > 0) {
1576             print STDERR <<END or die $!;
1577
1578 Version actually in archive:   $cversion (older)
1579 Last version pushed with dgit: $oversion (newer or same)
1580 $later_warning_msg
1581 END
1582             @output = $lastpush_mergeinput;
1583         } else {
1584             # Same version.  Use what's in the server git branch,
1585             # discarding our own import.  (This could happen if the
1586             # server automatically imports all packages into git.)
1587             @output = $lastpush_mergeinput;
1588         }
1589     }
1590     changedir '../../../..';
1591     rmtree($ud);
1592     return @output;
1593 }
1594
1595 sub complete_file_from_dsc ($$) {
1596     our ($dstdir, $fi) = @_;
1597     # Ensures that we have, in $dir, the file $fi, with the correct
1598     # contents.  (Downloading it from alongside $dscurl if necessary.)
1599
1600     my $f = $fi->{Filename};
1601     my $tf = "$dstdir/$f";
1602     my $downloaded = 0;
1603
1604     if (stat_exists $tf) {
1605         progress "using existing $f";
1606     } else {
1607         my $furl = $dscurl;
1608         $furl =~ s{/[^/]+$}{};
1609         $furl .= "/$f";
1610         die "$f ?" unless $f =~ m/^\Q${package}\E_/;
1611         die "$f ?" if $f =~ m#/#;
1612         runcmd_ordryrun_local @curl,qw(-o),$tf,'--',"$furl";
1613         return 0 if !act_local();
1614         $downloaded = 1;
1615     }
1616
1617     open F, "<", "$tf" or die "$tf: $!";
1618     $fi->{Digester}->reset();
1619     $fi->{Digester}->addfile(*F);
1620     F->error and die $!;
1621     my $got = $fi->{Digester}->hexdigest();
1622     $got eq $fi->{Hash} or
1623         fail "file $f has hash $got but .dsc".
1624             " demands hash $fi->{Hash} ".
1625             ($downloaded ? "(got wrong file from archive!)"
1626              : "(perhaps you should delete this file?)");
1627
1628     return 1;
1629 }
1630
1631 sub ensure_we_have_orig () {
1632     foreach my $fi (dsc_files_info()) {
1633         my $f = $fi->{Filename};
1634         next unless is_orig_file($f);
1635         complete_file_from_dsc('..', $fi)
1636             or next;
1637     }
1638 }
1639
1640 sub git_fetch_us () {
1641     # Want to fetch only what we are going to use, unless
1642     # deliberately-not-ff, in which case we must fetch everything.
1643
1644     my @specs = deliberately_not_fast_forward ? qw(tags/*) :
1645         map { "tags/$_" } debiantags('*',access_basedistro);
1646     push @specs, server_branch($csuite);
1647     push @specs, qw(heads/*) if deliberately_not_fast_forward;
1648
1649     # This is rather miserable:
1650     # When git-fetch --prune is passed a fetchspec ending with a *,
1651     # it does a plausible thing.  If there is no * then:
1652     # - it matches subpaths too, even if the supplied refspec
1653     #   starts refs, and behaves completely madly if the source
1654     #   has refs/refs/something.  (See, for example, Debian #NNNN.)
1655     # - if there is no matching remote ref, it bombs out the whole
1656     #   fetch.
1657     # We want to fetch a fixed ref, and we don't know in advance
1658     # if it exists, so this is not suitable.
1659     #
1660     # Our workaround is to use git-ls-remote.  git-ls-remote has its
1661     # own qairks.  Notably, it has the absurd multi-tail-matching
1662     # behaviour: git-ls-remote R refs/foo can report refs/foo AND
1663     # refs/refs/foo etc.
1664     #
1665     # Also, we want an idempotent snapshot, but we have to make two
1666     # calls to the remote: one to git-ls-remote and to git-fetch.  The
1667     # solution is use git-ls-remote to obtain a target state, and
1668     # git-fetch to try to generate it.  If we don't manage to generate
1669     # the target state, we try again.
1670
1671     my $specre = join '|', map {
1672         my $x = $_;
1673         $x =~ s/\W/\\$&/g;
1674         $x =~ s/\\\*$/.*/;
1675         "(?:refs/$x)";
1676     } @specs;
1677     printdebug "git_fetch_us specre=$specre\n";
1678     my $wanted_rref = sub {
1679         local ($_) = @_;
1680         return m/^(?:$specre)$/o;
1681     };
1682
1683     my $fetch_iteration = 0;
1684     FETCH_ITERATION:
1685     for (;;) {
1686         if (++$fetch_iteration > 10) {
1687             fail "too many iterations trying to get sane fetch!";
1688         }
1689
1690         my @look = map { "refs/$_" } @specs;
1691         my @lcmd = (@git, qw(ls-remote -q --refs), access_giturl(), @look);
1692         debugcmd "|",@lcmd;
1693
1694         my %wantr;
1695         open GITLS, "-|", @lcmd or die $!;
1696         while (<GITLS>) {
1697             printdebug "=> ", $_;
1698             m/^(\w+)\s+(\S+)\n/ or die "ls-remote $_ ?";
1699             my ($objid,$rrefname) = ($1,$2);
1700             if (!$wanted_rref->($rrefname)) {
1701                 print STDERR <<END;
1702 warning: git-ls-remote @look reported $rrefname; this is silly, ignoring it.
1703 END
1704                 next;
1705             }
1706             $wantr{$rrefname} = $objid;
1707         }
1708         $!=0; $?=0;
1709         close GITLS or failedcmd @lcmd;
1710
1711         # OK, now %want is exactly what we want for refs in @specs
1712         my @fspecs = map {
1713             return () if !m/\*$/ && !exists $wantr{"refs/$_"};
1714             "+refs/$_:".lrfetchrefs."/$_";
1715         } @specs;
1716
1717         my @fcmd = (@git, qw(fetch -p -n -q), access_giturl(), @fspecs);
1718         runcmd_ordryrun_local @git, qw(fetch -p -n -q), access_giturl(),
1719             @fspecs;
1720
1721         %lrfetchrefs_f = ();
1722         my %objgot;
1723
1724         git_for_each_ref(lrfetchrefs, sub {
1725             my ($objid,$objtype,$lrefname,$reftail) = @_;
1726             $lrfetchrefs_f{$lrefname} = $objid;
1727             $objgot{$objid} = 1;
1728         });
1729
1730         foreach my $lrefname (sort keys %lrfetchrefs_f) {
1731             my $rrefname = 'refs'.substr($lrefname, length lrfetchrefs);
1732             if (!exists $wantr{$rrefname}) {
1733                 if ($wanted_rref->($rrefname)) {
1734                     printdebug <<END;
1735 git-fetch @fspecs created $lrefname which git-ls-remote @look didn't list.
1736 END
1737                 } else {
1738                     print STDERR <<END
1739 warning: git-fetch @fspecs created $lrefname; this is silly, deleting it.
1740 END
1741                 }
1742                 runcmd_ordryrun_local @git, qw(update-ref -d), $lrefname;
1743                 delete $lrfetchrefs_f{$lrefname};
1744                 next;
1745             }
1746         }
1747         foreach my $rrefname (sort keys %wantr) {
1748             my $lrefname = lrfetchrefs.substr($rrefname, 4);
1749             my $got = $lrfetchrefs_f{$lrefname} // '<none>';
1750             my $want = $wantr{$rrefname};
1751             next if $got eq $want;
1752             if (!defined $objgot{$want}) {
1753                 print STDERR <<END;
1754 warning: git-ls-remote suggests we want $lrefname
1755 warning:  and it should refer to $want
1756 warning:  but git-fetch didn't fetch that object to any relevant ref.
1757 warning:  This may be due to a race with someone updating the server.
1758 warning:  Will try again...
1759 END
1760                 next FETCH_ITERATION;
1761             }
1762             printdebug <<END;
1763 git-fetch @fspecs made $lrefname=$got but want git-ls-remote @look says $want
1764 END
1765             runcmd_ordryrun_local @git, qw(update-ref -m),
1766                 "dgit fetch git-fetch fixup", $lrefname, $want;
1767             $lrfetchrefs_f{$lrefname} = $want;
1768         }
1769         last;
1770     }
1771     printdebug "git_fetch_us: git-fetch --no-insane emulation complete\n",
1772         Dumper(\%lrfetchrefs_f);
1773
1774     my %here;
1775     my @tagpats = debiantags('*',access_basedistro);
1776
1777     git_for_each_ref([map { "refs/tags/$_" } @tagpats], sub {
1778         my ($objid,$objtype,$fullrefname,$reftail) = @_;
1779         printdebug "currently $fullrefname=$objid\n";
1780         $here{$fullrefname} = $objid;
1781     });
1782     git_for_each_ref([map { lrfetchrefs."/tags/".$_ } @tagpats], sub {
1783         my ($objid,$objtype,$fullrefname,$reftail) = @_;
1784         my $lref = "refs".substr($fullrefname, length(lrfetchrefs));
1785         printdebug "offered $lref=$objid\n";
1786         if (!defined $here{$lref}) {
1787             my @upd = (@git, qw(update-ref), $lref, $objid, '');
1788             runcmd_ordryrun_local @upd;
1789             lrfetchref_used $fullrefname;
1790         } elsif ($here{$lref} eq $objid) {
1791             lrfetchref_used $fullrefname;
1792         } else {
1793             print STDERR \
1794                 "Not updateting $lref from $here{$lref} to $objid.\n";
1795         }
1796     });
1797 }
1798
1799 sub mergeinfo_getclogp ($) {
1800     # Ensures thit $mi->{Clogp} exists and returns it
1801     my ($mi) = @_;
1802     $mi->{Clogp} = commit_getclogp($mi->{Commit});
1803 }
1804
1805 sub mergeinfo_version ($) {
1806     return getfield( (mergeinfo_getclogp $_[0]), 'Version' );
1807 }
1808
1809 sub fetch_from_archive () {
1810     # Ensures that lrref() is what is actually in the archive, one way
1811     # or another, according to us - ie this client's
1812     # appropritaely-updated archive view.  Also returns the commit id.
1813     # If there is nothing in the archive, leaves lrref alone and
1814     # returns undef.  git_fetch_us must have already been called.
1815     get_archive_dsc();
1816
1817     if ($dsc) {
1818         foreach my $field (@ourdscfield) {
1819             $dsc_hash = $dsc->{$field};
1820             last if defined $dsc_hash;
1821         }
1822         if (defined $dsc_hash) {
1823             $dsc_hash =~ m/\w+/ or fail "invalid hash in .dsc \`$dsc_hash'";
1824             $dsc_hash = $&;
1825             progress "last upload to archive specified git hash";
1826         } else {
1827             progress "last upload to archive has NO git hash";
1828         }
1829     } else {
1830         progress "no version available from the archive";
1831     }
1832
1833     # If the archive's .dsc has a Dgit field, there are three
1834     # relevant git commitids we need to choose between and/or merge
1835     # together:
1836     #   1. $dsc_hash: the Dgit field from the archive
1837     #   2. $lastpush_hash: the suite branch on the dgit git server
1838     #   3. $lastfetch_hash: our local tracking brach for the suite
1839     #
1840     # These may all be distinct and need not be in any fast forward
1841     # relationship:
1842     #
1843     # If the dsc was pushed to this suite, then the server suite
1844     # branch will have been updated; but it might have been pushed to
1845     # a different suite and copied by the archive.  Conversely a more
1846     # recent version may have been pushed with dgit but not appeared
1847     # in the archive (yet).
1848     #
1849     # $lastfetch_hash may be awkward because archive imports
1850     # (particularly, imports of Dgit-less .dscs) are performed only as
1851     # needed on individual clients, so different clients may perform a
1852     # different subset of them - and these imports are only made
1853     # public during push.  So $lastfetch_hash may represent a set of
1854     # imports different to a subsequent upload by a different dgit
1855     # client.
1856     #
1857     # Our approach is as follows:
1858     #
1859     # As between $dsc_hash and $lastpush_hash: if $lastpush_hash is a
1860     # descendant of $dsc_hash, then it was pushed by a dgit user who
1861     # had based their work on $dsc_hash, so we should prefer it.
1862     # Otherwise, $dsc_hash was installed into this suite in the
1863     # archive other than by a dgit push, and (necessarily) after the
1864     # last dgit push into that suite (since a dgit push would have
1865     # been descended from the dgit server git branch); thus, in that
1866     # case, we prefer the archive's version (and produce a
1867     # pseudo-merge to overwrite the dgit server git branch).
1868     #
1869     # (If there is no Dgit field in the archive's .dsc then
1870     # generate_commit_from_dsc uses the version numbers to decide
1871     # whether the suite branch or the archive is newer.  If the suite
1872     # branch is newer it ignores the archive's .dsc; otherwise it
1873     # generates an import of the .dsc, and produces a pseudo-merge to
1874     # overwrite the suite branch with the archive contents.)
1875     #
1876     # The outcome of that part of the algorithm is the `public view',
1877     # and is same for all dgit clients: it does not depend on any
1878     # unpublished history in the local tracking branch.
1879     #
1880     # As between the public view and the local tracking branch: The
1881     # local tracking branch is only updated by dgit fetch, and
1882     # whenever dgit fetch runs it includes the public view in the
1883     # local tracking branch.  Therefore if the public view is not
1884     # descended from the local tracking branch, the local tracking
1885     # branch must contain history which was imported from the archive
1886     # but never pushed; and, its tip is now out of date.  So, we make
1887     # a pseudo-merge to overwrite the old imports and stitch the old
1888     # history in.
1889     #
1890     # Finally: we do not necessarily reify the public view (as
1891     # described above).  This is so that we do not end up stacking two
1892     # pseudo-merges.  So what we actually do is figure out the inputs
1893     # to any public view psuedo-merge and put them in @mergeinputs.
1894
1895     my @mergeinputs;
1896     # $mergeinputs[]{Commit}
1897     # $mergeinputs[]{Info}
1898     # $mergeinputs[0] is the one whose tree we use
1899     # @mergeinputs is in the order we use in the actual commit)
1900     #
1901     # Also:
1902     # $mergeinputs[]{Message} is a commit message to use
1903     # $mergeinputs[]{ReverseParents} if def specifies that parent
1904     #                                list should be in opposite order
1905     # Such an entry has no Commit or Info.  It applies only when found
1906     # in the last entry.  (This ugliness is to support making
1907     # identical imports to previous dgit versions.)
1908
1909     my $lastpush_hash = git_get_ref(lrfetchref());
1910     printdebug "previous reference hash=$lastpush_hash\n";
1911     $lastpush_mergeinput = $lastpush_hash && {
1912         Commit => $lastpush_hash,
1913         Info => "dgit suite branch on dgit git server",
1914     };
1915
1916     my $lastfetch_hash = git_get_ref(lrref());
1917     printdebug "fetch_from_archive: lastfetch=$lastfetch_hash\n";
1918     my $lastfetch_mergeinput = $lastfetch_hash && {
1919         Commit => $lastfetch_hash,
1920         Info => "dgit client's archive history view",
1921     };
1922
1923     my $dsc_mergeinput = $dsc_hash && {
1924         Commit => $dsc_hash,
1925         Info => "Dgit field in .dsc from archive",
1926     };
1927
1928     my $cwd = getcwd();
1929     my $del_lrfetchrefs = sub {
1930         changedir $cwd;
1931         my $gur;
1932         printdebug "del_lrfetchrefs...\n";
1933         foreach my $fullrefname (sort keys %lrfetchrefs_d) {
1934             my $objid = $lrfetchrefs_d{$fullrefname};
1935             printdebug "del_lrfetchrefs: $objid $fullrefname\n";
1936             if (!$gur) {
1937                 $gur ||= new IO::Handle;
1938                 open $gur, "|-", qw(git update-ref --stdin) or die $!;
1939             }
1940             printf $gur "delete %s %s\n", $fullrefname, $objid;
1941         }
1942         if ($gur) {
1943             close $gur or failedcmd "git update-ref delete lrfetchrefs";
1944         }
1945     };
1946
1947     if (defined $dsc_hash) {
1948         fail "missing remote git history even though dsc has hash -".
1949             " could not find ref ".rref()." at ".access_giturl()
1950             unless $lastpush_hash;
1951         ensure_we_have_orig();
1952         if ($dsc_hash eq $lastpush_hash) {
1953             @mergeinputs = $dsc_mergeinput
1954         } elsif (is_fast_fwd($dsc_hash,$lastpush_hash)) {
1955             print STDERR <<END or die $!;
1956
1957 Git commit in archive is behind the last version allegedly pushed/uploaded.
1958 Commit referred to by archive: $dsc_hash
1959 Last version pushed with dgit: $lastpush_hash
1960 $later_warning_msg
1961 END
1962             @mergeinputs = ($lastpush_mergeinput);
1963         } else {
1964             # Archive has .dsc which is not a descendant of the last dgit
1965             # push.  This can happen if the archive moves .dscs about.
1966             # Just follow its lead.
1967             if (is_fast_fwd($lastpush_hash,$dsc_hash)) {
1968                 progress "archive .dsc names newer git commit";
1969                 @mergeinputs = ($dsc_mergeinput);
1970             } else {
1971                 progress "archive .dsc names other git commit, fixing up";
1972                 @mergeinputs = ($dsc_mergeinput, $lastpush_mergeinput);
1973             }
1974         }
1975     } elsif ($dsc) {
1976         @mergeinputs = generate_commits_from_dsc();
1977         # We have just done an import.  Now, our import algorithm might
1978         # have been improved.  But even so we do not want to generate
1979         # a new different import of the same package.  So if the
1980         # version numbers are the same, just use our existing version.
1981         # If the version numbers are different, the archive has changed
1982         # (perhaps, rewound).
1983         if ($lastfetch_mergeinput &&
1984             !version_compare( (mergeinfo_version $lastfetch_mergeinput),
1985                               (mergeinfo_version $mergeinputs[0]) )) {
1986             @mergeinputs = ($lastfetch_mergeinput);
1987         }
1988     } elsif ($lastpush_hash) {
1989         # only in git, not in the archive yet
1990         @mergeinputs = ($lastpush_mergeinput);
1991         print STDERR <<END or die $!;
1992
1993 Package not found in the archive, but has allegedly been pushed using dgit.
1994 $later_warning_msg
1995 END
1996     } else {
1997         printdebug "nothing found!\n";
1998         if (defined $skew_warning_vsn) {
1999             print STDERR <<END or die $!;
2000
2001 Warning: relevant archive skew detected.
2002 Archive allegedly contains $skew_warning_vsn
2003 But we were not able to obtain any version from the archive or git.
2004
2005 END
2006         }
2007         unshift @end, $del_lrfetchrefs;
2008         return undef;
2009     }
2010
2011     if ($lastfetch_hash &&
2012         !grep {
2013             my $h = $_->{Commit};
2014             $h and is_fast_fwd($lastfetch_hash, $h);
2015             # If true, one of the existing parents of this commit
2016             # is a descendant of the $lastfetch_hash, so we'll
2017             # be ff from that automatically.
2018         } @mergeinputs
2019         ) {
2020         # Otherwise:
2021         push @mergeinputs, $lastfetch_mergeinput;
2022     }
2023
2024     printdebug "fetch mergeinfos:\n";
2025     foreach my $mi (@mergeinputs) {
2026         if ($mi->{Info}) {
2027             printdebug " commit $mi->{Commit} $mi->{Info}\n";
2028         } else {
2029             printdebug sprintf " ReverseParents=%d Message=%s",
2030                 $mi->{ReverseParents}, $mi->{Message};
2031         }
2032     }
2033
2034     my $compat_info= pop @mergeinputs
2035         if $mergeinputs[$#mergeinputs]{Message};
2036
2037     @mergeinputs = grep { defined $_->{Commit} } @mergeinputs;
2038
2039     my $hash;
2040     if (@mergeinputs > 1) {
2041         # here we go, then:
2042         my $tree_commit = $mergeinputs[0]{Commit};
2043
2044         my $tree = cmdoutput @git, qw(cat-file commit), $tree_commit;
2045         $tree =~ m/\n\n/;  $tree = $`;
2046         $tree =~ m/^tree (\w+)$/m or die "$dsc_hash tree ?";
2047         $tree = $1;
2048
2049         # We use the changelog author of the package in question the
2050         # author of this pseudo-merge.  This is (roughly) correct if
2051         # this commit is simply representing aa non-dgit upload.
2052         # (Roughly because it does not record sponsorship - but we
2053         # don't have sponsorship info because that's in the .changes,
2054         # which isn't in the archivw.)
2055         #
2056         # But, it might be that we are representing archive history
2057         # updates (including in-archive copies).  These are not really
2058         # the responsibility of the person who created the .dsc, but
2059         # there is no-one whose name we should better use.  (The
2060         # author of the .dsc-named commit is clearly worse.)
2061
2062         my $useclogp = mergeinfo_getclogp $mergeinputs[0];
2063         my $author = clogp_authline $useclogp;
2064         my $cversion = getfield $useclogp, 'Version';
2065
2066         my $mcf = ".git/dgit/mergecommit";
2067         open MC, ">", $mcf or die "$mcf $!";
2068         print MC <<END or die $!;
2069 tree $tree
2070 END
2071
2072         my @parents = grep { $_->{Commit} } @mergeinputs;
2073         @parents = reverse @parents if $compat_info->{ReverseParents};
2074         print MC <<END or die $! foreach @parents;
2075 parent $_->{Commit}
2076 END
2077
2078         print MC <<END or die $!;
2079 author $author
2080 committer $author
2081
2082 END
2083
2084         if (defined $compat_info->{Message}) {
2085             print MC $compat_info->{Message} or die $!;
2086         } else {
2087             print MC <<END or die $!;
2088 Record $package ($cversion) in archive suite $csuite
2089
2090 Record that
2091 END
2092             my $message_add_info = sub {
2093                 my ($mi) = (@_);
2094                 my $mversion = mergeinfo_version $mi;
2095                 printf MC "  %-20s %s\n", $mversion, $mi->{Info}
2096                     or die $!;
2097             };
2098
2099             $message_add_info->($mergeinputs[0]);
2100             print MC <<END or die $!;
2101 should be treated as descended from
2102 END
2103             $message_add_info->($_) foreach @mergeinputs[1..$#mergeinputs];
2104         }
2105
2106         close MC or die $!;
2107         $hash = make_commit $mcf;
2108     } else {
2109         $hash = $mergeinputs[0]{Commit};
2110     }
2111     progress "fetch hash=$hash\n";
2112
2113     my $chkff = sub {
2114         my ($lasth, $what) = @_;
2115         return unless $lasth;
2116         die "$lasth $hash $what ?" unless is_fast_fwd($lasth, $hash);
2117     };
2118
2119     $chkff->($lastpush_hash, 'dgit repo server tip (last push)');
2120     $chkff->($lastfetch_hash, 'local tracking tip (last fetch)');
2121
2122     runcmd @git, qw(update-ref -m), "dgit fetch $csuite",
2123             'DGIT_ARCHIVE', $hash;
2124     cmdoutput @git, qw(log -n2), $hash;
2125     # ... gives git a chance to complain if our commit is malformed
2126
2127     if (defined $skew_warning_vsn) {
2128         mkpath '.git/dgit';
2129         printdebug "SKEW CHECK WANT $skew_warning_vsn\n";
2130         my $clogf = ".git/dgit/changelog.tmp";
2131         runcmd shell_cmd "exec >$clogf",
2132             @git, qw(cat-file blob), "$hash:debian/changelog";
2133         my $gotclogp = parsechangelog("-l$clogf");
2134         my $got_vsn = getfield $gotclogp, 'Version';
2135         printdebug "SKEW CHECK GOT $got_vsn\n";
2136         if (version_compare($got_vsn, $skew_warning_vsn) < 0) {
2137             print STDERR <<END or die $!;
2138
2139 Warning: archive skew detected.  Using the available version:
2140 Archive allegedly contains    $skew_warning_vsn
2141 We were able to obtain only   $got_vsn
2142
2143 END
2144         }
2145     }
2146
2147     if ($lastfetch_hash ne $hash) {
2148         my @upd_cmd = (@git, qw(update-ref -m), 'dgit fetch', lrref(), $hash);
2149         if (act_local()) {
2150             cmdoutput @upd_cmd;
2151         } else {
2152             dryrun_report @upd_cmd;
2153         }
2154     }
2155
2156     lrfetchref_used lrfetchref();
2157
2158     unshift @end, $del_lrfetchrefs;
2159     return $hash;
2160 }
2161
2162 sub set_local_git_config ($$) {
2163     my ($k, $v) = @_;
2164     runcmd @git, qw(config), $k, $v;
2165 }
2166
2167 sub setup_mergechangelogs (;$) {
2168     my ($always) = @_;
2169     return unless $always || access_cfg_bool(1, 'setup-mergechangelogs');
2170
2171     my $driver = 'dpkg-mergechangelogs';
2172     my $cb = "merge.$driver";
2173     my $attrs = '.git/info/attributes';
2174     ensuredir '.git/info';
2175
2176     open NATTRS, ">", "$attrs.new" or die "$attrs.new $!";
2177     if (!open ATTRS, "<", $attrs) {
2178         $!==ENOENT or die "$attrs: $!";
2179     } else {
2180         while (<ATTRS>) {
2181             chomp;
2182             next if m{^debian/changelog\s};
2183             print NATTRS $_, "\n" or die $!;
2184         }
2185         ATTRS->error and die $!;
2186         close ATTRS;
2187     }
2188     print NATTRS "debian/changelog merge=$driver\n" or die $!;
2189     close NATTRS;
2190
2191     set_local_git_config "$cb.name", 'debian/changelog merge driver';
2192     set_local_git_config "$cb.driver", 'dpkg-mergechangelogs -m %O %A %B %A';
2193
2194     rename "$attrs.new", "$attrs" or die "$attrs: $!";
2195 }
2196
2197 sub setup_useremail (;$) {
2198     my ($always) = @_;
2199     return unless $always || access_cfg_bool(1, 'setup-useremail');
2200
2201     my $setup = sub {
2202         my ($k, $envvar) = @_;
2203         my $v = access_cfg("user-$k", 'RETURN-UNDEF') // $ENV{$envvar};
2204         return unless defined $v;
2205         set_local_git_config "user.$k", $v;
2206     };
2207
2208     $setup->('email', 'DEBEMAIL');
2209     $setup->('name', 'DEBFULLNAME');
2210 }
2211
2212 sub setup_new_tree () {
2213     setup_mergechangelogs();
2214     setup_useremail();
2215 }
2216
2217 sub clone ($) {
2218     my ($dstdir) = @_;
2219     canonicalise_suite();
2220     badusage "dry run makes no sense with clone" unless act_local();
2221     my $hasgit = check_for_git();
2222     mkdir $dstdir or fail "create \`$dstdir': $!";
2223     changedir $dstdir;
2224     runcmd @git, qw(init -q);
2225     my $giturl = access_giturl(1);
2226     if (defined $giturl) {
2227         open H, "> .git/HEAD" or die $!;
2228         print H "ref: ".lref()."\n" or die $!;
2229         close H or die $!;
2230         runcmd @git, qw(remote add), 'origin', $giturl;
2231     }
2232     if ($hasgit) {
2233         progress "fetching existing git history";
2234         git_fetch_us();
2235         runcmd_ordryrun_local @git, qw(fetch origin);
2236     } else {
2237         progress "starting new git history";
2238     }
2239     fetch_from_archive() or no_such_package;
2240     my $vcsgiturl = $dsc->{'Vcs-Git'};
2241     if (length $vcsgiturl) {
2242         $vcsgiturl =~ s/\s+-b\s+\S+//g;
2243         runcmd @git, qw(remote add vcs-git), $vcsgiturl;
2244     }
2245     setup_new_tree();
2246     runcmd @git, qw(reset --hard), lrref();
2247     printdone "ready for work in $dstdir";
2248 }
2249
2250 sub fetch () {
2251     if (check_for_git()) {
2252         git_fetch_us();
2253     }
2254     fetch_from_archive() or no_such_package();
2255     printdone "fetched into ".lrref();
2256 }
2257
2258 sub pull () {
2259     fetch();
2260     runcmd_ordryrun_local @git, qw(merge -m),"Merge from $csuite [dgit]",
2261         lrref();
2262     printdone "fetched to ".lrref()." and merged into HEAD";
2263 }
2264
2265 sub check_not_dirty () {
2266     foreach my $f (qw(local-options local-patch-header)) {
2267         if (stat_exists "debian/source/$f") {
2268             fail "git tree contains debian/source/$f";
2269         }
2270     }
2271
2272     return if $ignoredirty;
2273
2274     my @cmd = (@git, qw(diff --quiet HEAD));
2275     debugcmd "+",@cmd;
2276     $!=0; $?=-1; system @cmd;
2277     return if !$?;
2278     if ($?==256) {
2279         fail "working tree is dirty (does not match HEAD)";
2280     } else {
2281         failedcmd @cmd;
2282     }
2283 }
2284
2285 sub commit_admin ($) {
2286     my ($m) = @_;
2287     progress "$m";
2288     runcmd_ordryrun_local @git, qw(commit -m), $m;
2289 }
2290
2291 sub commit_quilty_patch () {
2292     my $output = cmdoutput @git, qw(status --porcelain);
2293     my %adds;
2294     foreach my $l (split /\n/, $output) {
2295         next unless $l =~ m/\S/;
2296         if ($l =~ m{^(?:\?\?| M) (.pc|debian/patches)}) {
2297             $adds{$1}++;
2298         }
2299     }
2300     delete $adds{'.pc'}; # if there wasn't one before, don't add it
2301     if (!%adds) {
2302         progress "nothing quilty to commit, ok.";
2303         return;
2304     }
2305     my @adds = map { s/[][*?\\]/\\$&/g; $_; } sort keys %adds;
2306     runcmd_ordryrun_local @git, qw(add -f), @adds;
2307     commit_admin "Commit Debian 3.0 (quilt) metadata";
2308 }
2309
2310 sub get_source_format () {
2311     my %options;
2312     if (open F, "debian/source/options") {
2313         while (<F>) {
2314             next if m/^\s*\#/;
2315             next unless m/\S/;
2316             s/\s+$//; # ignore missing final newline
2317             if (m/\s*\#\s*/) {
2318                 my ($k, $v) = ($`, $'); #');
2319                 $v =~ s/^"(.*)"$/$1/;
2320                 $options{$k} = $v;
2321             } else {
2322                 $options{$_} = 1;
2323             }
2324         }
2325         F->error and die $!;
2326         close F;
2327     } else {
2328         die $! unless $!==&ENOENT;
2329     }
2330
2331     if (!open F, "debian/source/format") {
2332         die $! unless $!==&ENOENT;
2333         return '';
2334     }
2335     $_ = <F>;
2336     F->error and die $!;
2337     chomp;
2338     return ($_, \%options);
2339 }
2340
2341 sub madformat ($) {
2342     my ($format) = @_;
2343     return 0 unless $format eq '3.0 (quilt)';
2344     our $quilt_mode_warned;
2345     if ($quilt_mode eq 'nocheck') {
2346         progress "Not doing any fixup of \`$format' due to".
2347             " ----no-quilt-fixup or --quilt=nocheck"
2348             unless $quilt_mode_warned++;
2349         return 0;
2350     }
2351     progress "Format \`$format', need to check/update patch stack"
2352         unless $quilt_mode_warned++;
2353     return 1;
2354 }
2355
2356 sub push_parse_changelog ($) {
2357     my ($clogpfn) = @_;
2358
2359     my $clogp = Dpkg::Control::Hash->new();
2360     $clogp->load($clogpfn) or die;
2361
2362     $package = getfield $clogp, 'Source';
2363     my $cversion = getfield $clogp, 'Version';
2364     my $tag = debiantag($cversion, access_basedistro);
2365     runcmd @git, qw(check-ref-format), $tag;
2366
2367     my $dscfn = dscfn($cversion);
2368
2369     return ($clogp, $cversion, $dscfn);
2370 }
2371
2372 sub push_parse_dsc ($$$) {
2373     my ($dscfn,$dscfnwhat, $cversion) = @_;
2374     $dsc = parsecontrol($dscfn,$dscfnwhat);
2375     my $dversion = getfield $dsc, 'Version';
2376     my $dscpackage = getfield $dsc, 'Source';
2377     ($dscpackage eq $package && $dversion eq $cversion) or
2378         fail "$dscfn is for $dscpackage $dversion".
2379             " but debian/changelog is for $package $cversion";
2380 }
2381
2382 sub push_tagwants ($$$$) {
2383     my ($cversion, $dgithead, $maintviewhead, $tfbase) = @_;
2384     my @tagwants;
2385     push @tagwants, {
2386         TagFn => \&debiantag,
2387         Objid => $dgithead,
2388         TfSuffix => '',
2389         View => 'dgit',
2390     };
2391     if (defined $maintviewhead) {
2392         push @tagwants, {
2393             TagFn => \&debiantag_maintview,
2394             Objid => $maintviewhead,
2395             TfSuffix => '-maintview',
2396             View => 'maint',
2397         };
2398     }
2399     foreach my $tw (@tagwants) {
2400         $tw->{Tag} = $tw->{TagFn}($cversion, access_basedistro);
2401         $tw->{Tfn} = sub { $tfbase.$tw->{TfSuffix}.$_[0]; };
2402     }
2403     return @tagwants;
2404 }
2405
2406 sub push_mktags ($$ $$ $) {
2407     my ($clogp,$dscfn,
2408         $changesfile,$changesfilewhat,
2409         $tagwants) = @_;
2410
2411     die unless $tagwants->[0]{View} eq 'dgit';
2412
2413     $dsc->{$ourdscfield[0]} = $tagwants->[0]{Objid};
2414     $dsc->save("$dscfn.tmp") or die $!;
2415
2416     my $changes = parsecontrol($changesfile,$changesfilewhat);
2417     foreach my $field (qw(Source Distribution Version)) {
2418         $changes->{$field} eq $clogp->{$field} or
2419             fail "changes field $field \`$changes->{$field}'".
2420                 " does not match changelog \`$clogp->{$field}'";
2421     }
2422
2423     my $cversion = getfield $clogp, 'Version';
2424     my $clogsuite = getfield $clogp, 'Distribution';
2425
2426     # We make the git tag by hand because (a) that makes it easier
2427     # to control the "tagger" (b) we can do remote signing
2428     my $authline = clogp_authline $clogp;
2429     my $delibs = join(" ", "",@deliberatelies);
2430     my $declaredistro = access_basedistro();
2431
2432     my $mktag = sub {
2433         my ($tw) = @_;
2434         my $tfn = $tw->{Tfn};
2435         my $head = $tw->{Objid};
2436         my $tag = $tw->{Tag};
2437
2438         open TO, '>', $tfn->('.tmp') or die $!;
2439         print TO <<END or die $!;
2440 object $head
2441 type commit
2442 tag $tag
2443 tagger $authline
2444
2445 END
2446         if ($tw->{View} eq 'dgit') {
2447             print TO <<END or die $!;
2448 $package release $cversion for $clogsuite ($csuite) [dgit]
2449 [dgit distro=$declaredistro$delibs]
2450 END
2451             foreach my $ref (sort keys %previously) {
2452                 print TO <<END or die $!;
2453 [dgit previously:$ref=$previously{$ref}]
2454 END
2455             }
2456         } elsif ($tw->{View} eq 'maint') {
2457             print TO <<END or die $!;
2458 $package release $cversion for $clogsuite ($csuite)
2459 (maintainer view tag generated by dgit --quilt=$quilt_mode)
2460 END
2461         } else {
2462             die Dumper($tw)."?";
2463         }
2464
2465         close TO or die $!;
2466
2467         my $tagobjfn = $tfn->('.tmp');
2468         if ($sign) {
2469             if (!defined $keyid) {
2470                 $keyid = access_cfg('keyid','RETURN-UNDEF');
2471             }
2472             if (!defined $keyid) {
2473                 $keyid = getfield $clogp, 'Maintainer';
2474             }
2475             unlink $tfn->('.tmp.asc') or $!==&ENOENT or die $!;
2476             my @sign_cmd = (@gpg, qw(--detach-sign --armor));
2477             push @sign_cmd, qw(-u),$keyid if defined $keyid;
2478             push @sign_cmd, $tfn->('.tmp');
2479             runcmd_ordryrun @sign_cmd;
2480             if (act_scary()) {
2481                 $tagobjfn = $tfn->('.signed.tmp');
2482                 runcmd shell_cmd "exec >$tagobjfn", qw(cat --),
2483                     $tfn->('.tmp'), $tfn->('.tmp.asc');
2484             }
2485         }
2486         return $tagobjfn;
2487     };
2488
2489     my @r = map { $mktag->($_); } @$tagwants;
2490     return @r;
2491 }
2492
2493 sub sign_changes ($) {
2494     my ($changesfile) = @_;
2495     if ($sign) {
2496         my @debsign_cmd = @debsign;
2497         push @debsign_cmd, "-k$keyid" if defined $keyid;
2498         push @debsign_cmd, "-p$gpg[0]" if $gpg[0] ne 'gpg';
2499         push @debsign_cmd, $changesfile;
2500         runcmd_ordryrun @debsign_cmd;
2501     }
2502 }
2503
2504 sub dopush () {
2505     printdebug "actually entering push\n";
2506
2507     supplementary_message(<<'END');
2508 Push failed, while checking state of the archive.
2509 You can retry the push, after fixing the problem, if you like.
2510 END
2511     if (check_for_git()) {
2512         git_fetch_us();
2513     }
2514     my $archive_hash = fetch_from_archive();
2515     if (!$archive_hash) {
2516         $new_package or
2517             fail "package appears to be new in this suite;".
2518                 " if this is intentional, use --new";
2519     }
2520
2521     supplementary_message(<<'END');
2522 Push failed, while preparing your push.
2523 You can retry the push, after fixing the problem, if you like.
2524 END
2525
2526     need_tagformat 'new', "quilt mode $quilt_mode"
2527         if quiltmode_splitbrain;
2528
2529     prep_ud();
2530
2531     access_giturl(); # check that success is vaguely likely
2532     select_tagformat();
2533
2534     my $clogpfn = ".git/dgit/changelog.822.tmp";
2535     runcmd shell_cmd "exec >$clogpfn", qw(dpkg-parsechangelog);
2536
2537     responder_send_file('parsed-changelog', $clogpfn);
2538
2539     my ($clogp, $cversion, $dscfn) =
2540         push_parse_changelog("$clogpfn");
2541
2542     my $dscpath = "$buildproductsdir/$dscfn";
2543     stat_exists $dscpath or
2544         fail "looked for .dsc $dscfn, but $!;".
2545             " maybe you forgot to build";
2546
2547     responder_send_file('dsc', $dscpath);
2548
2549     push_parse_dsc($dscpath, $dscfn, $cversion);
2550
2551     my $format = getfield $dsc, 'Format';
2552     printdebug "format $format\n";
2553
2554     my $actualhead = git_rev_parse('HEAD');
2555     my $dgithead = $actualhead;
2556     my $maintviewhead = undef;
2557
2558     if (madformat($format)) {
2559         # user might have not used dgit build, so maybe do this now:
2560         if (quiltmode_splitbrain()) {
2561             my $upstreamversion = $clogp->{Version};
2562             $upstreamversion =~ s/-[^-]*$//;
2563             changedir $ud;
2564             quilt_make_fake_dsc($upstreamversion);
2565             my ($dgitview, $cachekey) =
2566                 quilt_check_splitbrain_cache($actualhead, $upstreamversion);
2567             $dgitview or fail
2568  "--quilt=$quilt_mode but no cached dgit view:
2569  perhaps tree changed since dgit build[-source] ?";
2570             $split_brain = 1;
2571             $dgithead = $dgitview;
2572             $maintviewhead = $actualhead;
2573             changedir '../../../..';
2574             prep_ud(); # so _only_subdir() works, below
2575         } else {
2576             commit_quilty_patch();
2577         }
2578     }
2579
2580     check_not_dirty();
2581
2582     my $forceflag = '';
2583     if ($archive_hash) {
2584         if (is_fast_fwd($archive_hash, $dgithead)) {
2585             # ok
2586         } elsif (deliberately_not_fast_forward) {
2587             $forceflag = '+';
2588         } else {
2589             fail "dgit push: HEAD is not a descendant".
2590                 " of the archive's version.\n".
2591                 "dgit: To overwrite its contents,".
2592                 " use git merge -s ours ".lrref().".\n".
2593                 "dgit: To rewind history, if permitted by the archive,".
2594                 " use --deliberately-not-fast-forward";
2595         }
2596     }
2597
2598     changedir $ud;
2599     progress "checking that $dscfn corresponds to HEAD";
2600     runcmd qw(dpkg-source -x --),
2601         $dscpath =~ m#^/# ? $dscpath : "../../../$dscpath";
2602     my ($tree,$dir) = mktree_in_ud_from_only_subdir();
2603     check_for_vendor_patches() if madformat($dsc->{format});
2604     changedir '../../../..';
2605     my $diffopt = $debuglevel>0 ? '--exit-code' : '--quiet';
2606     my @diffcmd = (@git, qw(diff), $diffopt, $tree, $dgithead);
2607     debugcmd "+",@diffcmd;
2608     $!=0; $?=-1;
2609     my $r = system @diffcmd;
2610     if ($r) {
2611         if ($r==256) {
2612             fail "$dscfn specifies a different tree to your HEAD commit;".
2613                 " perhaps you forgot to build".
2614                 ($diffopt eq '--exit-code' ? "" :
2615                  " (run with -D to see full diff output)");
2616         } else {
2617             failedcmd @diffcmd;
2618         }
2619     }
2620     if (!$changesfile) {
2621         my $pat = changespat $cversion;
2622         my @cs = glob "$buildproductsdir/$pat";
2623         fail "failed to find unique changes file".
2624             " (looked for $pat in $buildproductsdir);".
2625             " perhaps you need to use dgit -C"
2626             unless @cs==1;
2627         ($changesfile) = @cs;
2628     } else {
2629         $changesfile = "$buildproductsdir/$changesfile";
2630     }
2631
2632     # Checks complete, we're going to try and go ahead:
2633
2634     responder_send_file('changes',$changesfile);
2635     responder_send_command("param head $dgithead");
2636     responder_send_command("param csuite $csuite");
2637     responder_send_command("param tagformat $tagformat");
2638     if (quiltmode_splitbrain) {
2639         die unless ($protovsn//4) >= 4;
2640         responder_send_command("param maint-view $maintviewhead");
2641     }
2642
2643     if (deliberately_not_fast_forward) {
2644         git_for_each_ref(lrfetchrefs, sub {
2645             my ($objid,$objtype,$lrfetchrefname,$reftail) = @_;
2646             my $rrefname= substr($lrfetchrefname, length(lrfetchrefs) + 1);
2647             responder_send_command("previously $rrefname=$objid");
2648             $previously{$rrefname} = $objid;
2649         });
2650     }
2651
2652     my @tagwants = push_tagwants($cversion, $dgithead, $maintviewhead,
2653                                  ".git/dgit/tag");
2654     my @tagobjfns;
2655
2656     supplementary_message(<<'END');
2657 Push failed, while signing the tag.
2658 You can retry the push, after fixing the problem, if you like.
2659 END
2660     # If we manage to sign but fail to record it anywhere, it's fine.
2661     if ($we_are_responder) {
2662         @tagobjfns = map { $_->{Tfn}('.signed-tmp') } @tagwants;
2663         responder_receive_files('signed-tag', @tagobjfns);
2664     } else {
2665         @tagobjfns = push_mktags($clogp,$dscpath,
2666                               $changesfile,$changesfile,
2667                               \@tagwants);
2668     }
2669     supplementary_message(<<'END');
2670 Push failed, *after* signing the tag.
2671 If you want to try again, you should use a new version number.
2672 END
2673
2674     pairwise { $a->{TagObjFn} = $b } @tagwants, @tagobjfns;
2675
2676     foreach my $tw (@tagwants) {
2677         my $tag = $tw->{Tag};
2678         my $tagobjfn = $tw->{TagObjFn};
2679         my $tag_obj_hash =
2680             cmdoutput @git, qw(hash-object -w -t tag), $tagobjfn;
2681         runcmd_ordryrun @git, qw(verify-tag), $tag_obj_hash;
2682         runcmd_ordryrun_local
2683             @git, qw(update-ref), "refs/tags/$tag", $tag_obj_hash;
2684     }
2685
2686     supplementary_message(<<'END');
2687 Push failed, while updating the remote git repository - see messages above.
2688 If you want to try again, you should use a new version number.
2689 END
2690     if (!check_for_git()) {
2691         create_remote_git_repo();
2692     }
2693
2694     my @pushrefs = $forceflag.$dgithead.":".rrref();
2695     foreach my $tw (@tagwants) {
2696         my $view = $tw->{View};
2697         next unless $view eq 'dgit'
2698             or any { $_ eq $view } access_cfg_tagformats();
2699             # ^ $view is "dgit" or "maint" so this looks for "maint"
2700             # in archive supported tagformats.
2701         push @pushrefs, $forceflag."refs/tags/$tw->{Tag}";
2702     }
2703
2704     runcmd_ordryrun @git, qw(push),access_giturl(), @pushrefs;
2705     runcmd_ordryrun @git, qw(update-ref -m), 'dgit push', lrref(), $dgithead;
2706
2707     supplementary_message(<<'END');
2708 Push failed, after updating the remote git repository.
2709 If you want to try again, you must use a new version number.
2710 END
2711     if ($we_are_responder) {
2712         my $dryrunsuffix = act_local() ? "" : ".tmp";
2713         responder_receive_files('signed-dsc-changes',
2714                                 "$dscpath$dryrunsuffix",
2715                                 "$changesfile$dryrunsuffix");
2716     } else {
2717         if (act_local()) {
2718             rename "$dscpath.tmp",$dscpath or die "$dscfn $!";
2719         } else {
2720             progress "[new .dsc left in $dscpath.tmp]";
2721         }
2722         sign_changes $changesfile;
2723     }
2724
2725     supplementary_message(<<END);
2726 Push failed, while uploading package(s) to the archive server.
2727 You can retry the upload of exactly these same files with dput of:
2728   $changesfile
2729 If that .changes file is broken, you will need to use a new version
2730 number for your next attempt at the upload.
2731 END
2732     my $host = access_cfg('upload-host','RETURN-UNDEF');
2733     my @hostarg = defined($host) ? ($host,) : ();
2734     runcmd_ordryrun @dput, @hostarg, $changesfile;
2735     printdone "pushed and uploaded $cversion";
2736
2737     supplementary_message('');
2738     responder_send_command("complete");
2739 }
2740
2741 sub cmd_clone {
2742     parseopts();
2743     notpushing();
2744     my $dstdir;
2745     badusage "-p is not allowed with clone; specify as argument instead"
2746         if defined $package;
2747     if (@ARGV==1) {
2748         ($package) = @ARGV;
2749     } elsif (@ARGV==2 && $ARGV[1] =~ m#^\w#) {
2750         ($package,$isuite) = @ARGV;
2751     } elsif (@ARGV==2 && $ARGV[1] =~ m#^[./]#) {
2752         ($package,$dstdir) = @ARGV;
2753     } elsif (@ARGV==3) {
2754         ($package,$isuite,$dstdir) = @ARGV;
2755     } else {
2756         badusage "incorrect arguments to dgit clone";
2757     }
2758     $dstdir ||= "$package";
2759
2760     if (stat_exists $dstdir) {
2761         fail "$dstdir already exists";
2762     }
2763
2764     my $cwd_remove;
2765     if ($rmonerror && !$dryrun_level) {
2766         $cwd_remove= getcwd();
2767         unshift @end, sub { 
2768             return unless defined $cwd_remove;
2769             if (!chdir "$cwd_remove") {
2770                 return if $!==&ENOENT;
2771                 die "chdir $cwd_remove: $!";
2772             }
2773             if (stat $dstdir) {
2774                 rmtree($dstdir) or die "remove $dstdir: $!\n";
2775             } elsif (!grep { $! == $_ }
2776                      (ENOENT, ENOTDIR, EACCES, EPERM, ELOOP)) {
2777             } else {
2778                 print STDERR "check whether to remove $dstdir: $!\n";
2779             }
2780         };
2781     }
2782
2783     clone($dstdir);
2784     $cwd_remove = undef;
2785 }
2786
2787 sub branchsuite () {
2788     my $branch = cmdoutput_errok @git, qw(symbolic-ref HEAD);
2789     if ($branch =~ m#$lbranch_re#o) {
2790         return $1;
2791     } else {
2792         return undef;
2793     }
2794 }
2795
2796 sub fetchpullargs () {
2797     notpushing();
2798     if (!defined $package) {
2799         my $sourcep = parsecontrol('debian/control','debian/control');
2800         $package = getfield $sourcep, 'Source';
2801     }
2802     if (@ARGV==0) {
2803 #       $isuite = branchsuite();  # this doesn't work because dak hates canons
2804         if (!$isuite) {
2805             my $clogp = parsechangelog();
2806             $isuite = getfield $clogp, 'Distribution';
2807         }
2808         canonicalise_suite();
2809         progress "fetching from suite $csuite";
2810     } elsif (@ARGV==1) {
2811         ($isuite) = @ARGV;
2812         canonicalise_suite();
2813     } else {
2814         badusage "incorrect arguments to dgit fetch or dgit pull";
2815     }
2816 }
2817
2818 sub cmd_fetch {
2819     parseopts();
2820     fetchpullargs();
2821     fetch();
2822 }
2823
2824 sub cmd_pull {
2825     parseopts();
2826     fetchpullargs();
2827     pull();
2828 }
2829
2830 sub cmd_push {
2831     parseopts();
2832     pushing();
2833     badusage "-p is not allowed with dgit push" if defined $package;
2834     check_not_dirty();
2835     my $clogp = parsechangelog();
2836     $package = getfield $clogp, 'Source';
2837     my $specsuite;
2838     if (@ARGV==0) {
2839     } elsif (@ARGV==1) {
2840         ($specsuite) = (@ARGV);
2841     } else {
2842         badusage "incorrect arguments to dgit push";
2843     }
2844     $isuite = getfield $clogp, 'Distribution';
2845     if ($new_package) {
2846         local ($package) = $existing_package; # this is a hack
2847         canonicalise_suite();
2848     } else {
2849         canonicalise_suite();
2850     }
2851     if (defined $specsuite &&
2852         $specsuite ne $isuite &&
2853         $specsuite ne $csuite) {
2854             fail "dgit push: changelog specifies $isuite ($csuite)".
2855                 " but command line specifies $specsuite";
2856     }
2857     dopush();
2858 }
2859
2860 #---------- remote commands' implementation ----------
2861
2862 sub cmd_remote_push_build_host {
2863     my ($nrargs) = shift @ARGV;
2864     my (@rargs) = @ARGV[0..$nrargs-1];
2865     @ARGV = @ARGV[$nrargs..$#ARGV];
2866     die unless @rargs;
2867     my ($dir,$vsnwant) = @rargs;
2868     # vsnwant is a comma-separated list; we report which we have
2869     # chosen in our ready response (so other end can tell if they
2870     # offered several)
2871     $debugprefix = ' ';
2872     $we_are_responder = 1;
2873     $us .= " (build host)";
2874
2875     pushing();
2876
2877     open PI, "<&STDIN" or die $!;
2878     open STDIN, "/dev/null" or die $!;
2879     open PO, ">&STDOUT" or die $!;
2880     autoflush PO 1;
2881     open STDOUT, ">&STDERR" or die $!;
2882     autoflush STDOUT 1;
2883
2884     $vsnwant //= 1;
2885     ($protovsn) = grep {
2886         $vsnwant =~ m{^(?:.*,)?$_(?:,.*)?$}
2887     } @rpushprotovsn_support;
2888
2889     fail "build host has dgit rpush protocol versions ".
2890         (join ",", @rpushprotovsn_support).
2891         " but invocation host has $vsnwant"
2892         unless defined $protovsn;
2893
2894     responder_send_command("dgit-remote-push-ready $protovsn");
2895     rpush_handle_protovsn_bothends();
2896     changedir $dir;
2897     &cmd_push;
2898 }
2899
2900 sub cmd_remote_push_responder { cmd_remote_push_build_host(); }
2901 # ... for compatibility with proto vsn.1 dgit (just so that user gets
2902 #     a good error message)
2903
2904 sub rpush_handle_protovsn_bothends () {
2905     if ($protovsn < 4) {
2906         need_tagformat 'old', "rpush negotiated protocol $protovsn";
2907     }
2908     select_tagformat();
2909 }
2910
2911 our $i_tmp;
2912
2913 sub i_cleanup {
2914     local ($@, $?);
2915     my $report = i_child_report();
2916     if (defined $report) {
2917         printdebug "($report)\n";
2918     } elsif ($i_child_pid) {
2919         printdebug "(killing build host child $i_child_pid)\n";
2920         kill 15, $i_child_pid;
2921     }
2922     if (defined $i_tmp && !defined $initiator_tempdir) {
2923         changedir "/";
2924         eval { rmtree $i_tmp; };
2925     }
2926 }
2927
2928 END { i_cleanup(); }
2929
2930 sub i_method {
2931     my ($base,$selector,@args) = @_;
2932     $selector =~ s/\-/_/g;
2933     { no strict qw(refs); &{"${base}_${selector}"}(@args); }
2934 }
2935
2936 sub cmd_rpush {
2937     pushing();
2938     my $host = nextarg;
2939     my $dir;
2940     if ($host =~ m/^((?:[^][]|\[[^][]*\])*)\:/) {
2941         $host = $1;
2942         $dir = $'; #';
2943     } else {
2944         $dir = nextarg;
2945     }
2946     $dir =~ s{^-}{./-};
2947     my @rargs = ($dir);
2948     push @rargs, join ",", @rpushprotovsn_support;
2949     my @rdgit;
2950     push @rdgit, @dgit;
2951     push @rdgit, @ropts;
2952     push @rdgit, qw(remote-push-build-host), (scalar @rargs), @rargs;
2953     push @rdgit, @ARGV;
2954     my @cmd = (@ssh, $host, shellquote @rdgit);
2955     debugcmd "+",@cmd;
2956
2957     if (defined $initiator_tempdir) {
2958         rmtree $initiator_tempdir;
2959         mkdir $initiator_tempdir, 0700 or die "$initiator_tempdir: $!";
2960         $i_tmp = $initiator_tempdir;
2961     } else {
2962         $i_tmp = tempdir();
2963     }
2964     $i_child_pid = open2(\*RO, \*RI, @cmd);
2965     changedir $i_tmp;
2966     ($protovsn) = initiator_expect { m/^dgit-remote-push-ready (\S+)/ };
2967     die "$protovsn ?" unless grep { $_ eq $protovsn } @rpushprotovsn_support;
2968     $supplementary_message = '' unless $protovsn >= 3;
2969
2970     fail "rpush negotiated protocol version $protovsn".
2971         " which does not support quilt mode $quilt_mode"
2972         if quiltmode_splitbrain;
2973
2974     rpush_handle_protovsn_bothends();
2975     for (;;) {
2976         my ($icmd,$iargs) = initiator_expect {
2977             m/^(\S+)(?: (.*))?$/;
2978             ($1,$2);
2979         };
2980         i_method "i_resp", $icmd, $iargs;
2981     }
2982 }
2983
2984 sub i_resp_progress ($) {
2985     my ($rhs) = @_;
2986     my $msg = protocol_read_bytes \*RO, $rhs;
2987     progress $msg;
2988 }
2989
2990 sub i_resp_supplementary_message ($) {
2991     my ($rhs) = @_;
2992     $supplementary_message = protocol_read_bytes \*RO, $rhs;
2993 }
2994
2995 sub i_resp_complete {
2996     my $pid = $i_child_pid;
2997     $i_child_pid = undef; # prevents killing some other process with same pid
2998     printdebug "waiting for build host child $pid...\n";
2999     my $got = waitpid $pid, 0;
3000     die $! unless $got == $pid;
3001     die "build host child failed $?" if $?;
3002
3003     i_cleanup();
3004     printdebug "all done\n";
3005     exit 0;
3006 }
3007
3008 sub i_resp_file ($) {
3009     my ($keyword) = @_;
3010     my $localname = i_method "i_localname", $keyword;
3011     my $localpath = "$i_tmp/$localname";
3012     stat_exists $localpath and
3013         badproto \*RO, "file $keyword ($localpath) twice";
3014     protocol_receive_file \*RO, $localpath;
3015     i_method "i_file", $keyword;
3016 }
3017
3018 our %i_param;
3019
3020 sub i_resp_param ($) {
3021     $_[0] =~ m/^(\S+) (.*)$/ or badproto \*RO, "bad param spec";
3022     $i_param{$1} = $2;
3023 }
3024
3025 sub i_resp_previously ($) {
3026     $_[0] =~ m#^(refs/tags/\S+)=(\w+)$#
3027         or badproto \*RO, "bad previously spec";
3028     my $r = system qw(git check-ref-format), $1;
3029     die "bad previously ref spec ($r)" if $r;
3030     $previously{$1} = $2;
3031 }
3032
3033 our %i_wanted;
3034
3035 sub i_resp_want ($) {
3036     my ($keyword) = @_;
3037     die "$keyword ?" if $i_wanted{$keyword}++;
3038     my @localpaths = i_method "i_want", $keyword;
3039     printdebug "[[  $keyword @localpaths\n";
3040     foreach my $localpath (@localpaths) {
3041         protocol_send_file \*RI, $localpath;
3042     }
3043     print RI "files-end\n" or die $!;
3044 }
3045
3046 our ($i_clogp, $i_version, $i_dscfn, $i_changesfn);
3047
3048 sub i_localname_parsed_changelog {
3049     return "remote-changelog.822";
3050 }
3051 sub i_file_parsed_changelog {
3052     ($i_clogp, $i_version, $i_dscfn) =
3053         push_parse_changelog "$i_tmp/remote-changelog.822";
3054     die if $i_dscfn =~ m#/|^\W#;
3055 }
3056
3057 sub i_localname_dsc {
3058     defined $i_dscfn or badproto \*RO, "dsc (before parsed-changelog)";
3059     return $i_dscfn;
3060 }
3061 sub i_file_dsc { }
3062
3063 sub i_localname_changes {
3064     defined $i_dscfn or badproto \*RO, "dsc (before parsed-changelog)";
3065     $i_changesfn = $i_dscfn;
3066     $i_changesfn =~ s/\.dsc$/_dgit.changes/ or die;
3067     return $i_changesfn;
3068 }
3069 sub i_file_changes { }
3070
3071 sub i_want_signed_tag {
3072     printdebug Dumper(\%i_param, $i_dscfn);
3073     defined $i_param{'head'} && defined $i_dscfn && defined $i_clogp
3074         && defined $i_param{'csuite'}
3075         or badproto \*RO, "premature desire for signed-tag";
3076     my $head = $i_param{'head'};
3077     die if $head =~ m/[^0-9a-f]/ || $head !~ m/^../;
3078
3079     my $maintview = $i_param{'maint-view'};
3080     die if defined $maintview && $maintview =~ m/[^0-9a-f]/;
3081
3082     select_tagformat();
3083     if ($protovsn >= 4) {
3084         my $p = $i_param{'tagformat'} // '<undef>';
3085         $p eq $tagformat
3086             or badproto \*RO, "tag format mismatch: $p vs. $tagformat";
3087     }
3088
3089     die unless $i_param{'csuite'} =~ m/^$suite_re$/;
3090     $csuite = $&;
3091     push_parse_dsc $i_dscfn, 'remote dsc', $i_version;
3092
3093     my @tagwants = push_tagwants $i_version, $head, $maintview, "tag";
3094
3095     return
3096         push_mktags $i_clogp, $i_dscfn,
3097             $i_changesfn, 'remote changes',
3098             \@tagwants;
3099 }
3100
3101 sub i_want_signed_dsc_changes {
3102     rename "$i_dscfn.tmp","$i_dscfn" or die "$i_dscfn $!";
3103     sign_changes $i_changesfn;
3104     return ($i_dscfn, $i_changesfn);
3105 }
3106
3107 #---------- building etc. ----------
3108
3109 our $version;
3110 our $sourcechanges;
3111 our $dscfn;
3112
3113 #----- `3.0 (quilt)' handling -----
3114
3115 our $fakeeditorenv = 'DGIT_FAKE_EDITOR_QUILT';
3116
3117 sub quiltify_dpkg_commit ($$$;$) {
3118     my ($patchname,$author,$msg, $xinfo) = @_;
3119     $xinfo //= '';
3120
3121     mkpath '.git/dgit';
3122     my $descfn = ".git/dgit/quilt-description.tmp";
3123     open O, '>', $descfn or die "$descfn: $!";
3124     $msg =~ s/\s+$//g;
3125     $msg =~ s/\n/\n /g;
3126     $msg =~ s/^\s+$/ ./mg;
3127     print O <<END or die $!;
3128 Description: $msg
3129 Author: $author
3130 $xinfo
3131 ---
3132
3133 END
3134     close O or die $!;
3135
3136     {
3137         local $ENV{'EDITOR'} = cmdoutput qw(realpath --), $0;
3138         local $ENV{'VISUAL'} = $ENV{'EDITOR'};
3139         local $ENV{$fakeeditorenv} = cmdoutput qw(realpath --), $descfn;
3140         runcmd @dpkgsource, qw(--commit .), $patchname;
3141     }
3142 }
3143
3144 sub quiltify_trees_differ ($$;$$) {
3145     my ($x,$y,$finegrained,$ignorenamesr) = @_;
3146     # returns true iff the two tree objects differ other than in debian/
3147     # with $finegrained,
3148     # returns bitmask 01 - differ in upstream files except .gitignore
3149     #                 02 - differ in .gitignore
3150     # if $ignorenamesr is defined, $ingorenamesr->{$fn}
3151     #  is set for each modified .gitignore filename $fn
3152     local $/=undef;
3153     my @cmd = (@git, qw(diff-tree --name-only -z));
3154     push @cmd, qw(-r) if $finegrained;
3155     push @cmd, $x, $y;
3156     my $diffs= cmdoutput @cmd;
3157     my $r = 0;
3158     foreach my $f (split /\0/, $diffs) {
3159         next if $f =~ m#^debian(?:/.*)?$#s;
3160         my $isignore = $f =~ m#^(?:.*/)?.gitignore$#s;
3161         $r |= $isignore ? 02 : 01;
3162         $ignorenamesr->{$f}=1 if $ignorenamesr && $isignore;
3163     }
3164     printdebug "quiltify_trees_differ $x $y => $r\n";
3165     return $r;
3166 }
3167
3168 sub quiltify_tree_sentinelfiles ($) {
3169     # lists the `sentinel' files present in the tree
3170     my ($x) = @_;
3171     my $r = cmdoutput @git, qw(ls-tree --name-only), $x,
3172         qw(-- debian/rules debian/control);
3173     $r =~ s/\n/,/g;
3174     return $r;
3175 }
3176
3177 sub quiltify_splitbrain_needed () {
3178     if (!$split_brain) {
3179         progress "dgit view: changes are required...";
3180         runcmd @git, qw(checkout -q -b dgit-view);
3181         $split_brain = 1;
3182     }
3183 }
3184
3185 sub quiltify_splitbrain ($$$$$$) {
3186     my ($clogp, $unapplied, $headref, $diffbits,
3187         $editedignores, $cachekey) = @_;
3188     if ($quilt_mode !~ m/gbp|dpm/) {
3189         # treat .gitignore just like any other upstream file
3190         $diffbits = { %$diffbits };
3191         $_ = !!$_ foreach values %$diffbits;
3192     }
3193     # We would like any commits we generate to be reproducible
3194     my @authline = clogp_authline($clogp);
3195     local $ENV{GIT_COMMITTER_NAME} =  $authline[0];
3196     local $ENV{GIT_COMMITTER_EMAIL} = $authline[1];
3197     local $ENV{GIT_COMMITTER_DATE} =  $authline[2];
3198         
3199     if ($quilt_mode =~ m/gbp|unapplied/ &&
3200         ($diffbits->{H2O} & 01)) {
3201         my $msg =
3202  "--quilt=$quilt_mode specified, implying patches-unapplied git tree\n".
3203  " but git tree differs from orig in upstream files.";
3204         if (!stat_exists "debian/patches") {
3205             $msg .=
3206  "\n ... debian/patches is missing; perhaps this is a patch queue branch?";
3207         }  
3208         fail $msg;
3209     }
3210     if ($quilt_mode =~ m/gbp|unapplied/ &&
3211         ($diffbits->{O2A} & 01)) { # some patches
3212         quiltify_splitbrain_needed();
3213         progress "dgit view: creating patches-applied version using gbp pq";
3214         runcmd shell_cmd 'exec >/dev/null', @gbp, qw(pq import);
3215         # gbp pq import creates a fresh branch; push back to dgit-view
3216         runcmd @git, qw(update-ref refs/heads/dgit-view HEAD);
3217         runcmd @git, qw(checkout -q dgit-view);
3218     }
3219     if (($diffbits->{H2O} & 02) && # user has modified .gitignore
3220         !($diffbits->{O2A} & 02)) { # patches do not change .gitignore
3221         quiltify_splitbrain_needed();
3222         progress "dgit view: creating patch to represent .gitignore changes";
3223         ensuredir "debian/patches";
3224         my $gipatch = "debian/patches/auto-gitignore";
3225         open GIPATCH, ">>", "$gipatch" or die "$gipatch: $!";
3226         stat GIPATCH or die "$gipatch: $!";
3227         fail "$gipatch already exists; but want to create it".
3228             " to record .gitignore changes" if (stat _)[7];
3229         print GIPATCH <<END or die "$gipatch: $!";
3230 Subject: Update .gitignore from Debian packaging branch
3231
3232 The Debian packaging git branch contains these updates to the upstream
3233 .gitignore file(s).  This patch is autogenerated, to provide these
3234 updates to users of the official Debian archive view of the package.
3235
3236 [dgit version $our_version]
3237 ---
3238 END
3239         close GIPATCH or die "$gipatch: $!";
3240         runcmd shell_cmd "exec >>$gipatch", @git, qw(diff),
3241             $unapplied, $headref, "--", sort keys %$editedignores;
3242         open SERIES, "+>>", "debian/patches/series" or die $!;
3243         defined seek SERIES, -1, 2 or $!==EINVAL or die $!;
3244         my $newline;
3245         defined read SERIES, $newline, 1 or die $!;
3246         print SERIES "\n" or die $! unless $newline eq "\n";
3247         print SERIES "auto-gitignore\n" or die $!;
3248         close SERIES or die  $!;
3249         runcmd @git, qw(add -- debian/patches/series), $gipatch;
3250         commit_admin "Commit patch to update .gitignore";
3251     }
3252
3253     my $dgitview = git_rev_parse 'refs/heads/dgit-view';
3254
3255     changedir '../../../..';
3256     ensuredir ".git/logs/refs/dgit-intern";
3257     my $makelogfh = new IO::File ".git/logs/refs/$splitbraincache", '>>'
3258       or die $!;
3259     runcmd @git, qw(update-ref -m), $cachekey, "refs/$splitbraincache",
3260         $dgitview;
3261
3262     progress "dgit view: created (commit id $dgitview)";
3263
3264     changedir '.git/dgit/unpack/work';
3265 }
3266
3267 sub quiltify ($$$$) {
3268     my ($clogp,$target,$oldtiptree,$failsuggestion) = @_;
3269
3270     # Quilt patchification algorithm
3271     #
3272     # We search backwards through the history of the main tree's HEAD
3273     # (T) looking for a start commit S whose tree object is identical
3274     # to to the patch tip tree (ie the tree corresponding to the
3275     # current dpkg-committed patch series).  For these purposes
3276     # `identical' disregards anything in debian/ - this wrinkle is
3277     # necessary because dpkg-source treates debian/ specially.
3278     #
3279     # We can only traverse edges where at most one of the ancestors'
3280     # trees differs (in changes outside in debian/).  And we cannot
3281     # handle edges which change .pc/ or debian/patches.  To avoid
3282     # going down a rathole we avoid traversing edges which introduce
3283     # debian/rules or debian/control.  And we set a limit on the
3284     # number of edges we are willing to look at.
3285     #
3286     # If we succeed, we walk forwards again.  For each traversed edge
3287     # PC (with P parent, C child) (starting with P=S and ending with
3288     # C=T) to we do this:
3289     #  - git checkout C
3290     #  - dpkg-source --commit with a patch name and message derived from C
3291     # After traversing PT, we git commit the changes which
3292     # should be contained within debian/patches.
3293
3294     # The search for the path S..T is breadth-first.  We maintain a
3295     # todo list containing search nodes.  A search node identifies a
3296     # commit, and looks something like this:
3297     #  $p = {
3298     #      Commit => $git_commit_id,
3299     #      Child => $c,                          # or undef if P=T
3300     #      Whynot => $reason_edge_PC_unsuitable, # in @nots only
3301     #      Nontrivial => true iff $p..$c has relevant changes
3302     #  };
3303
3304     my @todo;
3305     my @nots;
3306     my $sref_S;
3307     my $max_work=100;
3308     my %considered; # saves being exponential on some weird graphs
3309
3310     my $t_sentinels = quiltify_tree_sentinelfiles $target;
3311
3312     my $not = sub {
3313         my ($search,$whynot) = @_;
3314         printdebug " search NOT $search->{Commit} $whynot\n";
3315         $search->{Whynot} = $whynot;
3316         push @nots, $search;
3317         no warnings qw(exiting);
3318         next;
3319     };
3320
3321     push @todo, {
3322         Commit => $target,
3323     };
3324
3325     while (@todo) {
3326         my $c = shift @todo;
3327         next if $considered{$c->{Commit}}++;
3328
3329         $not->($c, "maximum search space exceeded") if --$max_work <= 0;
3330
3331         printdebug "quiltify investigate $c->{Commit}\n";
3332
3333         # are we done?
3334         if (!quiltify_trees_differ $c->{Commit}, $oldtiptree) {
3335             printdebug " search finished hooray!\n";
3336             $sref_S = $c;
3337             last;
3338         }
3339
3340         if ($quilt_mode eq 'nofix') {
3341             fail "quilt fixup required but quilt mode is \`nofix'\n".
3342                 "HEAD commit $c->{Commit} differs from tree implied by ".
3343                 " debian/patches (tree object $oldtiptree)";
3344         }
3345         if ($quilt_mode eq 'smash') {
3346             printdebug " search quitting smash\n";
3347             last;
3348         }
3349
3350         my $c_sentinels = quiltify_tree_sentinelfiles $c->{Commit};
3351         $not->($c, "has $c_sentinels not $t_sentinels")
3352             if $c_sentinels ne $t_sentinels;
3353
3354         my $commitdata = cmdoutput @git, qw(cat-file commit), $c->{Commit};
3355         $commitdata =~ m/\n\n/;
3356         $commitdata =~ $`;
3357         my @parents = ($commitdata =~ m/^parent (\w+)$/gm);
3358         @parents = map { { Commit => $_, Child => $c } } @parents;
3359
3360         $not->($c, "root commit") if !@parents;
3361
3362         foreach my $p (@parents) {
3363             $p->{Nontrivial}= quiltify_trees_differ $p->{Commit},$c->{Commit};
3364         }
3365         my $ndiffers = grep { $_->{Nontrivial} } @parents;
3366         $not->($c, "merge ($ndiffers nontrivial parents)") if $ndiffers > 1;
3367
3368         foreach my $p (@parents) {
3369             printdebug "considering C=$c->{Commit} P=$p->{Commit}\n";
3370
3371             my @cmd= (@git, qw(diff-tree -r --name-only),
3372                       $p->{Commit},$c->{Commit}, qw(-- debian/patches .pc));
3373             my $patchstackchange = cmdoutput @cmd;
3374             if (length $patchstackchange) {
3375                 $patchstackchange =~ s/\n/,/g;
3376                 $not->($p, "changed $patchstackchange");
3377             }
3378
3379             printdebug " search queue P=$p->{Commit} ",
3380                 ($p->{Nontrivial} ? "NT" : "triv"),"\n";
3381             push @todo, $p;
3382         }
3383     }
3384
3385     if (!$sref_S) {
3386         printdebug "quiltify want to smash\n";
3387
3388         my $abbrev = sub {
3389             my $x = $_[0]{Commit};
3390             $x =~ s/(.*?[0-9a-z]{8})[0-9a-z]*$/$1/;
3391             return $x;
3392         };
3393         my $reportnot = sub {
3394             my ($notp) = @_;
3395             my $s = $abbrev->($notp);
3396             my $c = $notp->{Child};
3397             $s .= "..".$abbrev->($c) if $c;
3398             $s .= ": ".$notp->{Whynot};
3399             return $s;
3400         };
3401         if ($quilt_mode eq 'linear') {
3402             print STDERR "$us: quilt fixup cannot be linear.  Stopped at:\n";
3403             foreach my $notp (@nots) {
3404                 print STDERR "$us:  ", $reportnot->($notp), "\n";
3405             }
3406             print STDERR "$us: $_\n" foreach @$failsuggestion;
3407             fail "quilt fixup naive history linearisation failed.\n".
3408  "Use dpkg-source --commit by hand; or, --quilt=smash for one ugly patch";
3409         } elsif ($quilt_mode eq 'smash') {
3410         } elsif ($quilt_mode eq 'auto') {
3411             progress "quilt fixup cannot be linear, smashing...";
3412         } else {
3413             die "$quilt_mode ?";
3414         }
3415
3416         my $time = $ENV{'GIT_COMMITTER_DATE'} || time;
3417         $time =~ s/\s.*//; # trim timezone from GIT_COMMITTER_DATE
3418         my $ncommits = 3;
3419         my $msg = cmdoutput @git, qw(log), "-n$ncommits";
3420
3421         quiltify_dpkg_commit "auto-$version-$target-$time",
3422             (getfield $clogp, 'Maintainer'),
3423             "Automatically generated patch ($clogp->{Version})\n".
3424             "Last (up to) $ncommits git changes, FYI:\n\n". $msg;
3425         return;
3426     }
3427
3428     progress "quiltify linearisation planning successful, executing...";
3429
3430     for (my $p = $sref_S;
3431          my $c = $p->{Child};
3432          $p = $p->{Child}) {
3433         printdebug "quiltify traverse $p->{Commit}..$c->{Commit}\n";
3434         next unless $p->{Nontrivial};
3435
3436         my $cc = $c->{Commit};
3437
3438         my $commitdata = cmdoutput @git, qw(cat-file commit), $cc;
3439         $commitdata =~ m/\n\n/ or die "$c ?";
3440         $commitdata = $`;
3441         my $msg = $'; #';
3442         $commitdata =~ m/^author (.*) \d+ [-+0-9]+$/m or die "$cc ?";
3443         my $author = $1;
3444
3445         $msg =~ s/^(.*)\n*/$1\n/ or die "$cc $msg ?";
3446
3447         my $title = $1;
3448         my $patchname = $title;
3449         $patchname =~ s/[.:]$//;
3450         $patchname =~ y/ A-Z/-a-z/;
3451         $patchname =~ y/-a-z0-9_.+=~//cd;
3452         $patchname =~ s/^\W/x-$&/;
3453         $patchname = substr($patchname,0,40);
3454         my $index;
3455         for ($index='';
3456              stat "debian/patches/$patchname$index";
3457              $index++) { }
3458         $!==ENOENT or die "$patchname$index $!";
3459
3460         runcmd @git, qw(checkout -q), $cc;
3461
3462         # We use the tip's changelog so that dpkg-source doesn't
3463         # produce complaining messages from dpkg-parsechangelog.  None
3464         # of the information dpkg-source gets from the changelog is
3465         # actually relevant - it gets put into the original message
3466         # which dpkg-source provides our stunt editor, and then
3467         # overwritten.
3468         runcmd @git, qw(checkout -q), $target, qw(debian/changelog);
3469
3470         quiltify_dpkg_commit "$patchname$index", $author, $msg,
3471             "X-Dgit-Generated: $clogp->{Version} $cc\n";
3472
3473         runcmd @git, qw(checkout -q), $cc, qw(debian/changelog);
3474     }
3475
3476     runcmd @git, qw(checkout -q master);
3477 }
3478
3479 sub build_maybe_quilt_fixup () {
3480     my ($format,$fopts) = get_source_format;
3481     return unless madformat $format;
3482     # sigh
3483
3484     check_for_vendor_patches();
3485
3486     my $clogp = parsechangelog();
3487     my $headref = git_rev_parse('HEAD');
3488
3489     prep_ud();
3490     changedir $ud;
3491
3492     my $upstreamversion=$version;
3493     $upstreamversion =~ s/-[^-]*$//;
3494
3495     if ($fopts->{'single-debian-patch'}) {
3496         quilt_fixup_singlepatch($clogp, $headref, $upstreamversion);
3497     } else {
3498         quilt_fixup_multipatch($clogp, $headref, $upstreamversion);
3499     }
3500
3501     die 'bug' if $split_brain && !$need_split_build_invocation;
3502
3503     changedir '../../../..';
3504     runcmd_ordryrun_local
3505         @git, qw(pull --ff-only -q .git/dgit/unpack/work master);
3506 }
3507
3508 sub quilt_fixup_mkwork ($) {
3509     my ($headref) = @_;
3510
3511     mkdir "work" or die $!;
3512     changedir "work";
3513     mktree_in_ud_here();
3514     runcmd @git, qw(reset -q --hard), $headref;
3515 }
3516
3517 sub quilt_fixup_linkorigs ($$) {
3518     my ($upstreamversion, $fn) = @_;
3519     # calls $fn->($leafname);
3520
3521     foreach my $f (<../../../../*>) { #/){
3522         my $b=$f; $b =~ s{.*/}{};
3523         {
3524             local ($debuglevel) = $debuglevel-1;
3525             printdebug "QF linkorigs $b, $f ?\n";
3526         }
3527         next unless is_orig_file $b, srcfn $upstreamversion,'';
3528         printdebug "QF linkorigs $b, $f Y\n";
3529         link_ltarget $f, $b or die "$b $!";
3530         $fn->($b);
3531     }
3532 }
3533
3534 sub quilt_fixup_delete_pc () {
3535     runcmd @git, qw(rm -rqf .pc);
3536     commit_admin "Commit removal of .pc (quilt series tracking data)";
3537 }
3538
3539 sub quilt_fixup_singlepatch ($$$) {
3540     my ($clogp, $headref, $upstreamversion) = @_;
3541
3542     progress "starting quiltify (single-debian-patch)";
3543
3544     # dpkg-source --commit generates new patches even if
3545     # single-debian-patch is in debian/source/options.  In order to
3546     # get it to generate debian/patches/debian-changes, it is
3547     # necessary to build the source package.
3548
3549     quilt_fixup_linkorigs($upstreamversion, sub { });
3550     quilt_fixup_mkwork($headref);
3551
3552     rmtree("debian/patches");
3553
3554     runcmd @dpkgsource, qw(-b .);
3555     chdir "..";
3556     runcmd @dpkgsource, qw(-x), (srcfn $version, ".dsc");
3557     rename srcfn("$upstreamversion", "/debian/patches"), 
3558            "work/debian/patches";
3559
3560     chdir "work";
3561     commit_quilty_patch();
3562 }
3563
3564 sub quilt_make_fake_dsc ($) {
3565     my ($upstreamversion) = @_;
3566
3567     my $fakeversion="$upstreamversion-~~DGITFAKE";
3568
3569     my $fakedsc=new IO::File 'fake.dsc', '>' or die $!;
3570     print $fakedsc <<END or die $!;
3571 Format: 3.0 (quilt)
3572 Source: $package
3573 Version: $fakeversion
3574 Files:
3575 END
3576
3577     my $dscaddfile=sub {
3578         my ($b) = @_;
3579         
3580         my $md = new Digest::MD5;
3581
3582         my $fh = new IO::File $b, '<' or die "$b $!";
3583         stat $fh or die $!;
3584         my $size = -s _;
3585
3586         $md->addfile($fh);
3587         print $fakedsc " ".$md->hexdigest." $size $b\n" or die $!;
3588     };
3589
3590     quilt_fixup_linkorigs($upstreamversion, $dscaddfile);
3591
3592     my @files=qw(debian/source/format debian/rules
3593                  debian/control debian/changelog);
3594     foreach my $maybe (qw(debian/patches debian/source/options
3595                           debian/tests/control)) {
3596         next unless stat_exists "../../../$maybe";
3597         push @files, $maybe;
3598     }
3599
3600     my $debtar= srcfn $fakeversion,'.debian.tar.gz';
3601     runcmd qw(env GZIP=-1n tar -zcf), "./$debtar", qw(-C ../../..), @files;
3602
3603     $dscaddfile->($debtar);
3604     close $fakedsc or die $!;
3605 }
3606
3607 sub quilt_check_splitbrain_cache ($$) {
3608     my ($headref, $upstreamversion) = @_;
3609     # Called only if we are in (potentially) split brain mode.
3610     # Called in $ud.
3611     # Computes the cache key and looks in the cache.
3612     # Returns ($dgit_view_commitid, $cachekey) or (undef, $cachekey)
3613
3614     my $splitbrain_cachekey;
3615     
3616     progress
3617  "dgit: split brain (separate dgit view) may be needed (--quilt=$quilt_mode).";
3618     # we look in the reflog of dgit-intern/quilt-cache
3619     # we look for an entry whose message is the key for the cache lookup
3620     my @cachekey = (qw(dgit), $our_version);
3621     push @cachekey, $upstreamversion;
3622     push @cachekey, $quilt_mode;
3623     push @cachekey, $headref;
3624
3625     push @cachekey, hashfile('fake.dsc');
3626
3627     my $srcshash = Digest::SHA->new(256);
3628     my %sfs = ( %INC, '$0(dgit)' => $0 );
3629     foreach my $sfk (sort keys %sfs) {
3630         next unless m/^\$0\b/ || m{^Debian/Dgit\b};
3631         $srcshash->add($sfk,"  ");
3632         $srcshash->add(hashfile($sfs{$sfk}));
3633         $srcshash->add("\n");
3634     }
3635     push @cachekey, $srcshash->hexdigest();
3636     $splitbrain_cachekey = "@cachekey";
3637
3638     my @cmd = (@git, qw(reflog), '--pretty=format:%H %gs',
3639                $splitbraincache);
3640     printdebug "splitbrain cachekey $splitbrain_cachekey\n";
3641     debugcmd "|(probably)",@cmd;
3642     my $child = open GC, "-|";  defined $child or die $!;
3643     if (!$child) {
3644         chdir '../../..' or die $!;
3645         if (!stat ".git/logs/refs/$splitbraincache") {
3646             $! == ENOENT or die $!;
3647             printdebug ">(no reflog)\n";
3648             exit 0;
3649         }
3650         exec @cmd; die $!;
3651     }
3652     while (<GC>) {
3653         chomp;
3654         printdebug ">| ", $_, "\n" if $debuglevel > 1;
3655         next unless m/^(\w+) (\S.*\S)$/ && $2 eq $splitbrain_cachekey;
3656             
3657         my $cachehit = $1;
3658         quilt_fixup_mkwork($headref);
3659         if ($cachehit ne $headref) {
3660             progress "dgit view: found cached (commit id $cachehit)";
3661             runcmd @git, qw(checkout -q -b dgit-view), $cachehit;
3662             $split_brain = 1;
3663             return ($cachehit, $splitbrain_cachekey);
3664         }
3665         progress "dgit view: found cached, no changes required";
3666         return ($headref, $splitbrain_cachekey);
3667     }
3668     die $! if GC->error;
3669     failedcmd unless close GC;
3670
3671     printdebug "splitbrain cache miss\n";
3672     return (undef, $splitbrain_cachekey);
3673 }
3674
3675 sub quilt_fixup_multipatch ($$$) {
3676     my ($clogp, $headref, $upstreamversion) = @_;
3677
3678     progress "examining quilt state (multiple patches, $quilt_mode mode)";
3679
3680     # Our objective is:
3681     #  - honour any existing .pc in case it has any strangeness
3682     #  - determine the git commit corresponding to the tip of
3683     #    the patch stack (if there is one)
3684     #  - if there is such a git commit, convert each subsequent
3685     #    git commit into a quilt patch with dpkg-source --commit
3686     #  - otherwise convert all the differences in the tree into
3687     #    a single git commit
3688     #
3689     # To do this we:
3690
3691     # Our git tree doesn't necessarily contain .pc.  (Some versions of
3692     # dgit would include the .pc in the git tree.)  If there isn't
3693     # one, we need to generate one by unpacking the patches that we
3694     # have.
3695     #
3696     # We first look for a .pc in the git tree.  If there is one, we
3697     # will use it.  (This is not the normal case.)
3698     #
3699     # Otherwise need to regenerate .pc so that dpkg-source --commit
3700     # can work.  We do this as follows:
3701     #     1. Collect all relevant .orig from parent directory
3702     #     2. Generate a debian.tar.gz out of
3703     #         debian/{patches,rules,source/format,source/options}
3704     #     3. Generate a fake .dsc containing just these fields:
3705     #          Format Source Version Files
3706     #     4. Extract the fake .dsc
3707     #        Now the fake .dsc has a .pc directory.
3708     # (In fact we do this in every case, because in future we will
3709     # want to search for a good base commit for generating patches.)
3710     #
3711     # Then we can actually do the dpkg-source --commit
3712     #     1. Make a new working tree with the same object
3713     #        store as our main tree and check out the main
3714     #        tree's HEAD.
3715     #     2. Copy .pc from the fake's extraction, if necessary
3716     #     3. Run dpkg-source --commit
3717     #     4. If the result has changes to debian/, then
3718     #          - git-add them them
3719     #          - git-add .pc if we had a .pc in-tree
3720     #          - git-commit
3721     #     5. If we had a .pc in-tree, delete it, and git-commit
3722     #     6. Back in the main tree, fast forward to the new HEAD
3723
3724     # Another situation we may have to cope with is gbp-style
3725     # patches-unapplied trees.
3726     #
3727     # We would want to detect these, so we know to escape into
3728     # quilt_fixup_gbp.  However, this is in general not possible.
3729     # Consider a package with a one patch which the dgit user reverts
3730     # (with git-revert or the moral equivalent).
3731     #
3732     # That is indistinguishable in contents from a patches-unapplied
3733     # tree.  And looking at the history to distinguish them is not
3734     # useful because the user might have made a confusing-looking git
3735     # history structure (which ought to produce an error if dgit can't
3736     # cope, not a silent reintroduction of an unwanted patch).
3737     #
3738     # So gbp users will have to pass an option.  But we can usually
3739     # detect their failure to do so: if the tree is not a clean
3740     # patches-applied tree, quilt linearisation fails, but the tree
3741     # _is_ a clean patches-unapplied tree, we can suggest that maybe
3742     # they want --quilt=unapplied.
3743     #
3744     # To help detect this, when we are extracting the fake dsc, we
3745     # first extract it with --skip-patches, and then apply the patches
3746     # afterwards with dpkg-source --before-build.  That lets us save a
3747     # tree object corresponding to .origs.
3748
3749     my $splitbrain_cachekey;
3750
3751     quilt_make_fake_dsc($upstreamversion);
3752
3753     if (quiltmode_splitbrain()) {
3754         my $cachehit;
3755         ($cachehit, $splitbrain_cachekey) =
3756             quilt_check_splitbrain_cache($headref, $upstreamversion);
3757         return if $cachehit;
3758     }
3759
3760     runcmd qw(sh -ec),
3761         'exec dpkg-source --no-check --skip-patches -x fake.dsc >/dev/null';
3762
3763     my $fakexdir= $package.'-'.(stripepoch $upstreamversion);
3764     rename $fakexdir, "fake" or die "$fakexdir $!";
3765
3766     changedir 'fake';
3767
3768     remove_stray_gits();
3769     mktree_in_ud_here();
3770
3771     rmtree '.pc';
3772
3773     runcmd @git, qw(add -Af .);
3774     my $unapplied=git_write_tree();
3775     printdebug "fake orig tree object $unapplied\n";
3776
3777     ensuredir '.pc';
3778
3779     runcmd qw(sh -ec),
3780         'exec dpkg-source --before-build . >/dev/null';
3781
3782     changedir '..';
3783
3784     quilt_fixup_mkwork($headref);
3785
3786     my $mustdeletepc=0;
3787     if (stat_exists ".pc") {
3788         -d _ or die;
3789         progress "Tree already contains .pc - will use it then delete it.";
3790         $mustdeletepc=1;
3791     } else {
3792         rename '../fake/.pc','.pc' or die $!;
3793     }
3794
3795     changedir '../fake';
3796     rmtree '.pc';
3797     runcmd @git, qw(add -Af .);
3798     my $oldtiptree=git_write_tree();
3799     printdebug "fake o+d/p tree object $unapplied\n";
3800     changedir '../work';
3801
3802
3803     # We calculate some guesswork now about what kind of tree this might
3804     # be.  This is mostly for error reporting.
3805
3806     my %editedignores;
3807     my $diffbits = {
3808         # H = user's HEAD
3809         # O = orig, without patches applied
3810         # A = "applied", ie orig with H's debian/patches applied
3811         H2O => quiltify_trees_differ($headref,  $unapplied, 1,\%editedignores),
3812         H2A => quiltify_trees_differ($headref,  $oldtiptree,1),
3813         O2A => quiltify_trees_differ($unapplied,$oldtiptree,1),
3814     };
3815
3816     my @dl;
3817     foreach my $b (qw(01 02)) {
3818         foreach my $v (qw(H2O O2A H2A)) {
3819             push @dl, ($diffbits->{$v} & $b) ? '##' : '==';
3820         }
3821     }
3822     printdebug "differences \@dl @dl.\n";
3823
3824     progress sprintf
3825 "$us: quilt differences: src:  %s orig %s     gitignores:  %s orig %s\n".
3826 "$us: quilt differences:      HEAD %s o+d/p               HEAD %s o+d/p",
3827                              $dl[0], $dl[1],              $dl[3], $dl[4],
3828                                  $dl[2],                     $dl[5];
3829
3830     my @failsuggestion;
3831     if (!($diffbits->{H2O} & $diffbits->{O2A})) {
3832         push @failsuggestion, "This might be a patches-unapplied branch.";
3833     }  elsif (!($diffbits->{H2A} & $diffbits->{O2A})) {
3834         push @failsuggestion, "This might be a patches-applied branch.";
3835     }
3836     push @failsuggestion, "Maybe you need to specify one of".
3837         " --quilt=gbp --quilt=dpm --quilt=unapplied ?";
3838
3839     if (quiltmode_splitbrain()) {
3840         quiltify_splitbrain($clogp, $unapplied, $headref,
3841                             $diffbits, \%editedignores,
3842                             $splitbrain_cachekey);
3843         return;
3844     }
3845
3846     progress "starting quiltify (multiple patches, $quilt_mode mode)";
3847     quiltify($clogp,$headref,$oldtiptree,\@failsuggestion);
3848
3849     if (!open P, '>>', ".pc/applied-patches") {
3850         $!==&ENOENT or die $!;
3851     } else {
3852         close P;
3853     }
3854
3855     commit_quilty_patch();
3856
3857     if ($mustdeletepc) {
3858         quilt_fixup_delete_pc();
3859     }
3860 }
3861
3862 sub quilt_fixup_editor () {
3863     my $descfn = $ENV{$fakeeditorenv};
3864     my $editing = $ARGV[$#ARGV];
3865     open I1, '<', $descfn or die "$descfn: $!";
3866     open I2, '<', $editing or die "$editing: $!";
3867     unlink $editing or die "$editing: $!";
3868     open O, '>', $editing or die "$editing: $!";
3869     while (<I1>) { print O or die $!; } I1->error and die $!;
3870     my $copying = 0;
3871     while (<I2>) {
3872         $copying ||= m/^\-\-\- /;
3873         next unless $copying;
3874         print O or die $!;
3875     }
3876     I2->error and die $!;
3877     close O or die $1;
3878     exit 0;
3879 }
3880
3881 sub maybe_apply_patches_dirtily () {
3882     return unless $quilt_mode =~ m/gbp|unapplied/;
3883     print STDERR <<END or die $!;
3884
3885 dgit: Building, or cleaning with rules target, in patches-unapplied tree.
3886 dgit: Have to apply the patches - making the tree dirty.
3887 dgit: (Consider specifying --clean=git and (or) using dgit sbuild.)
3888
3889 END
3890     $patches_applied_dirtily = 01;
3891     $patches_applied_dirtily |= 02 unless stat_exists '.pc';
3892     runcmd qw(dpkg-source --before-build .);
3893 }
3894
3895 sub maybe_unapply_patches_again () {
3896     progress "dgit: Unapplying patches again to tidy up the tree."
3897         if $patches_applied_dirtily;
3898     runcmd qw(dpkg-source --after-build .)
3899         if $patches_applied_dirtily & 01;
3900     rmtree '.pc'
3901         if $patches_applied_dirtily & 02;
3902     $patches_applied_dirtily = 0;
3903 }
3904
3905 #----- other building -----
3906
3907 our $clean_using_builder;
3908 # ^ tree is to be cleaned by dpkg-source's builtin idea that it should
3909 #   clean the tree before building (perhaps invoked indirectly by
3910 #   whatever we are using to run the build), rather than separately
3911 #   and explicitly by us.
3912
3913 sub clean_tree () {
3914     return if $clean_using_builder;
3915     if ($cleanmode eq 'dpkg-source') {
3916         maybe_apply_patches_dirtily();
3917         runcmd_ordryrun_local @dpkgbuildpackage, qw(-T clean);
3918     } elsif ($cleanmode eq 'dpkg-source-d') {
3919         maybe_apply_patches_dirtily();
3920         runcmd_ordryrun_local @dpkgbuildpackage, qw(-d -T clean);
3921     } elsif ($cleanmode eq 'git') {
3922         runcmd_ordryrun_local @git, qw(clean -xdf);
3923     } elsif ($cleanmode eq 'git-ff') {
3924         runcmd_ordryrun_local @git, qw(clean -xdff);
3925     } elsif ($cleanmode eq 'check') {
3926         my $leftovers = cmdoutput @git, qw(clean -xdn);
3927         if (length $leftovers) {
3928             print STDERR $leftovers, "\n" or die $!;
3929             fail "tree contains uncommitted files and --clean=check specified";
3930         }
3931     } elsif ($cleanmode eq 'none') {
3932     } else {
3933         die "$cleanmode ?";
3934     }
3935 }
3936
3937 sub cmd_clean () {
3938     badusage "clean takes no additional arguments" if @ARGV;
3939     notpushing();
3940     clean_tree();
3941     maybe_unapply_patches_again();
3942 }
3943
3944 sub build_prep () {
3945     notpushing();
3946     badusage "-p is not allowed when building" if defined $package;
3947     check_not_dirty();
3948     clean_tree();
3949     my $clogp = parsechangelog();
3950     $isuite = getfield $clogp, 'Distribution';
3951     $package = getfield $clogp, 'Source';
3952     $version = getfield $clogp, 'Version';
3953     build_maybe_quilt_fixup();
3954     if ($rmchanges) {
3955         my $pat = changespat $version;
3956         foreach my $f (glob "$buildproductsdir/$pat") {
3957             if (act_local()) {
3958                 unlink $f or fail "remove old changes file $f: $!";
3959             } else {
3960                 progress "would remove $f";
3961             }
3962         }
3963     }
3964 }
3965
3966 sub changesopts_initial () {
3967     my @opts =@changesopts[1..$#changesopts];
3968 }
3969
3970 sub changesopts_version () {
3971     if (!defined $changes_since_version) {
3972         my @vsns = archive_query('archive_query');
3973         my @quirk = access_quirk();
3974         if ($quirk[0] eq 'backports') {
3975             local $isuite = $quirk[2];
3976             local $csuite;
3977             canonicalise_suite();
3978             push @vsns, archive_query('archive_query');
3979         }
3980         if (@vsns) {
3981             @vsns = map { $_->[0] } @vsns;
3982             @vsns = sort { -version_compare($a, $b) } @vsns;
3983             $changes_since_version = $vsns[0];
3984             progress "changelog will contain changes since $vsns[0]";
3985         } else {
3986             $changes_since_version = '_';
3987             progress "package seems new, not specifying -v<version>";
3988         }
3989     }
3990     if ($changes_since_version ne '_') {
3991         return ("-v$changes_since_version");
3992     } else {
3993         return ();
3994     }
3995 }
3996
3997 sub changesopts () {
3998     return (changesopts_initial(), changesopts_version());
3999 }
4000
4001 sub massage_dbp_args ($;$) {
4002     my ($cmd,$xargs) = @_;
4003     # We need to:
4004     #
4005     #  - if we're going to split the source build out so we can
4006     #    do strange things to it, massage the arguments to dpkg-buildpackage
4007     #    so that the main build doessn't build source (or add an argument
4008     #    to stop it building source by default).
4009     #
4010     #  - add -nc to stop dpkg-source cleaning the source tree,
4011     #    unless we're not doing a split build and want dpkg-source
4012     #    as cleanmode, in which case we can do nothing
4013     #
4014     # return values:
4015     #    0 - source will NOT need to be built separately by caller
4016     #   +1 - source will need to be built separately by caller
4017     #   +2 - source will need to be built separately by caller AND
4018     #        dpkg-buildpackage should not in fact be run at all!
4019     debugcmd '#massaging#', @$cmd if $debuglevel>1;
4020 #print STDERR "MASS0 ",Dumper($cmd, $xargs, $need_split_build_invocation);
4021     if ($cleanmode eq 'dpkg-source' && !$need_split_build_invocation) {
4022         $clean_using_builder = 1;
4023         return 0;
4024     }
4025     # -nc has the side effect of specifying -b if nothing else specified
4026     # and some combinations of -S, -b, et al, are errors, rather than
4027     # later simply overriding earlie.  So we need to:
4028     #  - search the command line for these options
4029     #  - pick the last one
4030     #  - perhaps add our own as a default
4031     #  - perhaps adjust it to the corresponding non-source-building version
4032     my $dmode = '-F';
4033     foreach my $l ($cmd, $xargs) {
4034         next unless $l;
4035         @$l = grep { !(m/^-[SgGFABb]$/s and $dmode=$_) } @$l;
4036     }
4037     push @$cmd, '-nc';
4038 #print STDERR "MASS1 ",Dumper($cmd, $xargs, $dmode);
4039     my $r = 0;
4040     if ($need_split_build_invocation) {
4041         printdebug "massage split $dmode.\n";
4042         $r = $dmode =~ m/[S]/     ? +2 :
4043              $dmode =~ y/gGF/ABb/ ? +1 :
4044              $dmode =~ m/[ABb]/   ?  0 :
4045              die "$dmode ?";
4046     }
4047     printdebug "massage done $r $dmode.\n";
4048     push @$cmd, $dmode;
4049 #print STDERR "MASS2 ",Dumper($cmd, $xargs, $r);
4050     return $r;
4051 }
4052
4053 sub cmd_build {
4054     my @dbp = (@dpkgbuildpackage, qw(-us -uc), changesopts_initial(), @ARGV);
4055     my $wantsrc = massage_dbp_args \@dbp;
4056     if ($wantsrc > 0) {
4057         build_source();
4058     } else {
4059         build_prep();
4060     }
4061     if ($wantsrc < 2) {
4062         push @dbp, changesopts_version();
4063         maybe_apply_patches_dirtily();
4064         runcmd_ordryrun_local @dbp;
4065     }
4066     maybe_unapply_patches_again();
4067     printdone "build successful\n";
4068 }
4069
4070 sub cmd_gbp_build {
4071     my @dbp = @dpkgbuildpackage;
4072
4073     my $wantsrc = massage_dbp_args \@dbp, \@ARGV;
4074
4075     my @cmd;
4076     if (length executable_on_path('git-buildpackage')) {
4077         @cmd = qw(git-buildpackage);
4078     } else {
4079         @cmd = qw(gbp buildpackage);
4080     }
4081     push @cmd, (qw(-us -uc --git-no-sign-tags), "--git-builder=@dbp");
4082
4083     if ($wantsrc > 0) {
4084         build_source();
4085     } else {
4086         if (!$clean_using_builder) {
4087             push @cmd, '--git-cleaner=true';
4088         }
4089         build_prep();
4090     }
4091     maybe_unapply_patches_again();
4092     if ($wantsrc < 2) {
4093         unless (grep { m/^--git-debian-branch|^--git-ignore-branch/ } @ARGV) {
4094             canonicalise_suite();
4095             push @cmd, "--git-debian-branch=".lbranch();
4096         }
4097         push @cmd, changesopts();
4098         runcmd_ordryrun_local @cmd, @ARGV;
4099     }
4100     printdone "build successful\n";
4101 }
4102 sub cmd_git_build { cmd_gbp_build(); } # compatibility with <= 1.0
4103
4104 sub build_source {
4105     my $our_cleanmode = $cleanmode;
4106     if ($need_split_build_invocation) {
4107         # Pretend that clean is being done some other way.  This
4108         # forces us not to try to use dpkg-buildpackage to clean and
4109         # build source all in one go; and instead we run dpkg-source
4110         # (and build_prep() will do the clean since $clean_using_builder
4111         # is false).
4112         $our_cleanmode = 'ELSEWHERE';
4113     }
4114     if ($our_cleanmode =~ m/^dpkg-source/) {
4115         # dpkg-source invocation (below) will clean, so build_prep shouldn't
4116         $clean_using_builder = 1;
4117     }
4118     build_prep();
4119     $sourcechanges = changespat $version,'source';
4120     if (act_local()) {
4121         unlink "../$sourcechanges" or $!==ENOENT
4122             or fail "remove $sourcechanges: $!";
4123     }
4124     $dscfn = dscfn($version);
4125     if ($our_cleanmode eq 'dpkg-source') {
4126         maybe_apply_patches_dirtily();
4127         runcmd_ordryrun_local @dpkgbuildpackage, qw(-us -uc -S),
4128             changesopts();
4129     } elsif ($our_cleanmode eq 'dpkg-source-d') {
4130         maybe_apply_patches_dirtily();
4131         runcmd_ordryrun_local @dpkgbuildpackage, qw(-us -uc -S -d),
4132             changesopts();
4133     } else {
4134         my @cmd = (@dpkgsource, qw(-b --));
4135         if ($split_brain) {
4136             changedir $ud;
4137             runcmd_ordryrun_local @cmd, "work";
4138             my @udfiles = <${package}_*>;
4139             changedir "../../..";
4140             foreach my $f (@udfiles) {
4141                 printdebug "source copy, found $f\n";
4142                 next unless
4143                     $f eq $dscfn or
4144                     ($f =~ m/\.debian\.tar(?:\.\w+)$/ &&
4145                      $f eq srcfn($version, $&));
4146                 printdebug "source copy, found $f - renaming\n";
4147                 rename "$ud/$f", "../$f" or $!==ENOENT
4148                     or fail "put in place new source file ($f): $!";
4149             }
4150         } else {
4151             my $pwd = must_getcwd();
4152             my $leafdir = basename $pwd;
4153             changedir "..";
4154             runcmd_ordryrun_local @cmd, $leafdir;
4155             changedir $pwd;
4156         }
4157         runcmd_ordryrun_local qw(sh -ec),
4158             'exec >$1; shift; exec "$@"','x',
4159             "../$sourcechanges",
4160             @dpkggenchanges, qw(-S), changesopts();
4161     }
4162 }
4163
4164 sub cmd_build_source {
4165     badusage "build-source takes no additional arguments" if @ARGV;
4166     build_source();
4167     maybe_unapply_patches_again();
4168     printdone "source built, results in $dscfn and $sourcechanges";
4169 }
4170
4171 sub cmd_sbuild {
4172     build_source();
4173     my $pat = changespat $version;
4174     if (!$rmchanges) {
4175         my @unwanted = map { s#^\.\./##; $_; } glob "../$pat";
4176         @unwanted = grep { $_ ne changespat $version,'source' } @unwanted;
4177         fail "changes files other than source matching $pat".
4178             " already present (@unwanted);".
4179             " building would result in ambiguity about the intended results"
4180             if @unwanted;
4181     }
4182     my $wasdir = must_getcwd();
4183     changedir "..";
4184     if (act_local()) {
4185         stat_exists $dscfn or fail "$dscfn (in parent directory): $!";
4186         stat_exists $sourcechanges
4187             or fail "$sourcechanges (in parent directory): $!";
4188     }
4189     runcmd_ordryrun_local @sbuild, qw(-d), $isuite, @ARGV, $dscfn;
4190     my @changesfiles = glob $pat;
4191     @changesfiles = sort {
4192         ($b =~ m/_source\.changes$/ <=> $a =~ m/_source\.changes$/)
4193             or $a cmp $b
4194     } @changesfiles;
4195     fail "wrong number of different changes files (@changesfiles)"
4196         unless @changesfiles==2;
4197     my $binchanges = parsecontrol($changesfiles[1], "binary changes file");
4198     foreach my $l (split /\n/, getfield $binchanges, 'Files') {
4199         fail "$l found in binaries changes file $binchanges"
4200             if $l =~ m/\.dsc$/;
4201     }
4202     runcmd_ordryrun_local @mergechanges, @changesfiles;
4203     my $multichanges = changespat $version,'multi';
4204     if (act_local()) {
4205         stat_exists $multichanges or fail "$multichanges: $!";
4206         foreach my $cf (glob $pat) {
4207             next if $cf eq $multichanges;
4208             rename "$cf", "$cf.inmulti" or fail "$cf\{,.inmulti}: $!";
4209         }
4210     }
4211     changedir $wasdir;
4212     maybe_unapply_patches_again();
4213     printdone "build successful, results in $multichanges\n" or die $!;
4214 }    
4215
4216 sub cmd_quilt_fixup {
4217     badusage "incorrect arguments to dgit quilt-fixup" if @ARGV;
4218     my $clogp = parsechangelog();
4219     $version = getfield $clogp, 'Version';
4220     $package = getfield $clogp, 'Source';
4221     check_not_dirty();
4222     clean_tree();
4223     build_maybe_quilt_fixup();
4224 }
4225
4226 sub cmd_archive_api_query {
4227     badusage "need only 1 subpath argument" unless @ARGV==1;
4228     my ($subpath) = @ARGV;
4229     my @cmd = archive_api_query_cmd($subpath);
4230     debugcmd ">",@cmd;
4231     exec @cmd or fail "exec curl: $!\n";
4232 }
4233
4234 sub cmd_clone_dgit_repos_server {
4235     badusage "need destination argument" unless @ARGV==1;
4236     my ($destdir) = @ARGV;
4237     $package = '_dgit-repos-server';
4238     my @cmd = (@git, qw(clone), access_giturl(), $destdir);
4239     debugcmd ">",@cmd;
4240     exec @cmd or fail "exec git clone: $!\n";
4241 }
4242
4243 sub cmd_setup_mergechangelogs {
4244     badusage "no arguments allowed to dgit setup-mergechangelogs" if @ARGV;
4245     setup_mergechangelogs(1);
4246 }
4247
4248 sub cmd_setup_useremail {
4249     badusage "no arguments allowed to dgit setup-mergechangelogs" if @ARGV;
4250     setup_useremail(1);
4251 }
4252
4253 sub cmd_setup_new_tree {
4254     badusage "no arguments allowed to dgit setup-tree" if @ARGV;
4255     setup_new_tree();
4256 }
4257
4258 #---------- argument parsing and main program ----------
4259
4260 sub cmd_version {
4261     print "dgit version $our_version\n" or die $!;
4262     exit 0;
4263 }
4264
4265 our (%valopts_long, %valopts_short);
4266 our @rvalopts;
4267
4268 sub defvalopt ($$$$) {
4269     my ($long,$short,$val_re,$how) = @_;
4270     my $oi = { Long => $long, Short => $short, Re => $val_re, How => $how };
4271     $valopts_long{$long} = $oi;
4272     $valopts_short{$short} = $oi;
4273     # $how subref should:
4274     #   do whatever assignemnt or thing it likes with $_[0]
4275     #   if the option should not be passed on to remote, @rvalopts=()
4276     # or $how can be a scalar ref, meaning simply assign the value
4277 }
4278
4279 defvalopt '--since-version', '-v', '[^_]+|_', \$changes_since_version;
4280 defvalopt '--distro',        '-d', '.+',      \$idistro;
4281 defvalopt '',                '-k', '.+',      \$keyid;
4282 defvalopt '--existing-package','', '.*',      \$existing_package;
4283 defvalopt '--build-products-dir','','.*',     \$buildproductsdir;
4284 defvalopt '--clean',       '', $cleanmode_re, \$cleanmode;
4285 defvalopt '--quilt',     '', $quilt_modes_re, \$quilt_mode;
4286
4287 defvalopt '', '-c', '.*=.*', sub { push @git, '-c', @_; };
4288
4289 defvalopt '', '-C', '.+', sub {
4290     ($changesfile) = (@_);
4291     if ($changesfile =~ s#^(.*)/##) {
4292         $buildproductsdir = $1;
4293     }
4294 };
4295
4296 defvalopt '--initiator-tempdir','','.*', sub {
4297     ($initiator_tempdir) = (@_);
4298     $initiator_tempdir =~ m#^/# or
4299         badusage "--initiator-tempdir must be used specify an".
4300         " absolute, not relative, directory."
4301 };
4302
4303 sub parseopts () {
4304     my $om;
4305
4306     if (defined $ENV{'DGIT_SSH'}) {
4307         @ssh = string_to_ssh $ENV{'DGIT_SSH'};
4308     } elsif (defined $ENV{'GIT_SSH'}) {
4309         @ssh = ($ENV{'GIT_SSH'});
4310     }
4311
4312     my $oi;
4313     my $val;
4314     my $valopt = sub {
4315         my ($what) = @_;
4316         @rvalopts = ($_);
4317         if (!defined $val) {
4318             badusage "$what needs a value" unless @ARGV;
4319             $val = shift @ARGV;
4320             push @rvalopts, $val;
4321         }
4322         badusage "bad value \`$val' for $what" unless
4323             $val =~ m/^$oi->{Re}$(?!\n)/s;
4324         my $how = $oi->{How};
4325         if (ref($how) eq 'SCALAR') {
4326             $$how = $val;
4327         } else {
4328             $how->($val);
4329         }
4330         push @ropts, @rvalopts;
4331     };
4332
4333     while (@ARGV) {
4334         last unless $ARGV[0] =~ m/^-/;
4335         $_ = shift @ARGV;
4336         last if m/^--?$/;
4337         if (m/^--/) {
4338             if (m/^--dry-run$/) {
4339                 push @ropts, $_;
4340                 $dryrun_level=2;
4341             } elsif (m/^--damp-run$/) {
4342                 push @ropts, $_;
4343                 $dryrun_level=1;
4344             } elsif (m/^--no-sign$/) {
4345                 push @ropts, $_;
4346                 $sign=0;
4347             } elsif (m/^--help$/) {
4348                 cmd_help();
4349             } elsif (m/^--version$/) {
4350                 cmd_version();
4351             } elsif (m/^--new$/) {
4352                 push @ropts, $_;
4353                 $new_package=1;
4354             } elsif (m/^--([-0-9a-z]+)=(.+)/s &&
4355                      ($om = $opts_opt_map{$1}) &&
4356                      length $om->[0]) {
4357                 push @ropts, $_;
4358                 $om->[0] = $2;
4359             } elsif (m/^--([-0-9a-z]+):(.*)/s &&
4360                      !$opts_opt_cmdonly{$1} &&
4361                      ($om = $opts_opt_map{$1})) {
4362                 push @ropts, $_;
4363                 push @$om, $2;
4364             } elsif (m/^--ignore-dirty$/s) {
4365                 push @ropts, $_;
4366                 $ignoredirty = 1;
4367             } elsif (m/^--no-quilt-fixup$/s) {
4368                 push @ropts, $_;
4369                 $quilt_mode = 'nocheck';
4370             } elsif (m/^--no-rm-on-error$/s) {
4371                 push @ropts, $_;
4372                 $rmonerror = 0;
4373             } elsif (m/^--(no-)?rm-old-changes$/s) {
4374                 push @ropts, $_;
4375                 $rmchanges = !$1;
4376             } elsif (m/^--deliberately-($deliberately_re)$/s) {
4377                 push @ropts, $_;
4378                 push @deliberatelies, $&;
4379             } elsif (m/^--dgit-tag-format=(old|new)$/s) {
4380                 # undocumented, for testing
4381                 push @ropts, $_;
4382                 $tagformat_want = [ $1, 'command line', 1 ];
4383                 # 1 menas overrides distro configuration
4384             } elsif (m/^--always-split-source-build$/s) {
4385                 # undocumented, for testing
4386                 push @ropts, $_;
4387                 $need_split_build_invocation = 1;
4388             } elsif (m/^(--[-0-9a-z]+)(=|$)/ && ($oi = $valopts_long{$1})) {
4389                 $val = $2 ? $' : undef; #';
4390                 $valopt->($oi->{Long});
4391             } else {
4392                 badusage "unknown long option \`$_'";
4393             }
4394         } else {
4395             while (m/^-./s) {
4396                 if (s/^-n/-/) {
4397                     push @ropts, $&;
4398                     $dryrun_level=2;
4399                 } elsif (s/^-L/-/) {
4400                     push @ropts, $&;
4401                     $dryrun_level=1;
4402                 } elsif (s/^-h/-/) {
4403                     cmd_help();
4404                 } elsif (s/^-D/-/) {
4405                     push @ropts, $&;
4406                     $debuglevel++;
4407                     enabledebug();
4408                 } elsif (s/^-N/-/) {
4409                     push @ropts, $&;
4410                     $new_package=1;
4411                 } elsif (m/^-m/) {
4412                     push @ropts, $&;
4413                     push @changesopts, $_;
4414                     $_ = '';
4415                 } elsif (s/^-wn$//s) {
4416                     push @ropts, $&;
4417                     $cleanmode = 'none';
4418                 } elsif (s/^-wg$//s) {
4419                     push @ropts, $&;
4420                     $cleanmode = 'git';
4421                 } elsif (s/^-wgf$//s) {
4422                     push @ropts, $&;
4423                     $cleanmode = 'git-ff';
4424                 } elsif (s/^-wd$//s) {
4425                     push @ropts, $&;
4426                     $cleanmode = 'dpkg-source';
4427                 } elsif (s/^-wdd$//s) {
4428                     push @ropts, $&;
4429                     $cleanmode = 'dpkg-source-d';
4430                 } elsif (s/^-wc$//s) {
4431                     push @ropts, $&;
4432                     $cleanmode = 'check';
4433                 } elsif (m/^-[a-zA-Z]/ && ($oi = $valopts_short{$&})) {
4434                     $val = $'; #';
4435                     $val = undef unless length $val;
4436                     $valopt->($oi->{Short});
4437                     $_ = '';
4438                 } else {
4439                     badusage "unknown short option \`$_'";
4440                 }
4441             }
4442         }
4443     }
4444 }
4445
4446 sub finalise_opts_opts () {
4447     foreach my $k (keys %opts_opt_map) {
4448         my $om = $opts_opt_map{$k};
4449
4450         my $v = access_cfg("cmd-$k", 'RETURN-UNDEF');
4451         if (defined $v) {
4452             badcfg "cannot set command for $k"
4453                 unless length $om->[0];
4454             $om->[0] = $v;
4455         }
4456
4457         foreach my $c (access_cfg_cfgs("opts-$k")) {
4458             my $vl = $gitcfg{$c};
4459             printdebug "CL $c ",
4460                 ($vl ? join " ", map { shellquote } @$vl : ""),
4461                 "\n" if $debuglevel >= 4;
4462             next unless $vl;
4463             badcfg "cannot configure options for $k"
4464                 if $opts_opt_cmdonly{$k};
4465             my $insertpos = $opts_cfg_insertpos{$k};
4466             @$om = ( @$om[0..$insertpos-1],
4467                      @$vl,
4468                      @$om[$insertpos..$#$om] );
4469         }
4470     }
4471 }
4472
4473 if ($ENV{$fakeeditorenv}) {
4474     git_slurp_config();
4475     quilt_fixup_editor();
4476 }
4477
4478 parseopts();
4479 git_slurp_config();
4480
4481 print STDERR "DRY RUN ONLY\n" if $dryrun_level > 1;
4482 print STDERR "DAMP RUN - WILL MAKE LOCAL (UNSIGNED) CHANGES\n"
4483     if $dryrun_level == 1;
4484 if (!@ARGV) {
4485     print STDERR $helpmsg or die $!;
4486     exit 8;
4487 }
4488 my $cmd = shift @ARGV;
4489 $cmd =~ y/-/_/;
4490
4491 if (!defined $rmchanges) {
4492     local $access_forpush;
4493     $rmchanges = access_cfg_bool(0, 'rm-old-changes');
4494 }
4495
4496 if (!defined $quilt_mode) {
4497     local $access_forpush;
4498     $quilt_mode = cfg('dgit.force.quilt-mode', 'RETURN-UNDEF')
4499         // access_cfg('quilt-mode', 'RETURN-UNDEF')
4500         // 'linear';
4501     $quilt_mode =~ m/^($quilt_modes_re)$/ 
4502         or badcfg "unknown quilt-mode \`$quilt_mode'";
4503     $quilt_mode = $1;
4504 }
4505
4506 $need_split_build_invocation ||= quiltmode_splitbrain();
4507
4508 if (!defined $cleanmode) {
4509     local $access_forpush;
4510     $cleanmode = access_cfg('clean-mode', 'RETURN-UNDEF');
4511     $cleanmode //= 'dpkg-source';
4512
4513     badcfg "unknown clean-mode \`$cleanmode'" unless
4514         $cleanmode =~ m/^($cleanmode_re)$(?!\n)/s;
4515 }
4516
4517 my $fn = ${*::}{"cmd_$cmd"};
4518 $fn or badusage "unknown operation $cmd";
4519 $fn->();