chiark / gitweb /
4f6d111b18c25fbe67356fad08c85d900c5bc483
[dgit.git] / dgit
1 #!/usr/bin/perl -w
2 # dgit
3 # Integration between git and Debian-style archives
4 #
5 # Copyright (C)2013-2016 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 Text::Glob qw(match_glob);
40 use Fcntl qw(:DEFAULT :flock);
41 use Carp;
42
43 use Debian::Dgit;
44
45 our $our_version = 'UNRELEASED'; ###substituted###
46 our $absurdity = undef; ###substituted###
47
48 our @rpushprotovsn_support = qw(4 3 2); # 4 is new tag format
49 our $protovsn;
50
51 our $isuite = 'unstable';
52 our $idistro;
53 our $package;
54 our @ropts;
55
56 our $sign = 1;
57 our $dryrun_level = 0;
58 our $changesfile;
59 our $buildproductsdir = '..';
60 our $new_package = 0;
61 our $ignoredirty = 0;
62 our $rmonerror = 1;
63 our @deliberatelies;
64 our %previously;
65 our $existing_package = 'dpkg';
66 our $cleanmode;
67 our $changes_since_version;
68 our $rmchanges;
69 our $overwrite_version; # undef: not specified; '': check changelog
70 our $quilt_mode;
71 our $quilt_modes_re = 'linear|smash|auto|nofix|nocheck|gbp|dpm|unapplied';
72 our $dodep14tag;
73 our $dodep14tag_re = 'want|no|always';
74 our $split_brain_save;
75 our $we_are_responder;
76 our $initiator_tempdir;
77 our $patches_applied_dirtily = 00;
78 our $tagformat_want;
79 our $tagformat;
80 our $tagformatfn;
81
82 our %forceopts = map { $_=>0 }
83     qw(unrepresentable unsupported-source-format
84        dsc-changes-mismatch changes-origs-exactly
85        import-gitapply-absurd
86        import-gitapply-no-absurd
87        import-dsc-with-dgit-field);
88
89 our %format_ok = map { $_=>1 } ("1.0","3.0 (native)","3.0 (quilt)");
90
91 our $suite_re = '[-+.0-9a-z]+';
92 our $cleanmode_re = 'dpkg-source(?:-d)?|git|git-ff|check|none';
93 our $orig_f_comp_re = 'orig(?:-[-0-9a-z]+)?';
94 our $orig_f_sig_re = '\\.(?:asc|gpg|pgp)';
95 our $orig_f_tail_re = "$orig_f_comp_re\\.tar(?:\\.\\w+)?(?:$orig_f_sig_re)?";
96
97 our $git_authline_re = '^([^<>]+) \<(\S+)\> (\d+ [-+]\d+)$';
98 our $splitbraincache = 'dgit-intern/quilt-cache';
99 our $rewritemap = 'dgit-rewrite/map';
100
101 our (@git) = qw(git);
102 our (@dget) = qw(dget);
103 our (@curl) = qw(curl);
104 our (@dput) = qw(dput);
105 our (@debsign) = qw(debsign);
106 our (@gpg) = qw(gpg);
107 our (@sbuild) = qw(sbuild);
108 our (@ssh) = 'ssh';
109 our (@dgit) = qw(dgit);
110 our (@aptget) = qw(apt-get);
111 our (@aptcache) = qw(apt-cache);
112 our (@dpkgbuildpackage) = qw(dpkg-buildpackage -i\.git/ -I.git);
113 our (@dpkgsource) = qw(dpkg-source -i\.git/ -I.git);
114 our (@dpkggenchanges) = qw(dpkg-genchanges);
115 our (@mergechanges) = qw(mergechanges -f);
116 our (@gbp_build) = ('');
117 our (@gbp_pq) = ('gbp pq');
118 our (@changesopts) = ('');
119
120 our %opts_opt_map = ('dget' => \@dget, # accept for compatibility
121                      'curl' => \@curl,
122                      'dput' => \@dput,
123                      'debsign' => \@debsign,
124                      'gpg' => \@gpg,
125                      'sbuild' => \@sbuild,
126                      'ssh' => \@ssh,
127                      'dgit' => \@dgit,
128                      'git' => \@git,
129                      'apt-get' => \@aptget,
130                      'apt-cache' => \@aptcache,
131                      'dpkg-source' => \@dpkgsource,
132                      'dpkg-buildpackage' => \@dpkgbuildpackage,
133                      'dpkg-genchanges' => \@dpkggenchanges,
134                      'gbp-build' => \@gbp_build,
135                      'gbp-pq' => \@gbp_pq,
136                      'ch' => \@changesopts,
137                      'mergechanges' => \@mergechanges);
138
139 our %opts_opt_cmdonly = ('gpg' => 1, 'git' => 1);
140 our %opts_cfg_insertpos = map {
141     $_,
142     scalar @{ $opts_opt_map{$_} }
143 } keys %opts_opt_map;
144
145 sub parseopts_late_defaults();
146
147 our $keyid;
148
149 autoflush STDOUT 1;
150
151 our $supplementary_message = '';
152 our $need_split_build_invocation = 0;
153 our $split_brain = 0;
154
155 END {
156     local ($@, $?);
157     print STDERR "! $_\n" foreach $supplementary_message =~ m/^.+$/mg;
158 }
159
160 our $remotename = 'dgit';
161 our @ourdscfield = qw(Dgit Vcs-Dgit-Master);
162 our $csuite;
163 our $instead_distro;
164
165 if (!defined $absurdity) {
166     $absurdity = $0;
167     $absurdity =~ s{/[^/]+$}{/absurd} or die;
168 }
169
170 sub debiantag ($$) {
171     my ($v,$distro) = @_;
172     return $tagformatfn->($v, $distro);
173 }
174
175 sub debiantag_maintview ($$) { 
176     my ($v,$distro) = @_;
177     return "$distro/".dep14_version_mangle $v;
178 }
179
180 sub madformat ($) { $_[0] eq '3.0 (quilt)' }
181
182 sub lbranch () { return "$branchprefix/$csuite"; }
183 my $lbranch_re = '^refs/heads/'.$branchprefix.'/([^/.]+)$';
184 sub lref () { return "refs/heads/".lbranch(); }
185 sub lrref () { return "refs/remotes/$remotename/".server_branch($csuite); }
186 sub rrref () { return server_ref($csuite); }
187
188 sub stripepoch ($) {
189     my ($vsn) = @_;
190     $vsn =~ s/^\d+\://;
191     return $vsn;
192 }
193
194 sub srcfn ($$) {
195     my ($vsn,$sfx) = @_;
196     return "${package}_".(stripepoch $vsn).$sfx
197 }
198
199 sub dscfn ($) {
200     my ($vsn) = @_;
201     return srcfn($vsn,".dsc");
202 }
203
204 sub changespat ($;$) {
205     my ($vsn, $arch) = @_;
206     return "${package}_".(stripepoch $vsn)."_".($arch//'*').".changes";
207 }
208
209 sub upstreamversion ($) {
210     my ($vsn) = @_;
211     $vsn =~ s/-[^-]+$//;
212     return $vsn;
213 }
214
215 our $us = 'dgit';
216 initdebug('');
217
218 our @end;
219 END { 
220     local ($?);
221     foreach my $f (@end) {
222         eval { $f->(); };
223         print STDERR "$us: cleanup: $@" if length $@;
224     }
225 };
226
227 sub badcfg { print STDERR "$us: invalid configuration: @_\n"; exit 12; }
228
229 sub forceable_fail ($$) {
230     my ($forceoptsl, $msg) = @_;
231     fail $msg unless grep { $forceopts{$_} } @$forceoptsl;
232     print STDERR "warning: overriding problem due to --force:\n". $msg;
233 }
234
235 sub forceing ($) {
236     my ($forceoptsl) = @_;
237     my @got = grep { $forceopts{$_} } @$forceoptsl;
238     return 0 unless @got;
239     print STDERR
240  "warning: skipping checks or functionality due to --force-$got[0]\n";
241 }
242
243 sub no_such_package () {
244     print STDERR "$us: package $package does not exist in suite $isuite\n";
245     exit 4;
246 }
247
248 sub changedir ($) {
249     my ($newdir) = @_;
250     printdebug "CD $newdir\n";
251     chdir $newdir or confess "chdir: $newdir: $!";
252 }
253
254 sub deliberately ($) {
255     my ($enquiry) = @_;
256     return !!grep { $_ eq "--deliberately-$enquiry" } @deliberatelies;
257 }
258
259 sub deliberately_not_fast_forward () {
260     foreach (qw(not-fast-forward fresh-repo)) {
261         return 1 if deliberately($_) || deliberately("TEST-dgit-only-$_");
262     }
263 }
264
265 sub quiltmode_splitbrain () {
266     $quilt_mode =~ m/gbp|dpm|unapplied/;
267 }
268
269 sub opts_opt_multi_cmd {
270     my @cmd;
271     push @cmd, split /\s+/, shift @_;
272     push @cmd, @_;
273     @cmd;
274 }
275
276 sub gbp_pq {
277     return opts_opt_multi_cmd @gbp_pq;
278 }
279
280 #---------- remote protocol support, common ----------
281
282 # remote push initiator/responder protocol:
283 #  $ dgit remote-push-build-host <n-rargs> <rargs>... <push-args>...
284 #  where <rargs> is <push-host-dir> <supported-proto-vsn>,... ...
285 #  < dgit-remote-push-ready <actual-proto-vsn>
286 #
287 # occasionally:
288 #
289 #  > progress NBYTES
290 #  [NBYTES message]
291 #
292 #  > supplementary-message NBYTES          # $protovsn >= 3
293 #  [NBYTES message]
294 #
295 # main sequence:
296 #
297 #  > file parsed-changelog
298 #  [indicates that output of dpkg-parsechangelog follows]
299 #  > data-block NBYTES
300 #  > [NBYTES bytes of data (no newline)]
301 #  [maybe some more blocks]
302 #  > data-end
303 #
304 #  > file dsc
305 #  [etc]
306 #
307 #  > file changes
308 #  [etc]
309 #
310 #  > param head DGIT-VIEW-HEAD
311 #  > param csuite SUITE
312 #  > param tagformat old|new
313 #  > param maint-view MAINT-VIEW-HEAD
314 #
315 #  > previously REFNAME=OBJNAME       # if --deliberately-not-fast-forward
316 #                                     # goes into tag, for replay prevention
317 #
318 #  > want signed-tag
319 #  [indicates that signed tag is wanted]
320 #  < data-block NBYTES
321 #  < [NBYTES bytes of data (no newline)]
322 #  [maybe some more blocks]
323 #  < data-end
324 #  < files-end
325 #
326 #  > want signed-dsc-changes
327 #  < data-block NBYTES    [transfer of signed dsc]
328 #  [etc]
329 #  < data-block NBYTES    [transfer of signed changes]
330 #  [etc]
331 #  < files-end
332 #
333 #  > complete
334
335 our $i_child_pid;
336
337 sub i_child_report () {
338     # Sees if our child has died, and reap it if so.  Returns a string
339     # describing how it died if it failed, or undef otherwise.
340     return undef unless $i_child_pid;
341     my $got = waitpid $i_child_pid, WNOHANG;
342     return undef if $got <= 0;
343     die unless $got == $i_child_pid;
344     $i_child_pid = undef;
345     return undef unless $?;
346     return "build host child ".waitstatusmsg();
347 }
348
349 sub badproto ($$) {
350     my ($fh, $m) = @_;
351     fail "connection lost: $!" if $fh->error;
352     fail "protocol violation; $m not expected";
353 }
354
355 sub badproto_badread ($$) {
356     my ($fh, $wh) = @_;
357     fail "connection lost: $!" if $!;
358     my $report = i_child_report();
359     fail $report if defined $report;
360     badproto $fh, "eof (reading $wh)";
361 }
362
363 sub protocol_expect (&$) {
364     my ($match, $fh) = @_;
365     local $_;
366     $_ = <$fh>;
367     defined && chomp or badproto_badread $fh, "protocol message";
368     if (wantarray) {
369         my @r = &$match;
370         return @r if @r;
371     } else {
372         my $r = &$match;
373         return $r if $r;
374     }
375     badproto $fh, "\`$_'";
376 }
377
378 sub protocol_send_file ($$) {
379     my ($fh, $ourfn) = @_;
380     open PF, "<", $ourfn or die "$ourfn: $!";
381     for (;;) {
382         my $d;
383         my $got = read PF, $d, 65536;
384         die "$ourfn: $!" unless defined $got;
385         last if !$got;
386         print $fh "data-block ".length($d)."\n" or die $!;
387         print $fh $d or die $!;
388     }
389     PF->error and die "$ourfn $!";
390     print $fh "data-end\n" or die $!;
391     close PF;
392 }
393
394 sub protocol_read_bytes ($$) {
395     my ($fh, $nbytes) = @_;
396     $nbytes =~ m/^[1-9]\d{0,5}$|^0$/ or badproto \*RO, "bad byte count";
397     my $d;
398     my $got = read $fh, $d, $nbytes;
399     $got==$nbytes or badproto_badread $fh, "data block";
400     return $d;
401 }
402
403 sub protocol_receive_file ($$) {
404     my ($fh, $ourfn) = @_;
405     printdebug "() $ourfn\n";
406     open PF, ">", $ourfn or die "$ourfn: $!";
407     for (;;) {
408         my ($y,$l) = protocol_expect {
409             m/^data-block (.*)$/ ? (1,$1) :
410             m/^data-end$/ ? (0,) :
411             ();
412         } $fh;
413         last unless $y;
414         my $d = protocol_read_bytes $fh, $l;
415         print PF $d or die $!;
416     }
417     close PF or die $!;
418 }
419
420 #---------- remote protocol support, responder ----------
421
422 sub responder_send_command ($) {
423     my ($command) = @_;
424     return unless $we_are_responder;
425     # called even without $we_are_responder
426     printdebug ">> $command\n";
427     print PO $command, "\n" or die $!;
428 }    
429
430 sub responder_send_file ($$) {
431     my ($keyword, $ourfn) = @_;
432     return unless $we_are_responder;
433     printdebug "]] $keyword $ourfn\n";
434     responder_send_command "file $keyword";
435     protocol_send_file \*PO, $ourfn;
436 }
437
438 sub responder_receive_files ($@) {
439     my ($keyword, @ourfns) = @_;
440     die unless $we_are_responder;
441     printdebug "[[ $keyword @ourfns\n";
442     responder_send_command "want $keyword";
443     foreach my $fn (@ourfns) {
444         protocol_receive_file \*PI, $fn;
445     }
446     printdebug "[[\$\n";
447     protocol_expect { m/^files-end$/ } \*PI;
448 }
449
450 #---------- remote protocol support, initiator ----------
451
452 sub initiator_expect (&) {
453     my ($match) = @_;
454     protocol_expect { &$match } \*RO;
455 }
456
457 #---------- end remote code ----------
458
459 sub progress {
460     if ($we_are_responder) {
461         my $m = join '', @_;
462         responder_send_command "progress ".length($m) or die $!;
463         print PO $m or die $!;
464     } else {
465         print @_, "\n";
466     }
467 }
468
469 our $ua;
470
471 sub url_get {
472     if (!$ua) {
473         $ua = LWP::UserAgent->new();
474         $ua->env_proxy;
475     }
476     my $what = $_[$#_];
477     progress "downloading $what...";
478     my $r = $ua->get(@_) or die $!;
479     return undef if $r->code == 404;
480     $r->is_success or fail "failed to fetch $what: ".$r->status_line;
481     return $r->decoded_content(charset => 'none');
482 }
483
484 our ($dscdata,$dscurl,$dsc,$dsc_checked,$skew_warning_vsn);
485
486 sub runcmd {
487     debugcmd "+",@_;
488     $!=0; $?=-1;
489     failedcmd @_ if system @_;
490 }
491
492 sub act_local () { return $dryrun_level <= 1; }
493 sub act_scary () { return !$dryrun_level; }
494
495 sub printdone {
496     if (!$dryrun_level) {
497         progress "$us ok: @_";
498     } else {
499         progress "would be ok: @_ (but dry run only)";
500     }
501 }
502
503 sub dryrun_report {
504     printcmd(\*STDERR,$debugprefix."#",@_);
505 }
506
507 sub runcmd_ordryrun {
508     if (act_scary()) {
509         runcmd @_;
510     } else {
511         dryrun_report @_;
512     }
513 }
514
515 sub runcmd_ordryrun_local {
516     if (act_local()) {
517         runcmd @_;
518     } else {
519         dryrun_report @_;
520     }
521 }
522
523 sub shell_cmd {
524     my ($first_shell, @cmd) = @_;
525     return qw(sh -ec), $first_shell.'; exec "$@"', 'x', @cmd;
526 }
527
528 our $helpmsg = <<END;
529 main usages:
530   dgit [dgit-opts] clone [dgit-opts] package [suite] [./dir|/dir]
531   dgit [dgit-opts] fetch|pull [dgit-opts] [suite]
532   dgit [dgit-opts] build [dpkg-buildpackage-opts]
533   dgit [dgit-opts] sbuild [sbuild-opts]
534   dgit [dgit-opts] push [dgit-opts] [suite]
535   dgit [dgit-opts] rpush build-host:build-dir ...
536 important dgit options:
537   -k<keyid>           sign tag and package with <keyid> instead of default
538   --dry-run -n        do not change anything, but go through the motions
539   --damp-run -L       like --dry-run but make local changes, without signing
540   --new -N            allow introducing a new package
541   --debug -D          increase debug level
542   -c<name>=<value>    set git config option (used directly by dgit too)
543 END
544
545 our $later_warning_msg = <<END;
546 Perhaps the upload is stuck in incoming.  Using the version from git.
547 END
548
549 sub badusage {
550     print STDERR "$us: @_\n", $helpmsg or die $!;
551     exit 8;
552 }
553
554 sub nextarg {
555     @ARGV or badusage "too few arguments";
556     return scalar shift @ARGV;
557 }
558
559 sub cmd_help () {
560     print $helpmsg or die $!;
561     exit 0;
562 }
563
564 our $td = $ENV{DGIT_TEST_DUMMY_DIR} || "DGIT_TEST_DUMMY_DIR-unset";
565
566 our %defcfg = ('dgit.default.distro' => 'debian',
567                'dgit-suite.*-security.distro' => 'debian-security',
568                'dgit.default.username' => '',
569                'dgit.default.archive-query-default-component' => 'main',
570                'dgit.default.ssh' => 'ssh',
571                'dgit.default.archive-query' => 'madison:',
572                'dgit.default.sshpsql-dbname' => 'service=projectb',
573                'dgit.default.aptget-components' => 'main',
574                'dgit.default.dgit-tag-format' => 'new,old,maint',
575                # old means "repo server accepts pushes with old dgit tags"
576                # new means "repo server accepts pushes with new dgit tags"
577                # maint means "repo server accepts split brain pushes"
578                # hist means "repo server may have old pushes without new tag"
579                #   ("hist" is implied by "old")
580                'dgit-distro.debian.archive-query' => 'ftpmasterapi:',
581                'dgit-distro.debian.git-check' => 'url',
582                'dgit-distro.debian.git-check-suffix' => '/info/refs',
583                'dgit-distro.debian.new-private-pushers' => 't',
584                'dgit-distro.debian/push.git-url' => '',
585                'dgit-distro.debian/push.git-host' => 'push.dgit.debian.org',
586                'dgit-distro.debian/push.git-user-force' => 'dgit',
587                'dgit-distro.debian/push.git-proto' => 'git+ssh://',
588                'dgit-distro.debian/push.git-path' => '/dgit/debian/repos',
589                'dgit-distro.debian/push.git-create' => 'true',
590                'dgit-distro.debian/push.git-check' => 'ssh-cmd',
591  'dgit-distro.debian.archive-query-url', 'https://api.ftp-master.debian.org/',
592 # 'dgit-distro.debian.archive-query-tls-key',
593 #    '/etc/ssl/certs/%HOST%.pem:/etc/dgit/%HOST%.pem',
594 # ^ this does not work because curl is broken nowadays
595 # Fixing #790093 properly will involve providing providing the key
596 # in some pacagke and maybe updating these paths.
597 #
598 # 'dgit-distro.debian.archive-query-tls-curl-args',
599 #   '--ca-path=/etc/ssl/ca-debian',
600 # ^ this is a workaround but works (only) on DSA-administered machines
601                'dgit-distro.debian.git-url' => 'https://git.dgit.debian.org',
602                'dgit-distro.debian.git-url-suffix' => '',
603                'dgit-distro.debian.upload-host' => 'ftp-master', # for dput
604                'dgit-distro.debian.mirror' => 'http://ftp.debian.org/debian/',
605  'dgit-distro.debian-security.archive-query' => 'aptget:',
606  'dgit-distro.debian-security.mirror' => 'http://security.debian.org/debian-security/',
607  'dgit-distro.debian-security.aptget-suite-map' => 's#-security$#/updates#',
608  'dgit-distro.debian-security.aptget-suite-rmap' => 's#$#-security#',
609  'dgit-distro.debian-security.nominal-distro' => 'debian',
610  'dgit-distro.debian.backports-quirk' => '(squeeze)-backports*',
611  'dgit-distro.debian-backports.mirror' => 'http://backports.debian.org/debian-backports/',
612                'dgit-distro.ubuntu.git-check' => 'false',
613  'dgit-distro.ubuntu.mirror' => 'http://archive.ubuntu.com/ubuntu',
614                'dgit-distro.test-dummy.ssh' => "$td/ssh",
615                'dgit-distro.test-dummy.username' => "alice",
616                'dgit-distro.test-dummy.git-check' => "ssh-cmd",
617                'dgit-distro.test-dummy.git-create' => "ssh-cmd",
618                'dgit-distro.test-dummy.git-url' => "$td/git",
619                'dgit-distro.test-dummy.git-host' => "git",
620                'dgit-distro.test-dummy.git-path' => "$td/git",
621                'dgit-distro.test-dummy.archive-query' => "dummycatapi:",
622                'dgit-distro.test-dummy.archive-query-url' => "file://$td/aq/",
623                'dgit-distro.test-dummy.mirror' => "file://$td/mirror/",
624                'dgit-distro.test-dummy.upload-host' => 'test-dummy',
625                );
626
627 our %gitcfgs;
628 our @gitcfgsources = qw(cmdline local global system);
629
630 sub git_slurp_config () {
631     local ($debuglevel) = $debuglevel-2;
632     local $/="\0";
633
634     # This algoritm is a bit subtle, but this is needed so that for
635     # options which we want to be single-valued, we allow the
636     # different config sources to override properly.  See #835858.
637     foreach my $src (@gitcfgsources) {
638         next if $src eq 'cmdline';
639         # we do this ourselves since git doesn't handle it
640         
641         my @cmd = (@git, qw(config -z --get-regexp), "--$src", qw(.*));
642         debugcmd "|",@cmd;
643
644         open GITS, "-|", @cmd or die $!;
645         while (<GITS>) {
646             chomp or die;
647             printdebug "=> ", (messagequote $_), "\n";
648             m/\n/ or die "$_ ?";
649             push @{ $gitcfgs{$src}{$`} }, $'; #';
650         }
651         $!=0; $?=0;
652         close GITS
653             or ($!==0 && $?==256)
654             or failedcmd @cmd;
655     }
656 }
657
658 sub git_get_config ($) {
659     my ($c) = @_;
660     foreach my $src (@gitcfgsources) {
661         my $l = $gitcfgs{$src}{$c};
662         printdebug"C $c ".(defined $l ?
663                            join " ", map { messagequote "'$_'" } @$l :
664                            "undef")."\n"
665             if $debuglevel >= 4;
666         $l or next;
667         @$l==1 or badcfg "multiple values for $c".
668             " (in $src git config)" if @$l > 1;
669         return $l->[0];
670     }
671     return undef;
672 }
673
674 sub cfg {
675     foreach my $c (@_) {
676         return undef if $c =~ /RETURN-UNDEF/;
677         my $v = git_get_config($c);
678         return $v if defined $v;
679         my $dv = $defcfg{$c};
680         if (defined $dv) {
681             printdebug "CD $c $dv\n" if $debuglevel >= 4;
682             return $dv;
683         }
684     }
685     badcfg "need value for one of: @_\n".
686         "$us: distro or suite appears not to be (properly) supported";
687 }
688
689 sub access_basedistro () {
690     if (defined $idistro) {
691         return $idistro;
692     } else {    
693         my $def = cfg("dgit-suite.$isuite.distro", 'RETURN-UNDEF');
694         return $def if defined $def;
695         foreach my $src (@gitcfgsources, 'internal') {
696             my $kl = $src eq 'internal' ? \%defcfg : $gitcfgs{$src};
697             next unless $kl;
698             foreach my $k (keys %$kl) {
699                 next unless $k =~ m#^dgit-suite\.(.*)\.distro$#;
700                 my $dpat = $1;
701                 next unless match_glob $dpat, $isuite;
702                 return $kl->{$k};
703             }
704         }
705         return cfg("dgit.default.distro");
706     }
707 }
708
709 sub access_nomdistro () {
710     my $base = access_basedistro();
711     my $r = cfg("dgit-distro.$base.nominal-distro",'RETURN-UNDEF') // $base;
712     $r =~ m/^$distro_re$/ or badcfg
713  "bad syntax for (nominal) distro \`$r' (does not match /^$distro_re$/)";
714     return $r;
715 }
716
717 sub access_quirk () {
718     # returns (quirk name, distro to use instead or undef, quirk-specific info)
719     my $basedistro = access_basedistro();
720     my $backports_quirk = cfg("dgit-distro.$basedistro.backports-quirk",
721                               'RETURN-UNDEF');
722     if (defined $backports_quirk) {
723         my $re = $backports_quirk;
724         $re =~ s/[^-0-9a-z_\%*()]/\\$&/ig;
725         $re =~ s/\*/.*/g;
726         $re =~ s/\%/([-0-9a-z_]+)/
727             or $re =~ m/[()]/ or badcfg "backports-quirk needs \% or ( )";
728         if ($isuite =~ m/^$re$/) {
729             return ('backports',"$basedistro-backports",$1);
730         }
731     }
732     return ('none',undef);
733 }
734
735 our $access_forpush;
736
737 sub parse_cfg_bool ($$$) {
738     my ($what,$def,$v) = @_;
739     $v //= $def;
740     return
741         $v =~ m/^[ty1]/ ? 1 :
742         $v =~ m/^[fn0]/ ? 0 :
743         badcfg "$what needs t (true, y, 1) or f (false, n, 0) not \`$v'";
744 }       
745
746 sub access_forpush_config () {
747     my $d = access_basedistro();
748
749     return 1 if
750         $new_package &&
751         parse_cfg_bool('new-private-pushers', 0,
752                        cfg("dgit-distro.$d.new-private-pushers",
753                            'RETURN-UNDEF'));
754
755     my $v = cfg("dgit-distro.$d.readonly", 'RETURN-UNDEF');
756     $v //= 'a';
757     return
758         $v =~ m/^[ty1]/ ? 0 : # force readonly,    forpush = 0
759         $v =~ m/^[fn0]/ ? 1 : # force nonreadonly, forpush = 1
760         $v =~ m/^[a]/  ? '' : # auto,              forpush = ''
761         badcfg "readonly needs t (true, y, 1) or f (false, n, 0) or a (auto)";
762 }
763
764 sub access_forpush () {
765     $access_forpush //= access_forpush_config();
766     return $access_forpush;
767 }
768
769 sub pushing () {
770     die "$access_forpush ?" if ($access_forpush // 1) ne 1;
771     badcfg "pushing but distro is configured readonly"
772         if access_forpush_config() eq '0';
773     $access_forpush = 1;
774     $supplementary_message = <<'END' unless $we_are_responder;
775 Push failed, before we got started.
776 You can retry the push, after fixing the problem, if you like.
777 END
778     parseopts_late_defaults();
779 }
780
781 sub notpushing () {
782     parseopts_late_defaults();
783 }
784
785 sub supplementary_message ($) {
786     my ($msg) = @_;
787     if (!$we_are_responder) {
788         $supplementary_message = $msg;
789         return;
790     } elsif ($protovsn >= 3) {
791         responder_send_command "supplementary-message ".length($msg)
792             or die $!;
793         print PO $msg or die $!;
794     }
795 }
796
797 sub access_distros () {
798     # Returns list of distros to try, in order
799     #
800     # We want to try:
801     #    0. `instead of' distro name(s) we have been pointed to
802     #    1. the access_quirk distro, if any
803     #    2a. the user's specified distro, or failing that  } basedistro
804     #    2b. the distro calculated from the suite          }
805     my @l = access_basedistro();
806
807     my (undef,$quirkdistro) = access_quirk();
808     unshift @l, $quirkdistro;
809     unshift @l, $instead_distro;
810     @l = grep { defined } @l;
811
812     push @l, access_nomdistro();
813
814     if (access_forpush()) {
815         @l = map { ("$_/push", $_) } @l;
816     }
817     @l;
818 }
819
820 sub access_cfg_cfgs (@) {
821     my (@keys) = @_;
822     my @cfgs;
823     # The nesting of these loops determines the search order.  We put
824     # the key loop on the outside so that we search all the distros
825     # for each key, before going on to the next key.  That means that
826     # if access_cfg is called with a more specific, and then a less
827     # specific, key, an earlier distro can override the less specific
828     # without necessarily overriding any more specific keys.  (If the
829     # distro wants to override the more specific keys it can simply do
830     # so; whereas if we did the loop the other way around, it would be
831     # impossible to for an earlier distro to override a less specific
832     # key but not the more specific ones without restating the unknown
833     # values of the more specific keys.
834     my @realkeys;
835     my @rundef;
836     # We have to deal with RETURN-UNDEF specially, so that we don't
837     # terminate the search prematurely.
838     foreach (@keys) {
839         if (m/RETURN-UNDEF/) { push @rundef, $_; last; }
840         push @realkeys, $_
841     }
842     foreach my $d (access_distros()) {
843         push @cfgs, map { "dgit-distro.$d.$_" } @realkeys;
844     }
845     push @cfgs, map { "dgit.default.$_" } @realkeys;
846     push @cfgs, @rundef;
847     return @cfgs;
848 }
849
850 sub access_cfg (@) {
851     my (@keys) = @_;
852     my (@cfgs) = access_cfg_cfgs(@keys);
853     my $value = cfg(@cfgs);
854     return $value;
855 }
856
857 sub access_cfg_bool ($$) {
858     my ($def, @keys) = @_;
859     parse_cfg_bool($keys[0], $def, access_cfg(@keys, 'RETURN-UNDEF'));
860 }
861
862 sub string_to_ssh ($) {
863     my ($spec) = @_;
864     if ($spec =~ m/\s/) {
865         return qw(sh -ec), 'exec '.$spec.' "$@"', 'x';
866     } else {
867         return ($spec);
868     }
869 }
870
871 sub access_cfg_ssh () {
872     my $gitssh = access_cfg('ssh', 'RETURN-UNDEF');
873     if (!defined $gitssh) {
874         return @ssh;
875     } else {
876         return string_to_ssh $gitssh;
877     }
878 }
879
880 sub access_runeinfo ($) {
881     my ($info) = @_;
882     return ": dgit ".access_basedistro()." $info ;";
883 }
884
885 sub access_someuserhost ($) {
886     my ($some) = @_;
887     my $user = access_cfg("$some-user-force", 'RETURN-UNDEF');
888     defined($user) && length($user) or
889         $user = access_cfg("$some-user",'username');
890     my $host = access_cfg("$some-host");
891     return length($user) ? "$user\@$host" : $host;
892 }
893
894 sub access_gituserhost () {
895     return access_someuserhost('git');
896 }
897
898 sub access_giturl (;$) {
899     my ($optional) = @_;
900     my $url = access_cfg('git-url','RETURN-UNDEF');
901     my $suffix;
902     if (!length $url) {
903         my $proto = access_cfg('git-proto', 'RETURN-UNDEF');
904         return undef unless defined $proto;
905         $url =
906             $proto.
907             access_gituserhost().
908             access_cfg('git-path');
909     } else {
910         $suffix = access_cfg('git-url-suffix','RETURN-UNDEF');
911     }
912     $suffix //= '.git';
913     return "$url/$package$suffix";
914 }              
915
916 sub parsecontrolfh ($$;$) {
917     my ($fh, $desc, $allowsigned) = @_;
918     our $dpkgcontrolhash_noissigned;
919     my $c;
920     for (;;) {
921         my %opts = ('name' => $desc);
922         $opts{allow_pgp}= $allowsigned || !$dpkgcontrolhash_noissigned;
923         $c = Dpkg::Control::Hash->new(%opts);
924         $c->parse($fh,$desc) or die "parsing of $desc failed";
925         last if $allowsigned;
926         last if $dpkgcontrolhash_noissigned;
927         my $issigned= $c->get_option('is_pgp_signed');
928         if (!defined $issigned) {
929             $dpkgcontrolhash_noissigned= 1;
930             seek $fh, 0,0 or die "seek $desc: $!";
931         } elsif ($issigned) {
932             fail "control file $desc is (already) PGP-signed. ".
933                 " Note that dgit push needs to modify the .dsc and then".
934                 " do the signature itself";
935         } else {
936             last;
937         }
938     }
939     return $c;
940 }
941
942 sub parsecontrol {
943     my ($file, $desc, $allowsigned) = @_;
944     my $fh = new IO::Handle;
945     open $fh, '<', $file or die "$file: $!";
946     my $c = parsecontrolfh($fh,$desc,$allowsigned);
947     $fh->error and die $!;
948     close $fh;
949     return $c;
950 }
951
952 sub getfield ($$) {
953     my ($dctrl,$field) = @_;
954     my $v = $dctrl->{$field};
955     return $v if defined $v;
956     fail "missing field $field in ".$dctrl->get_option('name');
957 }
958
959 sub parsechangelog {
960     my $c = Dpkg::Control::Hash->new(name => 'parsed changelog');
961     my $p = new IO::Handle;
962     my @cmd = (qw(dpkg-parsechangelog), @_);
963     open $p, '-|', @cmd or die $!;
964     $c->parse($p);
965     $?=0; $!=0; close $p or failedcmd @cmd;
966     return $c;
967 }
968
969 sub commit_getclogp ($) {
970     # Returns the parsed changelog hashref for a particular commit
971     my ($objid) = @_;
972     our %commit_getclogp_memo;
973     my $memo = $commit_getclogp_memo{$objid};
974     return $memo if $memo;
975     mkpath '.git/dgit';
976     my $mclog = ".git/dgit/clog-$objid";
977     runcmd shell_cmd "exec >$mclog", @git, qw(cat-file blob),
978         "$objid:debian/changelog";
979     $commit_getclogp_memo{$objid} = parsechangelog("-l$mclog");
980 }
981
982 sub must_getcwd () {
983     my $d = getcwd();
984     defined $d or fail "getcwd failed: $!";
985     return $d;
986 }
987
988 sub parse_dscdata () {
989     my $dscfh = new IO::File \$dscdata, '<' or die $!;
990     printdebug Dumper($dscdata) if $debuglevel>1;
991     $dsc = parsecontrolfh($dscfh,$dscurl,1);
992     printdebug Dumper($dsc) if $debuglevel>1;
993 }
994
995 our %rmad;
996
997 sub archive_query ($;@) {
998     my ($method) = shift @_;
999     fail "this operation does not support multiple comma-separated suites"
1000         if $isuite =~ m/,/;
1001     my $query = access_cfg('archive-query','RETURN-UNDEF');
1002     $query =~ s/^(\w+):// or badcfg "invalid archive-query method \`$query'";
1003     my $proto = $1;
1004     my $data = $'; #';
1005     { no strict qw(refs); &{"${method}_${proto}"}($proto,$data,@_); }
1006 }
1007
1008 sub archive_query_prepend_mirror {
1009     my $m = access_cfg('mirror');
1010     return map { [ $_->[0], $m.$_->[1], @$_[2..$#$_] ] } @_;
1011 }
1012
1013 sub pool_dsc_subpath ($$) {
1014     my ($vsn,$component) = @_; # $package is implict arg
1015     my $prefix = substr($package, 0, $package =~ m/^l/ ? 4 : 1);
1016     return "/pool/$component/$prefix/$package/".dscfn($vsn);
1017 }
1018
1019 sub cfg_apply_map ($$$) {
1020     my ($varref, $what, $mapspec) = @_;
1021     return unless $mapspec;
1022
1023     printdebug "config $what EVAL{ $mapspec; }\n";
1024     $_ = $$varref;
1025     eval "package Dgit::Config; $mapspec;";
1026     die $@ if $@;
1027     $$varref = $_;
1028 }
1029
1030 #---------- `ftpmasterapi' archive query method (nascent) ----------
1031
1032 sub archive_api_query_cmd ($) {
1033     my ($subpath) = @_;
1034     my @cmd = (@curl, qw(-sS));
1035     my $url = access_cfg('archive-query-url');
1036     if ($url =~ m#^https://([-.0-9a-z]+)/#) {
1037         my $host = $1;
1038         my $keys = access_cfg('archive-query-tls-key','RETURN-UNDEF') //'';
1039         foreach my $key (split /\:/, $keys) {
1040             $key =~ s/\%HOST\%/$host/g;
1041             if (!stat $key) {
1042                 fail "for $url: stat $key: $!" unless $!==ENOENT;
1043                 next;
1044             }
1045             fail "config requested specific TLS key but do not know".
1046                 " how to get curl to use exactly that EE key ($key)";
1047 #           push @cmd, "--cacert", $key, "--capath", "/dev/enoent";
1048 #           # Sadly the above line does not work because of changes
1049 #           # to gnutls.   The real fix for #790093 may involve
1050 #           # new curl options.
1051             last;
1052         }
1053         # Fixing #790093 properly will involve providing a value
1054         # for this on clients.
1055         my $kargs = access_cfg('archive-query-tls-curl-ca-args','RETURN-UNDEF');
1056         push @cmd, split / /, $kargs if defined $kargs;
1057     }
1058     push @cmd, $url.$subpath;
1059     return @cmd;
1060 }
1061
1062 sub api_query ($$;$) {
1063     use JSON;
1064     my ($data, $subpath, $ok404) = @_;
1065     badcfg "ftpmasterapi archive query method takes no data part"
1066         if length $data;
1067     my @cmd = archive_api_query_cmd($subpath);
1068     my $url = $cmd[$#cmd];
1069     push @cmd, qw(-w %{http_code});
1070     my $json = cmdoutput @cmd;
1071     unless ($json =~ s/\d+\d+\d$//) {
1072         failedcmd_report_cmd undef, @cmd;
1073         fail "curl failed to print 3-digit HTTP code";
1074     }
1075     my $code = $&;
1076     return undef if $code eq '404' && $ok404;
1077     fail "fetch of $url gave HTTP code $code"
1078         unless $url =~ m#^file://# or $code =~ m/^2/;
1079     return decode_json($json);
1080 }
1081
1082 sub canonicalise_suite_ftpmasterapi {
1083     my ($proto,$data) = @_;
1084     my $suites = api_query($data, 'suites');
1085     my @matched;
1086     foreach my $entry (@$suites) {
1087         next unless grep { 
1088             my $v = $entry->{$_};
1089             defined $v && $v eq $isuite;
1090         } qw(codename name);
1091         push @matched, $entry;
1092     }
1093     fail "unknown suite $isuite" unless @matched;
1094     my $cn;
1095     eval {
1096         @matched==1 or die "multiple matches for suite $isuite\n";
1097         $cn = "$matched[0]{codename}";
1098         defined $cn or die "suite $isuite info has no codename\n";
1099         $cn =~ m/^$suite_re$/ or die "suite $isuite maps to bad codename\n";
1100     };
1101     die "bad ftpmaster api response: $@\n".Dumper(\@matched)
1102         if length $@;
1103     return $cn;
1104 }
1105
1106 sub archive_query_ftpmasterapi {
1107     my ($proto,$data) = @_;
1108     my $info = api_query($data, "dsc_in_suite/$isuite/$package");
1109     my @rows;
1110     my $digester = Digest::SHA->new(256);
1111     foreach my $entry (@$info) {
1112         eval {
1113             my $vsn = "$entry->{version}";
1114             my ($ok,$msg) = version_check $vsn;
1115             die "bad version: $msg\n" unless $ok;
1116             my $component = "$entry->{component}";
1117             $component =~ m/^$component_re$/ or die "bad component";
1118             my $filename = "$entry->{filename}";
1119             $filename && $filename !~ m#[^-+:._~0-9a-zA-Z/]|^[/.]|/[/.]#
1120                 or die "bad filename";
1121             my $sha256sum = "$entry->{sha256sum}";
1122             $sha256sum =~ m/^[0-9a-f]+$/ or die "bad sha256sum";
1123             push @rows, [ $vsn, "/pool/$component/$filename",
1124                           $digester, $sha256sum ];
1125         };
1126         die "bad ftpmaster api response: $@\n".Dumper($entry)
1127             if length $@;
1128     }
1129     @rows = sort { -version_compare($a->[0],$b->[0]) } @rows;
1130     return archive_query_prepend_mirror @rows;
1131 }
1132
1133 sub file_in_archive_ftpmasterapi {
1134     my ($proto,$data,$filename) = @_;
1135     my $pat = $filename;
1136     $pat =~ s/_/\\_/g;
1137     $pat = "%/$pat";
1138     $pat =~ s#[^-+_.0-9a-z/]# sprintf '%%%02x', ord $& #ge;
1139     my $info = api_query($data, "file_in_archive/$pat", 1);
1140 }
1141
1142 #---------- `aptget' archive query method ----------
1143
1144 our $aptget_base;
1145 our $aptget_releasefile;
1146 our $aptget_configpath;
1147
1148 sub aptget_aptget   () { return @aptget,   qw(-c), $aptget_configpath; }
1149 sub aptget_aptcache () { return @aptcache, qw(-c), $aptget_configpath; }
1150
1151 sub aptget_cache_clean {
1152     runcmd_ordryrun_local qw(sh -ec),
1153         'cd "$1"; find -atime +30 -type f -print0 | xargs -0r rm --',
1154         'x', $aptget_base;
1155 }
1156
1157 sub aptget_lock_acquire () {
1158     my $lockfile = "$aptget_base/lock";
1159     open APTGET_LOCK, '>', $lockfile or die "open $lockfile: $!";
1160     flock APTGET_LOCK, LOCK_EX or die "lock $lockfile: $!";
1161 }
1162
1163 sub aptget_prep ($) {
1164     my ($data) = @_;
1165     return if defined $aptget_base;
1166
1167     badcfg "aptget archive query method takes no data part"
1168         if length $data;
1169
1170     my $cache = $ENV{XDG_CACHE_DIR} // "$ENV{HOME}/.cache";
1171
1172     ensuredir $cache;
1173     ensuredir "$cache/dgit";
1174     my $cachekey =
1175         access_cfg('aptget-cachekey','RETURN-UNDEF')
1176         // access_nomdistro();
1177
1178     $aptget_base = "$cache/dgit/aptget";
1179     ensuredir $aptget_base;
1180
1181     my $quoted_base = $aptget_base;
1182     die "$quoted_base contains bad chars, cannot continue"
1183         if $quoted_base =~ m/["\\]/; # apt.conf(5) says no escaping :-/
1184
1185     ensuredir $aptget_base;
1186
1187     aptget_lock_acquire();
1188
1189     aptget_cache_clean();
1190
1191     $aptget_configpath = "$aptget_base/apt.conf#$cachekey";
1192     my $sourceslist = "source.list#$cachekey";
1193
1194     my $aptsuites = $isuite;
1195     cfg_apply_map(\$aptsuites, 'suite map',
1196                   access_cfg('aptget-suite-map', 'RETURN-UNDEF'));
1197
1198     open SRCS, ">", "$aptget_base/$sourceslist" or die $!;
1199     printf SRCS "deb-src %s %s %s\n",
1200         access_cfg('mirror'),
1201         $aptsuites,
1202         access_cfg('aptget-components')
1203         or die $!;
1204
1205     ensuredir "$aptget_base/cache";
1206     ensuredir "$aptget_base/lists";
1207
1208     open CONF, ">", $aptget_configpath or die $!;
1209     print CONF <<END;
1210 Debug::NoLocking "true";
1211 APT::Get::List-Cleanup "false";
1212 #clear APT::Update::Post-Invoke-Success;
1213 Dir::Etc::SourceList "$quoted_base/$sourceslist";
1214 Dir::State::Lists "$quoted_base/lists";
1215 Dir::Etc::preferences "$quoted_base/preferences";
1216 Dir::Cache::srcpkgcache "$quoted_base/cache/srcs#$cachekey";
1217 Dir::Cache::pkgcache "$quoted_base/cache/pkgs#$cachekey";
1218 END
1219
1220     foreach my $key (qw(
1221                         Dir::Cache
1222                         Dir::State
1223                         Dir::Cache::Archives
1224                         Dir::Etc::SourceParts
1225                         Dir::Etc::preferencesparts
1226                       )) {
1227         ensuredir "$aptget_base/$key";
1228         print CONF "$key \"$quoted_base/$key\";\n" or die $!;
1229     };
1230
1231     my $oldatime = (time // die $!) - 1;
1232     foreach my $oldlist (<$aptget_base/lists/*Release>) {
1233         next unless stat_exists $oldlist;
1234         my ($mtime) = (stat _)[9];
1235         utime $oldatime, $mtime, $oldlist or die "$oldlist $!";
1236     }
1237
1238     runcmd_ordryrun_local aptget_aptget(), qw(update);
1239
1240     my @releasefiles;
1241     foreach my $oldlist (<$aptget_base/lists/*Release>) {
1242         next unless stat_exists $oldlist;
1243         my ($atime) = (stat _)[8];
1244         next if $atime == $oldatime;
1245         push @releasefiles, $oldlist;
1246     }
1247     my @inreleasefiles = grep { m#/InRelease$# } @releasefiles;
1248     @releasefiles = @inreleasefiles if @inreleasefiles;
1249     die "apt updated wrong number of Release files (@releasefiles), erk"
1250         unless @releasefiles == 1;
1251
1252     ($aptget_releasefile) = @releasefiles;
1253 }
1254
1255 sub canonicalise_suite_aptget {
1256     my ($proto,$data) = @_;
1257     aptget_prep($data);
1258
1259     my $release = parsecontrol $aptget_releasefile, "Release file", 1;
1260
1261     foreach my $name (qw(Codename Suite)) {
1262         my $val = $release->{$name};
1263         if (defined $val) {
1264             printdebug "release file $name: $val\n";
1265             $val =~ m/^$suite_re$/o or fail
1266  "Release file ($aptget_releasefile) specifies intolerable $name";
1267             cfg_apply_map(\$val, 'suite rmap',
1268                           access_cfg('aptget-suite-rmap', 'RETURN-UNDEF'));
1269             return $val
1270         }
1271     }
1272     return $isuite;
1273 }
1274
1275 sub archive_query_aptget {
1276     my ($proto,$data) = @_;
1277     aptget_prep($data);
1278
1279     ensuredir "$aptget_base/source";
1280     foreach my $old (<$aptget_base/source/*.dsc>) {
1281         unlink $old or die "$old: $!";
1282     }
1283
1284     my $showsrc = cmdoutput aptget_aptcache(), qw(showsrc), $package;
1285     return () unless $showsrc =~ m/^package:\s*\Q$package\E\s*$/mi;
1286     # avoids apt-get source failing with ambiguous error code
1287
1288     runcmd_ordryrun_local
1289         shell_cmd 'cd "$1"/source; shift', $aptget_base,
1290         aptget_aptget(), qw(--download-only --only-source source), $package;
1291
1292     my @dscs = <$aptget_base/source/*.dsc>;
1293     fail "apt-get source did not produce a .dsc" unless @dscs;
1294     fail "apt-get source produced several .dscs (@dscs)" unless @dscs==1;
1295
1296     my $pre_dsc = parsecontrol $dscs[0], $dscs[0], 1;
1297
1298     use URI::Escape;
1299     my $uri = "file://". uri_escape $dscs[0];
1300     $uri =~ s{\%2f}{/}gi;
1301     return [ (getfield $pre_dsc, 'Version'), $uri ];
1302 }
1303
1304 #---------- `dummyapicat' archive query method ----------
1305
1306 sub archive_query_dummycatapi { archive_query_ftpmasterapi @_; }
1307 sub canonicalise_suite_dummycatapi { canonicalise_suite_ftpmasterapi @_; }
1308
1309 sub file_in_archive_dummycatapi ($$$) {
1310     my ($proto,$data,$filename) = @_;
1311     my $mirror = access_cfg('mirror');
1312     $mirror =~ s#^file://#/# or die "$mirror ?";
1313     my @out;
1314     my @cmd = (qw(sh -ec), '
1315             cd "$1"
1316             find -name "$2" -print0 |
1317             xargs -0r sha256sum
1318         ', qw(x), $mirror, $filename);
1319     debugcmd "-|", @cmd;
1320     open FIA, "-|", @cmd or die $!;
1321     while (<FIA>) {
1322         chomp or die;
1323         printdebug "| $_\n";
1324         m/^(\w+)  (\S+)$/ or die "$_ ?";
1325         push @out, { sha256sum => $1, filename => $2 };
1326     }
1327     close FIA or die failedcmd @cmd;
1328     return \@out;
1329 }
1330
1331 #---------- `madison' archive query method ----------
1332
1333 sub archive_query_madison {
1334     return archive_query_prepend_mirror
1335         map { [ @$_[0..1] ] } madison_get_parse(@_);
1336 }
1337
1338 sub madison_get_parse {
1339     my ($proto,$data) = @_;
1340     die unless $proto eq 'madison';
1341     if (!length $data) {
1342         $data= access_cfg('madison-distro','RETURN-UNDEF');
1343         $data //= access_basedistro();
1344     }
1345     $rmad{$proto,$data,$package} ||= cmdoutput
1346         qw(rmadison -asource),"-s$isuite","-u$data",$package;
1347     my $rmad = $rmad{$proto,$data,$package};
1348
1349     my @out;
1350     foreach my $l (split /\n/, $rmad) {
1351         $l =~ m{^ \s*( [^ \t|]+ )\s* \|
1352                   \s*( [^ \t|]+ )\s* \|
1353                   \s*( [^ \t|/]+ )(?:/([^ \t|/]+))? \s* \|
1354                   \s*( [^ \t|]+ )\s* }x or die "$rmad ?";
1355         $1 eq $package or die "$rmad $package ?";
1356         my $vsn = $2;
1357         my $newsuite = $3;
1358         my $component;
1359         if (defined $4) {
1360             $component = $4;
1361         } else {
1362             $component = access_cfg('archive-query-default-component');
1363         }
1364         $5 eq 'source' or die "$rmad ?";
1365         push @out, [$vsn,pool_dsc_subpath($vsn,$component),$newsuite];
1366     }
1367     return sort { -version_compare($a->[0],$b->[0]); } @out;
1368 }
1369
1370 sub canonicalise_suite_madison {
1371     # madison canonicalises for us
1372     my @r = madison_get_parse(@_);
1373     @r or fail
1374         "unable to canonicalise suite using package $package".
1375         " which does not appear to exist in suite $isuite;".
1376         " --existing-package may help";
1377     return $r[0][2];
1378 }
1379
1380 sub file_in_archive_madison { return undef; }
1381
1382 #---------- `sshpsql' archive query method ----------
1383
1384 sub sshpsql ($$$) {
1385     my ($data,$runeinfo,$sql) = @_;
1386     if (!length $data) {
1387         $data= access_someuserhost('sshpsql').':'.
1388             access_cfg('sshpsql-dbname');
1389     }
1390     $data =~ m/:/ or badcfg "invalid sshpsql method string \`$data'";
1391     my ($userhost,$dbname) = ($`,$'); #';
1392     my @rows;
1393     my @cmd = (access_cfg_ssh, $userhost,
1394                access_runeinfo("ssh-psql $runeinfo").
1395                " export LC_MESSAGES=C; export LC_CTYPE=C;".
1396                " ".shellquote qw(psql -A), $dbname, qw(-c), $sql);
1397     debugcmd "|",@cmd;
1398     open P, "-|", @cmd or die $!;
1399     while (<P>) {
1400         chomp or die;
1401         printdebug(">|$_|\n");
1402         push @rows, $_;
1403     }
1404     $!=0; $?=0; close P or failedcmd @cmd;
1405     @rows or die;
1406     my $nrows = pop @rows;
1407     $nrows =~ s/^\((\d+) rows?\)$/$1/ or die "$nrows ?";
1408     @rows == $nrows+1 or die "$nrows ".(scalar @rows)." ?";
1409     @rows = map { [ split /\|/, $_ ] } @rows;
1410     my $ncols = scalar @{ shift @rows };
1411     die if grep { scalar @$_ != $ncols } @rows;
1412     return @rows;
1413 }
1414
1415 sub sql_injection_check {
1416     foreach (@_) { die "$_ $& ?" if m{[^-+=:_.,/0-9a-zA-Z]}; }
1417 }
1418
1419 sub archive_query_sshpsql ($$) {
1420     my ($proto,$data) = @_;
1421     sql_injection_check $isuite, $package;
1422     my @rows = sshpsql($data, "archive-query $isuite $package", <<END);
1423         SELECT source.version, component.name, files.filename, files.sha256sum
1424           FROM source
1425           JOIN src_associations ON source.id = src_associations.source
1426           JOIN suite ON suite.id = src_associations.suite
1427           JOIN dsc_files ON dsc_files.source = source.id
1428           JOIN files_archive_map ON files_archive_map.file_id = dsc_files.file
1429           JOIN component ON component.id = files_archive_map.component_id
1430           JOIN files ON files.id = dsc_files.file
1431          WHERE ( suite.suite_name='$isuite' OR suite.codename='$isuite' )
1432            AND source.source='$package'
1433            AND files.filename LIKE '%.dsc';
1434 END
1435     @rows = sort { -version_compare($a->[0],$b->[0]) } @rows;
1436     my $digester = Digest::SHA->new(256);
1437     @rows = map {
1438         my ($vsn,$component,$filename,$sha256sum) = @$_;
1439         [ $vsn, "/pool/$component/$filename",$digester,$sha256sum ];
1440     } @rows;
1441     return archive_query_prepend_mirror @rows;
1442 }
1443
1444 sub canonicalise_suite_sshpsql ($$) {
1445     my ($proto,$data) = @_;
1446     sql_injection_check $isuite;
1447     my @rows = sshpsql($data, "canonicalise-suite $isuite", <<END);
1448         SELECT suite.codename
1449           FROM suite where suite_name='$isuite' or codename='$isuite';
1450 END
1451     @rows = map { $_->[0] } @rows;
1452     fail "unknown suite $isuite" unless @rows;
1453     die "ambiguous $isuite: @rows ?" if @rows>1;
1454     return $rows[0];
1455 }
1456
1457 sub file_in_archive_sshpsql ($$$) { return undef; }
1458
1459 #---------- `dummycat' archive query method ----------
1460
1461 sub canonicalise_suite_dummycat ($$) {
1462     my ($proto,$data) = @_;
1463     my $dpath = "$data/suite.$isuite";
1464     if (!open C, "<", $dpath) {
1465         $!==ENOENT or die "$dpath: $!";
1466         printdebug "dummycat canonicalise_suite $isuite $dpath ENOENT\n";
1467         return $isuite;
1468     }
1469     $!=0; $_ = <C>;
1470     chomp or die "$dpath: $!";
1471     close C;
1472     printdebug "dummycat canonicalise_suite $isuite $dpath = $_\n";
1473     return $_;
1474 }
1475
1476 sub archive_query_dummycat ($$) {
1477     my ($proto,$data) = @_;
1478     canonicalise_suite();
1479     my $dpath = "$data/package.$csuite.$package";
1480     if (!open C, "<", $dpath) {
1481         $!==ENOENT or die "$dpath: $!";
1482         printdebug "dummycat query $csuite $package $dpath ENOENT\n";
1483         return ();
1484     }
1485     my @rows;
1486     while (<C>) {
1487         next if m/^\#/;
1488         next unless m/\S/;
1489         die unless chomp;
1490         printdebug "dummycat query $csuite $package $dpath | $_\n";
1491         my @row = split /\s+/, $_;
1492         @row==2 or die "$dpath: $_ ?";
1493         push @rows, \@row;
1494     }
1495     C->error and die "$dpath: $!";
1496     close C;
1497     return archive_query_prepend_mirror
1498         sort { -version_compare($a->[0],$b->[0]); } @rows;
1499 }
1500
1501 sub file_in_archive_dummycat () { return undef; }
1502
1503 #---------- tag format handling ----------
1504
1505 sub access_cfg_tagformats () {
1506     split /\,/, access_cfg('dgit-tag-format');
1507 }
1508
1509 sub access_cfg_tagformats_can_splitbrain () {
1510     my %y = map { $_ => 1 } access_cfg_tagformats;
1511     foreach my $needtf (qw(new maint)) {
1512         next if $y{$needtf};
1513         return 0;
1514     }
1515     return 1;
1516 }
1517
1518 sub need_tagformat ($$) {
1519     my ($fmt, $why) = @_;
1520     fail "need to use tag format $fmt ($why) but also need".
1521         " to use tag format $tagformat_want->[0] ($tagformat_want->[1])".
1522         " - no way to proceed"
1523         if $tagformat_want && $tagformat_want->[0] ne $fmt;
1524     $tagformat_want = [$fmt, $why, $tagformat_want->[2] // 0];
1525 }
1526
1527 sub select_tagformat () {
1528     # sets $tagformatfn
1529     return if $tagformatfn && !$tagformat_want;
1530     die 'bug' if $tagformatfn && $tagformat_want;
1531     # ... $tagformat_want assigned after previous select_tagformat
1532
1533     my (@supported) = grep { $_ =~ m/^(?:old|new)$/ } access_cfg_tagformats();
1534     printdebug "select_tagformat supported @supported\n";
1535
1536     $tagformat_want //= [ $supported[0], "distro access configuration", 0 ];
1537     printdebug "select_tagformat specified @$tagformat_want\n";
1538
1539     my ($fmt,$why,$override) = @$tagformat_want;
1540
1541     fail "target distro supports tag formats @supported".
1542         " but have to use $fmt ($why)"
1543         unless $override
1544             or grep { $_ eq $fmt } @supported;
1545
1546     $tagformat_want = undef;
1547     $tagformat = $fmt;
1548     $tagformatfn = ${*::}{"debiantag_$fmt"};
1549
1550     fail "trying to use unknown tag format \`$fmt' ($why) !"
1551         unless $tagformatfn;
1552 }
1553
1554 #---------- archive query entrypoints and rest of program ----------
1555
1556 sub canonicalise_suite () {
1557     return if defined $csuite;
1558     fail "cannot operate on $isuite suite" if $isuite eq 'UNRELEASED';
1559     $csuite = archive_query('canonicalise_suite');
1560     if ($isuite ne $csuite) {
1561         progress "canonical suite name for $isuite is $csuite";
1562     } else {
1563         progress "canonical suite name is $csuite";
1564     }
1565 }
1566
1567 sub get_archive_dsc () {
1568     canonicalise_suite();
1569     my @vsns = archive_query('archive_query');
1570     foreach my $vinfo (@vsns) {
1571         my ($vsn,$vsn_dscurl,$digester,$digest) = @$vinfo;
1572         $dscurl = $vsn_dscurl;
1573         $dscdata = url_get($dscurl);
1574         if (!$dscdata) {
1575             $skew_warning_vsn = $vsn if !defined $skew_warning_vsn;
1576             next;
1577         }
1578         if ($digester) {
1579             $digester->reset();
1580             $digester->add($dscdata);
1581             my $got = $digester->hexdigest();
1582             $got eq $digest or
1583                 fail "$dscurl has hash $got but".
1584                     " archive told us to expect $digest";
1585         }
1586         parse_dscdata();
1587         my $fmt = getfield $dsc, 'Format';
1588         $format_ok{$fmt} or forceable_fail [qw(unsupported-source-format)],
1589             "unsupported source format $fmt, sorry";
1590             
1591         $dsc_checked = !!$digester;
1592         printdebug "get_archive_dsc: Version ".(getfield $dsc, 'Version')."\n";
1593         return;
1594     }
1595     $dsc = undef;
1596     printdebug "get_archive_dsc: nothing in archive, returning undef\n";
1597 }
1598
1599 sub check_for_git ();
1600 sub check_for_git () {
1601     # returns 0 or 1
1602     my $how = access_cfg('git-check');
1603     if ($how eq 'ssh-cmd') {
1604         my @cmd =
1605             (access_cfg_ssh, access_gituserhost(),
1606              access_runeinfo("git-check $package").
1607              " set -e; cd ".access_cfg('git-path').";".
1608              " if test -d $package.git; then echo 1; else echo 0; fi");
1609         my $r= cmdoutput @cmd;
1610         if (defined $r and $r =~ m/^divert (\w+)$/) {
1611             my $divert=$1;
1612             my ($usedistro,) = access_distros();
1613             # NB that if we are pushing, $usedistro will be $distro/push
1614             $instead_distro= cfg("dgit-distro.$usedistro.diverts.$divert");
1615             $instead_distro =~ s{^/}{ access_basedistro()."/" }e;
1616             progress "diverting to $divert (using config for $instead_distro)";
1617             return check_for_git();
1618         }
1619         failedcmd @cmd unless defined $r and $r =~ m/^[01]$/;
1620         return $r+0;
1621     } elsif ($how eq 'url') {
1622         my $prefix = access_cfg('git-check-url','git-url');
1623         my $suffix = access_cfg('git-check-suffix','git-suffix',
1624                                 'RETURN-UNDEF') // '.git';
1625         my $url = "$prefix/$package$suffix";
1626         my @cmd = (@curl, qw(-sS -I), $url);
1627         my $result = cmdoutput @cmd;
1628         $result =~ s/^\S+ 200 .*\n\r?\n//;
1629         # curl -sS -I with https_proxy prints
1630         # HTTP/1.0 200 Connection established
1631         $result =~ m/^\S+ (404|200) /s or
1632             fail "unexpected results from git check query - ".
1633                 Dumper($prefix, $result);
1634         my $code = $1;
1635         if ($code eq '404') {
1636             return 0;
1637         } elsif ($code eq '200') {
1638             return 1;
1639         } else {
1640             die;
1641         }
1642     } elsif ($how eq 'true') {
1643         return 1;
1644     } elsif ($how eq 'false') {
1645         return 0;
1646     } else {
1647         badcfg "unknown git-check \`$how'";
1648     }
1649 }
1650
1651 sub create_remote_git_repo () {
1652     my $how = access_cfg('git-create');
1653     if ($how eq 'ssh-cmd') {
1654         runcmd_ordryrun
1655             (access_cfg_ssh, access_gituserhost(),
1656              access_runeinfo("git-create $package").
1657              "set -e; cd ".access_cfg('git-path').";".
1658              " cp -a _template $package.git");
1659     } elsif ($how eq 'true') {
1660         # nothing to do
1661     } else {
1662         badcfg "unknown git-create \`$how'";
1663     }
1664 }
1665
1666 our ($dsc_hash,$lastpush_mergeinput);
1667 our ($dsc_distro, $dsc_hint_tag, $dsc_hint_url);
1668
1669 our $ud = '.git/dgit/unpack';
1670
1671 sub prep_ud (;$) {
1672     my ($d) = @_;
1673     $d //= $ud;
1674     rmtree($d);
1675     mkpath '.git/dgit';
1676     mkdir $d or die $!;
1677 }
1678
1679 sub mktree_in_ud_here () {
1680     runcmd qw(git init -q);
1681     runcmd qw(git config gc.auto 0);
1682     rmtree('.git/objects');
1683     symlink '../../../../objects','.git/objects' or die $!;
1684 }
1685
1686 sub git_write_tree () {
1687     my $tree = cmdoutput @git, qw(write-tree);
1688     $tree =~ m/^\w+$/ or die "$tree ?";
1689     return $tree;
1690 }
1691
1692 sub git_add_write_tree () {
1693     runcmd @git, qw(add -Af .);
1694     return git_write_tree();
1695 }
1696
1697 sub remove_stray_gits ($) {
1698     my ($what) = @_;
1699     my @gitscmd = qw(find -name .git -prune -print0);
1700     debugcmd "|",@gitscmd;
1701     open GITS, "-|", @gitscmd or die $!;
1702     {
1703         local $/="\0";
1704         while (<GITS>) {
1705             chomp or die;
1706             print STDERR "$us: warning: removing from $what: ",
1707                 (messagequote $_), "\n";
1708             rmtree $_;
1709         }
1710     }
1711     $!=0; $?=0; close GITS or failedcmd @gitscmd;
1712 }
1713
1714 sub mktree_in_ud_from_only_subdir ($;$) {
1715     my ($what,$raw) = @_;
1716
1717     # changes into the subdir
1718     my (@dirs) = <*/.>;
1719     die "expected one subdir but found @dirs ?" unless @dirs==1;
1720     $dirs[0] =~ m#^([^/]+)/\.$# or die;
1721     my $dir = $1;
1722     changedir $dir;
1723
1724     remove_stray_gits($what);
1725     mktree_in_ud_here();
1726     if (!$raw) {
1727         my ($format, $fopts) = get_source_format();
1728         if (madformat($format)) {
1729             rmtree '.pc';
1730         }
1731     }
1732
1733     my $tree=git_add_write_tree();
1734     return ($tree,$dir);
1735 }
1736
1737 our @files_csum_info_fields = 
1738     (['Checksums-Sha256','Digest::SHA', 'new(256)', 'sha256sum'],
1739      ['Checksums-Sha1',  'Digest::SHA', 'new(1)',   'sha1sum'],
1740      ['Files',           'Digest::MD5', 'new()',    'md5sum']);
1741
1742 sub dsc_files_info () {
1743     foreach my $csumi (@files_csum_info_fields) {
1744         my ($fname, $module, $method) = @$csumi;
1745         my $field = $dsc->{$fname};
1746         next unless defined $field;
1747         eval "use $module; 1;" or die $@;
1748         my @out;
1749         foreach (split /\n/, $field) {
1750             next unless m/\S/;
1751             m/^(\w+) (\d+) (\S+)$/ or
1752                 fail "could not parse .dsc $fname line \`$_'";
1753             my $digester = eval "$module"."->$method;" or die $@;
1754             push @out, {
1755                 Hash => $1,
1756                 Bytes => $2,
1757                 Filename => $3,
1758                 Digester => $digester,
1759             };
1760         }
1761         return @out;
1762     }
1763     fail "missing any supported Checksums-* or Files field in ".
1764         $dsc->get_option('name');
1765 }
1766
1767 sub dsc_files () {
1768     map { $_->{Filename} } dsc_files_info();
1769 }
1770
1771 sub files_compare_inputs (@) {
1772     my $inputs = \@_;
1773     my %record;
1774     my %fchecked;
1775
1776     my $showinputs = sub {
1777         return join "; ", map { $_->get_option('name') } @$inputs;
1778     };
1779
1780     foreach my $in (@$inputs) {
1781         my $expected_files;
1782         my $in_name = $in->get_option('name');
1783
1784         printdebug "files_compare_inputs $in_name\n";
1785
1786         foreach my $csumi (@files_csum_info_fields) {
1787             my ($fname) = @$csumi;
1788             printdebug "files_compare_inputs $in_name $fname\n";
1789
1790             my $field = $in->{$fname};
1791             next unless defined $field;
1792
1793             my @files;
1794             foreach (split /\n/, $field) {
1795                 next unless m/\S/;
1796
1797                 my ($info, $f) = m/^(\w+ \d+) (?:\S+ \S+ )?(\S+)$/ or
1798                     fail "could not parse $in_name $fname line \`$_'";
1799
1800                 printdebug "files_compare_inputs $in_name $fname $f\n";
1801
1802                 push @files, $f;
1803
1804                 my $re = \ $record{$f}{$fname};
1805                 if (defined $$re) {
1806                     $fchecked{$f}{$in_name} = 1;
1807                     $$re eq $info or
1808                         fail "hash or size of $f varies in $fname fields".
1809                         " (between: ".$showinputs->().")";
1810                 } else {
1811                     $$re = $info;
1812                 }
1813             }
1814             @files = sort @files;
1815             $expected_files //= \@files;
1816             "@$expected_files" eq "@files" or
1817                 fail "file list in $in_name varies between hash fields!";
1818         }
1819         $expected_files or
1820             fail "$in_name has no files list field(s)";
1821     }
1822     printdebug "files_compare_inputs ".Dumper(\%fchecked, \%record)
1823         if $debuglevel>=2;
1824
1825     grep { keys %$_ == @$inputs-1 } values %fchecked
1826         or fail "no file appears in all file lists".
1827         " (looked in: ".$showinputs->().")";
1828 }
1829
1830 sub is_orig_file_in_dsc ($$) {
1831     my ($f, $dsc_files_info) = @_;
1832     return 0 if @$dsc_files_info <= 1;
1833     # One file means no origs, and the filename doesn't have a "what
1834     # part of dsc" component.  (Consider versions ending `.orig'.)
1835     return 0 unless $f =~ m/\.$orig_f_tail_re$/o;
1836     return 1;
1837 }
1838
1839 sub is_orig_file_of_vsn ($$) {
1840     my ($f, $upstreamvsn) = @_;
1841     my $base = srcfn $upstreamvsn, '';
1842     return 0 unless $f =~ m/^\Q$base\E\.$orig_f_tail_re$/;
1843     return 1;
1844 }
1845
1846 sub changes_update_origs_from_dsc ($$$$) {
1847     my ($dsc, $changes, $upstreamvsn, $changesfile) = @_;
1848     my %changes_f;
1849     printdebug "checking origs needed ($upstreamvsn)...\n";
1850     $_ = getfield $changes, 'Files';
1851     m/^\w+ \d+ (\S+ \S+) \S+$/m or
1852         fail "cannot find section/priority from .changes Files field";
1853     my $placementinfo = $1;
1854     my %changed;
1855     printdebug "checking origs needed placement '$placementinfo'...\n";
1856     foreach my $l (split /\n/, getfield $dsc, 'Files') {
1857         $l =~ m/\S+$/ or next;
1858         my $file = $&;
1859         printdebug "origs $file | $l\n";
1860         next unless is_orig_file_of_vsn $file, $upstreamvsn;
1861         printdebug "origs $file is_orig\n";
1862         my $have = archive_query('file_in_archive', $file);
1863         if (!defined $have) {
1864             print STDERR <<END;
1865 archive does not support .orig check; hope you used --ch:--sa/-sd if needed
1866 END
1867             return;
1868         }
1869         my $found_same = 0;
1870         my @found_differ;
1871         printdebug "origs $file \$#\$have=$#$have\n";
1872         foreach my $h (@$have) {
1873             my $same = 0;
1874             my @differ;
1875             foreach my $csumi (@files_csum_info_fields) {
1876                 my ($fname, $module, $method, $archivefield) = @$csumi;
1877                 next unless defined $h->{$archivefield};
1878                 $_ = $dsc->{$fname};
1879                 next unless defined;
1880                 m/^(\w+) .* \Q$file\E$/m or
1881                     fail ".dsc $fname missing entry for $file";
1882                 if ($h->{$archivefield} eq $1) {
1883                     $same++;
1884                 } else {
1885                     push @differ,
1886  "$archivefield: $h->{$archivefield} (archive) != $1 (local .dsc)";
1887                 }
1888             }
1889             die "$file ".Dumper($h)." ?!" if $same && @differ;
1890             $found_same++
1891                 if $same;
1892             push @found_differ, "archive $h->{filename}: ".join "; ", @differ
1893                 if @differ;
1894         }
1895         printdebug "origs $file f.same=$found_same".
1896             " #f._differ=$#found_differ\n";
1897         if (@found_differ && !$found_same) {
1898             fail join "\n",
1899                 "archive contains $file with different checksum",
1900                 @found_differ;
1901         }
1902         # Now we edit the changes file to add or remove it
1903         foreach my $csumi (@files_csum_info_fields) {
1904             my ($fname, $module, $method, $archivefield) = @$csumi;
1905             next unless defined $changes->{$fname};
1906             if ($found_same) {
1907                 # in archive, delete from .changes if it's there
1908                 $changed{$file} = "removed" if
1909                     $changes->{$fname} =~ s/^.* \Q$file\E$(?:)\n//m;
1910             } elsif ($changes->{$fname} =~ m/^.* \Q$file\E$(?:)\n/m) {
1911                 # not in archive, but it's here in the .changes
1912             } else {
1913                 my $dsc_data = getfield $dsc, $fname;
1914                 $dsc_data =~ m/^(.* \Q$file\E$)\n/m or die "$dsc_data $file ?";
1915                 my $extra = $1;
1916                 $extra =~ s/ \d+ /$&$placementinfo /
1917                     or die "$fname $extra >$dsc_data< ?"
1918                     if $fname eq 'Files';
1919                 $changes->{$fname} .= "\n". $extra;
1920                 $changed{$file} = "added";
1921             }
1922         }
1923     }
1924     if (%changed) {
1925         foreach my $file (keys %changed) {
1926             progress sprintf
1927                 "edited .changes for archive .orig contents: %s %s",
1928                 $changed{$file}, $file;
1929         }
1930         my $chtmp = "$changesfile.tmp";
1931         $changes->save($chtmp);
1932         if (act_local()) {
1933             rename $chtmp,$changesfile or die "$changesfile $!";
1934         } else {
1935             progress "[new .changes left in $changesfile]";
1936         }
1937     } else {
1938         progress "$changesfile already has appropriate .orig(s) (if any)";
1939     }
1940 }
1941
1942 sub make_commit ($) {
1943     my ($file) = @_;
1944     return cmdoutput @git, qw(hash-object -w -t commit), $file;
1945 }
1946
1947 sub make_commit_text ($) {
1948     my ($text) = @_;
1949     my ($out, $in);
1950     my @cmd = (@git, qw(hash-object -w -t commit --stdin));
1951     debugcmd "|",@cmd;
1952     print Dumper($text) if $debuglevel > 1;
1953     my $child = open2($out, $in, @cmd) or die $!;
1954     my $h;
1955     eval {
1956         print $in $text or die $!;
1957         close $in or die $!;
1958         $h = <$out>;
1959         $h =~ m/^\w+$/ or die;
1960         $h = $&;
1961         printdebug "=> $h\n";
1962     };
1963     close $out;
1964     waitpid $child, 0 == $child or die "$child $!";
1965     $? and failedcmd @cmd;
1966     return $h;
1967 }
1968
1969 sub clogp_authline ($) {
1970     my ($clogp) = @_;
1971     my $author = getfield $clogp, 'Maintainer';
1972     $author =~ s#,.*##ms;
1973     my $date = cmdoutput qw(date), '+%s %z', qw(-d), getfield($clogp,'Date');
1974     my $authline = "$author $date";
1975     $authline =~ m/$git_authline_re/o or
1976         fail "unexpected commit author line format \`$authline'".
1977         " (was generated from changelog Maintainer field)";
1978     return ($1,$2,$3) if wantarray;
1979     return $authline;
1980 }
1981
1982 sub vendor_patches_distro ($$) {
1983     my ($checkdistro, $what) = @_;
1984     return unless defined $checkdistro;
1985
1986     my $series = "debian/patches/\L$checkdistro\E.series";
1987     printdebug "checking for vendor-specific $series ($what)\n";
1988
1989     if (!open SERIES, "<", $series) {
1990         die "$series $!" unless $!==ENOENT;
1991         return;
1992     }
1993     while (<SERIES>) {
1994         next unless m/\S/;
1995         next if m/^\s+\#/;
1996
1997         print STDERR <<END;
1998
1999 Unfortunately, this source package uses a feature of dpkg-source where
2000 the same source package unpacks to different source code on different
2001 distros.  dgit cannot safely operate on such packages on affected
2002 distros, because the meaning of source packages is not stable.
2003
2004 Please ask the distro/maintainer to remove the distro-specific series
2005 files and use a different technique (if necessary, uploading actually
2006 different packages, if different distros are supposed to have
2007 different code).
2008
2009 END
2010         fail "Found active distro-specific series file for".
2011             " $checkdistro ($what): $series, cannot continue";
2012     }
2013     die "$series $!" if SERIES->error;
2014     close SERIES;
2015 }
2016
2017 sub check_for_vendor_patches () {
2018     # This dpkg-source feature doesn't seem to be documented anywhere!
2019     # But it can be found in the changelog (reformatted):
2020
2021     #   commit  4fa01b70df1dc4458daee306cfa1f987b69da58c
2022     #   Author: Raphael Hertzog <hertzog@debian.org>
2023     #   Date: Sun  Oct  3  09:36:48  2010 +0200
2024
2025     #   dpkg-source: correctly create .pc/.quilt_series with alternate
2026     #   series files
2027     #   
2028     #   If you have debian/patches/ubuntu.series and you were
2029     #   unpacking the source package on ubuntu, quilt was still
2030     #   directed to debian/patches/series instead of
2031     #   debian/patches/ubuntu.series.
2032     #   
2033     #   debian/changelog                        |    3 +++
2034     #   scripts/Dpkg/Source/Package/V3/quilt.pm |    4 +++-
2035     #   2 files changed, 6 insertions(+), 1 deletion(-)
2036
2037     use Dpkg::Vendor;
2038     vendor_patches_distro($ENV{DEB_VENDOR}, "DEB_VENDOR");
2039     vendor_patches_distro(Dpkg::Vendor::get_current_vendor(),
2040                          "Dpkg::Vendor \`current vendor'");
2041     vendor_patches_distro(access_basedistro(),
2042                           "(base) distro being accessed");
2043     vendor_patches_distro(access_nomdistro(),
2044                           "(nominal) distro being accessed");
2045 }
2046
2047 sub generate_commits_from_dsc () {
2048     # See big comment in fetch_from_archive, below.
2049     # See also README.dsc-import.
2050     prep_ud();
2051     changedir $ud;
2052
2053     my @dfi = dsc_files_info();
2054     foreach my $fi (@dfi) {
2055         my $f = $fi->{Filename};
2056         die "$f ?" if $f =~ m#/|^\.|\.dsc$|\.tmp$#;
2057
2058         printdebug "considering linking $f: ";
2059
2060         link_ltarget "../../../../$f", $f
2061             or ((printdebug "($!) "), 0)
2062             or $!==&ENOENT
2063             or die "$f $!";
2064
2065         printdebug "linked.\n";
2066
2067         complete_file_from_dsc('.', $fi)
2068             or next;
2069
2070         if (is_orig_file_in_dsc($f, \@dfi)) {
2071             link $f, "../../../../$f"
2072                 or $!==&EEXIST
2073                 or die "$f $!";
2074         }
2075     }
2076
2077     # We unpack and record the orig tarballs first, so that we only
2078     # need disk space for one private copy of the unpacked source.
2079     # But we can't make them into commits until we have the metadata
2080     # from the debian/changelog, so we record the tree objects now and
2081     # make them into commits later.
2082     my @tartrees;
2083     my $upstreamv = upstreamversion $dsc->{version};
2084     my $orig_f_base = srcfn $upstreamv, '';
2085
2086     foreach my $fi (@dfi) {
2087         # We actually import, and record as a commit, every tarball
2088         # (unless there is only one file, in which case there seems
2089         # little point.
2090
2091         my $f = $fi->{Filename};
2092         printdebug "import considering $f ";
2093         (printdebug "only one dfi\n"), next if @dfi == 1;
2094         (printdebug "not tar\n"), next unless $f =~ m/\.tar(\.\w+)?$/;
2095         (printdebug "signature\n"), next if $f =~ m/$orig_f_sig_re$/o;
2096         my $compr_ext = $1;
2097
2098         my ($orig_f_part) =
2099             $f =~ m/^\Q$orig_f_base\E\.([^._]+)?\.tar(?:\.\w+)?$/;
2100
2101         printdebug "Y ", (join ' ', map { $_//"(none)" }
2102                           $compr_ext, $orig_f_part
2103                          ), "\n";
2104
2105         my $input = new IO::File $f, '<' or die "$f $!";
2106         my $compr_pid;
2107         my @compr_cmd;
2108
2109         if (defined $compr_ext) {
2110             my $cname =
2111                 Dpkg::Compression::compression_guess_from_filename $f;
2112             fail "Dpkg::Compression cannot handle file $f in source package"
2113                 if defined $compr_ext && !defined $cname;
2114             my $compr_proc =
2115                 new Dpkg::Compression::Process compression => $cname;
2116             my @compr_cmd = $compr_proc->get_uncompress_cmdline();
2117             my $compr_fh = new IO::Handle;
2118             my $compr_pid = open $compr_fh, "-|" // die $!;
2119             if (!$compr_pid) {
2120                 open STDIN, "<&", $input or die $!;
2121                 exec @compr_cmd;
2122                 die "dgit (child): exec $compr_cmd[0]: $!\n";
2123             }
2124             $input = $compr_fh;
2125         }
2126
2127         rmtree "_unpack-tar";
2128         mkdir "_unpack-tar" or die $!;
2129         my @tarcmd = qw(tar -x -f -
2130                         --no-same-owner --no-same-permissions
2131                         --no-acls --no-xattrs --no-selinux);
2132         my $tar_pid = fork // die $!;
2133         if (!$tar_pid) {
2134             chdir "_unpack-tar" or die $!;
2135             open STDIN, "<&", $input or die $!;
2136             exec @tarcmd;
2137             die "dgit (child): exec $tarcmd[0]: $!";
2138         }
2139         $!=0; (waitpid $tar_pid, 0) == $tar_pid or die $!;
2140         !$? or failedcmd @tarcmd;
2141
2142         close $input or
2143             (@compr_cmd ? failedcmd @compr_cmd
2144              : die $!);
2145         # finally, we have the results in "tarball", but maybe
2146         # with the wrong permissions
2147
2148         runcmd qw(chmod -R +rwX _unpack-tar);
2149         changedir "_unpack-tar";
2150         remove_stray_gits($f);
2151         mktree_in_ud_here();
2152         
2153         my ($tree) = git_add_write_tree();
2154         my $tentries = cmdoutput @git, qw(ls-tree -z), $tree;
2155         if ($tentries =~ m/^\d+ tree (\w+)\t[^\000]+\000$/s) {
2156             $tree = $1;
2157             printdebug "one subtree $1\n";
2158         } else {
2159             printdebug "multiple subtrees\n";
2160         }
2161         changedir "..";
2162         rmtree "_unpack-tar";
2163
2164         my $ent = [ $f, $tree ];
2165         push @tartrees, {
2166             Orig => !!$orig_f_part,
2167             Sort => (!$orig_f_part         ? 2 :
2168                      $orig_f_part =~ m/-/g ? 1 :
2169                                              0),
2170             F => $f,
2171             Tree => $tree,
2172         };
2173     }
2174
2175     @tartrees = sort {
2176         # put any without "_" first (spec is not clear whether files
2177         # are always in the usual order).  Tarballs without "_" are
2178         # the main orig or the debian tarball.
2179         $a->{Sort} <=> $b->{Sort} or
2180         $a->{F}    cmp $b->{F}
2181     } @tartrees;
2182
2183     my $any_orig = grep { $_->{Orig} } @tartrees;
2184
2185     my $dscfn = "$package.dsc";
2186
2187     my $treeimporthow = 'package';
2188
2189     open D, ">", $dscfn or die "$dscfn: $!";
2190     print D $dscdata or die "$dscfn: $!";
2191     close D or die "$dscfn: $!";
2192     my @cmd = qw(dpkg-source);
2193     push @cmd, '--no-check' if $dsc_checked;
2194     if (madformat $dsc->{format}) {
2195         push @cmd, '--skip-patches';
2196         $treeimporthow = 'unpatched';
2197     }
2198     push @cmd, qw(-x --), $dscfn;
2199     runcmd @cmd;
2200
2201     my ($tree,$dir) = mktree_in_ud_from_only_subdir("source package");
2202     if (madformat $dsc->{format}) { 
2203         check_for_vendor_patches();
2204     }
2205
2206     my $dappliedtree;
2207     if (madformat $dsc->{format}) {
2208         my @pcmd = qw(dpkg-source --before-build .);
2209         runcmd shell_cmd 'exec >/dev/null', @pcmd;
2210         rmtree '.pc';
2211         $dappliedtree = git_add_write_tree();
2212     }
2213
2214     my @clogcmd = qw(dpkg-parsechangelog --format rfc822 --all);
2215     debugcmd "|",@clogcmd;
2216     open CLOGS, "-|", @clogcmd or die $!;
2217
2218     my $clogp;
2219     my $r1clogp;
2220
2221     printdebug "import clog search...\n";
2222
2223     for (;;) {
2224         my $stanzatext = do { local $/=""; <CLOGS>; };
2225         printdebug "import clogp ".Dumper($stanzatext) if $debuglevel>1;
2226         last if !defined $stanzatext;
2227
2228         my $desc = "package changelog, entry no.$.";
2229         open my $stanzafh, "<", \$stanzatext or die;
2230         my $thisstanza = parsecontrolfh $stanzafh, $desc, 1;
2231         $clogp //= $thisstanza;
2232
2233         printdebug "import clog $thisstanza->{version} $desc...\n";
2234
2235         last if !$any_orig; # we don't need $r1clogp
2236
2237         # We look for the first (most recent) changelog entry whose
2238         # version number is lower than the upstream version of this
2239         # package.  Then the last (least recent) previous changelog
2240         # entry is treated as the one which introduced this upstream
2241         # version and used for the synthetic commits for the upstream
2242         # tarballs.
2243
2244         # One might think that a more sophisticated algorithm would be
2245         # necessary.  But: we do not want to scan the whole changelog
2246         # file.  Stopping when we see an earlier version, which
2247         # necessarily then is an earlier upstream version, is the only
2248         # realistic way to do that.  Then, either the earliest
2249         # changelog entry we have seen so far is indeed the earliest
2250         # upload of this upstream version; or there are only changelog
2251         # entries relating to later upstream versions (which is not
2252         # possible unless the changelog and .dsc disagree about the
2253         # version).  Then it remains to choose between the physically
2254         # last entry in the file, and the one with the lowest version
2255         # number.  If these are not the same, we guess that the
2256         # versions were created in a non-monotic order rather than
2257         # that the changelog entries have been misordered.
2258
2259         printdebug "import clog $thisstanza->{version} vs $upstreamv...\n";
2260
2261         last if version_compare($thisstanza->{version}, $upstreamv) < 0;
2262         $r1clogp = $thisstanza;
2263
2264         printdebug "import clog $r1clogp->{version} becomes r1\n";
2265     }
2266     die $! if CLOGS->error;
2267     close CLOGS or $?==SIGPIPE or failedcmd @clogcmd;
2268
2269     $clogp or fail "package changelog has no entries!";
2270
2271     my $authline = clogp_authline $clogp;
2272     my $changes = getfield $clogp, 'Changes';
2273     my $cversion = getfield $clogp, 'Version';
2274
2275     if (@tartrees) {
2276         $r1clogp //= $clogp; # maybe there's only one entry;
2277         my $r1authline = clogp_authline $r1clogp;
2278         # Strictly, r1authline might now be wrong if it's going to be
2279         # unused because !$any_orig.  Whatever.
2280
2281         printdebug "import tartrees authline   $authline\n";
2282         printdebug "import tartrees r1authline $r1authline\n";
2283
2284         foreach my $tt (@tartrees) {
2285             printdebug "import tartree $tt->{F} $tt->{Tree}\n";
2286
2287             $tt->{Commit} = make_commit_text($tt->{Orig} ? <<END_O : <<END_T);
2288 tree $tt->{Tree}
2289 author $r1authline
2290 committer $r1authline
2291
2292 Import $tt->{F}
2293
2294 [dgit import orig $tt->{F}]
2295 END_O
2296 tree $tt->{Tree}
2297 author $authline
2298 committer $authline
2299
2300 Import $tt->{F}
2301
2302 [dgit import tarball $package $cversion $tt->{F}]
2303 END_T
2304         }
2305     }
2306
2307     printdebug "import main commit\n";
2308
2309     open C, ">../commit.tmp" or die $!;
2310     print C <<END or die $!;
2311 tree $tree
2312 END
2313     print C <<END or die $! foreach @tartrees;
2314 parent $_->{Commit}
2315 END
2316     print C <<END or die $!;
2317 author $authline
2318 committer $authline
2319
2320 $changes
2321
2322 [dgit import $treeimporthow $package $cversion]
2323 END
2324
2325     close C or die $!;
2326     my $rawimport_hash = make_commit qw(../commit.tmp);
2327
2328     if (madformat $dsc->{format}) {
2329         printdebug "import apply patches...\n";
2330
2331         # regularise the state of the working tree so that
2332         # the checkout of $rawimport_hash works nicely.
2333         my $dappliedcommit = make_commit_text(<<END);
2334 tree $dappliedtree
2335 author $authline
2336 committer $authline
2337
2338 [dgit dummy commit]
2339 END
2340         runcmd @git, qw(checkout -q -b dapplied), $dappliedcommit;
2341
2342         runcmd @git, qw(checkout -q -b unpa), $rawimport_hash;
2343
2344         # We need the answers to be reproducible
2345         my @authline = clogp_authline($clogp);
2346         local $ENV{GIT_COMMITTER_NAME} =  $authline[0];
2347         local $ENV{GIT_COMMITTER_EMAIL} = $authline[1];
2348         local $ENV{GIT_COMMITTER_DATE} =  $authline[2];
2349         local $ENV{GIT_AUTHOR_NAME} =  $authline[0];
2350         local $ENV{GIT_AUTHOR_EMAIL} = $authline[1];
2351         local $ENV{GIT_AUTHOR_DATE} =  $authline[2];
2352
2353         my $path = $ENV{PATH} or die;
2354
2355         foreach my $use_absurd (qw(0 1)) {
2356             runcmd @git, qw(checkout -q unpa);
2357             runcmd @git, qw(update-ref -d refs/heads/patch-queue/unpa);
2358             local $ENV{PATH} = $path;
2359             if ($use_absurd) {
2360                 chomp $@;
2361                 progress "warning: $@";
2362                 $path = "$absurdity:$path";
2363                 progress "$us: trying slow absurd-git-apply...";
2364                 rename "../../gbp-pq-output","../../gbp-pq-output.0"
2365                     or $!==ENOENT
2366                     or die $!;
2367             }
2368             eval {
2369                 die "forbid absurd git-apply\n" if $use_absurd
2370                     && forceing [qw(import-gitapply-no-absurd)];
2371                 die "only absurd git-apply!\n" if !$use_absurd
2372                     && forceing [qw(import-gitapply-absurd)];
2373
2374                 local $ENV{DGIT_ABSURD_DEBUG} = $debuglevel if $use_absurd;
2375                 local $ENV{PATH} = $path                    if $use_absurd;
2376
2377                 my @showcmd = (gbp_pq, qw(import));
2378                 my @realcmd = shell_cmd
2379                     'exec >/dev/null 2>>../../gbp-pq-output', @showcmd;
2380                 debugcmd "+",@realcmd;
2381                 if (system @realcmd) {
2382                     die +(shellquote @showcmd).
2383                         " failed: ".
2384                         failedcmd_waitstatus()."\n";
2385                 }
2386
2387                 my $gapplied = git_rev_parse('HEAD');
2388                 my $gappliedtree = cmdoutput @git, qw(rev-parse HEAD:);
2389                 $gappliedtree eq $dappliedtree or
2390                     fail <<END;
2391 gbp-pq import and dpkg-source disagree!
2392  gbp-pq import gave commit $gapplied
2393  gbp-pq import gave tree $gappliedtree
2394  dpkg-source --before-build gave tree $dappliedtree
2395 END
2396                 $rawimport_hash = $gapplied;
2397             };
2398             last unless $@;
2399         }
2400         if ($@) {
2401             { local $@; eval { runcmd qw(cat ../../gbp-pq-output); }; }
2402             die $@;
2403         }
2404     }
2405
2406     progress "synthesised git commit from .dsc $cversion";
2407
2408     my $rawimport_mergeinput = {
2409         Commit => $rawimport_hash,
2410         Info => "Import of source package",
2411     };
2412     my @output = ($rawimport_mergeinput);
2413
2414     if ($lastpush_mergeinput) {
2415         my $oldclogp = mergeinfo_getclogp($lastpush_mergeinput);
2416         my $oversion = getfield $oldclogp, 'Version';
2417         my $vcmp =
2418             version_compare($oversion, $cversion);
2419         if ($vcmp < 0) {
2420             @output = ($rawimport_mergeinput, $lastpush_mergeinput,
2421                 { Message => <<END, ReverseParents => 1 });
2422 Record $package ($cversion) in archive suite $csuite
2423 END
2424         } elsif ($vcmp > 0) {
2425             print STDERR <<END or die $!;
2426
2427 Version actually in archive:   $cversion (older)
2428 Last version pushed with dgit: $oversion (newer or same)
2429 $later_warning_msg
2430 END
2431             @output = $lastpush_mergeinput;
2432         } else {
2433             # Same version.  Use what's in the server git branch,
2434             # discarding our own import.  (This could happen if the
2435             # server automatically imports all packages into git.)
2436             @output = $lastpush_mergeinput;
2437         }
2438     }
2439     changedir '../../../..';
2440     rmtree($ud);
2441     return @output;
2442 }
2443
2444 sub complete_file_from_dsc ($$) {
2445     our ($dstdir, $fi) = @_;
2446     # Ensures that we have, in $dir, the file $fi, with the correct
2447     # contents.  (Downloading it from alongside $dscurl if necessary.)
2448
2449     my $f = $fi->{Filename};
2450     my $tf = "$dstdir/$f";
2451     my $downloaded = 0;
2452
2453     if (stat_exists $tf) {
2454         progress "using existing $f";
2455     } else {
2456         printdebug "$tf does not exist, need to fetch\n";
2457         my $furl = $dscurl;
2458         $furl =~ s{/[^/]+$}{};
2459         $furl .= "/$f";
2460         die "$f ?" unless $f =~ m/^\Q${package}\E_/;
2461         die "$f ?" if $f =~ m#/#;
2462         runcmd_ordryrun_local @curl,qw(-f -o),$tf,'--',"$furl";
2463         return 0 if !act_local();
2464         $downloaded = 1;
2465     }
2466
2467     open F, "<", "$tf" or die "$tf: $!";
2468     $fi->{Digester}->reset();
2469     $fi->{Digester}->addfile(*F);
2470     F->error and die $!;
2471     my $got = $fi->{Digester}->hexdigest();
2472     $got eq $fi->{Hash} or
2473         fail "file $f has hash $got but .dsc".
2474             " demands hash $fi->{Hash} ".
2475             ($downloaded ? "(got wrong file from archive!)"
2476              : "(perhaps you should delete this file?)");
2477
2478     return 1;
2479 }
2480
2481 sub ensure_we_have_orig () {
2482     my @dfi = dsc_files_info();
2483     foreach my $fi (@dfi) {
2484         my $f = $fi->{Filename};
2485         next unless is_orig_file_in_dsc($f, \@dfi);
2486         complete_file_from_dsc('..', $fi)
2487             or next;
2488     }
2489 }
2490
2491 #---------- git fetch ----------
2492
2493 sub lrfetchrefs () { return "refs/dgit-fetch/".access_basedistro(); }
2494 sub lrfetchref () { return lrfetchrefs.'/'.server_branch($csuite); }
2495
2496 # We fetch some parts of lrfetchrefs/*.  Ideally we delete these
2497 # locally fetched refs because they have unhelpful names and clutter
2498 # up gitk etc.  So we track whether we have "used up" head ref (ie,
2499 # whether we have made another local ref which refers to this object).
2500 #
2501 # (If we deleted them unconditionally, then we might end up
2502 # re-fetching the same git objects each time dgit fetch was run.)
2503 #
2504 # So, leach use of lrfetchrefs needs to be accompanied by arrangements
2505 # in git_fetch_us to fetch the refs in question, and possibly a call
2506 # to lrfetchref_used.
2507
2508 our (%lrfetchrefs_f, %lrfetchrefs_d);
2509 # $lrfetchrefs_X{lrfetchrefs."/heads/whatever"} = $objid
2510
2511 sub lrfetchref_used ($) {
2512     my ($fullrefname) = @_;
2513     my $objid = $lrfetchrefs_f{$fullrefname};
2514     $lrfetchrefs_d{$fullrefname} = $objid if defined $objid;
2515 }
2516
2517 sub git_lrfetch_sane {
2518     my (@specs) = @_;
2519
2520     # This is rather miserable:
2521     # When git fetch --prune is passed a fetchspec ending with a *,
2522     # it does a plausible thing.  If there is no * then:
2523     # - it matches subpaths too, even if the supplied refspec
2524     #   starts refs, and behaves completely madly if the source
2525     #   has refs/refs/something.  (See, for example, Debian #NNNN.)
2526     # - if there is no matching remote ref, it bombs out the whole
2527     #   fetch.
2528     # We want to fetch a fixed ref, and we don't know in advance
2529     # if it exists, so this is not suitable.
2530     #
2531     # Our workaround is to use git ls-remote.  git ls-remote has its
2532     # own qairks.  Notably, it has the absurd multi-tail-matching
2533     # behaviour: git ls-remote R refs/foo can report refs/foo AND
2534     # refs/refs/foo etc.
2535     #
2536     # Also, we want an idempotent snapshot, but we have to make two
2537     # calls to the remote: one to git ls-remote and to git fetch.  The
2538     # solution is use git ls-remote to obtain a target state, and
2539     # git fetch to try to generate it.  If we don't manage to generate
2540     # the target state, we try again.
2541
2542     my $url = access_giturl();
2543
2544     printdebug "git_lrfetch_sane specs @specs\n";
2545
2546     my $specre = join '|', map {
2547         my $x = $_;
2548         $x =~ s/\W/\\$&/g;
2549         $x =~ s/\\\*$/.*/;
2550         "(?:refs/$x)";
2551     } @specs;
2552     printdebug "git_lrfetch_sane specre=$specre\n";
2553     my $wanted_rref = sub {
2554         local ($_) = @_;
2555         return m/^(?:$specre)$/o;
2556     };
2557
2558     my $fetch_iteration = 0;
2559     FETCH_ITERATION:
2560     for (;;) {
2561         printdebug "git_lrfetch_sane iteration $fetch_iteration\n";
2562         if (++$fetch_iteration > 10) {
2563             fail "too many iterations trying to get sane fetch!";
2564         }
2565
2566         my @look = map { "refs/$_" } @specs;
2567         my @lcmd = (@git, qw(ls-remote -q --refs), $url, @look);
2568         debugcmd "|",@lcmd;
2569
2570         my %wantr;
2571         open GITLS, "-|", @lcmd or die $!;
2572         while (<GITLS>) {
2573             printdebug "=> ", $_;
2574             m/^(\w+)\s+(\S+)\n/ or die "ls-remote $_ ?";
2575             my ($objid,$rrefname) = ($1,$2);
2576             if (!$wanted_rref->($rrefname)) {
2577                 print STDERR <<END;
2578 warning: git ls-remote @look reported $rrefname; this is silly, ignoring it.
2579 END
2580                 next;
2581             }
2582             $wantr{$rrefname} = $objid;
2583         }
2584         $!=0; $?=0;
2585         close GITLS or failedcmd @lcmd;
2586
2587         # OK, now %want is exactly what we want for refs in @specs
2588         my @fspecs = map {
2589             !m/\*$/ && !exists $wantr{"refs/$_"} ? () :
2590             "+refs/$_:".lrfetchrefs."/$_";
2591         } @specs;
2592
2593         printdebug "git_lrfetch_sane fspecs @fspecs\n";
2594
2595         my @fcmd = (@git, qw(fetch -p -n -q), $url, @fspecs);
2596         runcmd_ordryrun_local @fcmd if @fspecs;
2597
2598         %lrfetchrefs_f = ();
2599         my %objgot;
2600
2601         git_for_each_ref(lrfetchrefs, sub {
2602             my ($objid,$objtype,$lrefname,$reftail) = @_;
2603             $lrfetchrefs_f{$lrefname} = $objid;
2604             $objgot{$objid} = 1;
2605         });
2606
2607         foreach my $lrefname (sort keys %lrfetchrefs_f) {
2608             my $rrefname = 'refs'.substr($lrefname, length lrfetchrefs);
2609             if (!exists $wantr{$rrefname}) {
2610                 if ($wanted_rref->($rrefname)) {
2611                     printdebug <<END;
2612 git-fetch @fspecs created $lrefname which git ls-remote @look didn't list.
2613 END
2614                 } else {
2615                     print STDERR <<END
2616 warning: git fetch @fspecs created $lrefname; this is silly, deleting it.
2617 END
2618                 }
2619                 runcmd_ordryrun_local @git, qw(update-ref -d), $lrefname;
2620                 delete $lrfetchrefs_f{$lrefname};
2621                 next;
2622             }
2623         }
2624         foreach my $rrefname (sort keys %wantr) {
2625             my $lrefname = lrfetchrefs.substr($rrefname, 4);
2626             my $got = $lrfetchrefs_f{$lrefname} // '<none>';
2627             my $want = $wantr{$rrefname};
2628             next if $got eq $want;
2629             if (!defined $objgot{$want}) {
2630                 print STDERR <<END;
2631 warning: git ls-remote suggests we want $lrefname
2632 warning:  and it should refer to $want
2633 warning:  but git fetch didn't fetch that object to any relevant ref.
2634 warning:  This may be due to a race with someone updating the server.
2635 warning:  Will try again...
2636 END
2637                 next FETCH_ITERATION;
2638             }
2639             printdebug <<END;
2640 git-fetch @fspecs made $lrefname=$got but want git ls-remote @look says $want
2641 END
2642             runcmd_ordryrun_local @git, qw(update-ref -m),
2643                 "dgit fetch git fetch fixup", $lrefname, $want;
2644             $lrfetchrefs_f{$lrefname} = $want;
2645         }
2646         last;
2647     }
2648     printdebug "git_lrfetch_sane: git fetch --no-insane emulation complete\n",
2649         Dumper(\%lrfetchrefs_f);
2650 }
2651
2652 sub git_fetch_us () {
2653     # Want to fetch only what we are going to use, unless
2654     # deliberately-not-ff, in which case we must fetch everything.
2655
2656     my @specs = deliberately_not_fast_forward ? qw(tags/*) :
2657         map { "tags/$_" }
2658         (quiltmode_splitbrain
2659          ? (map { $_->('*',access_nomdistro) }
2660             \&debiantag_new, \&debiantag_maintview)
2661          : debiantags('*',access_nomdistro));
2662     push @specs, server_branch($csuite);
2663     push @specs, $rewritemap;
2664     push @specs, qw(heads/*) if deliberately_not_fast_forward;
2665
2666     git_lrfetch_sane @specs;
2667
2668     my %here;
2669     my @tagpats = debiantags('*',access_nomdistro);
2670
2671     git_for_each_ref([map { "refs/tags/$_" } @tagpats], sub {
2672         my ($objid,$objtype,$fullrefname,$reftail) = @_;
2673         printdebug "currently $fullrefname=$objid\n";
2674         $here{$fullrefname} = $objid;
2675     });
2676     git_for_each_ref([map { lrfetchrefs."/tags/".$_ } @tagpats], sub {
2677         my ($objid,$objtype,$fullrefname,$reftail) = @_;
2678         my $lref = "refs".substr($fullrefname, length(lrfetchrefs));
2679         printdebug "offered $lref=$objid\n";
2680         if (!defined $here{$lref}) {
2681             my @upd = (@git, qw(update-ref), $lref, $objid, '');
2682             runcmd_ordryrun_local @upd;
2683             lrfetchref_used $fullrefname;
2684         } elsif ($here{$lref} eq $objid) {
2685             lrfetchref_used $fullrefname;
2686         } else {
2687             print STDERR \
2688                 "Not updateting $lref from $here{$lref} to $objid.\n";
2689         }
2690     });
2691 }
2692
2693 #---------- dsc and archive handling ----------
2694
2695 sub mergeinfo_getclogp ($) {
2696     # Ensures thit $mi->{Clogp} exists and returns it
2697     my ($mi) = @_;
2698     $mi->{Clogp} = commit_getclogp($mi->{Commit});
2699 }
2700
2701 sub mergeinfo_version ($) {
2702     return getfield( (mergeinfo_getclogp $_[0]), 'Version' );
2703 }
2704
2705 sub fetch_from_archive_record_1 ($) {
2706     my ($hash) = @_;
2707     runcmd @git, qw(update-ref -m), "dgit fetch $csuite",
2708             'DGIT_ARCHIVE', $hash;
2709     cmdoutput @git, qw(log -n2), $hash;
2710     # ... gives git a chance to complain if our commit is malformed
2711 }
2712
2713 sub fetch_from_archive_record_2 ($) {
2714     my ($hash) = @_;
2715     my @upd_cmd = (@git, qw(update-ref -m), 'dgit fetch', lrref(), $hash);
2716     if (act_local()) {
2717         cmdoutput @upd_cmd;
2718     } else {
2719         dryrun_report @upd_cmd;
2720     }
2721 }
2722
2723 sub parse_dsc_field ($$) {
2724     my ($dsc, $what) = @_;
2725     my $f;
2726     foreach my $field (@ourdscfield) {
2727         $f = $dsc->{$field};
2728         last if defined $f;
2729     }
2730     if (!defined $f) {
2731         progress "$what: NO git hash";
2732     } elsif (($dsc_hash, $dsc_distro, $dsc_hint_tag, $dsc_hint_url)
2733              = $f =~ m/^(\w+) ($distro_re) ($versiontag_re) (\S+)(?:\s|$)/) {
2734         progress "$what: specified git info ($dsc_distro)";
2735         $dsc_hint_tag = [ $dsc_hint_tag ];
2736     } elsif ($f =~ m/^\w+\s*$/) {
2737         $dsc_hash = $&;
2738         $dsc_distro //= 'debian';
2739         $dsc_hint_tag = [ debiantags +(getfield $dsc, 'Version'),
2740                           $dsc_distro ];
2741         progress "$what: specified git hash";
2742     } else {
2743         fail "$what: invalid Dgit info";
2744     }
2745 }
2746
2747 sub resolve_dsc_field_commit ($$) {
2748     my ($already_distro, $already_mapref) = @_;
2749
2750     return unless defined $dsc_hash;
2751
2752     my $rewritemapdata = git_cat_file $already_mapref.':map';
2753     if (defined $rewritemapdata
2754         && $rewritemapdata =~ m/^$dsc_hash(?:[ \t](\w+))/m) {
2755         progress "server's git history rewrite map contains a relevant entry!";
2756
2757         $dsc_hash = $1;
2758         if (defined $dsc_hash) {
2759             progress "using rewritten git hash in place of .dsc value";
2760         } else {
2761             progress "server data says .dsc hash is to be disregarded";
2762         }
2763     }
2764 }
2765
2766 sub fetch_from_archive () {
2767     ensure_setup_existing_tree();
2768
2769     # Ensures that lrref() is what is actually in the archive, one way
2770     # or another, according to us - ie this client's
2771     # appropritaely-updated archive view.  Also returns the commit id.
2772     # If there is nothing in the archive, leaves lrref alone and
2773     # returns undef.  git_fetch_us must have already been called.
2774     get_archive_dsc();
2775
2776     if ($dsc) {
2777         parse_dsc_field($dsc, 'last upload to archive');
2778         resolve_dsc_field_commit access_basedistro,
2779             lrfetchrefs."/".$rewritemap
2780     } else {
2781         progress "no version available from the archive";
2782     }
2783
2784     # If the archive's .dsc has a Dgit field, there are three
2785     # relevant git commitids we need to choose between and/or merge
2786     # together:
2787     #   1. $dsc_hash: the Dgit field from the archive
2788     #   2. $lastpush_hash: the suite branch on the dgit git server
2789     #   3. $lastfetch_hash: our local tracking brach for the suite
2790     #
2791     # These may all be distinct and need not be in any fast forward
2792     # relationship:
2793     #
2794     # If the dsc was pushed to this suite, then the server suite
2795     # branch will have been updated; but it might have been pushed to
2796     # a different suite and copied by the archive.  Conversely a more
2797     # recent version may have been pushed with dgit but not appeared
2798     # in the archive (yet).
2799     #
2800     # $lastfetch_hash may be awkward because archive imports
2801     # (particularly, imports of Dgit-less .dscs) are performed only as
2802     # needed on individual clients, so different clients may perform a
2803     # different subset of them - and these imports are only made
2804     # public during push.  So $lastfetch_hash may represent a set of
2805     # imports different to a subsequent upload by a different dgit
2806     # client.
2807     #
2808     # Our approach is as follows:
2809     #
2810     # As between $dsc_hash and $lastpush_hash: if $lastpush_hash is a
2811     # descendant of $dsc_hash, then it was pushed by a dgit user who
2812     # had based their work on $dsc_hash, so we should prefer it.
2813     # Otherwise, $dsc_hash was installed into this suite in the
2814     # archive other than by a dgit push, and (necessarily) after the
2815     # last dgit push into that suite (since a dgit push would have
2816     # been descended from the dgit server git branch); thus, in that
2817     # case, we prefer the archive's version (and produce a
2818     # pseudo-merge to overwrite the dgit server git branch).
2819     #
2820     # (If there is no Dgit field in the archive's .dsc then
2821     # generate_commit_from_dsc uses the version numbers to decide
2822     # whether the suite branch or the archive is newer.  If the suite
2823     # branch is newer it ignores the archive's .dsc; otherwise it
2824     # generates an import of the .dsc, and produces a pseudo-merge to
2825     # overwrite the suite branch with the archive contents.)
2826     #
2827     # The outcome of that part of the algorithm is the `public view',
2828     # and is same for all dgit clients: it does not depend on any
2829     # unpublished history in the local tracking branch.
2830     #
2831     # As between the public view and the local tracking branch: The
2832     # local tracking branch is only updated by dgit fetch, and
2833     # whenever dgit fetch runs it includes the public view in the
2834     # local tracking branch.  Therefore if the public view is not
2835     # descended from the local tracking branch, the local tracking
2836     # branch must contain history which was imported from the archive
2837     # but never pushed; and, its tip is now out of date.  So, we make
2838     # a pseudo-merge to overwrite the old imports and stitch the old
2839     # history in.
2840     #
2841     # Finally: we do not necessarily reify the public view (as
2842     # described above).  This is so that we do not end up stacking two
2843     # pseudo-merges.  So what we actually do is figure out the inputs
2844     # to any public view pseudo-merge and put them in @mergeinputs.
2845
2846     my @mergeinputs;
2847     # $mergeinputs[]{Commit}
2848     # $mergeinputs[]{Info}
2849     # $mergeinputs[0] is the one whose tree we use
2850     # @mergeinputs is in the order we use in the actual commit)
2851     #
2852     # Also:
2853     # $mergeinputs[]{Message} is a commit message to use
2854     # $mergeinputs[]{ReverseParents} if def specifies that parent
2855     #                                list should be in opposite order
2856     # Such an entry has no Commit or Info.  It applies only when found
2857     # in the last entry.  (This ugliness is to support making
2858     # identical imports to previous dgit versions.)
2859
2860     my $lastpush_hash = git_get_ref(lrfetchref());
2861     printdebug "previous reference hash=$lastpush_hash\n";
2862     $lastpush_mergeinput = $lastpush_hash && {
2863         Commit => $lastpush_hash,
2864         Info => "dgit suite branch on dgit git server",
2865     };
2866
2867     my $lastfetch_hash = git_get_ref(lrref());
2868     printdebug "fetch_from_archive: lastfetch=$lastfetch_hash\n";
2869     my $lastfetch_mergeinput = $lastfetch_hash && {
2870         Commit => $lastfetch_hash,
2871         Info => "dgit client's archive history view",
2872     };
2873
2874     my $dsc_mergeinput = $dsc_hash && {
2875         Commit => $dsc_hash,
2876         Info => "Dgit field in .dsc from archive",
2877     };
2878
2879     my $cwd = getcwd();
2880     my $del_lrfetchrefs = sub {
2881         changedir $cwd;
2882         my $gur;
2883         printdebug "del_lrfetchrefs...\n";
2884         foreach my $fullrefname (sort keys %lrfetchrefs_d) {
2885             my $objid = $lrfetchrefs_d{$fullrefname};
2886             printdebug "del_lrfetchrefs: $objid $fullrefname\n";
2887             if (!$gur) {
2888                 $gur ||= new IO::Handle;
2889                 open $gur, "|-", qw(git update-ref --stdin) or die $!;
2890             }
2891             printf $gur "delete %s %s\n", $fullrefname, $objid;
2892         }
2893         if ($gur) {
2894             close $gur or failedcmd "git update-ref delete lrfetchrefs";
2895         }
2896     };
2897
2898     if (defined $dsc_hash) {
2899         ensure_we_have_orig();
2900         if (!$lastpush_hash || $dsc_hash eq $lastpush_hash) {
2901             @mergeinputs = $dsc_mergeinput
2902         } elsif (is_fast_fwd($dsc_hash,$lastpush_hash)) {
2903             print STDERR <<END or die $!;
2904
2905 Git commit in archive is behind the last version allegedly pushed/uploaded.
2906 Commit referred to by archive: $dsc_hash
2907 Last version pushed with dgit: $lastpush_hash
2908 $later_warning_msg
2909 END
2910             @mergeinputs = ($lastpush_mergeinput);
2911         } else {
2912             # Archive has .dsc which is not a descendant of the last dgit
2913             # push.  This can happen if the archive moves .dscs about.
2914             # Just follow its lead.
2915             if (is_fast_fwd($lastpush_hash,$dsc_hash)) {
2916                 progress "archive .dsc names newer git commit";
2917                 @mergeinputs = ($dsc_mergeinput);
2918             } else {
2919                 progress "archive .dsc names other git commit, fixing up";
2920                 @mergeinputs = ($dsc_mergeinput, $lastpush_mergeinput);
2921             }
2922         }
2923     } elsif ($dsc) {
2924         @mergeinputs = generate_commits_from_dsc();
2925         # We have just done an import.  Now, our import algorithm might
2926         # have been improved.  But even so we do not want to generate
2927         # a new different import of the same package.  So if the
2928         # version numbers are the same, just use our existing version.
2929         # If the version numbers are different, the archive has changed
2930         # (perhaps, rewound).
2931         if ($lastfetch_mergeinput &&
2932             !version_compare( (mergeinfo_version $lastfetch_mergeinput),
2933                               (mergeinfo_version $mergeinputs[0]) )) {
2934             @mergeinputs = ($lastfetch_mergeinput);
2935         }
2936     } elsif ($lastpush_hash) {
2937         # only in git, not in the archive yet
2938         @mergeinputs = ($lastpush_mergeinput);
2939         print STDERR <<END or die $!;
2940
2941 Package not found in the archive, but has allegedly been pushed using dgit.
2942 $later_warning_msg
2943 END
2944     } else {
2945         printdebug "nothing found!\n";
2946         if (defined $skew_warning_vsn) {
2947             print STDERR <<END or die $!;
2948
2949 Warning: relevant archive skew detected.
2950 Archive allegedly contains $skew_warning_vsn
2951 But we were not able to obtain any version from the archive or git.
2952
2953 END
2954         }
2955         unshift @end, $del_lrfetchrefs;
2956         return undef;
2957     }
2958
2959     if ($lastfetch_hash &&
2960         !grep {
2961             my $h = $_->{Commit};
2962             $h and is_fast_fwd($lastfetch_hash, $h);
2963             # If true, one of the existing parents of this commit
2964             # is a descendant of the $lastfetch_hash, so we'll
2965             # be ff from that automatically.
2966         } @mergeinputs
2967         ) {
2968         # Otherwise:
2969         push @mergeinputs, $lastfetch_mergeinput;
2970     }
2971
2972     printdebug "fetch mergeinfos:\n";
2973     foreach my $mi (@mergeinputs) {
2974         if ($mi->{Info}) {
2975             printdebug " commit $mi->{Commit} $mi->{Info}\n";
2976         } else {
2977             printdebug sprintf " ReverseParents=%d Message=%s",
2978                 $mi->{ReverseParents}, $mi->{Message};
2979         }
2980     }
2981
2982     my $compat_info= pop @mergeinputs
2983         if $mergeinputs[$#mergeinputs]{Message};
2984
2985     @mergeinputs = grep { defined $_->{Commit} } @mergeinputs;
2986
2987     my $hash;
2988     if (@mergeinputs > 1) {
2989         # here we go, then:
2990         my $tree_commit = $mergeinputs[0]{Commit};
2991
2992         my $tree = cmdoutput @git, qw(cat-file commit), $tree_commit;
2993         $tree =~ m/\n\n/;  $tree = $`;
2994         $tree =~ m/^tree (\w+)$/m or die "$dsc_hash tree ?";
2995         $tree = $1;
2996
2997         # We use the changelog author of the package in question the
2998         # author of this pseudo-merge.  This is (roughly) correct if
2999         # this commit is simply representing aa non-dgit upload.
3000         # (Roughly because it does not record sponsorship - but we
3001         # don't have sponsorship info because that's in the .changes,
3002         # which isn't in the archivw.)
3003         #
3004         # But, it might be that we are representing archive history
3005         # updates (including in-archive copies).  These are not really
3006         # the responsibility of the person who created the .dsc, but
3007         # there is no-one whose name we should better use.  (The
3008         # author of the .dsc-named commit is clearly worse.)
3009
3010         my $useclogp = mergeinfo_getclogp $mergeinputs[0];
3011         my $author = clogp_authline $useclogp;
3012         my $cversion = getfield $useclogp, 'Version';
3013
3014         my $mcf = ".git/dgit/mergecommit";
3015         open MC, ">", $mcf or die "$mcf $!";
3016         print MC <<END or die $!;
3017 tree $tree
3018 END
3019
3020         my @parents = grep { $_->{Commit} } @mergeinputs;
3021         @parents = reverse @parents if $compat_info->{ReverseParents};
3022         print MC <<END or die $! foreach @parents;
3023 parent $_->{Commit}
3024 END
3025
3026         print MC <<END or die $!;
3027 author $author
3028 committer $author
3029
3030 END
3031
3032         if (defined $compat_info->{Message}) {
3033             print MC $compat_info->{Message} or die $!;
3034         } else {
3035             print MC <<END or die $!;
3036 Record $package ($cversion) in archive suite $csuite
3037
3038 Record that
3039 END
3040             my $message_add_info = sub {
3041                 my ($mi) = (@_);
3042                 my $mversion = mergeinfo_version $mi;
3043                 printf MC "  %-20s %s\n", $mversion, $mi->{Info}
3044                     or die $!;
3045             };
3046
3047             $message_add_info->($mergeinputs[0]);
3048             print MC <<END or die $!;
3049 should be treated as descended from
3050 END
3051             $message_add_info->($_) foreach @mergeinputs[1..$#mergeinputs];
3052         }
3053
3054         close MC or die $!;
3055         $hash = make_commit $mcf;
3056     } else {
3057         $hash = $mergeinputs[0]{Commit};
3058     }
3059     printdebug "fetch hash=$hash\n";
3060
3061     my $chkff = sub {
3062         my ($lasth, $what) = @_;
3063         return unless $lasth;
3064         die "$lasth $hash $what ?" unless is_fast_fwd($lasth, $hash);
3065     };
3066
3067     $chkff->($lastpush_hash, 'dgit repo server tip (last push)')
3068         if $lastpush_hash;
3069     $chkff->($lastfetch_hash, 'local tracking tip (last fetch)');
3070
3071     fetch_from_archive_record_1($hash);
3072
3073     if (defined $skew_warning_vsn) {
3074         mkpath '.git/dgit';
3075         printdebug "SKEW CHECK WANT $skew_warning_vsn\n";
3076         my $gotclogp = commit_getclogp($hash);
3077         my $got_vsn = getfield $gotclogp, 'Version';
3078         printdebug "SKEW CHECK GOT $got_vsn\n";
3079         if (version_compare($got_vsn, $skew_warning_vsn) < 0) {
3080             print STDERR <<END or die $!;
3081
3082 Warning: archive skew detected.  Using the available version:
3083 Archive allegedly contains    $skew_warning_vsn
3084 We were able to obtain only   $got_vsn
3085
3086 END
3087         }
3088     }
3089
3090     if ($lastfetch_hash ne $hash) {
3091         fetch_from_archive_record_2($hash);
3092     }
3093
3094     lrfetchref_used lrfetchref();
3095
3096     unshift @end, $del_lrfetchrefs;
3097     return $hash;
3098 }
3099
3100 sub set_local_git_config ($$) {
3101     my ($k, $v) = @_;
3102     runcmd @git, qw(config), $k, $v;
3103 }
3104
3105 sub setup_mergechangelogs (;$) {
3106     my ($always) = @_;
3107     return unless $always || access_cfg_bool(1, 'setup-mergechangelogs');
3108
3109     my $driver = 'dpkg-mergechangelogs';
3110     my $cb = "merge.$driver";
3111     my $attrs = '.git/info/attributes';
3112     ensuredir '.git/info';
3113
3114     open NATTRS, ">", "$attrs.new" or die "$attrs.new $!";
3115     if (!open ATTRS, "<", $attrs) {
3116         $!==ENOENT or die "$attrs: $!";
3117     } else {
3118         while (<ATTRS>) {
3119             chomp;
3120             next if m{^debian/changelog\s};
3121             print NATTRS $_, "\n" or die $!;
3122         }
3123         ATTRS->error and die $!;
3124         close ATTRS;
3125     }
3126     print NATTRS "debian/changelog merge=$driver\n" or die $!;
3127     close NATTRS;
3128
3129     set_local_git_config "$cb.name", 'debian/changelog merge driver';
3130     set_local_git_config "$cb.driver", 'dpkg-mergechangelogs -m %O %A %B %A';
3131
3132     rename "$attrs.new", "$attrs" or die "$attrs: $!";
3133 }
3134
3135 sub setup_useremail (;$) {
3136     my ($always) = @_;
3137     return unless $always || access_cfg_bool(1, 'setup-useremail');
3138
3139     my $setup = sub {
3140         my ($k, $envvar) = @_;
3141         my $v = access_cfg("user-$k", 'RETURN-UNDEF') // $ENV{$envvar};
3142         return unless defined $v;
3143         set_local_git_config "user.$k", $v;
3144     };
3145
3146     $setup->('email', 'DEBEMAIL');
3147     $setup->('name', 'DEBFULLNAME');
3148 }
3149
3150 sub ensure_setup_existing_tree () {
3151     my $k = "remote.$remotename.skipdefaultupdate";
3152     my $c = git_get_config $k;
3153     return if defined $c;
3154     set_local_git_config $k, 'true';
3155 }
3156
3157 sub setup_new_tree () {
3158     setup_mergechangelogs();
3159     setup_useremail();
3160 }
3161
3162 sub multisuite_suite_child ($$$) {
3163     my ($tsuite, $merginputs, $fn) = @_;
3164     # in child, sets things up, calls $fn->(), and returns undef
3165     # in parent, returns canonical suite name for $tsuite
3166     my $canonsuitefh = IO::File::new_tmpfile;
3167     my $pid = fork // die $!;
3168     if (!$pid) {
3169         $isuite = $tsuite;
3170         $us .= " [$isuite]";
3171         $debugprefix .= " ";
3172         progress "fetching $tsuite...";
3173         canonicalise_suite();
3174         print $canonsuitefh $csuite, "\n" or die $!;
3175         close $canonsuitefh or die $!;
3176         $fn->();
3177         return undef;
3178     }
3179     waitpid $pid,0 == $pid or die $!;
3180     fail "failed to obtain $tsuite: ".waitstatusmsg() if $? && $?!=256*4;
3181     seek $canonsuitefh,0,0 or die $!;
3182     local $csuite = <$canonsuitefh>;
3183     die $! unless defined $csuite && chomp $csuite;
3184     if ($? == 256*4) {
3185         printdebug "multisuite $tsuite missing\n";
3186         return $csuite;
3187     }
3188     printdebug "multisuite $tsuite ok (canon=$csuite)\n";
3189     push @$merginputs, {
3190         Ref => lrref,
3191         Info => $csuite,
3192     };
3193     return $csuite;
3194 }
3195
3196 sub fork_for_multisuite ($) {
3197     my ($before_fetch_merge) = @_;
3198     # if nothing unusual, just returns ''
3199     #
3200     # if multisuite:
3201     # returns 0 to caller in child, to do first of the specified suites
3202     # in child, $csuite is not yet set
3203     #
3204     # returns 1 to caller in parent, to finish up anything needed after
3205     # in parent, $csuite is set to canonicalised portmanteau
3206
3207     my $org_isuite = $isuite;
3208     my @suites = split /\,/, $isuite;
3209     return '' unless @suites > 1;
3210     printdebug "fork_for_multisuite: @suites\n";
3211
3212     my @mergeinputs;
3213
3214     my $cbasesuite = multisuite_suite_child($suites[0], \@mergeinputs,
3215                                             sub { });
3216     return 0 unless defined $cbasesuite;
3217
3218     fail "package $package missing in (base suite) $cbasesuite"
3219         unless @mergeinputs;
3220
3221     my @csuites = ($cbasesuite);
3222
3223     $before_fetch_merge->();
3224
3225     foreach my $tsuite (@suites[1..$#suites]) {
3226         my $csubsuite = multisuite_suite_child($tsuite, \@mergeinputs,
3227                                                sub {
3228             @end = ();
3229             fetch();
3230             exit 0;
3231         });
3232         # xxx collecte the ref here
3233
3234         $csubsuite =~ s/^\Q$cbasesuite\E-/-/;
3235         push @csuites, $csubsuite;
3236     }
3237
3238     foreach my $mi (@mergeinputs) {
3239         my $ref = git_get_ref $mi->{Ref};
3240         die "$mi->{Ref} ?" unless length $ref;
3241         $mi->{Commit} = $ref;
3242     }
3243
3244     $csuite = join ",", @csuites;
3245
3246     my $previous = git_get_ref lrref;
3247     if ($previous) {
3248         unshift @mergeinputs, {
3249             Commit => $previous,
3250             Info => "local combined tracking branch",
3251             Warning =>
3252  "archive seems to have rewound: local tracking branch is ahead!",
3253         };
3254     }
3255
3256     foreach my $ix (0..$#mergeinputs) {
3257         $mergeinputs[$ix]{Index} = $ix;
3258     }
3259
3260     @mergeinputs = sort {
3261         -version_compare(mergeinfo_version $a,
3262                          mergeinfo_version $b) # highest version first
3263             or
3264         $a->{Index} <=> $b->{Index}; # earliest in spec first
3265     } @mergeinputs;
3266
3267     my @needed;
3268
3269   NEEDED:
3270     foreach my $mi (@mergeinputs) {
3271         printdebug "multisuite merge check $mi->{Info}\n";
3272         foreach my $previous (@needed) {
3273             next unless is_fast_fwd $mi->{Commit}, $previous->{Commit};
3274             printdebug "multisuite merge un-needed $previous->{Info}\n";
3275             next NEEDED;
3276         }
3277         push @needed, $mi;
3278         printdebug "multisuite merge this-needed\n";
3279         $mi->{Character} = '+';
3280     }
3281
3282     $needed[0]{Character} = '*';
3283
3284     my $output = $needed[0]{Commit};
3285
3286     if (@needed > 1) {
3287         printdebug "multisuite merge nontrivial\n";
3288         my $tree = cmdoutput qw(git rev-parse), $needed[0]{Commit}.':';
3289
3290         my $commit = "tree $tree\n";
3291         my $msg = "Combine archive branches $csuite [dgit]\n\n".
3292             "Input branches:\n";
3293
3294         foreach my $mi (sort { $a->{Index} <=> $b->{Index} } @mergeinputs) {
3295             printdebug "multisuite merge include $mi->{Info}\n";
3296             $mi->{Character} //= ' ';
3297             $commit .= "parent $mi->{Commit}\n";
3298             $msg .= sprintf " %s  %-25s %s\n",
3299                 $mi->{Character},
3300                 (mergeinfo_version $mi),
3301                 $mi->{Info};
3302         }
3303         my $authline = clogp_authline mergeinfo_getclogp $needed[0];
3304         $msg .= "\nKey\n".
3305             " * marks the highest version branch, which choose to use\n".
3306             " + marks each branch which was not already an ancestor\n\n".
3307             "[dgit multi-suite $csuite]\n";
3308         $commit .=
3309             "author $authline\n".
3310             "committer $authline\n\n";
3311         $output = make_commit_text $commit.$msg;
3312         printdebug "multisuite merge generated $output\n";
3313     }
3314
3315     fetch_from_archive_record_1($output);
3316     fetch_from_archive_record_2($output);
3317
3318     progress "calculated combined tracking suite $csuite";
3319
3320     return 1;
3321 }
3322
3323 sub clone_set_head () {
3324     open H, "> .git/HEAD" or die $!;
3325     print H "ref: ".lref()."\n" or die $!;
3326     close H or die $!;
3327 }
3328 sub clone_finish ($) {
3329     my ($dstdir) = @_;
3330     runcmd @git, qw(reset --hard), lrref();
3331     runcmd qw(bash -ec), <<'END';
3332         set -o pipefail
3333         git ls-tree -r --name-only -z HEAD | \
3334         xargs -0r touch -h -r . --
3335 END
3336     printdone "ready for work in $dstdir";
3337 }
3338
3339 sub clone ($) {
3340     my ($dstdir) = @_;
3341     badusage "dry run makes no sense with clone" unless act_local();
3342
3343     my $multi_fetched = fork_for_multisuite(sub {
3344         printdebug "multi clone before fetch merge\n";
3345         changedir $dstdir;
3346     });
3347     if ($multi_fetched) {
3348         printdebug "multi clone after fetch merge\n";
3349         clone_set_head();
3350         clone_finish($dstdir);
3351         exit 0;
3352     }
3353     printdebug "clone main body\n";
3354
3355     canonicalise_suite();
3356     my $hasgit = check_for_git();
3357     mkdir $dstdir or fail "create \`$dstdir': $!";
3358     changedir $dstdir;
3359     runcmd @git, qw(init -q);
3360     clone_set_head();
3361     my $giturl = access_giturl(1);
3362     if (defined $giturl) {
3363         runcmd @git, qw(remote add), 'origin', $giturl;
3364     }
3365     if ($hasgit) {
3366         progress "fetching existing git history";
3367         git_fetch_us();
3368         runcmd_ordryrun_local @git, qw(fetch origin);
3369     } else {
3370         progress "starting new git history";
3371     }
3372     fetch_from_archive() or no_such_package;
3373     my $vcsgiturl = $dsc->{'Vcs-Git'};
3374     if (length $vcsgiturl) {
3375         $vcsgiturl =~ s/\s+-b\s+\S+//g;
3376         runcmd @git, qw(remote add vcs-git), $vcsgiturl;
3377     }
3378     setup_new_tree();
3379     clone_finish($dstdir);
3380 }
3381
3382 sub fetch () {
3383     canonicalise_suite();
3384     if (check_for_git()) {
3385         git_fetch_us();
3386     }
3387     fetch_from_archive() or no_such_package();
3388     printdone "fetched into ".lrref();
3389 }
3390
3391 sub pull () {
3392     my $multi_fetched = fork_for_multisuite(sub { });
3393     fetch() unless $multi_fetched; # parent
3394     return if $multi_fetched eq '0'; # child
3395     runcmd_ordryrun_local @git, qw(merge -m),"Merge from $csuite [dgit]",
3396         lrref();
3397     printdone "fetched to ".lrref()." and merged into HEAD";
3398 }
3399
3400 sub check_not_dirty () {
3401     foreach my $f (qw(local-options local-patch-header)) {
3402         if (stat_exists "debian/source/$f") {
3403             fail "git tree contains debian/source/$f";
3404         }
3405     }
3406
3407     return if $ignoredirty;
3408
3409     my @cmd = (@git, qw(diff --quiet HEAD));
3410     debugcmd "+",@cmd;
3411     $!=0; $?=-1; system @cmd;
3412     return if !$?;
3413     if ($?==256) {
3414         fail "working tree is dirty (does not match HEAD)";
3415     } else {
3416         failedcmd @cmd;
3417     }
3418 }
3419
3420 sub commit_admin ($) {
3421     my ($m) = @_;
3422     progress "$m";
3423     runcmd_ordryrun_local @git, qw(commit -m), $m;
3424 }
3425
3426 sub commit_quilty_patch () {
3427     my $output = cmdoutput @git, qw(status --porcelain);
3428     my %adds;
3429     foreach my $l (split /\n/, $output) {
3430         next unless $l =~ m/\S/;
3431         if ($l =~ m{^(?:\?\?| M) (.pc|debian/patches)}) {
3432             $adds{$1}++;
3433         }
3434     }
3435     delete $adds{'.pc'}; # if there wasn't one before, don't add it
3436     if (!%adds) {
3437         progress "nothing quilty to commit, ok.";
3438         return;
3439     }
3440     my @adds = map { s/[][*?\\]/\\$&/g; $_; } sort keys %adds;
3441     runcmd_ordryrun_local @git, qw(add -f), @adds;
3442     commit_admin <<END
3443 Commit Debian 3.0 (quilt) metadata
3444
3445 [dgit ($our_version) quilt-fixup]
3446 END
3447 }
3448
3449 sub get_source_format () {
3450     my %options;
3451     if (open F, "debian/source/options") {
3452         while (<F>) {
3453             next if m/^\s*\#/;
3454             next unless m/\S/;
3455             s/\s+$//; # ignore missing final newline
3456             if (m/\s*\#\s*/) {
3457                 my ($k, $v) = ($`, $'); #');
3458                 $v =~ s/^"(.*)"$/$1/;
3459                 $options{$k} = $v;
3460             } else {
3461                 $options{$_} = 1;
3462             }
3463         }
3464         F->error and die $!;
3465         close F;
3466     } else {
3467         die $! unless $!==&ENOENT;
3468     }
3469
3470     if (!open F, "debian/source/format") {
3471         die $! unless $!==&ENOENT;
3472         return '';
3473     }
3474     $_ = <F>;
3475     F->error and die $!;
3476     chomp;
3477     return ($_, \%options);
3478 }
3479
3480 sub madformat_wantfixup ($) {
3481     my ($format) = @_;
3482     return 0 unless $format eq '3.0 (quilt)';
3483     our $quilt_mode_warned;
3484     if ($quilt_mode eq 'nocheck') {
3485         progress "Not doing any fixup of \`$format' due to".
3486             " ----no-quilt-fixup or --quilt=nocheck"
3487             unless $quilt_mode_warned++;
3488         return 0;
3489     }
3490     progress "Format \`$format', need to check/update patch stack"
3491         unless $quilt_mode_warned++;
3492     return 1;
3493 }
3494
3495 sub maybe_split_brain_save ($$$) {
3496     my ($headref, $dgitview, $msg) = @_;
3497     # => message fragment "$saved" describing disposition of $dgitview
3498     return "commit id $dgitview" unless defined $split_brain_save;
3499     my @cmd = (shell_cmd "cd ../../../..",
3500                @git, qw(update-ref -m),
3501                "dgit --dgit-view-save $msg HEAD=$headref",
3502                $split_brain_save, $dgitview);
3503     runcmd @cmd;
3504     return "and left in $split_brain_save";
3505 }
3506
3507 # An "infopair" is a tuple [ $thing, $what ]
3508 # (often $thing is a commit hash; $what is a description)
3509
3510 sub infopair_cond_equal ($$) {
3511     my ($x,$y) = @_;
3512     $x->[0] eq $y->[0] or fail <<END;
3513 $x->[1] ($x->[0]) not equal to $y->[1] ($y->[0])
3514 END
3515 };
3516
3517 sub infopair_lrf_tag_lookup ($$) {
3518     my ($tagnames, $what) = @_;
3519     # $tagname may be an array ref
3520     my @tagnames = ref $tagnames ? @$tagnames : ($tagnames);
3521     printdebug "infopair_lrfetchref_tag_lookup $what @tagnames\n";
3522     foreach my $tagname (@tagnames) {
3523         my $lrefname = lrfetchrefs."/tags/$tagname";
3524         my $tagobj = $lrfetchrefs_f{$lrefname};
3525         next unless defined $tagobj;
3526         printdebug "infopair_lrfetchref_tag_lookup $tagobj $tagname $what\n";
3527         return [ git_rev_parse($tagobj), $what ];
3528     }
3529     fail @tagnames==1 ? <<END : <<END;
3530 Wanted tag $what (@tagnames) on dgit server, but not found
3531 END
3532 Wanted tag $what (one of: @tagnames) on dgit server, but not found
3533 END
3534 }
3535
3536 sub infopair_cond_ff ($$) {
3537     my ($anc,$desc) = @_;
3538     is_fast_fwd($anc->[0], $desc->[0]) or fail <<END;
3539 $anc->[1] ($anc->[0]) .. $desc->[1] ($desc->[0]) is not fast forward
3540 END
3541 };
3542
3543 sub pseudomerge_version_check ($$) {
3544     my ($clogp, $archive_hash) = @_;
3545
3546     my $arch_clogp = commit_getclogp $archive_hash;
3547     my $i_arch_v = [ (getfield $arch_clogp, 'Version'),
3548                      'version currently in archive' ];
3549     if (defined $overwrite_version) {
3550         if (length $overwrite_version) {
3551             infopair_cond_equal([ $overwrite_version,
3552                                   '--overwrite= version' ],
3553                                 $i_arch_v);
3554         } else {
3555             my $v = $i_arch_v->[0];
3556             progress "Checking package changelog for archive version $v ...";
3557             eval {
3558                 my @xa = ("-f$v", "-t$v");
3559                 my $vclogp = parsechangelog @xa;
3560                 my $cv = [ (getfield $vclogp, 'Version'),
3561                            "Version field from dpkg-parsechangelog @xa" ];
3562                 infopair_cond_equal($i_arch_v, $cv);
3563             };
3564             if ($@) {
3565                 $@ =~ s/^dgit: //gm;
3566                 fail "$@".
3567                     "Perhaps debian/changelog does not mention $v ?";
3568             }
3569         }
3570     }
3571     
3572     printdebug "pseudomerge_version_check i_arch_v @$i_arch_v\n";
3573     return $i_arch_v;
3574 }
3575
3576 sub pseudomerge_make_commit ($$$$ $$) {
3577     my ($clogp, $dgitview, $archive_hash, $i_arch_v,
3578         $msg_cmd, $msg_msg) = @_;
3579     progress "Declaring that HEAD inciudes all changes in $i_arch_v->[0]...";
3580
3581     my $tree = cmdoutput qw(git rev-parse), "${dgitview}:";
3582     my $authline = clogp_authline $clogp;
3583
3584     chomp $msg_msg;
3585     $msg_cmd .=
3586         !defined $overwrite_version ? ""
3587         : !length  $overwrite_version ? " --overwrite"
3588         : " --overwrite=".$overwrite_version;
3589
3590     mkpath '.git/dgit';
3591     my $pmf = ".git/dgit/pseudomerge";
3592     open MC, ">", $pmf or die "$pmf $!";
3593     print MC <<END or die $!;
3594 tree $tree
3595 parent $dgitview
3596 parent $archive_hash
3597 author $authline
3598 committer $authline
3599
3600 $msg_msg
3601
3602 [$msg_cmd]
3603 END
3604     close MC or die $!;
3605
3606     return make_commit($pmf);
3607 }
3608
3609 sub splitbrain_pseudomerge ($$$$) {
3610     my ($clogp, $maintview, $dgitview, $archive_hash) = @_;
3611     # => $merged_dgitview
3612     printdebug "splitbrain_pseudomerge...\n";
3613     #
3614     #     We:      debian/PREVIOUS    HEAD($maintview)
3615     # expect:          o ----------------- o
3616     #                    \                   \
3617     #                     o                   o
3618     #                 a/d/PREVIOUS        $dgitview
3619     #                $archive_hash              \
3620     #  If so,                \                   \
3621     #  we do:                 `------------------ o
3622     #   this:                                   $dgitview'
3623     #
3624
3625     return $dgitview unless defined $archive_hash;
3626
3627     printdebug "splitbrain_pseudomerge...\n";
3628
3629     my $i_arch_v = pseudomerge_version_check($clogp, $archive_hash);
3630
3631     if (!defined $overwrite_version) {
3632         progress "Checking that HEAD inciudes all changes in archive...";
3633     }
3634
3635     return $dgitview if is_fast_fwd $archive_hash, $dgitview;
3636
3637     if (defined $overwrite_version) {
3638     } elsif (!eval {
3639         my $t_dep14 = debiantag_maintview $i_arch_v->[0], access_nomdistro;
3640         my $i_dep14 = infopair_lrf_tag_lookup($t_dep14, "maintainer view tag");
3641         my $t_dgit = debiantag_new $i_arch_v->[0], access_nomdistro;
3642         my $i_dgit = infopair_lrf_tag_lookup($t_dgit, "dgit view tag");
3643         my $i_archive = [ $archive_hash, "current archive contents" ];
3644
3645         printdebug "splitbrain_pseudomerge i_archive @$i_archive\n";
3646
3647         infopair_cond_equal($i_dgit, $i_archive);
3648         infopair_cond_ff($i_dep14, $i_dgit);
3649         infopair_cond_ff($i_dep14, [ $maintview, 'HEAD' ]);
3650         1;
3651     }) {
3652         print STDERR <<END;
3653 $us: check failed (maybe --overwrite is needed, consult documentation)
3654 END
3655         die "$@";
3656     }
3657
3658     my $r = pseudomerge_make_commit
3659         $clogp, $dgitview, $archive_hash, $i_arch_v,
3660         "dgit --quilt=$quilt_mode",
3661         (defined $overwrite_version ? <<END_OVERWR : <<END_MAKEFF);
3662 Declare fast forward from $i_arch_v->[0]
3663 END_OVERWR
3664 Make fast forward from $i_arch_v->[0]
3665 END_MAKEFF
3666
3667     maybe_split_brain_save $maintview, $r, "pseudomerge";
3668
3669     progress "Made pseudo-merge of $i_arch_v->[0] into dgit view.";
3670     return $r;
3671 }       
3672
3673 sub plain_overwrite_pseudomerge ($$$) {
3674     my ($clogp, $head, $archive_hash) = @_;
3675
3676     printdebug "plain_overwrite_pseudomerge...";
3677
3678     my $i_arch_v = pseudomerge_version_check($clogp, $archive_hash);
3679
3680     return $head if is_fast_fwd $archive_hash, $head;
3681
3682     my $m = "Declare fast forward from $i_arch_v->[0]";
3683
3684     my $r = pseudomerge_make_commit
3685         $clogp, $head, $archive_hash, $i_arch_v,
3686         "dgit", $m;
3687
3688     runcmd @git, qw(update-ref -m), $m, 'HEAD', $r, $head;
3689
3690     progress "Make pseudo-merge of $i_arch_v->[0] into your HEAD.";
3691     return $r;
3692 }
3693
3694 sub push_parse_changelog ($) {
3695     my ($clogpfn) = @_;
3696
3697     my $clogp = Dpkg::Control::Hash->new();
3698     $clogp->load($clogpfn) or die;
3699
3700     my $clogpackage = getfield $clogp, 'Source';
3701     $package //= $clogpackage;
3702     fail "-p specified $package but changelog specified $clogpackage"
3703         unless $package eq $clogpackage;
3704     my $cversion = getfield $clogp, 'Version';
3705     my $tag = debiantag($cversion, access_nomdistro);
3706     runcmd @git, qw(check-ref-format), $tag;
3707
3708     my $dscfn = dscfn($cversion);
3709
3710     return ($clogp, $cversion, $dscfn);
3711 }
3712
3713 sub push_parse_dsc ($$$) {
3714     my ($dscfn,$dscfnwhat, $cversion) = @_;
3715     $dsc = parsecontrol($dscfn,$dscfnwhat);
3716     my $dversion = getfield $dsc, 'Version';
3717     my $dscpackage = getfield $dsc, 'Source';
3718     ($dscpackage eq $package && $dversion eq $cversion) or
3719         fail "$dscfn is for $dscpackage $dversion".
3720             " but debian/changelog is for $package $cversion";
3721 }
3722
3723 sub push_tagwants ($$$$) {
3724     my ($cversion, $dgithead, $maintviewhead, $tfbase) = @_;
3725     my @tagwants;
3726     push @tagwants, {
3727         TagFn => \&debiantag,
3728         Objid => $dgithead,
3729         TfSuffix => '',
3730         View => 'dgit',
3731     };
3732     if (defined $maintviewhead) {
3733         push @tagwants, {
3734             TagFn => \&debiantag_maintview,
3735             Objid => $maintviewhead,
3736             TfSuffix => '-maintview',
3737             View => 'maint',
3738         };
3739     } elsif ($dodep14tag eq 'no' ? 0
3740              : $dodep14tag eq 'want' ? access_cfg_tagformats_can_splitbrain
3741              : $dodep14tag eq 'always'
3742              ? (access_cfg_tagformats_can_splitbrain or fail <<END)
3743 --dep14tag-always (or equivalent in config) means server must support
3744  both "new" and "maint" tag formats, but config says it doesn't.
3745 END
3746             : die "$dodep14tag ?") {
3747         push @tagwants, {
3748             TagFn => \&debiantag_maintview,
3749             Objid => $dgithead,
3750             TfSuffix => '-dgit',
3751             View => 'dgit',
3752         };
3753     };
3754     foreach my $tw (@tagwants) {
3755         $tw->{Tag} = $tw->{TagFn}($cversion, access_nomdistro);
3756         $tw->{Tfn} = sub { $tfbase.$tw->{TfSuffix}.$_[0]; };
3757     }
3758     printdebug 'push_tagwants: ', Dumper(\@_, \@tagwants);
3759     return @tagwants;
3760 }
3761
3762 sub push_mktags ($$ $$ $) {
3763     my ($clogp,$dscfn,
3764         $changesfile,$changesfilewhat,
3765         $tagwants) = @_;
3766
3767     die unless $tagwants->[0]{View} eq 'dgit';
3768
3769     my $declaredistro = access_nomdistro();
3770     my $reader_giturl = do { local $access_forpush=0; access_giturl(); };
3771     $dsc->{$ourdscfield[0]} = join " ",
3772         $tagwants->[0]{Objid}, $declaredistro, $tagwants->[0]{Tag},
3773         $reader_giturl;
3774     $dsc->save("$dscfn.tmp") or die $!;
3775
3776     my $changes = parsecontrol($changesfile,$changesfilewhat);
3777     foreach my $field (qw(Source Distribution Version)) {
3778         $changes->{$field} eq $clogp->{$field} or
3779             fail "changes field $field \`$changes->{$field}'".
3780                 " does not match changelog \`$clogp->{$field}'";
3781     }
3782
3783     my $cversion = getfield $clogp, 'Version';
3784     my $clogsuite = getfield $clogp, 'Distribution';
3785
3786     # We make the git tag by hand because (a) that makes it easier
3787     # to control the "tagger" (b) we can do remote signing
3788     my $authline = clogp_authline $clogp;
3789     my $delibs = join(" ", "",@deliberatelies);
3790
3791     my $mktag = sub {
3792         my ($tw) = @_;
3793         my $tfn = $tw->{Tfn};
3794         my $head = $tw->{Objid};
3795         my $tag = $tw->{Tag};
3796
3797         open TO, '>', $tfn->('.tmp') or die $!;
3798         print TO <<END or die $!;
3799 object $head
3800 type commit
3801 tag $tag
3802 tagger $authline
3803
3804 END
3805         if ($tw->{View} eq 'dgit') {
3806             print TO <<END or die $!;
3807 $package release $cversion for $clogsuite ($csuite) [dgit]
3808 [dgit distro=$declaredistro$delibs]
3809 END
3810             foreach my $ref (sort keys %previously) {
3811                 print TO <<END or die $!;
3812 [dgit previously:$ref=$previously{$ref}]
3813 END
3814             }
3815         } elsif ($tw->{View} eq 'maint') {
3816             print TO <<END or die $!;
3817 $package release $cversion for $clogsuite ($csuite)
3818 (maintainer view tag generated by dgit --quilt=$quilt_mode)
3819 END
3820         } else {
3821             die Dumper($tw)."?";
3822         }
3823
3824         close TO or die $!;
3825
3826         my $tagobjfn = $tfn->('.tmp');
3827         if ($sign) {
3828             if (!defined $keyid) {
3829                 $keyid = access_cfg('keyid','RETURN-UNDEF');
3830             }
3831             if (!defined $keyid) {
3832                 $keyid = getfield $clogp, 'Maintainer';
3833             }
3834             unlink $tfn->('.tmp.asc') or $!==&ENOENT or die $!;
3835             my @sign_cmd = (@gpg, qw(--detach-sign --armor));
3836             push @sign_cmd, qw(-u),$keyid if defined $keyid;
3837             push @sign_cmd, $tfn->('.tmp');
3838             runcmd_ordryrun @sign_cmd;
3839             if (act_scary()) {
3840                 $tagobjfn = $tfn->('.signed.tmp');
3841                 runcmd shell_cmd "exec >$tagobjfn", qw(cat --),
3842                     $tfn->('.tmp'), $tfn->('.tmp.asc');
3843             }
3844         }
3845         return $tagobjfn;
3846     };
3847
3848     my @r = map { $mktag->($_); } @$tagwants;
3849     return @r;
3850 }
3851
3852 sub sign_changes ($) {
3853     my ($changesfile) = @_;
3854     if ($sign) {
3855         my @debsign_cmd = @debsign;
3856         push @debsign_cmd, "-k$keyid" if defined $keyid;
3857         push @debsign_cmd, "-p$gpg[0]" if $gpg[0] ne 'gpg';
3858         push @debsign_cmd, $changesfile;
3859         runcmd_ordryrun @debsign_cmd;
3860     }
3861 }
3862
3863 sub dopush () {
3864     printdebug "actually entering push\n";
3865
3866     supplementary_message(<<'END');
3867 Push failed, while checking state of the archive.
3868 You can retry the push, after fixing the problem, if you like.
3869 END
3870     if (check_for_git()) {
3871         git_fetch_us();
3872     }
3873     my $archive_hash = fetch_from_archive();
3874     if (!$archive_hash) {
3875         $new_package or
3876             fail "package appears to be new in this suite;".
3877                 " if this is intentional, use --new";
3878     }
3879
3880     supplementary_message(<<'END');
3881 Push failed, while preparing your push.
3882 You can retry the push, after fixing the problem, if you like.
3883 END
3884
3885     need_tagformat 'new', "quilt mode $quilt_mode"
3886         if quiltmode_splitbrain;
3887
3888     prep_ud();
3889
3890     access_giturl(); # check that success is vaguely likely
3891     select_tagformat();
3892
3893     my $clogpfn = ".git/dgit/changelog.822.tmp";
3894     runcmd shell_cmd "exec >$clogpfn", qw(dpkg-parsechangelog);
3895
3896     responder_send_file('parsed-changelog', $clogpfn);
3897
3898     my ($clogp, $cversion, $dscfn) =
3899         push_parse_changelog("$clogpfn");
3900
3901     my $dscpath = "$buildproductsdir/$dscfn";
3902     stat_exists $dscpath or
3903         fail "looked for .dsc $dscpath, but $!;".
3904             " maybe you forgot to build";
3905
3906     responder_send_file('dsc', $dscpath);
3907
3908     push_parse_dsc($dscpath, $dscfn, $cversion);
3909
3910     my $format = getfield $dsc, 'Format';
3911     printdebug "format $format\n";
3912
3913     my $actualhead = git_rev_parse('HEAD');
3914     my $dgithead = $actualhead;
3915     my $maintviewhead = undef;
3916
3917     my $upstreamversion = upstreamversion $clogp->{Version};
3918
3919     if (madformat_wantfixup($format)) {
3920         # user might have not used dgit build, so maybe do this now:
3921         if (quiltmode_splitbrain()) {
3922             changedir $ud;
3923             quilt_make_fake_dsc($upstreamversion);
3924             my $cachekey;
3925             ($dgithead, $cachekey) =
3926                 quilt_check_splitbrain_cache($actualhead, $upstreamversion);
3927             $dgithead or fail
3928  "--quilt=$quilt_mode but no cached dgit view:
3929  perhaps tree changed since dgit build[-source] ?";
3930             $split_brain = 1;
3931             $dgithead = splitbrain_pseudomerge($clogp,
3932                                                $actualhead, $dgithead,
3933                                                $archive_hash);
3934             $maintviewhead = $actualhead;
3935             changedir '../../../..';
3936             prep_ud(); # so _only_subdir() works, below
3937         } else {
3938             commit_quilty_patch();
3939         }
3940     }
3941
3942     if (defined $overwrite_version && !defined $maintviewhead) {
3943         $dgithead = plain_overwrite_pseudomerge($clogp,
3944                                                 $dgithead,
3945                                                 $archive_hash);
3946     }
3947
3948     check_not_dirty();
3949
3950     my $forceflag = '';
3951     if ($archive_hash) {
3952         if (is_fast_fwd($archive_hash, $dgithead)) {
3953             # ok
3954         } elsif (deliberately_not_fast_forward) {
3955             $forceflag = '+';
3956         } else {
3957             fail "dgit push: HEAD is not a descendant".
3958                 " of the archive's version.\n".
3959                 "To overwrite the archive's contents,".
3960                 " pass --overwrite[=VERSION].\n".
3961                 "To rewind history, if permitted by the archive,".
3962                 " use --deliberately-not-fast-forward.";
3963         }
3964     }
3965
3966     changedir $ud;
3967     progress "checking that $dscfn corresponds to HEAD";
3968     runcmd qw(dpkg-source -x --),
3969         $dscpath =~ m#^/# ? $dscpath : "../../../$dscpath";
3970     my ($tree,$dir) = mktree_in_ud_from_only_subdir("source package");
3971     check_for_vendor_patches() if madformat($dsc->{format});
3972     changedir '../../../..';
3973     my @diffcmd = (@git, qw(diff --quiet), $tree, $dgithead);
3974     debugcmd "+",@diffcmd;
3975     $!=0; $?=-1;
3976     my $r = system @diffcmd;
3977     if ($r) {
3978         if ($r==256) {
3979             my $diffs = cmdoutput @git, qw(diff --stat), $tree, $dgithead;
3980             fail <<END
3981 HEAD specifies a different tree to $dscfn:
3982 $diffs
3983 Perhaps you forgot to build.  Or perhaps there is a problem with your
3984  source tree (see dgit(7) for some hints).  To see a full diff, run
3985    git diff $tree HEAD
3986 END
3987         } else {
3988             failedcmd @diffcmd;
3989         }
3990     }
3991     if (!$changesfile) {
3992         my $pat = changespat $cversion;
3993         my @cs = glob "$buildproductsdir/$pat";
3994         fail "failed to find unique changes file".
3995             " (looked for $pat in $buildproductsdir);".
3996             " perhaps you need to use dgit -C"
3997             unless @cs==1;
3998         ($changesfile) = @cs;
3999     } else {
4000         $changesfile = "$buildproductsdir/$changesfile";
4001     }
4002
4003     # Check that changes and .dsc agree enough
4004     $changesfile =~ m{[^/]*$};
4005     my $changes = parsecontrol($changesfile,$&);
4006     files_compare_inputs($dsc, $changes)
4007         unless forceing [qw(dsc-changes-mismatch)];
4008
4009     # Perhaps adjust .dsc to contain right set of origs
4010     changes_update_origs_from_dsc($dsc, $changes, $upstreamversion,
4011                                   $changesfile)
4012         unless forceing [qw(changes-origs-exactly)];
4013
4014     # Checks complete, we're going to try and go ahead:
4015
4016     responder_send_file('changes',$changesfile);
4017     responder_send_command("param head $dgithead");
4018     responder_send_command("param csuite $csuite");
4019     responder_send_command("param tagformat $tagformat");
4020     if (defined $maintviewhead) {
4021         die unless ($protovsn//4) >= 4;
4022         responder_send_command("param maint-view $maintviewhead");
4023     }
4024
4025     if (deliberately_not_fast_forward) {
4026         git_for_each_ref(lrfetchrefs, sub {
4027             my ($objid,$objtype,$lrfetchrefname,$reftail) = @_;
4028             my $rrefname= substr($lrfetchrefname, length(lrfetchrefs) + 1);
4029             responder_send_command("previously $rrefname=$objid");
4030             $previously{$rrefname} = $objid;
4031         });
4032     }
4033
4034     my @tagwants = push_tagwants($cversion, $dgithead, $maintviewhead,
4035                                  ".git/dgit/tag");
4036     my @tagobjfns;
4037
4038     supplementary_message(<<'END');
4039 Push failed, while signing the tag.
4040 You can retry the push, after fixing the problem, if you like.
4041 END
4042     # If we manage to sign but fail to record it anywhere, it's fine.
4043     if ($we_are_responder) {
4044         @tagobjfns = map { $_->{Tfn}('.signed-tmp') } @tagwants;
4045         responder_receive_files('signed-tag', @tagobjfns);
4046     } else {
4047         @tagobjfns = push_mktags($clogp,$dscpath,
4048                               $changesfile,$changesfile,
4049                               \@tagwants);
4050     }
4051     supplementary_message(<<'END');
4052 Push failed, *after* signing the tag.
4053 If you want to try again, you should use a new version number.
4054 END
4055
4056     pairwise { $a->{TagObjFn} = $b } @tagwants, @tagobjfns;
4057
4058     foreach my $tw (@tagwants) {
4059         my $tag = $tw->{Tag};
4060         my $tagobjfn = $tw->{TagObjFn};
4061         my $tag_obj_hash =
4062             cmdoutput @git, qw(hash-object -w -t tag), $tagobjfn;
4063         runcmd_ordryrun @git, qw(verify-tag), $tag_obj_hash;
4064         runcmd_ordryrun_local
4065             @git, qw(update-ref), "refs/tags/$tag", $tag_obj_hash;
4066     }
4067
4068     supplementary_message(<<'END');
4069 Push failed, while updating the remote git repository - see messages above.
4070 If you want to try again, you should use a new version number.
4071 END
4072     if (!check_for_git()) {
4073         create_remote_git_repo();
4074     }
4075
4076     my @pushrefs = $forceflag.$dgithead.":".rrref();
4077     foreach my $tw (@tagwants) {
4078         push @pushrefs, $forceflag."refs/tags/$tw->{Tag}";
4079     }
4080
4081     runcmd_ordryrun @git,
4082         qw(-c push.followTags=false push), access_giturl(), @pushrefs;
4083     runcmd_ordryrun @git, qw(update-ref -m), 'dgit push', lrref(), $dgithead;
4084
4085     supplementary_message(<<'END');
4086 Push failed, while obtaining signatures on the .changes and .dsc.
4087 If it was just that the signature failed, you may try again by using
4088 debsign by hand to sign the changes
4089    $changesfile
4090 and then dput to complete the upload.
4091 If you need to change the package, you must use a new version number.
4092 END
4093     if ($we_are_responder) {
4094         my $dryrunsuffix = act_local() ? "" : ".tmp";
4095         responder_receive_files('signed-dsc-changes',
4096                                 "$dscpath$dryrunsuffix",
4097                                 "$changesfile$dryrunsuffix");
4098     } else {
4099         if (act_local()) {
4100             rename "$dscpath.tmp",$dscpath or die "$dscfn $!";
4101         } else {
4102             progress "[new .dsc left in $dscpath.tmp]";
4103         }
4104         sign_changes $changesfile;
4105     }
4106
4107     supplementary_message(<<END);
4108 Push failed, while uploading package(s) to the archive server.
4109 You can retry the upload of exactly these same files with dput of:
4110   $changesfile
4111 If that .changes file is broken, you will need to use a new version
4112 number for your next attempt at the upload.
4113 END
4114     my $host = access_cfg('upload-host','RETURN-UNDEF');
4115     my @hostarg = defined($host) ? ($host,) : ();
4116     runcmd_ordryrun @dput, @hostarg, $changesfile;
4117     printdone "pushed and uploaded $cversion";
4118
4119     supplementary_message('');
4120     responder_send_command("complete");
4121 }
4122
4123 sub cmd_clone {
4124     parseopts();
4125     my $dstdir;
4126     badusage "-p is not allowed with clone; specify as argument instead"
4127         if defined $package;
4128     if (@ARGV==1) {
4129         ($package) = @ARGV;
4130     } elsif (@ARGV==2 && $ARGV[1] =~ m#^\w#) {
4131         ($package,$isuite) = @ARGV;
4132     } elsif (@ARGV==2 && $ARGV[1] =~ m#^[./]#) {
4133         ($package,$dstdir) = @ARGV;
4134     } elsif (@ARGV==3) {
4135         ($package,$isuite,$dstdir) = @ARGV;
4136     } else {
4137         badusage "incorrect arguments to dgit clone";
4138     }
4139     notpushing();
4140
4141     $dstdir ||= "$package";
4142     if (stat_exists $dstdir) {
4143         fail "$dstdir already exists";
4144     }
4145
4146     my $cwd_remove;
4147     if ($rmonerror && !$dryrun_level) {
4148         $cwd_remove= getcwd();
4149         unshift @end, sub { 
4150             return unless defined $cwd_remove;
4151             if (!chdir "$cwd_remove") {
4152                 return if $!==&ENOENT;
4153                 die "chdir $cwd_remove: $!";
4154             }
4155             printdebug "clone rmonerror removing $dstdir\n";
4156             if (stat $dstdir) {
4157                 rmtree($dstdir) or die "remove $dstdir: $!\n";
4158             } elsif (grep { $! == $_ }
4159                      (ENOENT, ENOTDIR, EACCES, EPERM, ELOOP)) {
4160             } else {
4161                 print STDERR "check whether to remove $dstdir: $!\n";
4162             }
4163         };
4164     }
4165
4166     clone($dstdir);
4167     $cwd_remove = undef;
4168 }
4169
4170 sub branchsuite () {
4171     my $branch = cmdoutput_errok @git, qw(symbolic-ref HEAD);
4172     if ($branch =~ m#$lbranch_re#o) {
4173         return $1;
4174     } else {
4175         return undef;
4176     }
4177 }
4178
4179 sub fetchpullargs () {
4180     if (!defined $package) {
4181         my $sourcep = parsecontrol('debian/control','debian/control');
4182         $package = getfield $sourcep, 'Source';
4183     }
4184     if (@ARGV==0) {
4185         $isuite = branchsuite();
4186         if (!$isuite) {
4187             my $clogp = parsechangelog();
4188             $isuite = getfield $clogp, 'Distribution';
4189         }
4190     } elsif (@ARGV==1) {
4191         ($isuite) = @ARGV;
4192     } else {
4193         badusage "incorrect arguments to dgit fetch or dgit pull";
4194     }
4195     notpushing();
4196 }
4197
4198 sub cmd_fetch {
4199     parseopts();
4200     fetchpullargs();
4201     my $multi_fetched = fork_for_multisuite(sub { });
4202     exit 0 if $multi_fetched;
4203     fetch();
4204 }
4205
4206 sub cmd_pull {
4207     parseopts();
4208     fetchpullargs();
4209     if (quiltmode_splitbrain()) {
4210         my ($format, $fopts) = get_source_format();
4211         madformat($format) and fail <<END
4212 dgit pull not yet supported in split view mode (--quilt=$quilt_mode)
4213 END
4214     }
4215     pull();
4216 }
4217
4218 sub cmd_push {
4219     parseopts();
4220     pushing();
4221     badusage "-p is not allowed with dgit push" if defined $package;
4222     check_not_dirty();
4223     my $clogp = parsechangelog();
4224     $package = getfield $clogp, 'Source';
4225     my $specsuite;
4226     if (@ARGV==0) {
4227     } elsif (@ARGV==1) {
4228         ($specsuite) = (@ARGV);
4229     } else {
4230         badusage "incorrect arguments to dgit push";
4231     }
4232     $isuite = getfield $clogp, 'Distribution';
4233     if ($new_package) {
4234         local ($package) = $existing_package; # this is a hack
4235         canonicalise_suite();
4236     } else {
4237         canonicalise_suite();
4238     }
4239     if (defined $specsuite &&
4240         $specsuite ne $isuite &&
4241         $specsuite ne $csuite) {
4242             fail "dgit push: changelog specifies $isuite ($csuite)".
4243                 " but command line specifies $specsuite";
4244     }
4245     dopush();
4246 }
4247
4248 #---------- remote commands' implementation ----------
4249
4250 sub cmd_remote_push_build_host {
4251     my ($nrargs) = shift @ARGV;
4252     my (@rargs) = @ARGV[0..$nrargs-1];
4253     @ARGV = @ARGV[$nrargs..$#ARGV];
4254     die unless @rargs;
4255     my ($dir,$vsnwant) = @rargs;
4256     # vsnwant is a comma-separated list; we report which we have
4257     # chosen in our ready response (so other end can tell if they
4258     # offered several)
4259     $debugprefix = ' ';
4260     $we_are_responder = 1;
4261     $us .= " (build host)";
4262
4263     pushing();
4264
4265     open PI, "<&STDIN" or die $!;
4266     open STDIN, "/dev/null" or die $!;
4267     open PO, ">&STDOUT" or die $!;
4268     autoflush PO 1;
4269     open STDOUT, ">&STDERR" or die $!;
4270     autoflush STDOUT 1;
4271
4272     $vsnwant //= 1;
4273     ($protovsn) = grep {
4274         $vsnwant =~ m{^(?:.*,)?$_(?:,.*)?$}
4275     } @rpushprotovsn_support;
4276
4277     fail "build host has dgit rpush protocol versions ".
4278         (join ",", @rpushprotovsn_support).
4279         " but invocation host has $vsnwant"
4280         unless defined $protovsn;
4281
4282     responder_send_command("dgit-remote-push-ready $protovsn");
4283     rpush_handle_protovsn_bothends();
4284     changedir $dir;
4285     &cmd_push;
4286 }
4287
4288 sub cmd_remote_push_responder { cmd_remote_push_build_host(); }
4289 # ... for compatibility with proto vsn.1 dgit (just so that user gets
4290 #     a good error message)
4291
4292 sub rpush_handle_protovsn_bothends () {
4293     if ($protovsn < 4) {
4294         need_tagformat 'old', "rpush negotiated protocol $protovsn";
4295     }
4296     select_tagformat();
4297 }
4298
4299 our $i_tmp;
4300
4301 sub i_cleanup {
4302     local ($@, $?);
4303     my $report = i_child_report();
4304     if (defined $report) {
4305         printdebug "($report)\n";
4306     } elsif ($i_child_pid) {
4307         printdebug "(killing build host child $i_child_pid)\n";
4308         kill 15, $i_child_pid;
4309     }
4310     if (defined $i_tmp && !defined $initiator_tempdir) {
4311         changedir "/";
4312         eval { rmtree $i_tmp; };
4313     }
4314 }
4315
4316 END { i_cleanup(); }
4317
4318 sub i_method {
4319     my ($base,$selector,@args) = @_;
4320     $selector =~ s/\-/_/g;
4321     { no strict qw(refs); &{"${base}_${selector}"}(@args); }
4322 }
4323
4324 sub cmd_rpush {
4325     pushing();
4326     my $host = nextarg;
4327     my $dir;
4328     if ($host =~ m/^((?:[^][]|\[[^][]*\])*)\:/) {
4329         $host = $1;
4330         $dir = $'; #';
4331     } else {
4332         $dir = nextarg;
4333     }
4334     $dir =~ s{^-}{./-};
4335     my @rargs = ($dir);
4336     push @rargs, join ",", @rpushprotovsn_support;
4337     my @rdgit;
4338     push @rdgit, @dgit;
4339     push @rdgit, @ropts;
4340     push @rdgit, qw(remote-push-build-host), (scalar @rargs), @rargs;
4341     push @rdgit, @ARGV;
4342     my @cmd = (@ssh, $host, shellquote @rdgit);
4343     debugcmd "+",@cmd;
4344
4345     if (defined $initiator_tempdir) {
4346         rmtree $initiator_tempdir;
4347         mkdir $initiator_tempdir, 0700 or die "$initiator_tempdir: $!";
4348         $i_tmp = $initiator_tempdir;
4349     } else {
4350         $i_tmp = tempdir();
4351     }
4352     $i_child_pid = open2(\*RO, \*RI, @cmd);
4353     changedir $i_tmp;
4354     ($protovsn) = initiator_expect { m/^dgit-remote-push-ready (\S+)/ };
4355     die "$protovsn ?" unless grep { $_ eq $protovsn } @rpushprotovsn_support;
4356     $supplementary_message = '' unless $protovsn >= 3;
4357
4358     fail "rpush negotiated protocol version $protovsn".
4359         " which does not support quilt mode $quilt_mode"
4360         if quiltmode_splitbrain;
4361
4362     rpush_handle_protovsn_bothends();
4363     for (;;) {
4364         my ($icmd,$iargs) = initiator_expect {
4365             m/^(\S+)(?: (.*))?$/;
4366             ($1,$2);
4367         };
4368         i_method "i_resp", $icmd, $iargs;
4369     }
4370 }
4371
4372 sub i_resp_progress ($) {
4373     my ($rhs) = @_;
4374     my $msg = protocol_read_bytes \*RO, $rhs;
4375     progress $msg;
4376 }
4377
4378 sub i_resp_supplementary_message ($) {
4379     my ($rhs) = @_;
4380     $supplementary_message = protocol_read_bytes \*RO, $rhs;
4381 }
4382
4383 sub i_resp_complete {
4384     my $pid = $i_child_pid;
4385     $i_child_pid = undef; # prevents killing some other process with same pid
4386     printdebug "waiting for build host child $pid...\n";
4387     my $got = waitpid $pid, 0;
4388     die $! unless $got == $pid;
4389     die "build host child failed $?" if $?;
4390
4391     i_cleanup();
4392     printdebug "all done\n";
4393     exit 0;
4394 }
4395
4396 sub i_resp_file ($) {
4397     my ($keyword) = @_;
4398     my $localname = i_method "i_localname", $keyword;
4399     my $localpath = "$i_tmp/$localname";
4400     stat_exists $localpath and
4401         badproto \*RO, "file $keyword ($localpath) twice";
4402     protocol_receive_file \*RO, $localpath;
4403     i_method "i_file", $keyword;
4404 }
4405
4406 our %i_param;
4407
4408 sub i_resp_param ($) {
4409     $_[0] =~ m/^(\S+) (.*)$/ or badproto \*RO, "bad param spec";
4410     $i_param{$1} = $2;
4411 }
4412
4413 sub i_resp_previously ($) {
4414     $_[0] =~ m#^(refs/tags/\S+)=(\w+)$#
4415         or badproto \*RO, "bad previously spec";
4416     my $r = system qw(git check-ref-format), $1;
4417     die "bad previously ref spec ($r)" if $r;
4418     $previously{$1} = $2;
4419 }
4420
4421 our %i_wanted;
4422
4423 sub i_resp_want ($) {
4424     my ($keyword) = @_;
4425     die "$keyword ?" if $i_wanted{$keyword}++;
4426     my @localpaths = i_method "i_want", $keyword;
4427     printdebug "[[  $keyword @localpaths\n";
4428     foreach my $localpath (@localpaths) {
4429         protocol_send_file \*RI, $localpath;
4430     }
4431     print RI "files-end\n" or die $!;
4432 }
4433
4434 our ($i_clogp, $i_version, $i_dscfn, $i_changesfn);
4435
4436 sub i_localname_parsed_changelog {
4437     return "remote-changelog.822";
4438 }
4439 sub i_file_parsed_changelog {
4440     ($i_clogp, $i_version, $i_dscfn) =
4441         push_parse_changelog "$i_tmp/remote-changelog.822";
4442     die if $i_dscfn =~ m#/|^\W#;
4443 }
4444
4445 sub i_localname_dsc {
4446     defined $i_dscfn or badproto \*RO, "dsc (before parsed-changelog)";
4447     return $i_dscfn;
4448 }
4449 sub i_file_dsc { }
4450
4451 sub i_localname_changes {
4452     defined $i_dscfn or badproto \*RO, "dsc (before parsed-changelog)";
4453     $i_changesfn = $i_dscfn;
4454     $i_changesfn =~ s/\.dsc$/_dgit.changes/ or die;
4455     return $i_changesfn;
4456 }
4457 sub i_file_changes { }
4458
4459 sub i_want_signed_tag {
4460     printdebug Dumper(\%i_param, $i_dscfn);
4461     defined $i_param{'head'} && defined $i_dscfn && defined $i_clogp
4462         && defined $i_param{'csuite'}
4463         or badproto \*RO, "premature desire for signed-tag";
4464     my $head = $i_param{'head'};
4465     die if $head =~ m/[^0-9a-f]/ || $head !~ m/^../;
4466
4467     my $maintview = $i_param{'maint-view'};
4468     die if defined $maintview && $maintview =~ m/[^0-9a-f]/;
4469
4470     select_tagformat();
4471     if ($protovsn >= 4) {
4472         my $p = $i_param{'tagformat'} // '<undef>';
4473         $p eq $tagformat
4474             or badproto \*RO, "tag format mismatch: $p vs. $tagformat";
4475     }
4476
4477     die unless $i_param{'csuite'} =~ m/^$suite_re$/;
4478     $csuite = $&;
4479     push_parse_dsc $i_dscfn, 'remote dsc', $i_version;
4480
4481     my @tagwants = push_tagwants $i_version, $head, $maintview, "tag";
4482
4483     return
4484         push_mktags $i_clogp, $i_dscfn,
4485             $i_changesfn, 'remote changes',
4486             \@tagwants;
4487 }
4488
4489 sub i_want_signed_dsc_changes {
4490     rename "$i_dscfn.tmp","$i_dscfn" or die "$i_dscfn $!";
4491     sign_changes $i_changesfn;
4492     return ($i_dscfn, $i_changesfn);
4493 }
4494
4495 #---------- building etc. ----------
4496
4497 our $version;
4498 our $sourcechanges;
4499 our $dscfn;
4500
4501 #----- `3.0 (quilt)' handling -----
4502
4503 our $fakeeditorenv = 'DGIT_FAKE_EDITOR_QUILT';
4504
4505 sub quiltify_dpkg_commit ($$$;$) {
4506     my ($patchname,$author,$msg, $xinfo) = @_;
4507     $xinfo //= '';
4508
4509     mkpath '.git/dgit';
4510     my $descfn = ".git/dgit/quilt-description.tmp";
4511     open O, '>', $descfn or die "$descfn: $!";
4512     $msg =~ s/\n+/\n\n/;
4513     print O <<END or die $!;
4514 From: $author
4515 ${xinfo}Subject: $msg
4516 ---
4517
4518 END
4519     close O or die $!;
4520
4521     {
4522         local $ENV{'EDITOR'} = cmdoutput qw(realpath --), $0;
4523         local $ENV{'VISUAL'} = $ENV{'EDITOR'};
4524         local $ENV{$fakeeditorenv} = cmdoutput qw(realpath --), $descfn;
4525         runcmd @dpkgsource, qw(--commit --include-removal .), $patchname;
4526     }
4527 }
4528
4529 sub quiltify_trees_differ ($$;$$$) {
4530     my ($x,$y,$finegrained,$ignorenamesr,$unrepres) = @_;
4531     # returns true iff the two tree objects differ other than in debian/
4532     # with $finegrained,
4533     # returns bitmask 01 - differ in upstream files except .gitignore
4534     #                 02 - differ in .gitignore
4535     # if $ignorenamesr is defined, $ingorenamesr->{$fn}
4536     #  is set for each modified .gitignore filename $fn
4537     # if $unrepres is defined, array ref to which is appeneded
4538     #  a list of unrepresentable changes (removals of upstream files
4539     #  (as messages)
4540     local $/=undef;
4541     my @cmd = (@git, qw(diff-tree -z));
4542     push @cmd, qw(--name-only) unless $unrepres;
4543     push @cmd, qw(-r) if $finegrained || $unrepres;
4544     push @cmd, $x, $y;
4545     my $diffs= cmdoutput @cmd;
4546     my $r = 0;
4547     my @lmodes;
4548     foreach my $f (split /\0/, $diffs) {
4549         if ($unrepres && !@lmodes) {
4550             @lmodes = $f =~ m/^\:(\w+) (\w+) \w+ \w+ / or die "$_ ?";
4551             next;
4552         }
4553         my ($oldmode,$newmode) = @lmodes;
4554         @lmodes = ();
4555
4556         next if $f =~ m#^debian(?:/.*)?$#s;
4557
4558         if ($unrepres) {
4559             eval {
4560                 die "not a plain file\n"
4561                     unless $newmode =~ m/^10\d{4}$/ ||
4562                            $oldmode =~ m/^10\d{4}$/;
4563                 if ($oldmode =~ m/[^0]/ &&
4564                     $newmode =~ m/[^0]/) {
4565                     die "mode changed\n" if $oldmode ne $newmode;
4566                 } else {
4567                     die "non-default mode\n"
4568                         unless $newmode =~ m/^100644$/ ||
4569                                $oldmode =~ m/^100644$/;
4570                 }
4571             };
4572             if ($@) {
4573                 local $/="\n"; chomp $@;
4574                 push @$unrepres, [ $f, "$@ ($oldmode->$newmode)" ];
4575             }
4576         }
4577
4578         my $isignore = $f =~ m#^(?:.*/)?.gitignore$#s;
4579         $r |= $isignore ? 02 : 01;
4580         $ignorenamesr->{$f}=1 if $ignorenamesr && $isignore;
4581     }
4582     printdebug "quiltify_trees_differ $x $y => $r\n";
4583     return $r;
4584 }
4585
4586 sub quiltify_tree_sentinelfiles ($) {
4587     # lists the `sentinel' files present in the tree
4588     my ($x) = @_;
4589     my $r = cmdoutput @git, qw(ls-tree --name-only), $x,
4590         qw(-- debian/rules debian/control);
4591     $r =~ s/\n/,/g;
4592     return $r;
4593 }
4594
4595 sub quiltify_splitbrain_needed () {
4596     if (!$split_brain) {
4597         progress "dgit view: changes are required...";
4598         runcmd @git, qw(checkout -q -b dgit-view);
4599         $split_brain = 1;
4600     }
4601 }
4602
4603 sub quiltify_splitbrain ($$$$$$) {
4604     my ($clogp, $unapplied, $headref, $diffbits,
4605         $editedignores, $cachekey) = @_;
4606     if ($quilt_mode !~ m/gbp|dpm/) {
4607         # treat .gitignore just like any other upstream file
4608         $diffbits = { %$diffbits };
4609         $_ = !!$_ foreach values %$diffbits;
4610     }
4611     # We would like any commits we generate to be reproducible
4612     my @authline = clogp_authline($clogp);
4613     local $ENV{GIT_COMMITTER_NAME} =  $authline[0];
4614     local $ENV{GIT_COMMITTER_EMAIL} = $authline[1];
4615     local $ENV{GIT_COMMITTER_DATE} =  $authline[2];
4616     local $ENV{GIT_AUTHOR_NAME} =  $authline[0];
4617     local $ENV{GIT_AUTHOR_EMAIL} = $authline[1];
4618     local $ENV{GIT_AUTHOR_DATE} =  $authline[2];
4619
4620     if ($quilt_mode =~ m/gbp|unapplied/ &&
4621         ($diffbits->{O2H} & 01)) {
4622         my $msg =
4623  "--quilt=$quilt_mode specified, implying patches-unapplied git tree\n".
4624  " but git tree differs from orig in upstream files.";
4625         if (!stat_exists "debian/patches") {
4626             $msg .=
4627  "\n ... debian/patches is missing; perhaps this is a patch queue branch?";
4628         }  
4629         fail $msg;
4630     }
4631     if ($quilt_mode =~ m/dpm/ &&
4632         ($diffbits->{H2A} & 01)) {
4633         fail <<END;
4634 --quilt=$quilt_mode specified, implying patches-applied git tree
4635  but git tree differs from result of applying debian/patches to upstream
4636 END
4637     }
4638     if ($quilt_mode =~ m/gbp|unapplied/ &&
4639         ($diffbits->{O2A} & 01)) { # some patches
4640         quiltify_splitbrain_needed();
4641         progress "dgit view: creating patches-applied version using gbp pq";
4642         runcmd shell_cmd 'exec >/dev/null', gbp_pq, qw(import);
4643         # gbp pq import creates a fresh branch; push back to dgit-view
4644         runcmd @git, qw(update-ref refs/heads/dgit-view HEAD);
4645         runcmd @git, qw(checkout -q dgit-view);
4646     }
4647     if ($quilt_mode =~ m/gbp|dpm/ &&
4648         ($diffbits->{O2A} & 02)) {
4649         fail <<END
4650 --quilt=$quilt_mode specified, implying that HEAD is for use with a
4651  tool which does not create patches for changes to upstream
4652  .gitignores: but, such patches exist in debian/patches.
4653 END
4654     }
4655     if (($diffbits->{O2H} & 02) && # user has modified .gitignore
4656         !($diffbits->{O2A} & 02)) { # patches do not change .gitignore
4657         quiltify_splitbrain_needed();
4658         progress "dgit view: creating patch to represent .gitignore changes";
4659         ensuredir "debian/patches";
4660         my $gipatch = "debian/patches/auto-gitignore";
4661         open GIPATCH, ">>", "$gipatch" or die "$gipatch: $!";
4662         stat GIPATCH or die "$gipatch: $!";
4663         fail "$gipatch already exists; but want to create it".
4664             " to record .gitignore changes" if (stat _)[7];
4665         print GIPATCH <<END or die "$gipatch: $!";
4666 Subject: Update .gitignore from Debian packaging branch
4667
4668 The Debian packaging git branch contains these updates to the upstream
4669 .gitignore file(s).  This patch is autogenerated, to provide these
4670 updates to users of the official Debian archive view of the package.
4671
4672 [dgit ($our_version) update-gitignore]
4673 ---
4674 END
4675         close GIPATCH or die "$gipatch: $!";
4676         runcmd shell_cmd "exec >>$gipatch", @git, qw(diff),
4677             $unapplied, $headref, "--", sort keys %$editedignores;
4678         open SERIES, "+>>", "debian/patches/series" or die $!;
4679         defined seek SERIES, -1, 2 or $!==EINVAL or die $!;
4680         my $newline;
4681         defined read SERIES, $newline, 1 or die $!;
4682         print SERIES "\n" or die $! unless $newline eq "\n";
4683         print SERIES "auto-gitignore\n" or die $!;
4684         close SERIES or die  $!;
4685         runcmd @git, qw(add -- debian/patches/series), $gipatch;
4686         commit_admin <<END
4687 Commit patch to update .gitignore
4688
4689 [dgit ($our_version) update-gitignore-quilt-fixup]
4690 END
4691     }
4692
4693     my $dgitview = git_rev_parse 'HEAD';
4694
4695     changedir '../../../..';
4696     # When we no longer need to support squeeze, use --create-reflog
4697     # instead of this:
4698     ensuredir ".git/logs/refs/dgit-intern";
4699     my $makelogfh = new IO::File ".git/logs/refs/$splitbraincache", '>>'
4700       or die $!;
4701
4702     my $oldcache = git_get_ref "refs/$splitbraincache";
4703     if ($oldcache eq $dgitview) {
4704         my $tree = cmdoutput qw(git rev-parse), "$dgitview:";
4705         # git update-ref doesn't always update, in this case.  *sigh*
4706         my $dummy = make_commit_text <<END;
4707 tree $tree
4708 parent $dgitview
4709 author Dgit <dgit\@example.com> 1000000000 +0000
4710 committer Dgit <dgit\@example.com> 1000000000 +0000
4711
4712 Dummy commit - do not use
4713 END
4714         runcmd @git, qw(update-ref -m), "dgit $our_version - dummy",
4715             "refs/$splitbraincache", $dummy;
4716     }
4717     runcmd @git, qw(update-ref -m), $cachekey, "refs/$splitbraincache",
4718         $dgitview;
4719
4720     changedir '.git/dgit/unpack/work';
4721
4722     my $saved = maybe_split_brain_save $headref, $dgitview, "converted";
4723     progress "dgit view: created ($saved)";
4724 }
4725
4726 sub quiltify ($$$$) {
4727     my ($clogp,$target,$oldtiptree,$failsuggestion) = @_;
4728
4729     # Quilt patchification algorithm
4730     #
4731     # We search backwards through the history of the main tree's HEAD
4732     # (T) looking for a start commit S whose tree object is identical
4733     # to to the patch tip tree (ie the tree corresponding to the
4734     # current dpkg-committed patch series).  For these purposes
4735     # `identical' disregards anything in debian/ - this wrinkle is
4736     # necessary because dpkg-source treates debian/ specially.
4737     #
4738     # We can only traverse edges where at most one of the ancestors'
4739     # trees differs (in changes outside in debian/).  And we cannot
4740     # handle edges which change .pc/ or debian/patches.  To avoid
4741     # going down a rathole we avoid traversing edges which introduce
4742     # debian/rules or debian/control.  And we set a limit on the
4743     # number of edges we are willing to look at.
4744     #
4745     # If we succeed, we walk forwards again.  For each traversed edge
4746     # PC (with P parent, C child) (starting with P=S and ending with
4747     # C=T) to we do this:
4748     #  - git checkout C
4749     #  - dpkg-source --commit with a patch name and message derived from C
4750     # After traversing PT, we git commit the changes which
4751     # should be contained within debian/patches.
4752
4753     # The search for the path S..T is breadth-first.  We maintain a
4754     # todo list containing search nodes.  A search node identifies a
4755     # commit, and looks something like this:
4756     #  $p = {
4757     #      Commit => $git_commit_id,
4758     #      Child => $c,                          # or undef if P=T
4759     #      Whynot => $reason_edge_PC_unsuitable, # in @nots only
4760     #      Nontrivial => true iff $p..$c has relevant changes
4761     #  };
4762
4763     my @todo;
4764     my @nots;
4765     my $sref_S;
4766     my $max_work=100;
4767     my %considered; # saves being exponential on some weird graphs
4768
4769     my $t_sentinels = quiltify_tree_sentinelfiles $target;
4770
4771     my $not = sub {
4772         my ($search,$whynot) = @_;
4773         printdebug " search NOT $search->{Commit} $whynot\n";
4774         $search->{Whynot} = $whynot;
4775         push @nots, $search;
4776         no warnings qw(exiting);
4777         next;
4778     };
4779
4780     push @todo, {
4781         Commit => $target,
4782     };
4783
4784     while (@todo) {
4785         my $c = shift @todo;
4786         next if $considered{$c->{Commit}}++;
4787
4788         $not->($c, "maximum search space exceeded") if --$max_work <= 0;
4789
4790         printdebug "quiltify investigate $c->{Commit}\n";
4791
4792         # are we done?
4793         if (!quiltify_trees_differ $c->{Commit}, $oldtiptree) {
4794             printdebug " search finished hooray!\n";
4795             $sref_S = $c;
4796             last;
4797         }
4798
4799         if ($quilt_mode eq 'nofix') {
4800             fail "quilt fixup required but quilt mode is \`nofix'\n".
4801                 "HEAD commit $c->{Commit} differs from tree implied by ".
4802                 " debian/patches (tree object $oldtiptree)";
4803         }
4804         if ($quilt_mode eq 'smash') {
4805             printdebug " search quitting smash\n";
4806             last;
4807         }
4808
4809         my $c_sentinels = quiltify_tree_sentinelfiles $c->{Commit};
4810         $not->($c, "has $c_sentinels not $t_sentinels")
4811             if $c_sentinels ne $t_sentinels;
4812
4813         my $commitdata = cmdoutput @git, qw(cat-file commit), $c->{Commit};
4814         $commitdata =~ m/\n\n/;
4815         $commitdata =~ $`;
4816         my @parents = ($commitdata =~ m/^parent (\w+)$/gm);
4817         @parents = map { { Commit => $_, Child => $c } } @parents;
4818
4819         $not->($c, "root commit") if !@parents;
4820
4821         foreach my $p (@parents) {
4822             $p->{Nontrivial}= quiltify_trees_differ $p->{Commit},$c->{Commit};
4823         }
4824         my $ndiffers = grep { $_->{Nontrivial} } @parents;
4825         $not->($c, "merge ($ndiffers nontrivial parents)") if $ndiffers > 1;
4826
4827         foreach my $p (@parents) {
4828             printdebug "considering C=$c->{Commit} P=$p->{Commit}\n";
4829
4830             my @cmd= (@git, qw(diff-tree -r --name-only),
4831                       $p->{Commit},$c->{Commit}, qw(-- debian/patches .pc));
4832             my $patchstackchange = cmdoutput @cmd;
4833             if (length $patchstackchange) {
4834                 $patchstackchange =~ s/\n/,/g;
4835                 $not->($p, "changed $patchstackchange");
4836             }
4837
4838             printdebug " search queue P=$p->{Commit} ",
4839                 ($p->{Nontrivial} ? "NT" : "triv"),"\n";
4840             push @todo, $p;
4841         }
4842     }
4843
4844     if (!$sref_S) {
4845         printdebug "quiltify want to smash\n";
4846
4847         my $abbrev = sub {
4848             my $x = $_[0]{Commit};
4849             $x =~ s/(.*?[0-9a-z]{8})[0-9a-z]*$/$1/;
4850             return $x;
4851         };
4852         my $reportnot = sub {
4853             my ($notp) = @_;
4854             my $s = $abbrev->($notp);
4855             my $c = $notp->{Child};
4856             $s .= "..".$abbrev->($c) if $c;
4857             $s .= ": ".$notp->{Whynot};
4858             return $s;
4859         };
4860         if ($quilt_mode eq 'linear') {
4861             print STDERR "$us: quilt fixup cannot be linear.  Stopped at:\n";
4862             foreach my $notp (@nots) {
4863                 print STDERR "$us:  ", $reportnot->($notp), "\n";
4864             }
4865             print STDERR "$us: $_\n" foreach @$failsuggestion;
4866             fail "quilt fixup naive history linearisation failed.\n".
4867  "Use dpkg-source --commit by hand; or, --quilt=smash for one ugly patch";
4868         } elsif ($quilt_mode eq 'smash') {
4869         } elsif ($quilt_mode eq 'auto') {
4870             progress "quilt fixup cannot be linear, smashing...";
4871         } else {
4872             die "$quilt_mode ?";
4873         }
4874
4875         my $time = $ENV{'GIT_COMMITTER_DATE'} || time;
4876         $time =~ s/\s.*//; # trim timezone from GIT_COMMITTER_DATE
4877         my $ncommits = 3;
4878         my $msg = cmdoutput @git, qw(log), "-n$ncommits";
4879
4880         quiltify_dpkg_commit "auto-$version-$target-$time",
4881             (getfield $clogp, 'Maintainer'),
4882             "Automatically generated patch ($clogp->{Version})\n".
4883             "Last (up to) $ncommits git changes, FYI:\n\n". $msg;
4884         return;
4885     }
4886
4887     progress "quiltify linearisation planning successful, executing...";
4888
4889     for (my $p = $sref_S;
4890          my $c = $p->{Child};
4891          $p = $p->{Child}) {
4892         printdebug "quiltify traverse $p->{Commit}..$c->{Commit}\n";
4893         next unless $p->{Nontrivial};
4894
4895         my $cc = $c->{Commit};
4896
4897         my $commitdata = cmdoutput @git, qw(cat-file commit), $cc;
4898         $commitdata =~ m/\n\n/ or die "$c ?";
4899         $commitdata = $`;
4900         my $msg = $'; #';
4901         $commitdata =~ m/^author (.*) \d+ [-+0-9]+$/m or die "$cc ?";
4902         my $author = $1;
4903
4904         my $commitdate = cmdoutput
4905             @git, qw(log -n1 --pretty=format:%aD), $cc;
4906
4907         $msg =~ s/^(.*)\n*/$1\n/ or die "$cc $msg ?";
4908
4909         my $strip_nls = sub { $msg =~ s/\n+$//; $msg .= "\n"; };
4910         $strip_nls->();
4911
4912         my $title = $1;
4913         my $patchname;
4914         my $patchdir;
4915
4916         my $gbp_check_suitable = sub {
4917             $_ = shift;
4918             my ($what) = @_;
4919
4920             eval {
4921                 die "contains unexpected slashes\n" if m{//} || m{/$};
4922                 die "contains leading punctuation\n" if m{^\W} || m{/\W};
4923                 die "contains bad character(s)\n" if m{[^-a-z0-9_.+=~/]}i;
4924                 die "too long" if length > 200;
4925             };
4926             return $_ unless $@;
4927             print STDERR "quiltifying commit $cc:".
4928                 " ignoring/dropping Gbp-Pq $what: $@";
4929             return undef;
4930         };
4931
4932         if ($msg =~ s/^ (?: gbp(?:-pq)? : \s* name \s+ |
4933                            gbp-pq-name: \s* )
4934                        (\S+) \s* \n //ixm) {
4935             $patchname = $gbp_check_suitable->($1, 'Name');
4936         }
4937         if ($msg =~ s/^ (?: gbp(?:-pq)? : \s* topic \s+ |
4938                            gbp-pq-topic: \s* )
4939                        (\S+) \s* \n //ixm) {
4940             $patchdir = $gbp_check_suitable->($1, 'Topic');
4941         }
4942
4943         $strip_nls->();
4944
4945         if (!defined $patchname) {
4946             $patchname = $title;
4947             $patchname =~ s/[.:]$//;
4948             use Text::Iconv;
4949             eval {
4950                 my $converter = new Text::Iconv qw(UTF-8 ASCII//TRANSLIT);
4951                 my $translitname = $converter->convert($patchname);
4952                 die unless defined $translitname;
4953                 $patchname = $translitname;
4954             };
4955             print STDERR
4956                 "dgit: patch title transliteration error: $@"
4957                 if $@;
4958             $patchname =~ y/ A-Z/-a-z/;
4959             $patchname =~ y/-a-z0-9_.+=~//cd;
4960             $patchname =~ s/^\W/x-$&/;
4961             $patchname = substr($patchname,0,40);
4962         }
4963         if (!defined $patchdir) {
4964             $patchdir = '';
4965         }
4966         if (length $patchdir) {
4967             $patchname = "$patchdir/$patchname";
4968         }
4969         if ($patchname =~ m{^(.*)/}) {
4970             mkpath "debian/patches/$1";
4971         }
4972
4973         my $index;
4974         for ($index='';
4975              stat "debian/patches/$patchname$index";
4976              $index++) { }
4977         $!==ENOENT or die "$patchname$index $!";
4978
4979         runcmd @git, qw(checkout -q), $cc;
4980
4981         # We use the tip's changelog so that dpkg-source doesn't
4982         # produce complaining messages from dpkg-parsechangelog.  None
4983         # of the information dpkg-source gets from the changelog is
4984         # actually relevant - it gets put into the original message
4985         # which dpkg-source provides our stunt editor, and then
4986         # overwritten.
4987         runcmd @git, qw(checkout -q), $target, qw(debian/changelog);
4988
4989         quiltify_dpkg_commit "$patchname$index", $author, $msg,
4990             "Date: $commitdate\n".
4991             "X-Dgit-Generated: $clogp->{Version} $cc\n";
4992
4993         runcmd @git, qw(checkout -q), $cc, qw(debian/changelog);
4994     }
4995
4996     runcmd @git, qw(checkout -q master);
4997 }
4998
4999 sub build_maybe_quilt_fixup () {
5000     my ($format,$fopts) = get_source_format;
5001     return unless madformat_wantfixup $format;
5002     # sigh
5003
5004     check_for_vendor_patches();
5005
5006     if (quiltmode_splitbrain) {
5007         fail <<END unless access_cfg_tagformats_can_splitbrain;
5008 quilt mode $quilt_mode requires split view so server needs to support
5009  both "new" and "maint" tag formats, but config says it doesn't.
5010 END
5011     }
5012
5013     my $clogp = parsechangelog();
5014     my $headref = git_rev_parse('HEAD');
5015
5016     prep_ud();
5017     changedir $ud;
5018
5019     my $upstreamversion = upstreamversion $version;
5020
5021     if ($fopts->{'single-debian-patch'}) {
5022         quilt_fixup_singlepatch($clogp, $headref, $upstreamversion);
5023     } else {
5024         quilt_fixup_multipatch($clogp, $headref, $upstreamversion);
5025     }
5026
5027     die 'bug' if $split_brain && !$need_split_build_invocation;
5028
5029     changedir '../../../..';
5030     runcmd_ordryrun_local
5031         @git, qw(pull --ff-only -q .git/dgit/unpack/work master);
5032 }
5033
5034 sub quilt_fixup_mkwork ($) {
5035     my ($headref) = @_;
5036
5037     mkdir "work" or die $!;
5038     changedir "work";
5039     mktree_in_ud_here();
5040     runcmd @git, qw(reset -q --hard), $headref;
5041 }
5042
5043 sub quilt_fixup_linkorigs ($$) {
5044     my ($upstreamversion, $fn) = @_;
5045     # calls $fn->($leafname);
5046
5047     foreach my $f (<../../../../*>) { #/){
5048         my $b=$f; $b =~ s{.*/}{};
5049         {
5050             local ($debuglevel) = $debuglevel-1;
5051             printdebug "QF linkorigs $b, $f ?\n";
5052         }
5053         next unless is_orig_file_of_vsn $b, $upstreamversion;
5054         printdebug "QF linkorigs $b, $f Y\n";
5055         link_ltarget $f, $b or die "$b $!";
5056         $fn->($b);
5057     }
5058 }
5059
5060 sub quilt_fixup_delete_pc () {
5061     runcmd @git, qw(rm -rqf .pc);
5062     commit_admin <<END
5063 Commit removal of .pc (quilt series tracking data)
5064
5065 [dgit ($our_version) upgrade quilt-remove-pc]
5066 END
5067 }
5068
5069 sub quilt_fixup_singlepatch ($$$) {
5070     my ($clogp, $headref, $upstreamversion) = @_;
5071
5072     progress "starting quiltify (single-debian-patch)";
5073
5074     # dpkg-source --commit generates new patches even if
5075     # single-debian-patch is in debian/source/options.  In order to
5076     # get it to generate debian/patches/debian-changes, it is
5077     # necessary to build the source package.
5078
5079     quilt_fixup_linkorigs($upstreamversion, sub { });
5080     quilt_fixup_mkwork($headref);
5081
5082     rmtree("debian/patches");
5083
5084     runcmd @dpkgsource, qw(-b .);
5085     changedir "..";
5086     runcmd @dpkgsource, qw(-x), (srcfn $version, ".dsc");
5087     rename srcfn("$upstreamversion", "/debian/patches"), 
5088            "work/debian/patches";
5089
5090     changedir "work";
5091     commit_quilty_patch();
5092 }
5093
5094 sub quilt_make_fake_dsc ($) {
5095     my ($upstreamversion) = @_;
5096
5097     my $fakeversion="$upstreamversion-~~DGITFAKE";
5098
5099     my $fakedsc=new IO::File 'fake.dsc', '>' or die $!;
5100     print $fakedsc <<END or die $!;
5101 Format: 3.0 (quilt)
5102 Source: $package
5103 Version: $fakeversion
5104 Files:
5105 END
5106
5107     my $dscaddfile=sub {
5108         my ($b) = @_;
5109         
5110         my $md = new Digest::MD5;
5111
5112         my $fh = new IO::File $b, '<' or die "$b $!";
5113         stat $fh or die $!;
5114         my $size = -s _;
5115
5116         $md->addfile($fh);
5117         print $fakedsc " ".$md->hexdigest." $size $b\n" or die $!;
5118     };
5119
5120     quilt_fixup_linkorigs($upstreamversion, $dscaddfile);
5121
5122     my @files=qw(debian/source/format debian/rules
5123                  debian/control debian/changelog);
5124     foreach my $maybe (qw(debian/patches debian/source/options
5125                           debian/tests/control)) {
5126         next unless stat_exists "../../../$maybe";
5127         push @files, $maybe;
5128     }
5129
5130     my $debtar= srcfn $fakeversion,'.debian.tar.gz';
5131     runcmd qw(env GZIP=-1n tar -zcf), "./$debtar", qw(-C ../../..), @files;
5132
5133     $dscaddfile->($debtar);
5134     close $fakedsc or die $!;
5135 }
5136
5137 sub quilt_check_splitbrain_cache ($$) {
5138     my ($headref, $upstreamversion) = @_;
5139     # Called only if we are in (potentially) split brain mode.
5140     # Called in $ud.
5141     # Computes the cache key and looks in the cache.
5142     # Returns ($dgit_view_commitid, $cachekey) or (undef, $cachekey)
5143
5144     my $splitbrain_cachekey;
5145     
5146     progress
5147  "dgit: split brain (separate dgit view) may be needed (--quilt=$quilt_mode).";
5148     # we look in the reflog of dgit-intern/quilt-cache
5149     # we look for an entry whose message is the key for the cache lookup
5150     my @cachekey = (qw(dgit), $our_version);
5151     push @cachekey, $upstreamversion;
5152     push @cachekey, $quilt_mode;
5153     push @cachekey, $headref;
5154
5155     push @cachekey, hashfile('fake.dsc');
5156
5157     my $srcshash = Digest::SHA->new(256);
5158     my %sfs = ( %INC, '$0(dgit)' => $0 );
5159     foreach my $sfk (sort keys %sfs) {
5160         next unless $sfk =~ m/^\$0\b/ || $sfk =~ m{^Debian/Dgit\b};
5161         $srcshash->add($sfk,"  ");
5162         $srcshash->add(hashfile($sfs{$sfk}));
5163         $srcshash->add("\n");
5164     }
5165     push @cachekey, $srcshash->hexdigest();
5166     $splitbrain_cachekey = "@cachekey";
5167
5168     my @cmd = (@git, qw(log -g), '--pretty=format:%H %gs',
5169                $splitbraincache);
5170     printdebug "splitbrain cachekey $splitbrain_cachekey\n";
5171     debugcmd "|(probably)",@cmd;
5172     my $child = open GC, "-|";  defined $child or die $!;
5173     if (!$child) {
5174         chdir '../../..' or die $!;
5175         if (!stat ".git/logs/refs/$splitbraincache") {
5176             $! == ENOENT or die $!;
5177             printdebug ">(no reflog)\n";
5178             exit 0;
5179         }
5180         exec @cmd; die $!;
5181     }
5182     while (<GC>) {
5183         chomp;
5184         printdebug ">| ", $_, "\n" if $debuglevel > 1;
5185         next unless m/^(\w+) (\S.*\S)$/ && $2 eq $splitbrain_cachekey;
5186             
5187         my $cachehit = $1;
5188         quilt_fixup_mkwork($headref);
5189         my $saved = maybe_split_brain_save $headref, $cachehit, "cache-hit";
5190         if ($cachehit ne $headref) {
5191             progress "dgit view: found cached ($saved)";
5192             runcmd @git, qw(checkout -q -b dgit-view), $cachehit;
5193             $split_brain = 1;
5194             return ($cachehit, $splitbrain_cachekey);
5195         }
5196         progress "dgit view: found cached, no changes required";
5197         return ($headref, $splitbrain_cachekey);
5198     }
5199     die $! if GC->error;
5200     failedcmd unless close GC;
5201
5202     printdebug "splitbrain cache miss\n";
5203     return (undef, $splitbrain_cachekey);
5204 }
5205
5206 sub quilt_fixup_multipatch ($$$) {
5207     my ($clogp, $headref, $upstreamversion) = @_;
5208
5209     progress "examining quilt state (multiple patches, $quilt_mode mode)";
5210
5211     # Our objective is:
5212     #  - honour any existing .pc in case it has any strangeness
5213     #  - determine the git commit corresponding to the tip of
5214     #    the patch stack (if there is one)
5215     #  - if there is such a git commit, convert each subsequent
5216     #    git commit into a quilt patch with dpkg-source --commit
5217     #  - otherwise convert all the differences in the tree into
5218     #    a single git commit
5219     #
5220     # To do this we:
5221
5222     # Our git tree doesn't necessarily contain .pc.  (Some versions of
5223     # dgit would include the .pc in the git tree.)  If there isn't
5224     # one, we need to generate one by unpacking the patches that we
5225     # have.
5226     #
5227     # We first look for a .pc in the git tree.  If there is one, we
5228     # will use it.  (This is not the normal case.)
5229     #
5230     # Otherwise need to regenerate .pc so that dpkg-source --commit
5231     # can work.  We do this as follows:
5232     #     1. Collect all relevant .orig from parent directory
5233     #     2. Generate a debian.tar.gz out of
5234     #         debian/{patches,rules,source/format,source/options}
5235     #     3. Generate a fake .dsc containing just these fields:
5236     #          Format Source Version Files
5237     #     4. Extract the fake .dsc
5238     #        Now the fake .dsc has a .pc directory.
5239     # (In fact we do this in every case, because in future we will
5240     # want to search for a good base commit for generating patches.)
5241     #
5242     # Then we can actually do the dpkg-source --commit
5243     #     1. Make a new working tree with the same object
5244     #        store as our main tree and check out the main
5245     #        tree's HEAD.
5246     #     2. Copy .pc from the fake's extraction, if necessary
5247     #     3. Run dpkg-source --commit
5248     #     4. If the result has changes to debian/, then
5249     #          - git add them them
5250     #          - git add .pc if we had a .pc in-tree
5251     #          - git commit
5252     #     5. If we had a .pc in-tree, delete it, and git commit
5253     #     6. Back in the main tree, fast forward to the new HEAD
5254
5255     # Another situation we may have to cope with is gbp-style
5256     # patches-unapplied trees.
5257     #
5258     # We would want to detect these, so we know to escape into
5259     # quilt_fixup_gbp.  However, this is in general not possible.
5260     # Consider a package with a one patch which the dgit user reverts
5261     # (with git revert or the moral equivalent).
5262     #
5263     # That is indistinguishable in contents from a patches-unapplied
5264     # tree.  And looking at the history to distinguish them is not
5265     # useful because the user might have made a confusing-looking git
5266     # history structure (which ought to produce an error if dgit can't
5267     # cope, not a silent reintroduction of an unwanted patch).
5268     #
5269     # So gbp users will have to pass an option.  But we can usually
5270     # detect their failure to do so: if the tree is not a clean
5271     # patches-applied tree, quilt linearisation fails, but the tree
5272     # _is_ a clean patches-unapplied tree, we can suggest that maybe
5273     # they want --quilt=unapplied.
5274     #
5275     # To help detect this, when we are extracting the fake dsc, we
5276     # first extract it with --skip-patches, and then apply the patches
5277     # afterwards with dpkg-source --before-build.  That lets us save a
5278     # tree object corresponding to .origs.
5279
5280     my $splitbrain_cachekey;
5281
5282     quilt_make_fake_dsc($upstreamversion);
5283
5284     if (quiltmode_splitbrain()) {
5285         my $cachehit;
5286         ($cachehit, $splitbrain_cachekey) =
5287             quilt_check_splitbrain_cache($headref, $upstreamversion);
5288         return if $cachehit;
5289     }
5290
5291     runcmd qw(sh -ec),
5292         'exec dpkg-source --no-check --skip-patches -x fake.dsc >/dev/null';
5293
5294     my $fakexdir= $package.'-'.(stripepoch $upstreamversion);
5295     rename $fakexdir, "fake" or die "$fakexdir $!";
5296
5297     changedir 'fake';
5298
5299     remove_stray_gits("source package");
5300     mktree_in_ud_here();
5301
5302     rmtree '.pc';
5303
5304     my $unapplied=git_add_write_tree();
5305     printdebug "fake orig tree object $unapplied\n";
5306
5307     ensuredir '.pc';
5308
5309     my @bbcmd = (qw(sh -ec), 'exec dpkg-source --before-build . >/dev/null');
5310     $!=0; $?=-1;
5311     if (system @bbcmd) {
5312         failedcmd @bbcmd if $? < 0;
5313         fail <<END;
5314 failed to apply your git tree's patch stack (from debian/patches/) to
5315  the corresponding upstream tarball(s).  Your source tree and .orig
5316  are probably too inconsistent.  dgit can only fix up certain kinds of
5317  anomaly (depending on the quilt mode).  See --quilt= in dgit(1).
5318 END
5319     }
5320
5321     changedir '..';
5322
5323     quilt_fixup_mkwork($headref);
5324
5325     my $mustdeletepc=0;
5326     if (stat_exists ".pc") {
5327         -d _ or die;
5328         progress "Tree already contains .pc - will use it then delete it.";
5329         $mustdeletepc=1;
5330     } else {
5331         rename '../fake/.pc','.pc' or die $!;
5332     }
5333
5334     changedir '../fake';
5335     rmtree '.pc';
5336     my $oldtiptree=git_add_write_tree();
5337     printdebug "fake o+d/p tree object $unapplied\n";
5338     changedir '../work';
5339
5340
5341     # We calculate some guesswork now about what kind of tree this might
5342     # be.  This is mostly for error reporting.
5343
5344     my %editedignores;
5345     my @unrepres;
5346     my $diffbits = {
5347         # H = user's HEAD
5348         # O = orig, without patches applied
5349         # A = "applied", ie orig with H's debian/patches applied
5350         O2H => quiltify_trees_differ($unapplied,$headref,   1,
5351                                      \%editedignores, \@unrepres),
5352         H2A => quiltify_trees_differ($headref,  $oldtiptree,1),
5353         O2A => quiltify_trees_differ($unapplied,$oldtiptree,1),
5354     };
5355
5356     my @dl;
5357     foreach my $b (qw(01 02)) {
5358         foreach my $v (qw(O2H O2A H2A)) {
5359             push @dl, ($diffbits->{$v} & $b) ? '##' : '==';
5360         }
5361     }
5362     printdebug "differences \@dl @dl.\n";
5363
5364     progress sprintf
5365 "$us: base trees orig=%.20s o+d/p=%.20s",
5366               $unapplied, $oldtiptree;
5367     progress sprintf
5368 "$us: quilt differences: src:  %s orig %s     gitignores:  %s orig %s\n".
5369 "$us: quilt differences:      HEAD %s o+d/p               HEAD %s o+d/p",
5370                              $dl[0], $dl[1],              $dl[3], $dl[4],
5371                                  $dl[2],                     $dl[5];
5372
5373     if (@unrepres) {
5374         print STDERR "dgit:  cannot represent change: $_->[1]: $_->[0]\n"
5375             foreach @unrepres;
5376         forceable_fail [qw(unrepresentable)], <<END;
5377 HEAD has changes to .orig[s] which are not representable by `3.0 (quilt)'
5378 END
5379     }
5380
5381     my @failsuggestion;
5382     if (!($diffbits->{O2H} & $diffbits->{O2A})) {
5383         push @failsuggestion, "This might be a patches-unapplied branch.";
5384     }  elsif (!($diffbits->{H2A} & $diffbits->{O2A})) {
5385         push @failsuggestion, "This might be a patches-applied branch.";
5386     }
5387     push @failsuggestion, "Maybe you need to specify one of".
5388         " --[quilt=]gbp --[quilt=]dpm --quilt=unapplied ?";
5389
5390     if (quiltmode_splitbrain()) {
5391         quiltify_splitbrain($clogp, $unapplied, $headref,
5392                             $diffbits, \%editedignores,
5393                             $splitbrain_cachekey);
5394         return;
5395     }
5396
5397     progress "starting quiltify (multiple patches, $quilt_mode mode)";
5398     quiltify($clogp,$headref,$oldtiptree,\@failsuggestion);
5399
5400     if (!open P, '>>', ".pc/applied-patches") {
5401         $!==&ENOENT or die $!;
5402     } else {
5403         close P;
5404     }
5405
5406     commit_quilty_patch();
5407
5408     if ($mustdeletepc) {
5409         quilt_fixup_delete_pc();
5410     }
5411 }
5412
5413 sub quilt_fixup_editor () {
5414     my $descfn = $ENV{$fakeeditorenv};
5415     my $editing = $ARGV[$#ARGV];
5416     open I1, '<', $descfn or die "$descfn: $!";
5417     open I2, '<', $editing or die "$editing: $!";
5418     unlink $editing or die "$editing: $!";
5419     open O, '>', $editing or die "$editing: $!";
5420     while (<I1>) { print O or die $!; } I1->error and die $!;
5421     my $copying = 0;
5422     while (<I2>) {
5423         $copying ||= m/^\-\-\- /;
5424         next unless $copying;
5425         print O or die $!;
5426     }
5427     I2->error and die $!;
5428     close O or die $1;
5429     exit 0;
5430 }
5431
5432 sub maybe_apply_patches_dirtily () {
5433     return unless $quilt_mode =~ m/gbp|unapplied/;
5434     print STDERR <<END or die $!;
5435
5436 dgit: Building, or cleaning with rules target, in patches-unapplied tree.
5437 dgit: Have to apply the patches - making the tree dirty.
5438 dgit: (Consider specifying --clean=git and (or) using dgit sbuild.)
5439
5440 END
5441     $patches_applied_dirtily = 01;
5442     $patches_applied_dirtily |= 02 unless stat_exists '.pc';
5443     runcmd qw(dpkg-source --before-build .);
5444 }
5445
5446 sub maybe_unapply_patches_again () {
5447     progress "dgit: Unapplying patches again to tidy up the tree."
5448         if $patches_applied_dirtily;
5449     runcmd qw(dpkg-source --after-build .)
5450         if $patches_applied_dirtily & 01;
5451     rmtree '.pc'
5452         if $patches_applied_dirtily & 02;
5453     $patches_applied_dirtily = 0;
5454 }
5455
5456 #----- other building -----
5457
5458 our $clean_using_builder;
5459 # ^ tree is to be cleaned by dpkg-source's builtin idea that it should
5460 #   clean the tree before building (perhaps invoked indirectly by
5461 #   whatever we are using to run the build), rather than separately
5462 #   and explicitly by us.
5463
5464 sub clean_tree () {
5465     return if $clean_using_builder;
5466     if ($cleanmode eq 'dpkg-source') {
5467         maybe_apply_patches_dirtily();
5468         runcmd_ordryrun_local @dpkgbuildpackage, qw(-T clean);
5469     } elsif ($cleanmode eq 'dpkg-source-d') {
5470         maybe_apply_patches_dirtily();
5471         runcmd_ordryrun_local @dpkgbuildpackage, qw(-d -T clean);
5472     } elsif ($cleanmode eq 'git') {
5473         runcmd_ordryrun_local @git, qw(clean -xdf);
5474     } elsif ($cleanmode eq 'git-ff') {
5475         runcmd_ordryrun_local @git, qw(clean -xdff);
5476     } elsif ($cleanmode eq 'check') {
5477         my $leftovers = cmdoutput @git, qw(clean -xdn);
5478         if (length $leftovers) {
5479             print STDERR $leftovers, "\n" or die $!;
5480             fail "tree contains uncommitted files and --clean=check specified";
5481         }
5482     } elsif ($cleanmode eq 'none') {
5483     } else {
5484         die "$cleanmode ?";
5485     }
5486 }
5487
5488 sub cmd_clean () {
5489     badusage "clean takes no additional arguments" if @ARGV;
5490     notpushing();
5491     clean_tree();
5492     maybe_unapply_patches_again();
5493 }
5494
5495 sub build_prep_early () {
5496     our $build_prep_early_done //= 0;
5497     return if $build_prep_early_done++;
5498     notpushing();
5499     badusage "-p is not allowed when building" if defined $package;
5500     my $clogp = parsechangelog();
5501     $isuite = getfield $clogp, 'Distribution';
5502     $package = getfield $clogp, 'Source';
5503     $version = getfield $clogp, 'Version';
5504     check_not_dirty();
5505 }
5506
5507 sub build_prep () {
5508     build_prep_early();
5509     clean_tree();
5510     build_maybe_quilt_fixup();
5511     if ($rmchanges) {
5512         my $pat = changespat $version;
5513         foreach my $f (glob "$buildproductsdir/$pat") {
5514             if (act_local()) {
5515                 unlink $f or fail "remove old changes file $f: $!";
5516             } else {
5517                 progress "would remove $f";
5518             }
5519         }
5520     }
5521 }
5522
5523 sub changesopts_initial () {
5524     my @opts =@changesopts[1..$#changesopts];
5525 }
5526
5527 sub changesopts_version () {
5528     if (!defined $changes_since_version) {
5529         my @vsns = archive_query('archive_query');
5530         my @quirk = access_quirk();
5531         if ($quirk[0] eq 'backports') {
5532             local $isuite = $quirk[2];
5533             local $csuite;
5534             canonicalise_suite();
5535             push @vsns, archive_query('archive_query');
5536         }
5537         if (@vsns) {
5538             @vsns = map { $_->[0] } @vsns;
5539             @vsns = sort { -version_compare($a, $b) } @vsns;
5540             $changes_since_version = $vsns[0];
5541             progress "changelog will contain changes since $vsns[0]";
5542         } else {
5543             $changes_since_version = '_';
5544             progress "package seems new, not specifying -v<version>";
5545         }
5546     }
5547     if ($changes_since_version ne '_') {
5548         return ("-v$changes_since_version");
5549     } else {
5550         return ();
5551     }
5552 }
5553
5554 sub changesopts () {
5555     return (changesopts_initial(), changesopts_version());
5556 }
5557
5558 sub massage_dbp_args ($;$) {
5559     my ($cmd,$xargs) = @_;
5560     # We need to:
5561     #
5562     #  - if we're going to split the source build out so we can
5563     #    do strange things to it, massage the arguments to dpkg-buildpackage
5564     #    so that the main build doessn't build source (or add an argument
5565     #    to stop it building source by default).
5566     #
5567     #  - add -nc to stop dpkg-source cleaning the source tree,
5568     #    unless we're not doing a split build and want dpkg-source
5569     #    as cleanmode, in which case we can do nothing
5570     #
5571     # return values:
5572     #    0 - source will NOT need to be built separately by caller
5573     #   +1 - source will need to be built separately by caller
5574     #   +2 - source will need to be built separately by caller AND
5575     #        dpkg-buildpackage should not in fact be run at all!
5576     debugcmd '#massaging#', @$cmd if $debuglevel>1;
5577 #print STDERR "MASS0 ",Dumper($cmd, $xargs, $need_split_build_invocation);
5578     if ($cleanmode eq 'dpkg-source' && !$need_split_build_invocation) {
5579         $clean_using_builder = 1;
5580         return 0;
5581     }
5582     # -nc has the side effect of specifying -b if nothing else specified
5583     # and some combinations of -S, -b, et al, are errors, rather than
5584     # later simply overriding earlie.  So we need to:
5585     #  - search the command line for these options
5586     #  - pick the last one
5587     #  - perhaps add our own as a default
5588     #  - perhaps adjust it to the corresponding non-source-building version
5589     my $dmode = '-F';
5590     foreach my $l ($cmd, $xargs) {
5591         next unless $l;
5592         @$l = grep { !(m/^-[SgGFABb]$/s and $dmode=$_) } @$l;
5593     }
5594     push @$cmd, '-nc';
5595 #print STDERR "MASS1 ",Dumper($cmd, $xargs, $dmode);
5596     my $r = 0;
5597     if ($need_split_build_invocation) {
5598         printdebug "massage split $dmode.\n";
5599         $r = $dmode =~ m/[S]/     ? +2 :
5600              $dmode =~ y/gGF/ABb/ ? +1 :
5601              $dmode =~ m/[ABb]/   ?  0 :
5602              die "$dmode ?";
5603     }
5604     printdebug "massage done $r $dmode.\n";
5605     push @$cmd, $dmode;
5606 #print STDERR "MASS2 ",Dumper($cmd, $xargs, $r);
5607     return $r;
5608 }
5609
5610 sub in_parent (&) {
5611     my ($fn) = @_;
5612     my $wasdir = must_getcwd();
5613     changedir "..";
5614     $fn->();
5615     changedir $wasdir;
5616 }    
5617
5618 sub postbuild_mergechanges ($) { # must run with CWD=.. (eg in in_parent)
5619     my ($msg_if_onlyone) = @_;
5620     # If there is only one .changes file, fail with $msg_if_onlyone,
5621     # or if that is undef, be a no-op.
5622     # Returns the changes file to report to the user.
5623     my $pat = changespat $version;
5624     my @changesfiles = glob $pat;
5625     @changesfiles = sort {
5626         ($b =~ m/_source\.changes$/ <=> $a =~ m/_source\.changes$/)
5627             or $a cmp $b
5628     } @changesfiles;
5629     my $result;
5630     if (@changesfiles==1) {
5631         fail <<END.$msg_if_onlyone if defined $msg_if_onlyone;
5632 only one changes file from build (@changesfiles)
5633 END
5634         $result = $changesfiles[0];
5635     } elsif (@changesfiles==2) {
5636         my $binchanges = parsecontrol($changesfiles[1], "binary changes file");
5637         foreach my $l (split /\n/, getfield $binchanges, 'Files') {
5638             fail "$l found in binaries changes file $binchanges"
5639                 if $l =~ m/\.dsc$/;
5640         }
5641         runcmd_ordryrun_local @mergechanges, @changesfiles;
5642         my $multichanges = changespat $version,'multi';
5643         if (act_local()) {
5644             stat_exists $multichanges or fail "$multichanges: $!";
5645             foreach my $cf (glob $pat) {
5646                 next if $cf eq $multichanges;
5647                 rename "$cf", "$cf.inmulti" or fail "$cf\{,.inmulti}: $!";
5648             }
5649         }
5650         $result = $multichanges;
5651     } else {
5652         fail "wrong number of different changes files (@changesfiles)";
5653     }
5654     printdone "build successful, results in $result\n" or die $!;
5655 }
5656
5657 sub midbuild_checkchanges () {
5658     my $pat = changespat $version;
5659     return if $rmchanges;
5660     my @unwanted = map { s#^\.\./##; $_; } glob "../$pat";
5661     @unwanted = grep { $_ ne changespat $version,'source' } @unwanted;
5662     fail <<END
5663 changes files other than source matching $pat already present; building would result in ambiguity about the intended results.
5664 Suggest you delete @unwanted.
5665 END
5666         if @unwanted;
5667 }
5668
5669 sub midbuild_checkchanges_vanilla ($) {
5670     my ($wantsrc) = @_;
5671     midbuild_checkchanges() if $wantsrc == 1;
5672 }
5673
5674 sub postbuild_mergechanges_vanilla ($) {
5675     my ($wantsrc) = @_;
5676     if ($wantsrc == 1) {
5677         in_parent {
5678             postbuild_mergechanges(undef);
5679         };
5680     } else {
5681         printdone "build successful\n";
5682     }
5683 }
5684
5685 sub cmd_build {
5686     build_prep_early();
5687     my @dbp = (@dpkgbuildpackage, qw(-us -uc), changesopts_initial(), @ARGV);
5688     my $wantsrc = massage_dbp_args \@dbp;
5689     if ($wantsrc > 0) {
5690         build_source();
5691         midbuild_checkchanges_vanilla $wantsrc;
5692     } else {
5693         build_prep();
5694     }
5695     if ($wantsrc < 2) {
5696         push @dbp, changesopts_version();
5697         maybe_apply_patches_dirtily();
5698         runcmd_ordryrun_local @dbp;
5699     }
5700     maybe_unapply_patches_again();
5701     postbuild_mergechanges_vanilla $wantsrc;
5702 }
5703
5704 sub pre_gbp_build {
5705     $quilt_mode //= 'gbp';
5706 }
5707
5708 sub cmd_gbp_build {
5709     build_prep_early();
5710
5711     # gbp can make .origs out of thin air.  In my tests it does this
5712     # even for a 1.0 format package, with no origs present.  So I
5713     # guess it keys off just the version number.  We don't know
5714     # exactly what .origs ought to exist, but let's assume that we
5715     # should run gbp if: the version has an upstream part and the main
5716     # orig is absent.
5717     my $upstreamversion = upstreamversion $version;
5718     my $origfnpat = srcfn $upstreamversion, '.orig.tar.*';
5719     my $gbp_make_orig = $version =~ m/-/ && !(() = glob "../$origfnpat");
5720
5721     if ($gbp_make_orig) {
5722         clean_tree();
5723         $cleanmode = 'none'; # don't do it again
5724         $need_split_build_invocation = 1;
5725     }
5726
5727     my @dbp = @dpkgbuildpackage;
5728
5729     my $wantsrc = massage_dbp_args \@dbp, \@ARGV;
5730
5731     if (!length $gbp_build[0]) {
5732         if (length executable_on_path('git-buildpackage')) {
5733             $gbp_build[0] = qw(git-buildpackage);
5734         } else {
5735             $gbp_build[0] = 'gbp buildpackage';
5736         }
5737     }
5738     my @cmd = opts_opt_multi_cmd @gbp_build;
5739
5740     push @cmd, (qw(-us -uc --git-no-sign-tags), "--git-builder=@dbp");
5741
5742     if ($gbp_make_orig) {
5743         ensuredir '.git/dgit';
5744         my $ok = '.git/dgit/origs-gen-ok';
5745         unlink $ok or $!==&ENOENT or die $!;
5746         my @origs_cmd = @cmd;
5747         push @origs_cmd, qw(--git-cleaner=true);
5748         push @origs_cmd, "--git-prebuild=touch $ok .git/dgit/no-such-dir/ok";
5749         push @origs_cmd, @ARGV;
5750         if (act_local()) {
5751             debugcmd @origs_cmd;
5752             system @origs_cmd;
5753             do { local $!; stat_exists $ok; }
5754                 or failedcmd @origs_cmd;
5755         } else {
5756             dryrun_report @origs_cmd;
5757         }
5758     }
5759
5760     if ($wantsrc > 0) {
5761         build_source();
5762         midbuild_checkchanges_vanilla $wantsrc;
5763     } else {
5764         if (!$clean_using_builder) {
5765             push @cmd, '--git-cleaner=true';
5766         }
5767         build_prep();
5768     }
5769     maybe_unapply_patches_again();
5770     if ($wantsrc < 2) {
5771         push @cmd, changesopts();
5772         runcmd_ordryrun_local @cmd, @ARGV;
5773     }
5774     postbuild_mergechanges_vanilla $wantsrc;
5775 }
5776 sub cmd_git_build { cmd_gbp_build(); } # compatibility with <= 1.0
5777
5778 sub build_source {
5779     build_prep_early();
5780     my $our_cleanmode = $cleanmode;
5781     if ($need_split_build_invocation) {
5782         # Pretend that clean is being done some other way.  This
5783         # forces us not to try to use dpkg-buildpackage to clean and
5784         # build source all in one go; and instead we run dpkg-source
5785         # (and build_prep() will do the clean since $clean_using_builder
5786         # is false).
5787         $our_cleanmode = 'ELSEWHERE';
5788     }
5789     if ($our_cleanmode =~ m/^dpkg-source/) {
5790         # dpkg-source invocation (below) will clean, so build_prep shouldn't
5791         $clean_using_builder = 1;
5792     }
5793     build_prep();
5794     $sourcechanges = changespat $version,'source';
5795     if (act_local()) {
5796         unlink "../$sourcechanges" or $!==ENOENT
5797             or fail "remove $sourcechanges: $!";
5798     }
5799     $dscfn = dscfn($version);
5800     if ($our_cleanmode eq 'dpkg-source') {
5801         maybe_apply_patches_dirtily();
5802         runcmd_ordryrun_local @dpkgbuildpackage, qw(-us -uc -S),
5803             changesopts();
5804     } elsif ($our_cleanmode eq 'dpkg-source-d') {
5805         maybe_apply_patches_dirtily();
5806         runcmd_ordryrun_local @dpkgbuildpackage, qw(-us -uc -S -d),
5807             changesopts();
5808     } else {
5809         my @cmd = (@dpkgsource, qw(-b --));
5810         if ($split_brain) {
5811             changedir $ud;
5812             runcmd_ordryrun_local @cmd, "work";
5813             my @udfiles = <${package}_*>;
5814             changedir "../../..";
5815             foreach my $f (@udfiles) {
5816                 printdebug "source copy, found $f\n";
5817                 next unless
5818                     $f eq $dscfn or
5819                     ($f =~ m/\.debian\.tar(?:\.\w+)$/ &&
5820                      $f eq srcfn($version, $&));
5821                 printdebug "source copy, found $f - renaming\n";
5822                 rename "$ud/$f", "../$f" or $!==ENOENT
5823                     or fail "put in place new source file ($f): $!";
5824             }
5825         } else {
5826             my $pwd = must_getcwd();
5827             my $leafdir = basename $pwd;
5828             changedir "..";
5829             runcmd_ordryrun_local @cmd, $leafdir;
5830             changedir $pwd;
5831         }
5832         runcmd_ordryrun_local qw(sh -ec),
5833             'exec >$1; shift; exec "$@"','x',
5834             "../$sourcechanges",
5835             @dpkggenchanges, qw(-S), changesopts();
5836     }
5837 }
5838
5839 sub cmd_build_source {
5840     build_prep_early();
5841     badusage "build-source takes no additional arguments" if @ARGV;
5842     build_source();
5843     maybe_unapply_patches_again();
5844     printdone "source built, results in $dscfn and $sourcechanges";
5845 }
5846
5847 sub cmd_sbuild {
5848     build_source();
5849     midbuild_checkchanges();
5850     in_parent {
5851         if (act_local()) {
5852             stat_exists $dscfn or fail "$dscfn (in parent directory): $!";
5853             stat_exists $sourcechanges
5854                 or fail "$sourcechanges (in parent directory): $!";
5855         }
5856         runcmd_ordryrun_local @sbuild, qw(-d), $isuite, @ARGV, $dscfn;
5857     };
5858     maybe_unapply_patches_again();
5859     in_parent {
5860         postbuild_mergechanges(<<END);
5861 perhaps you need to pass -A ?  (sbuild's default is to build only
5862 arch-specific binaries; dgit 1.4 used to override that.)
5863 END
5864     };
5865 }    
5866
5867 sub cmd_quilt_fixup {
5868     badusage "incorrect arguments to dgit quilt-fixup" if @ARGV;
5869     build_prep_early();
5870     clean_tree();
5871     build_maybe_quilt_fixup();
5872 }
5873
5874 sub cmd_import_dsc {
5875     my $needsig = 0;
5876
5877     while (@ARGV) {
5878         last unless $ARGV[0] =~ m/^-/;
5879         $_ = shift @ARGV;
5880         last if m/^--?$/;
5881         if (m/^--require-valid-signature$/) {
5882             $needsig = 1;
5883         } else {
5884             badusage "unknown dgit import-dsc sub-option \`$_'";
5885         }
5886     }
5887
5888     badusage "usage: dgit import-dsc .../PATH/TO/.DSC BRANCH" unless @ARGV==2;
5889     my ($dscfn, $dstbranch) = @ARGV;
5890
5891     badusage "dry run makes no sense with import-dsc" unless act_local();
5892
5893     my $force = $dstbranch =~ s/^\+//   ? +1 :
5894                 $dstbranch =~ s/^\.\.// ? -1 :
5895                                            0;
5896     my $info = $force ? " $&" : '';
5897     $info = "$dscfn$info";
5898
5899     my $specbranch = $dstbranch;
5900     $dstbranch = "refs/heads/$dstbranch" unless $dstbranch =~ m#^refs/#;
5901     $dstbranch = cmdoutput @git, qw(check-ref-format --normalize), $dstbranch;
5902
5903     my @symcmd = (@git, qw(symbolic-ref -q HEAD));
5904     my $chead = cmdoutput_errok @symcmd;
5905     defined $chead or $?==256 or failedcmd @symcmd;
5906
5907     fail "$dstbranch is checked out - will not update it"
5908         if defined $chead and $chead eq $dstbranch;
5909
5910     my $oldhash = git_get_ref $dstbranch;
5911
5912     open D, "<", $dscfn or fail "open import .dsc ($dscfn): $!";
5913     $dscdata = do { local $/ = undef; <D>; };
5914     D->error and fail "read $dscfn: $!";
5915     close C;
5916
5917     # we don't normally need this so import it here
5918     use Dpkg::Source::Package;
5919     my $dp = new Dpkg::Source::Package filename => $dscfn,
5920         require_valid_signature => $needsig;
5921     {
5922         local $SIG{__WARN__} = sub {
5923             print STDERR $_[0];
5924             return unless $needsig;
5925             fail "import-dsc signature check failed";
5926         };
5927         if (!$dp->is_signed()) {
5928             warn "$us: warning: importing unsigned .dsc\n";
5929         } else {
5930             my $r = $dp->check_signature();
5931             die "->check_signature => $r" if $needsig && $r;
5932         }
5933     }
5934
5935     parse_dscdata();
5936
5937     parse_dsc_field($dsc, "Dgit metadata in .dsc");
5938
5939     if (defined $dsc_hash
5940         && !forceing [qw(import-dsc-with-dgit-field)]) {
5941         progress "dgit: import-dsc of .dsc with Dgit field, using git hash";
5942         my @cmd = (qw(sh -ec),
5943                    "echo $dsc_hash | git cat-file --batch-check");
5944         my $objgot = cmdoutput @cmd;
5945         if ($objgot =~ m#^\w+ missing\b#) {
5946             fail <<END
5947 .dsc contains Dgit field referring to object $dsc_hash
5948 Your git tree does not have that object.  Try `git fetch' from a
5949 plausible server (browse.dgit.d.o? alioth?), and try the import-dsc again.
5950 END
5951         }
5952         if ($oldhash && !is_fast_fwd $oldhash, $dsc_hash) {
5953             if ($force > 0) {
5954                 progress "Not fast forward, forced update.";
5955             } else {
5956                 fail "Not fast forward to $dsc_hash";
5957             }
5958         }
5959         @cmd = (@git, qw(update-ref -m), "dgit import-dsc (Dgit): $info",
5960                 $dstbranch, $dsc_hash);
5961         runcmd @cmd;
5962         progress "dgit: import-dsc updated git ref $dstbranch";
5963         return 0;
5964     }
5965
5966     fail <<END
5967 Branch $dstbranch already exists
5968 Specify ..$specbranch for a pseudo-merge, binding in existing history
5969 Specify  +$specbranch to overwrite, discarding existing history
5970 END
5971         if $oldhash && !$force;
5972
5973     $package = getfield $dsc, 'Source';
5974     my @dfi = dsc_files_info();
5975     foreach my $fi (@dfi) {
5976         my $f = $fi->{Filename};
5977         my $here = "../$f";
5978         next if lstat $here;
5979         fail "stat $here: $!" unless $! == ENOENT;
5980         my $there = $dscfn;
5981         if ($dscfn =~ m#^(?:\./+)?\.\./+#) {
5982             $there = $';
5983         } elsif ($dscfn =~ m#^/#) {
5984             $there = $dscfn;
5985         } else {
5986             fail "cannot import $dscfn which seems to be inside working tree!";
5987         }
5988         $there =~ s#/+[^/]+$## or
5989             fail "cannot import $dscfn which seems to not have a basename";
5990         $there .= "/$f";
5991         symlink $there, $here or fail "symlink $there to $here: $!";
5992         progress "made symlink $here -> $there";
5993 #       print STDERR Dumper($fi);
5994     }
5995     my @mergeinputs = generate_commits_from_dsc();
5996     die unless @mergeinputs == 1;
5997
5998     my $newhash = $mergeinputs[0]{Commit};
5999
6000     if ($oldhash) {
6001         if ($force > 0) {
6002             progress "Import, forced update - synthetic orphan git history.";
6003         } elsif ($force < 0) {
6004             progress "Import, merging.";
6005             my $tree = cmdoutput @git, qw(rev-parse), "$newhash:";
6006             my $version = getfield $dsc, 'Version';
6007             my $clogp = commit_getclogp $newhash;
6008             my $authline = clogp_authline $clogp;
6009             $newhash = make_commit_text <<END;
6010 tree $tree
6011 parent $newhash
6012 parent $oldhash
6013 author $authline
6014 committer $authline
6015
6016 Merge $package ($version) import into $dstbranch
6017 END
6018         } else {
6019             die; # caught earlier
6020         }
6021     }
6022
6023     my @cmd = (@git, qw(update-ref -m), "dgit import-dsc: $info",
6024                $dstbranch, $newhash);
6025     runcmd @cmd;
6026     progress "dgit: import-dsc results are in in git ref $dstbranch";
6027 }
6028
6029 sub cmd_archive_api_query {
6030     badusage "need only 1 subpath argument" unless @ARGV==1;
6031     my ($subpath) = @ARGV;
6032     my @cmd = archive_api_query_cmd($subpath);
6033     push @cmd, qw(-f);
6034     debugcmd ">",@cmd;
6035     exec @cmd or fail "exec curl: $!\n";
6036 }
6037
6038 sub cmd_clone_dgit_repos_server {
6039     badusage "need destination argument" unless @ARGV==1;
6040     my ($destdir) = @ARGV;
6041     $package = '_dgit-repos-server';
6042     my @cmd = (@git, qw(clone), access_giturl(), $destdir);
6043     debugcmd ">",@cmd;
6044     exec @cmd or fail "exec git clone: $!\n";
6045 }
6046
6047 sub cmd_setup_mergechangelogs {
6048     badusage "no arguments allowed to dgit setup-mergechangelogs" if @ARGV;
6049     setup_mergechangelogs(1);
6050 }
6051
6052 sub cmd_setup_useremail {
6053     badusage "no arguments allowed to dgit setup-mergechangelogs" if @ARGV;
6054     setup_useremail(1);
6055 }
6056
6057 sub cmd_setup_new_tree {
6058     badusage "no arguments allowed to dgit setup-tree" if @ARGV;
6059     setup_new_tree();
6060 }
6061
6062 #---------- argument parsing and main program ----------
6063
6064 sub cmd_version {
6065     print "dgit version $our_version\n" or die $!;
6066     exit 0;
6067 }
6068
6069 our (%valopts_long, %valopts_short);
6070 our @rvalopts;
6071
6072 sub defvalopt ($$$$) {
6073     my ($long,$short,$val_re,$how) = @_;
6074     my $oi = { Long => $long, Short => $short, Re => $val_re, How => $how };
6075     $valopts_long{$long} = $oi;
6076     $valopts_short{$short} = $oi;
6077     # $how subref should:
6078     #   do whatever assignemnt or thing it likes with $_[0]
6079     #   if the option should not be passed on to remote, @rvalopts=()
6080     # or $how can be a scalar ref, meaning simply assign the value
6081 }
6082
6083 defvalopt '--since-version', '-v', '[^_]+|_', \$changes_since_version;
6084 defvalopt '--distro',        '-d', '.+',      \$idistro;
6085 defvalopt '',                '-k', '.+',      \$keyid;
6086 defvalopt '--existing-package','', '.*',      \$existing_package;
6087 defvalopt '--build-products-dir','','.*',     \$buildproductsdir;
6088 defvalopt '--clean',       '', $cleanmode_re, \$cleanmode;
6089 defvalopt '--package',   '-p',   $package_re, \$package;
6090 defvalopt '--quilt',     '', $quilt_modes_re, \$quilt_mode;
6091
6092 defvalopt '', '-C', '.+', sub {
6093     ($changesfile) = (@_);
6094     if ($changesfile =~ s#^(.*)/##) {
6095         $buildproductsdir = $1;
6096     }
6097 };
6098
6099 defvalopt '--initiator-tempdir','','.*', sub {
6100     ($initiator_tempdir) = (@_);
6101     $initiator_tempdir =~ m#^/# or
6102         badusage "--initiator-tempdir must be used specify an".
6103         " absolute, not relative, directory."
6104 };
6105
6106 sub parseopts () {
6107     my $om;
6108
6109     if (defined $ENV{'DGIT_SSH'}) {
6110         @ssh = string_to_ssh $ENV{'DGIT_SSH'};
6111     } elsif (defined $ENV{'GIT_SSH'}) {
6112         @ssh = ($ENV{'GIT_SSH'});
6113     }
6114
6115     my $oi;
6116     my $val;
6117     my $valopt = sub {
6118         my ($what) = @_;
6119         @rvalopts = ($_);
6120         if (!defined $val) {
6121             badusage "$what needs a value" unless @ARGV;
6122             $val = shift @ARGV;
6123             push @rvalopts, $val;
6124         }
6125         badusage "bad value \`$val' for $what" unless
6126             $val =~ m/^$oi->{Re}$(?!\n)/s;
6127         my $how = $oi->{How};
6128         if (ref($how) eq 'SCALAR') {
6129             $$how = $val;
6130         } else {
6131             $how->($val);
6132         }
6133         push @ropts, @rvalopts;
6134     };
6135
6136     while (@ARGV) {
6137         last unless $ARGV[0] =~ m/^-/;
6138         $_ = shift @ARGV;
6139         last if m/^--?$/;
6140         if (m/^--/) {
6141             if (m/^--dry-run$/) {
6142                 push @ropts, $_;
6143                 $dryrun_level=2;
6144             } elsif (m/^--damp-run$/) {
6145                 push @ropts, $_;
6146                 $dryrun_level=1;
6147             } elsif (m/^--no-sign$/) {
6148                 push @ropts, $_;
6149                 $sign=0;
6150             } elsif (m/^--help$/) {
6151                 cmd_help();
6152             } elsif (m/^--version$/) {
6153                 cmd_version();
6154             } elsif (m/^--new$/) {
6155                 push @ropts, $_;
6156                 $new_package=1;
6157             } elsif (m/^--([-0-9a-z]+)=(.+)/s &&
6158                      ($om = $opts_opt_map{$1}) &&
6159                      length $om->[0]) {
6160                 push @ropts, $_;
6161                 $om->[0] = $2;
6162             } elsif (m/^--([-0-9a-z]+):(.*)/s &&
6163                      !$opts_opt_cmdonly{$1} &&
6164                      ($om = $opts_opt_map{$1})) {
6165                 push @ropts, $_;
6166                 push @$om, $2;
6167             } elsif (m/^--(gbp|dpm)$/s) {
6168                 push @ropts, "--quilt=$1";
6169                 $quilt_mode = $1;
6170             } elsif (m/^--ignore-dirty$/s) {
6171                 push @ropts, $_;
6172                 $ignoredirty = 1;
6173             } elsif (m/^--no-quilt-fixup$/s) {
6174                 push @ropts, $_;
6175                 $quilt_mode = 'nocheck';
6176             } elsif (m/^--no-rm-on-error$/s) {
6177                 push @ropts, $_;
6178                 $rmonerror = 0;
6179             } elsif (m/^--overwrite$/s) {
6180                 push @ropts, $_;
6181                 $overwrite_version = '';
6182             } elsif (m/^--overwrite=(.+)$/s) {
6183                 push @ropts, $_;
6184                 $overwrite_version = $1;
6185             } elsif (m/^--dep14tag$/s) {
6186                 push @ropts, $_;
6187                 $dodep14tag= 'want';
6188             } elsif (m/^--no-dep14tag$/s) {
6189                 push @ropts, $_;
6190                 $dodep14tag= 'no';
6191             } elsif (m/^--always-dep14tag$/s) {
6192                 push @ropts, $_;
6193                 $dodep14tag= 'always';
6194             } elsif (m/^--delayed=(\d+)$/s) {
6195                 push @ropts, $_;
6196                 push @dput, $_;
6197             } elsif (m/^--dgit-view-save=(.+)$/s) {
6198                 push @ropts, $_;
6199                 $split_brain_save = $1;
6200                 $split_brain_save =~ s#^(?!refs/)#refs/heads/#;
6201             } elsif (m/^--(no-)?rm-old-changes$/s) {
6202                 push @ropts, $_;
6203                 $rmchanges = !$1;
6204             } elsif (m/^--deliberately-($deliberately_re)$/s) {
6205                 push @ropts, $_;
6206                 push @deliberatelies, $&;
6207             } elsif (m/^--force-(.*)/ && defined $forceopts{$1}) {
6208                 push @ropts, $&;
6209                 $forceopts{$1} = 1;
6210                 $_='';
6211             } elsif (m/^--force-/) {
6212                 print STDERR
6213                     "$us: warning: ignoring unknown force option $_\n";
6214                 $_='';
6215             } elsif (m/^--dgit-tag-format=(old|new)$/s) {
6216                 # undocumented, for testing
6217                 push @ropts, $_;
6218                 $tagformat_want = [ $1, 'command line', 1 ];
6219                 # 1 menas overrides distro configuration
6220             } elsif (m/^--always-split-source-build$/s) {
6221                 # undocumented, for testing
6222                 push @ropts, $_;
6223                 $need_split_build_invocation = 1;
6224             } elsif (m/^(--[-0-9a-z]+)(=|$)/ && ($oi = $valopts_long{$1})) {
6225                 $val = $2 ? $' : undef; #';
6226                 $valopt->($oi->{Long});
6227             } else {
6228                 badusage "unknown long option \`$_'";
6229             }
6230         } else {
6231             while (m/^-./s) {
6232                 if (s/^-n/-/) {
6233                     push @ropts, $&;
6234                     $dryrun_level=2;
6235                 } elsif (s/^-L/-/) {
6236                     push @ropts, $&;
6237                     $dryrun_level=1;
6238                 } elsif (s/^-h/-/) {
6239                     cmd_help();
6240                 } elsif (s/^-D/-/) {
6241                     push @ropts, $&;
6242                     $debuglevel++;
6243                     enabledebug();
6244                 } elsif (s/^-N/-/) {
6245                     push @ropts, $&;
6246                     $new_package=1;
6247                 } elsif (m/^-m/) {
6248                     push @ropts, $&;
6249                     push @changesopts, $_;
6250                     $_ = '';
6251                 } elsif (s/^-wn$//s) {
6252                     push @ropts, $&;
6253                     $cleanmode = 'none';
6254                 } elsif (s/^-wg$//s) {
6255                     push @ropts, $&;
6256                     $cleanmode = 'git';
6257                 } elsif (s/^-wgf$//s) {
6258                     push @ropts, $&;
6259                     $cleanmode = 'git-ff';
6260                 } elsif (s/^-wd$//s) {
6261                     push @ropts, $&;
6262                     $cleanmode = 'dpkg-source';
6263                 } elsif (s/^-wdd$//s) {
6264                     push @ropts, $&;
6265                     $cleanmode = 'dpkg-source-d';
6266                 } elsif (s/^-wc$//s) {
6267                     push @ropts, $&;
6268                     $cleanmode = 'check';
6269                 } elsif (s/^-c([^=]*)\=(.*)$//s) {
6270                     push @git, '-c', $&;
6271                     $gitcfgs{cmdline}{$1} = [ $2 ];
6272                 } elsif (s/^-c([^=]+)$//s) {
6273                     push @git, '-c', $&;
6274                     $gitcfgs{cmdline}{$1} = [ 'true' ];
6275                 } elsif (m/^-[a-zA-Z]/ && ($oi = $valopts_short{$&})) {
6276                     $val = $'; #';
6277                     $val = undef unless length $val;
6278                     $valopt->($oi->{Short});
6279                     $_ = '';
6280                 } else {
6281                     badusage "unknown short option \`$_'";
6282                 }
6283             }
6284         }
6285     }
6286 }
6287
6288 sub check_env_sanity () {
6289     my $blocked = new POSIX::SigSet;
6290     sigprocmask SIG_UNBLOCK, $blocked, $blocked or die $!;
6291
6292     eval {
6293         foreach my $name (qw(PIPE CHLD)) {
6294             my $signame = "SIG$name";
6295             my $signum = eval "POSIX::$signame" // die;
6296             ($SIG{$name} // 'DEFAULT') eq 'DEFAULT' or
6297                 die "$signame is set to something other than SIG_DFL\n";
6298             $blocked->ismember($signum) and
6299                 die "$signame is blocked\n";
6300         }
6301     };
6302     return unless $@;
6303     chomp $@;
6304     fail <<END;
6305 On entry to dgit, $@
6306 This is a bug produced by something in in your execution environment.
6307 Giving up.
6308 END
6309 }
6310
6311
6312 sub parseopts_late_defaults () {
6313     foreach my $k (keys %opts_opt_map) {
6314         my $om = $opts_opt_map{$k};
6315
6316         my $v = access_cfg("cmd-$k", 'RETURN-UNDEF');
6317         if (defined $v) {
6318             badcfg "cannot set command for $k"
6319                 unless length $om->[0];
6320             $om->[0] = $v;
6321         }
6322
6323         foreach my $c (access_cfg_cfgs("opts-$k")) {
6324             my @vl =
6325                 map { $_ ? @$_ : () }
6326                 map { $gitcfgs{$_}{$c} }
6327                 reverse @gitcfgsources;
6328             printdebug "CL $c ", (join " ", map { shellquote } @vl),
6329                 "\n" if $debuglevel >= 4;
6330             next unless @vl;
6331             badcfg "cannot configure options for $k"
6332                 if $opts_opt_cmdonly{$k};
6333             my $insertpos = $opts_cfg_insertpos{$k};
6334             @$om = ( @$om[0..$insertpos-1],
6335                      @vl,
6336                      @$om[$insertpos..$#$om] );
6337         }
6338     }
6339
6340     if (!defined $rmchanges) {
6341         local $access_forpush;
6342         $rmchanges = access_cfg_bool(0, 'rm-old-changes');
6343     }
6344
6345     if (!defined $quilt_mode) {
6346         local $access_forpush;
6347         $quilt_mode = cfg('dgit.force.quilt-mode', 'RETURN-UNDEF')
6348             // access_cfg('quilt-mode', 'RETURN-UNDEF')
6349             // 'linear';
6350         $quilt_mode =~ m/^($quilt_modes_re)$/ 
6351             or badcfg "unknown quilt-mode \`$quilt_mode'";
6352         $quilt_mode = $1;
6353     }
6354
6355     if (!defined $dodep14tag) {
6356         local $access_forpush;
6357         $dodep14tag = access_cfg('dep14tag', 'RETURN-UNDEF') // 'want';
6358         $dodep14tag =~ m/^($dodep14tag_re)$/ 
6359             or badcfg "unknown dep14tag setting \`$dodep14tag'";
6360         $dodep14tag = $1;
6361     }
6362
6363     $need_split_build_invocation ||= quiltmode_splitbrain();
6364
6365     if (!defined $cleanmode) {
6366         local $access_forpush;
6367         $cleanmode = access_cfg('clean-mode', 'RETURN-UNDEF');
6368         $cleanmode //= 'dpkg-source';
6369
6370         badcfg "unknown clean-mode \`$cleanmode'" unless
6371             $cleanmode =~ m/^($cleanmode_re)$(?!\n)/s;
6372     }
6373 }
6374
6375 if ($ENV{$fakeeditorenv}) {
6376     git_slurp_config();
6377     quilt_fixup_editor();
6378 }
6379
6380 parseopts();
6381 check_env_sanity();
6382 git_slurp_config();
6383
6384 print STDERR "DRY RUN ONLY\n" if $dryrun_level > 1;
6385 print STDERR "DAMP RUN - WILL MAKE LOCAL (UNSIGNED) CHANGES\n"
6386     if $dryrun_level == 1;
6387 if (!@ARGV) {
6388     print STDERR $helpmsg or die $!;
6389     exit 8;
6390 }
6391 my $cmd = shift @ARGV;
6392 $cmd =~ y/-/_/;
6393
6394 my $pre_fn = ${*::}{"pre_$cmd"};
6395 $pre_fn->() if $pre_fn;
6396
6397 my $fn = ${*::}{"cmd_$cmd"};
6398 $fn or badusage "unknown operation $cmd";
6399 $fn->();