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