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