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