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