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