chiark / gitweb /
8d7af11212d430c9809bdbf9fbf9e987b54240ef
[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);
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;