chiark / gitweb /
Split brain: some work on integration into the rest of dgit
[dgit.git] / dgit
1 #!/usr/bin/perl -w
2 # dgit
3 # Integration between git and Debian-style archives
4 #
5 # Copyright (C)2013-2015 Ian Jackson
6 #
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
20 use strict;
21
22 use Debian::Dgit;
23 setup_sigwarn();
24
25 use IO::Handle;
26 use Data::Dumper;
27 use LWP::UserAgent;
28 use Dpkg::Control::Hash;
29 use File::Path;
30 use File::Temp qw(tempdir);
31 use File::Basename;
32 use Dpkg::Version;
33 use POSIX;
34 use IPC::Open2;
35 use Digest::SHA;
36 use Digest::MD5;
37
38 use Debian::Dgit;
39
40 our $our_version = 'UNRELEASED'; ###substituted###
41
42 our @rpushprotovsn_support = qw(3 2);
43 our $protovsn;
44
45 our $isuite = 'unstable';
46 our $idistro;
47 our $package;
48 our @ropts;
49
50 our $sign = 1;
51 our $dryrun_level = 0;
52 our $changesfile;
53 our $buildproductsdir = '..';
54 our $new_package = 0;
55 our $ignoredirty = 0;
56 our $rmonerror = 1;
57 our @deliberatelies;
58 our %previously;
59 our $existing_package = 'dpkg';
60 our $cleanmode;
61 our $changes_since_version;
62 our $rmchanges;
63 our $quilt_mode;
64 our $quilt_modes_re = 'linear|smash|auto|nofix|nocheck|gbp|unapplied';
65 our $we_are_responder;
66 our $initiator_tempdir;
67
68 our %format_ok = map { $_=>1 } ("1.0","3.0 (native)","3.0 (quilt)");
69
70 our $suite_re = '[-+.0-9a-z]+';
71 our $cleanmode_re = 'dpkg-source(?:-d)?|git|git-ff|check|none';
72
73 our $git_authline_re = '^([^<>]+) \<(\S+)\> (\d+ [-+]\d+)$';
74 our $splitbraincache = 'dgit-intern/quilt-cache';
75
76 our (@git) = qw(git);
77 our (@dget) = qw(dget);
78 our (@curl) = qw(curl -f);
79 our (@dput) = qw(dput);
80 our (@debsign) = qw(debsign);
81 our (@gpg) = qw(gpg);
82 our (@sbuild) = qw(sbuild);
83 our (@ssh) = 'ssh';
84 our (@dgit) = qw(dgit);
85 our (@dpkgbuildpackage) = qw(dpkg-buildpackage -i\.git/ -I.git);
86 our (@dpkgsource) = qw(dpkg-source -i\.git/ -I.git);
87 our (@dpkggenchanges) = qw(dpkg-genchanges);
88 our (@mergechanges) = qw(mergechanges -f);
89 our (@gbppq) = qw(gbp-pq);
90 our (@changesopts) = ('');
91
92 our %opts_opt_map = ('dget' => \@dget, # accept for compatibility
93                      'curl' => \@curl,
94                      'dput' => \@dput,
95                      'debsign' => \@debsign,
96                      'gpg' => \@gpg,
97                      'sbuild' => \@sbuild,
98                      'ssh' => \@ssh,
99                      'dgit' => \@dgit,
100                      'git' => \@git,
101                      'dpkg-source' => \@dpkgsource,
102                      'dpkg-buildpackage' => \@dpkgbuildpackage,
103                      'dpkg-genchanges' => \@dpkggenchanges,
104                      'ch' => \@changesopts,
105                      'mergechanges' => \@mergechanges);
106
107 our %opts_opt_cmdonly = ('gpg' => 1, 'git' => 1);
108 our %opts_cfg_insertpos = map {
109     $_,
110     scalar @{ $opts_opt_map{$_} }
111 } keys %opts_opt_map;
112
113 sub finalise_opts_opts();
114
115 our $keyid;
116
117 autoflush STDOUT 1;
118
119 our $supplementary_message = '';
120 our $need_split_build_invocation = 0;
121 our $split_brain = 0;
122
123 END {
124     local ($@, $?);
125     print STDERR "! $_\n" foreach $supplementary_message =~ m/^.+$/mg;
126 }
127
128 our $remotename = 'dgit';
129 our @ourdscfield = qw(Dgit Vcs-Dgit-Master);
130 our $csuite;
131 our $instead_distro;
132
133 sub lbranch () { return "$branchprefix/$csuite"; }
134 my $lbranch_re = '^refs/heads/'.$branchprefix.'/([^/.]+)$';
135 sub lref () { return "refs/heads/".lbranch(); }
136 sub lrref () { return "refs/remotes/$remotename/".server_branch($csuite); }
137 sub rrref () { return server_ref($csuite); }
138
139 sub lrfetchrefs () { return "refs/dgit-fetch/$csuite"; }
140
141 sub stripepoch ($) {
142     my ($vsn) = @_;
143     $vsn =~ s/^\d+\://;
144     return $vsn;
145 }
146
147 sub srcfn ($$) {
148     my ($vsn,$sfx) = @_;
149     return "${package}_".(stripepoch $vsn).$sfx
150 }
151
152 sub dscfn ($) {
153     my ($vsn) = @_;
154     return srcfn($vsn,".dsc");
155 }
156
157 sub changespat ($;$) {
158     my ($vsn, $arch) = @_;
159     return "${package}_".(stripepoch $vsn)."_".($arch//'*').".changes";
160 }
161
162 our $us = 'dgit';
163 initdebug('');
164
165 our @end;
166 END { 
167     local ($?);
168     foreach my $f (@end) {
169         eval { $f->(); };
170         print STDERR "$us: cleanup: $@" if length $@;
171     }
172 };
173
174 sub badcfg { print STDERR "$us: invalid configuration: @_\n"; exit 12; }
175
176 sub no_such_package () {
177     print STDERR "$us: package $package does not exist in suite $isuite\n";
178     exit 4;
179 }
180
181 sub fetchspec () {
182     local $csuite = '*';
183     return  "+".rrref().":".lrref();
184 }
185
186 sub changedir ($) {
187     my ($newdir) = @_;
188     printdebug "CD $newdir\n";
189     chdir $newdir or die "chdir: $newdir: $!";
190 }
191
192 sub deliberately ($) {
193     my ($enquiry) = @_;
194     return !!grep { $_ eq "--deliberately-$enquiry" } @deliberatelies;
195 }
196
197 sub deliberately_not_fast_forward () {
198     foreach (qw(not-fast-forward fresh-repo)) {
199         return 1 if deliberately($_) || deliberately("TEST-dgit-only-$_");
200     }
201 }
202
203 sub quiltmode_splitbrain () {
204     $quilt_mode =~ m/gbp|dpm|unapplied/;
205 }
206
207 #---------- remote protocol support, common ----------
208
209 # remote push initiator/responder protocol:
210 #  $ dgit remote-push-build-host <n-rargs> <rargs>... <push-args>...
211 #  where <rargs> is <push-host-dir> <supported-proto-vsn>,... ...
212 #  < dgit-remote-push-ready <actual-proto-vsn>
213 #
214 #  > file parsed-changelog
215 #  [indicates that output of dpkg-parsechangelog follows]
216 #  > data-block NBYTES
217 #  > [NBYTES bytes of data (no newline)]
218 #  [maybe some more blocks]
219 #  > data-end
220 #
221 #  > file dsc
222 #  [etc]
223 #
224 #  > file changes
225 #  [etc]
226 #
227 #  > param head HEAD
228 #
229 #  > want signed-tag
230 #  [indicates that signed tag is wanted]
231 #  < data-block NBYTES
232 #  < [NBYTES bytes of data (no newline)]
233 #  [maybe some more blocks]
234 #  < data-end
235 #  < files-end
236 #
237 #  > want signed-dsc-changes
238 #  < data-block NBYTES    [transfer of signed dsc]
239 #  [etc]
240 #  < data-block NBYTES    [transfer of signed changes]
241 #  [etc]
242 #  < files-end
243 #
244 #  > complete
245
246 our $i_child_pid;
247
248 sub i_child_report () {
249     # Sees if our child has died, and reap it if so.  Returns a string
250     # describing how it died if it failed, or undef otherwise.
251     return undef unless $i_child_pid;
252     my $got = waitpid $i_child_pid, WNOHANG;
253     return undef if $got <= 0;
254     die unless $got == $i_child_pid;
255     $i_child_pid = undef;
256     return undef unless $?;
257     return "build host child ".waitstatusmsg();
258 }
259
260 sub badproto ($$) {
261     my ($fh, $m) = @_;
262     fail "connection lost: $!" if $fh->error;
263     fail "protocol violation; $m not expected";
264 }
265
266 sub badproto_badread ($$) {
267     my ($fh, $wh) = @_;
268     fail "connection lost: $!" if $!;
269     my $report = i_child_report();
270     fail $report if defined $report;
271     badproto $fh, "eof (reading $wh)";
272 }
273
274 sub protocol_expect (&$) {
275     my ($match, $fh) = @_;
276     local $_;
277     $_ = <$fh>;
278     defined && chomp or badproto_badread $fh, "protocol message";
279     if (wantarray) {
280         my @r = &$match;
281         return @r if @r;
282     } else {
283         my $r = &$match;
284         return $r if $r;
285     }
286     badproto $fh, "\`$_'";
287 }
288
289 sub protocol_send_file ($$) {
290     my ($fh, $ourfn) = @_;
291     open PF, "<", $ourfn or die "$ourfn: $!";
292     for (;;) {
293         my $d;
294         my $got = read PF, $d, 65536;
295         die "$ourfn: $!" unless defined $got;
296         last if !$got;
297         print $fh "data-block ".length($d)."\n" or die $!;
298         print $fh $d or die $!;
299     }
300     PF->error and die "$ourfn $!";
301     print $fh "data-end\n" or die $!;
302     close PF;
303 }
304
305 sub protocol_read_bytes ($$) {
306     my ($fh, $nbytes) = @_;
307     $nbytes =~ m/^[1-9]\d{0,5}$|^0$/ or badproto \*RO, "bad byte count";
308     my $d;
309     my $got = read $fh, $d, $nbytes;
310     $got==$nbytes or badproto_badread $fh, "data block";
311     return $d;
312 }
313
314 sub protocol_receive_file ($$) {
315     my ($fh, $ourfn) = @_;
316     printdebug "() $ourfn\n";
317     open PF, ">", $ourfn or die "$ourfn: $!";
318     for (;;) {
319         my ($y,$l) = protocol_expect {
320             m/^data-block (.*)$/ ? (1,$1) :
321             m/^data-end$/ ? (0,) :
322             ();
323         } $fh;
324         last unless $y;
325         my $d = protocol_read_bytes $fh, $l;
326         print PF $d or die $!;
327     }
328     close PF or die $!;
329 }
330
331 #---------- remote protocol support, responder ----------
332
333 sub responder_send_command ($) {
334     my ($command) = @_;
335     return unless $we_are_responder;
336     # called even without $we_are_responder
337     printdebug ">> $command\n";
338     print PO $command, "\n" or die $!;
339 }    
340
341 sub responder_send_file ($$) {
342     my ($keyword, $ourfn) = @_;
343     return unless $we_are_responder;
344     printdebug "]] $keyword $ourfn\n";
345     responder_send_command "file $keyword";
346     protocol_send_file \*PO, $ourfn;
347 }
348
349 sub responder_receive_files ($@) {
350     my ($keyword, @ourfns) = @_;
351     die unless $we_are_responder;
352     printdebug "[[ $keyword @ourfns\n";
353     responder_send_command "want $keyword";
354     foreach my $fn (@ourfns) {
355         protocol_receive_file \*PI, $fn;
356     }
357     printdebug "[[\$\n";
358     protocol_expect { m/^files-end$/ } \*PI;
359 }
360
361 #---------- remote protocol support, initiator ----------
362
363 sub initiator_expect (&) {
364     my ($match) = @_;
365     protocol_expect { &$match } \*RO;
366 }
367
368 #---------- end remote code ----------
369
370 sub progress {
371     if ($we_are_responder) {
372         my $m = join '', @_;
373         responder_send_command "progress ".length($m) or die $!;
374         print PO $m or die $!;
375     } else {
376         print @_, "\n";
377     }
378 }
379
380 our $ua;
381
382 sub url_get {
383     if (!$ua) {
384         $ua = LWP::UserAgent->new();
385         $ua->env_proxy;
386     }
387     my $what = $_[$#_];
388     progress "downloading $what...";
389     my $r = $ua->get(@_) or die $!;
390     return undef if $r->code == 404;
391     $r->is_success or fail "failed to fetch $what: ".$r->status_line;
392     return $r->decoded_content(charset => 'none');
393 }
394
395 our ($dscdata,$dscurl,$dsc,$dsc_checked,$skew_warning_vsn);
396
397 sub runcmd {
398     debugcmd "+",@_;
399     $!=0; $?=0;
400     failedcmd @_ if system @_;
401 }
402
403 sub act_local () { return $dryrun_level <= 1; }
404 sub act_scary () { return !$dryrun_level; }
405
406 sub printdone {
407     if (!$dryrun_level) {
408         progress "dgit ok: @_";
409     } else {
410         progress "would be ok: @_ (but dry run only)";
411     }
412 }
413
414 sub dryrun_report {
415     printcmd(\*STDERR,$debugprefix."#",@_);
416 }
417
418 sub runcmd_ordryrun {
419     if (act_scary()) {
420         runcmd @_;
421     } else {
422         dryrun_report @_;
423     }
424 }
425
426 sub runcmd_ordryrun_local {
427     if (act_local()) {
428         runcmd @_;
429     } else {
430         dryrun_report @_;
431     }
432 }
433
434 sub shell_cmd {
435     my ($first_shell, @cmd) = @_;
436     return qw(sh -ec), $first_shell.'; exec "$@"', 'x', @cmd;
437 }
438
439 our $helpmsg = <<END;
440 main usages:
441   dgit [dgit-opts] clone [dgit-opts] package [suite] [./dir|/dir]
442   dgit [dgit-opts] fetch|pull [dgit-opts] [suite]
443   dgit [dgit-opts] build [dpkg-buildpackage-opts]
444   dgit [dgit-opts] sbuild [sbuild-opts]
445   dgit [dgit-opts] push [dgit-opts] [suite]
446   dgit [dgit-opts] rpush build-host:build-dir ...
447 important dgit options:
448   -k<keyid>           sign tag and package with <keyid> instead of default
449   --dry-run -n        do not change anything, but go through the motions
450   --damp-run -L       like --dry-run but make local changes, without signing
451   --new -N            allow introducing a new package
452   --debug -D          increase debug level
453   -c<name>=<value>    set git config option (used directly by dgit too)
454 END
455
456 our $later_warning_msg = <<END;
457 Perhaps the upload is stuck in incoming.  Using the version from git.
458 END
459
460 sub badusage {
461     print STDERR "$us: @_\n", $helpmsg or die $!;
462     exit 8;
463 }
464
465 sub nextarg {
466     @ARGV or badusage "too few arguments";
467     return scalar shift @ARGV;
468 }
469
470 sub cmd_help () {
471     print $helpmsg or die $!;
472     exit 0;
473 }
474
475 our $td = $ENV{DGIT_TEST_DUMMY_DIR} || "DGIT_TEST_DUMMY_DIR-unset";
476
477 our %defcfg = ('dgit.default.distro' => 'debian',
478                'dgit.default.username' => '',
479                'dgit.default.archive-query-default-component' => 'main',
480                'dgit.default.ssh' => 'ssh',
481                'dgit.default.archive-query' => 'madison:',
482                'dgit.default.sshpsql-dbname' => 'service=projectb',
483                'dgit-distro.debian.archive-query' => 'ftpmasterapi:',
484                'dgit-distro.debian.git-check' => 'url',
485                'dgit-distro.debian.git-check-suffix' => '/info/refs',
486                'dgit-distro.debian.new-private-pushers' => 't',
487                'dgit-distro.debian/push.git-url' => '',
488                'dgit-distro.debian/push.git-host' => 'push.dgit.debian.org',
489                'dgit-distro.debian/push.git-user-force' => 'dgit',
490                'dgit-distro.debian/push.git-proto' => 'git+ssh://',
491                'dgit-distro.debian/push.git-path' => '/dgit/debian/repos',
492                'dgit-distro.debian/push.git-create' => 'true',
493                'dgit-distro.debian/push.git-check' => 'ssh-cmd',
494  'dgit-distro.debian.archive-query-url', 'https://api.ftp-master.debian.org/',
495 # 'dgit-distro.debian.archive-query-tls-key',
496 #    '/etc/ssl/certs/%HOST%.pem:/etc/dgit/%HOST%.pem',
497 # ^ this does not work because curl is broken nowadays
498 # Fixing #790093 properly will involve providing providing the key
499 # in some pacagke and maybe updating these paths.
500 #
501 # 'dgit-distro.debian.archive-query-tls-curl-args',
502 #   '--ca-path=/etc/ssl/ca-debian',
503 # ^ this is a workaround but works (only) on DSA-administered machines
504                'dgit-distro.debian.git-url' => 'https://git.dgit.debian.org',
505                'dgit-distro.debian.git-url-suffix' => '',
506                'dgit-distro.debian.upload-host' => 'ftp-master', # for dput
507                'dgit-distro.debian.mirror' => 'http://ftp.debian.org/debian/',
508  'dgit-distro.debian.backports-quirk' => '(squeeze)-backports*',
509  'dgit-distro.debian-backports.mirror' => 'http://backports.debian.org/debian-backports/',
510                'dgit-distro.ubuntu.git-check' => 'false',
511  'dgit-distro.ubuntu.mirror' => 'http://archive.ubuntu.com/ubuntu',
512                'dgit-distro.test-dummy.ssh' => "$td/ssh",
513                'dgit-distro.test-dummy.username' => "alice",
514                'dgit-distro.test-dummy.git-check' => "ssh-cmd",
515                'dgit-distro.test-dummy.git-create' => "ssh-cmd",
516                'dgit-distro.test-dummy.git-url' => "$td/git",
517                'dgit-distro.test-dummy.git-host' => "git",
518                'dgit-distro.test-dummy.git-path' => "$td/git",
519                'dgit-distro.test-dummy.archive-query' => "ftpmasterapi:",
520                'dgit-distro.test-dummy.archive-query-url' => "file://$td/aq/",
521                'dgit-distro.test-dummy.mirror' => "file://$td/mirror/",
522                'dgit-distro.test-dummy.upload-host' => 'test-dummy',
523                );
524
525 our %gitcfg;
526
527 sub git_slurp_config () {
528     local ($debuglevel) = $debuglevel-2;
529     local $/="\0";
530
531     my @cmd = (@git, qw(config -z --get-regexp .*));
532     debugcmd "|",@cmd;
533
534     open GITS, "-|", @cmd or failedcmd @cmd;
535     while (<GITS>) {
536         chomp or die;
537         printdebug "=> ", (messagequote $_), "\n";
538         m/\n/ or die "$_ ?";
539         push @{ $gitcfg{$`} }, $'; #';
540     }
541     $!=0; $?=0;
542     close GITS
543         or ($!==0 && $?==256)
544         or failedcmd @cmd;
545 }
546
547 sub git_get_config ($) {
548     my ($c) = @_;
549     my $l = $gitcfg{$c};
550     printdebug"C $c ".(defined $l ? messagequote "'$l'" : "undef")."\n"
551         if $debuglevel >= 4;
552     $l or return undef;
553     @$l==1 or badcfg "multiple values for $c" if @$l > 1;
554     return $l->[0];
555 }
556
557 sub cfg {
558     foreach my $c (@_) {
559         return undef if $c =~ /RETURN-UNDEF/;
560         my $v = git_get_config($c);
561         return $v if defined $v;
562         my $dv = $defcfg{$c};
563         return $dv if defined $dv;
564     }
565     badcfg "need value for one of: @_\n".
566         "$us: distro or suite appears not to be (properly) supported";
567 }
568
569 sub access_basedistro () {
570     if (defined $idistro) {
571         return $idistro;
572     } else {    
573         return cfg("dgit-suite.$isuite.distro",
574                    "dgit.default.distro");
575     }
576 }
577
578 sub access_quirk () {
579     # returns (quirk name, distro to use instead or undef, quirk-specific info)
580     my $basedistro = access_basedistro();
581     my $backports_quirk = cfg("dgit-distro.$basedistro.backports-quirk",
582                               'RETURN-UNDEF');
583     if (defined $backports_quirk) {
584         my $re = $backports_quirk;
585         $re =~ s/[^-0-9a-z_\%*()]/\\$&/ig;
586         $re =~ s/\*/.*/g;
587         $re =~ s/\%/([-0-9a-z_]+)/
588             or $re =~ m/[()]/ or badcfg "backports-quirk needs \% or ( )";
589         if ($isuite =~ m/^$re$/) {
590             return ('backports',"$basedistro-backports",$1);
591         }
592     }
593     return ('none',undef);
594 }
595
596 our $access_forpush;
597
598 sub parse_cfg_bool ($$$) {
599     my ($what,$def,$v) = @_;
600     $v //= $def;
601     return
602         $v =~ m/^[ty1]/ ? 1 :
603         $v =~ m/^[fn0]/ ? 0 :
604         badcfg "$what needs t (true, y, 1) or f (false, n, 0) not \`$v'";
605 }       
606
607 sub access_forpush_config () {
608     my $d = access_basedistro();
609
610     return 1 if
611         $new_package &&
612         parse_cfg_bool('new-private-pushers', 0,
613                        cfg("dgit-distro.$d.new-private-pushers",
614                            'RETURN-UNDEF'));
615
616     my $v = cfg("dgit-distro.$d.readonly", 'RETURN-UNDEF');
617     $v //= 'a';
618     return
619         $v =~ m/^[ty1]/ ? 0 : # force readonly,    forpush = 0
620         $v =~ m/^[fn0]/ ? 1 : # force nonreadonly, forpush = 1
621         $v =~ m/^[a]/  ? '' : # auto,              forpush = ''
622         badcfg "readonly needs t (true, y, 1) or f (false, n, 0) or a (auto)";
623 }
624
625 sub access_forpush () {
626     $access_forpush //= access_forpush_config();
627     return $access_forpush;
628 }
629
630 sub pushing () {
631     die "$access_forpush ?" if ($access_forpush // 1) ne 1;
632     badcfg "pushing but distro is configured readonly"
633         if access_forpush_config() eq '0';
634     $access_forpush = 1;
635     $supplementary_message = <<'END' unless $we_are_responder;
636 Push failed, before we got started.
637 You can retry the push, after fixing the problem, if you like.
638 END
639     finalise_opts_opts();
640 }
641
642 sub notpushing () {
643     finalise_opts_opts();
644 }
645
646 sub supplementary_message ($) {
647     my ($msg) = @_;
648     if (!$we_are_responder) {
649         $supplementary_message = $msg;
650         return;
651     } elsif ($protovsn >= 3) {
652         responder_send_command "supplementary-message ".length($msg)
653             or die $!;
654         print PO $msg or die $!;
655     }
656 }
657
658 sub access_distros () {
659     # Returns list of distros to try, in order
660     #
661     # We want to try:
662     #    0. `instead of' distro name(s) we have been pointed to
663     #    1. the access_quirk distro, if any
664     #    2a. the user's specified distro, or failing that  } basedistro
665     #    2b. the distro calculated from the suite          }
666     my @l = access_basedistro();
667
668     my (undef,$quirkdistro) = access_quirk();
669     unshift @l, $quirkdistro;
670     unshift @l, $instead_distro;
671     @l = grep { defined } @l;
672
673     if (access_forpush()) {
674         @l = map { ("$_/push", $_) } @l;
675     }
676     @l;
677 }
678
679 sub access_cfg_cfgs (@) {
680     my (@keys) = @_;
681     my @cfgs;
682     # The nesting of these loops determines the search order.  We put
683     # the key loop on the outside so that we search all the distros
684     # for each key, before going on to the next key.  That means that
685     # if access_cfg is called with a more specific, and then a less
686     # specific, key, an earlier distro can override the less specific
687     # without necessarily overriding any more specific keys.  (If the
688     # distro wants to override the more specific keys it can simply do
689     # so; whereas if we did the loop the other way around, it would be
690     # impossible to for an earlier distro to override a less specific
691     # key but not the more specific ones without restating the unknown
692     # values of the more specific keys.
693     my @realkeys;
694     my @rundef;
695     # We have to deal with RETURN-UNDEF specially, so that we don't
696     # terminate the search prematurely.
697     foreach (@keys) {
698         if (m/RETURN-UNDEF/) { push @rundef, $_; last; }
699         push @realkeys, $_
700     }
701     foreach my $d (access_distros()) {
702         push @cfgs, map { "dgit-distro.$d.$_" } @realkeys;
703     }
704     push @cfgs, map { "dgit.default.$_" } @realkeys;
705     push @cfgs, @rundef;
706     return @cfgs;
707 }
708
709 sub access_cfg (@) {
710     my (@keys) = @_;
711     my (@cfgs) = access_cfg_cfgs(@keys);
712     my $value = cfg(@cfgs);
713     return $value;
714 }
715
716 sub access_cfg_bool ($$) {
717     my ($def, @keys) = @_;
718     parse_cfg_bool($keys[0], $def, access_cfg(@keys, 'RETURN-UNDEF'));
719 }
720
721 sub string_to_ssh ($) {
722     my ($spec) = @_;
723     if ($spec =~ m/\s/) {
724         return qw(sh -ec), 'exec '.$spec.' "$@"', 'x';
725     } else {
726         return ($spec);
727     }
728 }
729
730 sub access_cfg_ssh () {
731     my $gitssh = access_cfg('ssh', 'RETURN-UNDEF');
732     if (!defined $gitssh) {
733         return @ssh;
734     } else {
735         return string_to_ssh $gitssh;
736     }
737 }
738
739 sub access_runeinfo ($) {
740     my ($info) = @_;
741     return ": dgit ".access_basedistro()." $info ;";
742 }
743
744 sub access_someuserhost ($) {
745     my ($some) = @_;
746     my $user = access_cfg("$some-user-force", 'RETURN-UNDEF');
747     defined($user) && length($user) or
748         $user = access_cfg("$some-user",'username');
749     my $host = access_cfg("$some-host");
750     return length($user) ? "$user\@$host" : $host;
751 }
752
753 sub access_gituserhost () {
754     return access_someuserhost('git');
755 }
756
757 sub access_giturl (;$) {
758     my ($optional) = @_;
759     my $url = access_cfg('git-url','RETURN-UNDEF');
760     my $suffix;
761     if (!length $url) {
762         my $proto = access_cfg('git-proto', 'RETURN-UNDEF');
763         return undef unless defined $proto;
764         $url =
765             $proto.
766             access_gituserhost().
767             access_cfg('git-path');
768     } else {
769         $suffix = access_cfg('git-url-suffix','RETURN-UNDEF');
770     }
771     $suffix //= '.git';
772     return "$url/$package$suffix";
773 }              
774
775 sub parsecontrolfh ($$;$) {
776     my ($fh, $desc, $allowsigned) = @_;
777     our $dpkgcontrolhash_noissigned;
778     my $c;
779     for (;;) {
780         my %opts = ('name' => $desc);
781         $opts{allow_pgp}= $allowsigned || !$dpkgcontrolhash_noissigned;
782         $c = Dpkg::Control::Hash->new(%opts);
783         $c->parse($fh,$desc) or die "parsing of $desc failed";
784         last if $allowsigned;
785         last if $dpkgcontrolhash_noissigned;
786         my $issigned= $c->get_option('is_pgp_signed');
787         if (!defined $issigned) {
788             $dpkgcontrolhash_noissigned= 1;
789             seek $fh, 0,0 or die "seek $desc: $!";
790         } elsif ($issigned) {
791             fail "control file $desc is (already) PGP-signed. ".
792                 " Note that dgit push needs to modify the .dsc and then".
793                 " do the signature itself";
794         } else {
795             last;
796         }
797     }
798     return $c;
799 }
800
801 sub parsecontrol {
802     my ($file, $desc) = @_;
803     my $fh = new IO::Handle;
804     open $fh, '<', $file or die "$file: $!";
805     my $c = parsecontrolfh($fh,$desc);
806     $fh->error and die $!;
807     close $fh;
808     return $c;
809 }
810
811 sub getfield ($$) {
812     my ($dctrl,$field) = @_;
813     my $v = $dctrl->{$field};
814     return $v if defined $v;
815     fail "missing field $field in ".$v->get_option('name');
816 }
817
818 sub parsechangelog {
819     my $c = Dpkg::Control::Hash->new();
820     my $p = new IO::Handle;
821     my @cmd = (qw(dpkg-parsechangelog), @_);
822     open $p, '-|', @cmd or die $!;
823     $c->parse($p);
824     $?=0; $!=0; close $p or failedcmd @cmd;
825     return $c;
826 }
827
828 sub must_getcwd () {
829     my $d = getcwd();
830     defined $d or fail "getcwd failed: $!";
831     return $d;
832 }
833
834 our %rmad;
835
836 sub archive_query ($) {
837     my ($method) = @_;
838     my $query = access_cfg('archive-query','RETURN-UNDEF');
839     $query =~ s/^(\w+):// or badcfg "invalid archive-query method \`$query'";
840     my $proto = $1;
841     my $data = $'; #';
842     { no strict qw(refs); &{"${method}_${proto}"}($proto,$data); }
843 }
844
845 sub pool_dsc_subpath ($$) {
846     my ($vsn,$component) = @_; # $package is implict arg
847     my $prefix = substr($package, 0, $package =~ m/^l/ ? 4 : 1);
848     return "/pool/$component/$prefix/$package/".dscfn($vsn);
849 }
850
851 #---------- `ftpmasterapi' archive query method (nascent) ----------
852
853 sub archive_api_query_cmd ($) {
854     my ($subpath) = @_;
855     my @cmd = qw(curl -sS);
856     my $url = access_cfg('archive-query-url');
857     if ($url =~ m#^https://([-.0-9a-z]+)/#) {
858         my $host = $1;
859         my $keys = access_cfg('archive-query-tls-key','RETURN-UNDEF') //'';
860         foreach my $key (split /\:/, $keys) {
861             $key =~ s/\%HOST\%/$host/g;
862             if (!stat $key) {
863                 fail "for $url: stat $key: $!" unless $!==ENOENT;
864                 next;
865             }
866             fail "config requested specific TLS key but do not know".
867                 " how to get curl to use exactly that EE key ($key)";
868 #           push @cmd, "--cacert", $key, "--capath", "/dev/enoent";
869 #           # Sadly the above line does not work because of changes
870 #           # to gnutls.   The real fix for #790093 may involve
871 #           # new curl options.
872             last;
873         }
874         # Fixing #790093 properly will involve providing a value
875         # for this on clients.
876         my $kargs = access_cfg('archive-query-tls-curl-ca-args','RETURN-UNDEF');
877         push @cmd, split / /, $kargs if defined $kargs;
878     }
879     push @cmd, $url.$subpath;
880     return @cmd;
881 }
882
883 sub api_query ($$) {
884     use JSON;
885     my ($data, $subpath) = @_;
886     badcfg "ftpmasterapi archive query method takes no data part"
887         if length $data;
888     my @cmd = archive_api_query_cmd($subpath);
889     my $json = cmdoutput @cmd;
890     return decode_json($json);
891 }
892
893 sub canonicalise_suite_ftpmasterapi () {
894     my ($proto,$data) = @_;
895     my $suites = api_query($data, 'suites');
896     my @matched;
897     foreach my $entry (@$suites) {
898         next unless grep { 
899             my $v = $entry->{$_};
900             defined $v && $v eq $isuite;
901         } qw(codename name);
902         push @matched, $entry;
903     }
904     fail "unknown suite $isuite" unless @matched;
905     my $cn;
906     eval {
907         @matched==1 or die "multiple matches for suite $isuite\n";
908         $cn = "$matched[0]{codename}";
909         defined $cn or die "suite $isuite info has no codename\n";
910         $cn =~ m/^$suite_re$/ or die "suite $isuite maps to bad codename\n";
911     };
912     die "bad ftpmaster api response: $@\n".Dumper(\@matched)
913         if length $@;
914     return $cn;
915 }
916
917 sub archive_query_ftpmasterapi () {
918     my ($proto,$data) = @_;
919     my $info = api_query($data, "dsc_in_suite/$isuite/$package");
920     my @rows;
921     my $digester = Digest::SHA->new(256);
922     foreach my $entry (@$info) {
923         eval {
924             my $vsn = "$entry->{version}";
925             my ($ok,$msg) = version_check $vsn;
926             die "bad version: $msg\n" unless $ok;
927             my $component = "$entry->{component}";
928             $component =~ m/^$component_re$/ or die "bad component";
929             my $filename = "$entry->{filename}";
930             $filename && $filename !~ m#[^-+:._~0-9a-zA-Z/]|^[/.]|/[/.]#
931                 or die "bad filename";
932             my $sha256sum = "$entry->{sha256sum}";
933             $sha256sum =~ m/^[0-9a-f]+$/ or die "bad sha256sum";
934             push @rows, [ $vsn, "/pool/$component/$filename",
935                           $digester, $sha256sum ];
936         };
937         die "bad ftpmaster api response: $@\n".Dumper($entry)
938             if length $@;
939     }
940     @rows = sort { -version_compare($a->[0],$b->[0]) } @rows;
941     return @rows;
942 }
943
944 #---------- `madison' archive query method ----------
945
946 sub archive_query_madison {
947     return map { [ @$_[0..1] ] } madison_get_parse(@_);
948 }
949
950 sub madison_get_parse {
951     my ($proto,$data) = @_;
952     die unless $proto eq 'madison';
953     if (!length $data) {
954         $data= access_cfg('madison-distro','RETURN-UNDEF');
955         $data //= access_basedistro();
956     }
957     $rmad{$proto,$data,$package} ||= cmdoutput
958         qw(rmadison -asource),"-s$isuite","-u$data",$package;
959     my $rmad = $rmad{$proto,$data,$package};
960
961     my @out;
962     foreach my $l (split /\n/, $rmad) {
963         $l =~ m{^ \s*( [^ \t|]+ )\s* \|
964                   \s*( [^ \t|]+ )\s* \|
965                   \s*( [^ \t|/]+ )(?:/([^ \t|/]+))? \s* \|
966                   \s*( [^ \t|]+ )\s* }x or die "$rmad ?";
967         $1 eq $package or die "$rmad $package ?";
968         my $vsn = $2;
969         my $newsuite = $3;
970         my $component;
971         if (defined $4) {
972             $component = $4;
973         } else {
974             $component = access_cfg('archive-query-default-component');
975         }
976         $5 eq 'source' or die "$rmad ?";
977         push @out, [$vsn,pool_dsc_subpath($vsn,$component),$newsuite];
978     }
979     return sort { -version_compare($a->[0],$b->[0]); } @out;
980 }
981
982 sub canonicalise_suite_madison {
983     # madison canonicalises for us
984     my @r = madison_get_parse(@_);
985     @r or fail
986         "unable to canonicalise suite using package $package".
987         " which does not appear to exist in suite $isuite;".
988         " --existing-package may help";
989     return $r[0][2];
990 }
991
992 #---------- `sshpsql' archive query method ----------
993
994 sub sshpsql ($$$) {
995     my ($data,$runeinfo,$sql) = @_;
996     if (!length $data) {
997         $data= access_someuserhost('sshpsql').':'.
998             access_cfg('sshpsql-dbname');
999     }
1000     $data =~ m/:/ or badcfg "invalid sshpsql method string \`$data'";
1001     my ($userhost,$dbname) = ($`,$'); #';
1002     my @rows;
1003     my @cmd = (access_cfg_ssh, $userhost,
1004                access_runeinfo("ssh-psql $runeinfo").
1005                " export LC_MESSAGES=C; export LC_CTYPE=C;".
1006                " ".shellquote qw(psql -A), $dbname, qw(-c), $sql);
1007     debugcmd "|",@cmd;
1008     open P, "-|", @cmd or die $!;
1009     while (<P>) {
1010         chomp or die;
1011         printdebug(">|$_|\n");
1012         push @rows, $_;
1013     }
1014     $!=0; $?=0; close P or failedcmd @cmd;
1015     @rows or die;
1016     my $nrows = pop @rows;
1017     $nrows =~ s/^\((\d+) rows?\)$/$1/ or die "$nrows ?";
1018     @rows == $nrows+1 or die "$nrows ".(scalar @rows)." ?";
1019     @rows = map { [ split /\|/, $_ ] } @rows;
1020     my $ncols = scalar @{ shift @rows };
1021     die if grep { scalar @$_ != $ncols } @rows;
1022     return @rows;
1023 }
1024
1025 sub sql_injection_check {
1026     foreach (@_) { die "$_ $& ?" if m{[^-+=:_.,/0-9a-zA-Z]}; }
1027 }
1028
1029 sub archive_query_sshpsql ($$) {
1030     my ($proto,$data) = @_;
1031     sql_injection_check $isuite, $package;
1032     my @rows = sshpsql($data, "archive-query $isuite $package", <<END);
1033         SELECT source.version, component.name, files.filename, files.sha256sum
1034           FROM source
1035           JOIN src_associations ON source.id = src_associations.source
1036           JOIN suite ON suite.id = src_associations.suite
1037           JOIN dsc_files ON dsc_files.source = source.id
1038           JOIN files_archive_map ON files_archive_map.file_id = dsc_files.file
1039           JOIN component ON component.id = files_archive_map.component_id
1040           JOIN files ON files.id = dsc_files.file
1041          WHERE ( suite.suite_name='$isuite' OR suite.codename='$isuite' )
1042            AND source.source='$package'
1043            AND files.filename LIKE '%.dsc';
1044 END
1045     @rows = sort { -version_compare($a->[0],$b->[0]) } @rows;
1046     my $digester = Digest::SHA->new(256);
1047     @rows = map {
1048         my ($vsn,$component,$filename,$sha256sum) = @$_;
1049         [ $vsn, "/pool/$component/$filename",$digester,$sha256sum ];
1050     } @rows;
1051     return @rows;
1052 }
1053
1054 sub canonicalise_suite_sshpsql ($$) {
1055     my ($proto,$data) = @_;
1056     sql_injection_check $isuite;
1057     my @rows = sshpsql($data, "canonicalise-suite $isuite", <<END);
1058         SELECT suite.codename
1059           FROM suite where suite_name='$isuite' or codename='$isuite';
1060 END
1061     @rows = map { $_->[0] } @rows;
1062     fail "unknown suite $isuite" unless @rows;
1063     die "ambiguous $isuite: @rows ?" if @rows>1;
1064     return $rows[0];
1065 }
1066
1067 #---------- `dummycat' archive query method ----------
1068
1069 sub canonicalise_suite_dummycat ($$) {
1070     my ($proto,$data) = @_;
1071     my $dpath = "$data/suite.$isuite";
1072     if (!open C, "<", $dpath) {
1073         $!==ENOENT or die "$dpath: $!";
1074         printdebug "dummycat canonicalise_suite $isuite $dpath ENOENT\n";
1075         return $isuite;
1076     }
1077     $!=0; $_ = <C>;
1078     chomp or die "$dpath: $!";
1079     close C;
1080     printdebug "dummycat canonicalise_suite $isuite $dpath = $_\n";
1081     return $_;
1082 }
1083
1084 sub archive_query_dummycat ($$) {
1085     my ($proto,$data) = @_;
1086     canonicalise_suite();
1087     my $dpath = "$data/package.$csuite.$package";
1088     if (!open C, "<", $dpath) {
1089         $!==ENOENT or die "$dpath: $!";
1090         printdebug "dummycat query $csuite $package $dpath ENOENT\n";
1091         return ();
1092     }
1093     my @rows;
1094     while (<C>) {
1095         next if m/^\#/;
1096         next unless m/\S/;
1097         die unless chomp;
1098         printdebug "dummycat query $csuite $package $dpath | $_\n";
1099         my @row = split /\s+/, $_;
1100         @row==2 or die "$dpath: $_ ?";
1101         push @rows, \@row;
1102     }
1103     C->error and die "$dpath: $!";
1104     close C;
1105     return sort { -version_compare($a->[0],$b->[0]); } @rows;
1106 }
1107
1108 #---------- archive query entrypoints and rest of program ----------
1109
1110 sub canonicalise_suite () {
1111     return if defined $csuite;
1112     fail "cannot operate on $isuite suite" if $isuite eq 'UNRELEASED';
1113     $csuite = archive_query('canonicalise_suite');
1114     if ($isuite ne $csuite) {
1115         progress "canonical suite name for $isuite is $csuite";
1116     }
1117 }
1118
1119 sub get_archive_dsc () {
1120     canonicalise_suite();
1121     my @vsns = archive_query('archive_query');
1122     foreach my $vinfo (@vsns) {
1123         my ($vsn,$subpath,$digester,$digest) = @$vinfo;
1124         $dscurl = access_cfg('mirror').$subpath;
1125         $dscdata = url_get($dscurl);
1126         if (!$dscdata) {
1127             $skew_warning_vsn = $vsn if !defined $skew_warning_vsn;
1128             next;
1129         }
1130         if ($digester) {
1131             $digester->reset();
1132             $digester->add($dscdata);
1133             my $got = $digester->hexdigest();
1134             $got eq $digest or
1135                 fail "$dscurl has hash $got but".
1136                     " archive told us to expect $digest";
1137         }
1138         my $dscfh = new IO::File \$dscdata, '<' or die $!;
1139         printdebug Dumper($dscdata) if $debuglevel>1;
1140         $dsc = parsecontrolfh($dscfh,$dscurl,1);
1141         printdebug Dumper($dsc) if $debuglevel>1;
1142         my $fmt = getfield $dsc, 'Format';
1143         fail "unsupported source format $fmt, sorry" unless $format_ok{$fmt};
1144         $dsc_checked = !!$digester;
1145         return;
1146     }
1147     $dsc = undef;
1148 }
1149
1150 sub check_for_git ();
1151 sub check_for_git () {
1152     # returns 0 or 1
1153     my $how = access_cfg('git-check');
1154     if ($how eq 'ssh-cmd') {
1155         my @cmd =
1156             (access_cfg_ssh, access_gituserhost(),
1157              access_runeinfo("git-check $package").
1158              " set -e; cd ".access_cfg('git-path').";".
1159              " if test -d $package.git; then echo 1; else echo 0; fi");
1160         my $r= cmdoutput @cmd;
1161         if ($r =~ m/^divert (\w+)$/) {
1162             my $divert=$1;
1163             my ($usedistro,) = access_distros();
1164             # NB that if we are pushing, $usedistro will be $distro/push
1165             $instead_distro= cfg("dgit-distro.$usedistro.diverts.$divert");
1166             $instead_distro =~ s{^/}{ access_basedistro()."/" }e;
1167             progress "diverting to $divert (using config for $instead_distro)";
1168             return check_for_git();
1169         }
1170         failedcmd @cmd unless $r =~ m/^[01]$/;
1171         return $r+0;
1172     } elsif ($how eq 'url') {
1173         my $prefix = access_cfg('git-check-url','git-url');
1174         my $suffix = access_cfg('git-check-suffix','git-suffix',
1175                                 'RETURN-UNDEF') // '.git';
1176         my $url = "$prefix/$package$suffix";
1177         my @cmd = (qw(curl -sS -I), $url);
1178         my $result = cmdoutput @cmd;
1179         $result =~ s/^\S+ 200 .*\n\r?\n//;
1180         # curl -sS -I with https_proxy prints
1181         # HTTP/1.0 200 Connection established
1182         $result =~ m/^\S+ (404|200) /s or
1183             fail "unexpected results from git check query - ".
1184                 Dumper($prefix, $result);
1185         my $code = $1;
1186         if ($code eq '404') {
1187             return 0;
1188         } elsif ($code eq '200') {
1189             return 1;
1190         } else {
1191             die;
1192         }
1193     } elsif ($how eq 'true') {
1194         return 1;
1195     } elsif ($how eq 'false') {
1196         return 0;
1197     } else {
1198         badcfg "unknown git-check \`$how'";
1199     }
1200 }
1201
1202 sub create_remote_git_repo () {
1203     my $how = access_cfg('git-create');
1204     if ($how eq 'ssh-cmd') {
1205         runcmd_ordryrun
1206             (access_cfg_ssh, access_gituserhost(),
1207              access_runeinfo("git-create $package").
1208              "set -e; cd ".access_cfg('git-path').";".
1209              " cp -a _template $package.git");
1210     } elsif ($how eq 'true') {
1211         # nothing to do
1212     } else {
1213         badcfg "unknown git-create \`$how'";
1214     }
1215 }
1216
1217 our ($dsc_hash,$lastpush_hash);
1218
1219 our $ud = '.git/dgit/unpack';
1220
1221 sub prep_ud (;$) {
1222     my ($d) = @_;
1223     $d //= $ud;
1224     rmtree($d);
1225     mkpath '.git/dgit';
1226     mkdir $d or die $!;
1227 }
1228
1229 sub mktree_in_ud_here () {
1230     runcmd qw(git init -q);
1231     rmtree('.git/objects');
1232     symlink '../../../../objects','.git/objects' or die $!;
1233 }
1234
1235 sub git_write_tree () {
1236     my $tree = cmdoutput @git, qw(write-tree);
1237     $tree =~ m/^\w+$/ or die "$tree ?";
1238     return $tree;
1239 }
1240
1241 sub remove_stray_gits () {
1242     my @gitscmd = qw(find -name .git -prune -print0);
1243     debugcmd "|",@gitscmd;
1244     open GITS, "-|", @gitscmd or failedcmd @gitscmd;
1245     {
1246         local $/="\0";
1247         while (<GITS>) {
1248             chomp or die;
1249             print STDERR "$us: warning: removing from source package: ",
1250                 (messagequote $_), "\n";
1251             rmtree $_;
1252         }
1253     }
1254     $!=0; $?=0; close GITS or failedcmd @gitscmd;
1255 }
1256
1257 sub mktree_in_ud_from_only_subdir () {
1258     # changes into the subdir
1259     my (@dirs) = <*/.>;
1260     die unless @dirs==1;
1261     $dirs[0] =~ m#^([^/]+)/\.$# or die;
1262     my $dir = $1;
1263     changedir $dir;
1264
1265     remove_stray_gits();
1266     mktree_in_ud_here();
1267     my ($format, $fopts) = get_source_format();
1268     if (madformat($format)) {
1269         rmtree '.pc';
1270     }
1271     runcmd @git, qw(add -Af);
1272     my $tree=git_write_tree();
1273     return ($tree,$dir);
1274 }
1275
1276 sub dsc_files_info () {
1277     foreach my $csumi (['Checksums-Sha256','Digest::SHA', 'new(256)'],
1278                        ['Checksums-Sha1',  'Digest::SHA', 'new(1)'],
1279                        ['Files',           'Digest::MD5', 'new()']) {
1280         my ($fname, $module, $method) = @$csumi;
1281         my $field = $dsc->{$fname};
1282         next unless defined $field;
1283         eval "use $module; 1;" or die $@;
1284         my @out;
1285         foreach (split /\n/, $field) {
1286             next unless m/\S/;
1287             m/^(\w+) (\d+) (\S+)$/ or
1288                 fail "could not parse .dsc $fname line \`$_'";
1289             my $digester = eval "$module"."->$method;" or die $@;
1290             push @out, {
1291                 Hash => $1,
1292                 Bytes => $2,
1293                 Filename => $3,
1294                 Digester => $digester,
1295             };
1296         }
1297         return @out;
1298     }
1299     fail "missing any supported Checksums-* or Files field in ".
1300         $dsc->get_option('name');
1301 }
1302
1303 sub dsc_files () {
1304     map { $_->{Filename} } dsc_files_info();
1305 }
1306
1307 sub is_orig_file ($;$) {
1308     local ($_) = $_[0];
1309     my $base = $_[1];
1310     m/\.orig(?:-\w+)?\.tar\.\w+$/ or return 0;
1311     defined $base or return 1;
1312     return $` eq $base;
1313 }
1314
1315 sub make_commit ($) {
1316     my ($file) = @_;
1317     return cmdoutput @git, qw(hash-object -w -t commit), $file;
1318 }
1319
1320 sub clogp_authline ($) {
1321     my ($clogp) = @_;
1322     my $author = getfield $clogp, 'Maintainer';
1323     $author =~ s#,.*##ms;
1324     my $date = cmdoutput qw(date), '+%s %z', qw(-d), getfield($clogp,'Date');
1325     my $authline = "$author $date";
1326     $authline =~ m/$git_authline_re/o or
1327         fail "unexpected commit author line format \`$authline'".
1328         " (was generated from changelog Maintainer field)";
1329     return ($1,$2,$3) if wantarray;
1330     return $authline;
1331 }
1332
1333 sub vendor_patches_distro ($$) {
1334     my ($checkdistro, $what) = @_;
1335     return unless defined $checkdistro;
1336
1337     my $series = "debian/patches/\L$checkdistro\E.series";
1338     printdebug "checking for vendor-specific $series ($what)\n";
1339
1340     if (!open SERIES, "<", $series) {
1341         die "$series $!" unless $!==ENOENT;
1342         return;
1343     }
1344     while (<SERIES>) {
1345         next unless m/\S/;
1346         next if m/^\s+\#/;
1347
1348         print STDERR <<END;
1349
1350 Unfortunately, this source package uses a feature of dpkg-source where
1351 the same source package unpacks to different source code on different
1352 distros.  dgit cannot safely operate on such packages on affected
1353 distros, because the meaning of source packages is not stable.
1354
1355 Please ask the distro/maintainer to remove the distro-specific series
1356 files and use a different technique (if necessary, uploading actually
1357 different packages, if different distros are supposed to have
1358 different code).
1359
1360 END
1361         fail "Found active distro-specific series file for".
1362             " $checkdistro ($what): $series, cannot continue";
1363     }
1364     die "$series $!" if SERIES->error;
1365     close SERIES;
1366 }
1367
1368 sub check_for_vendor_patches () {
1369     # This dpkg-source feature doesn't seem to be documented anywhere!
1370     # But it can be found in the changelog (reformatted):
1371
1372     #   commit  4fa01b70df1dc4458daee306cfa1f987b69da58c
1373     #   Author: Raphael Hertzog <hertzog@debian.org>
1374     #   Date: Sun  Oct  3  09:36:48  2010 +0200
1375
1376     #   dpkg-source: correctly create .pc/.quilt_series with alternate
1377     #   series files
1378     #   
1379     #   If you have debian/patches/ubuntu.series and you were
1380     #   unpacking the source package on ubuntu, quilt was still
1381     #   directed to debian/patches/series instead of
1382     #   debian/patches/ubuntu.series.
1383     #   
1384     #   debian/changelog                        |    3 +++
1385     #   scripts/Dpkg/Source/Package/V3/quilt.pm |    4 +++-
1386     #   2 files changed, 6 insertions(+), 1 deletion(-)
1387
1388     use Dpkg::Vendor;
1389     vendor_patches_distro($ENV{DEB_VENDOR}, "DEB_VENDOR");
1390     vendor_patches_distro(Dpkg::Vendor::get_current_vendor(),
1391                          "Dpkg::Vendor \`current vendor'");
1392     vendor_patches_distro(access_basedistro(),
1393                           "distro being accessed");
1394 }
1395
1396 sub generate_commit_from_dsc () {
1397     prep_ud();
1398     changedir $ud;
1399
1400     foreach my $fi (dsc_files_info()) {
1401         my $f = $fi->{Filename};
1402         die "$f ?" if $f =~ m#/|^\.|\.dsc$|\.tmp$#;
1403
1404         link_ltarget "../../../$f", $f
1405             or $!==&ENOENT
1406             or die "$f $!";
1407
1408         complete_file_from_dsc('.', $fi)
1409             or next;
1410
1411         if (is_orig_file($f)) {
1412             link $f, "../../../../$f"
1413                 or $!==&EEXIST
1414                 or die "$f $!";
1415         }
1416     }
1417
1418     my $dscfn = "$package.dsc";
1419
1420     open D, ">", $dscfn or die "$dscfn: $!";
1421     print D $dscdata or die "$dscfn: $!";
1422     close D or die "$dscfn: $!";
1423     my @cmd = qw(dpkg-source);
1424     push @cmd, '--no-check' if $dsc_checked;
1425     push @cmd, qw(-x --), $dscfn;
1426     runcmd @cmd;
1427
1428     my ($tree,$dir) = mktree_in_ud_from_only_subdir();
1429     check_for_vendor_patches() if madformat($dsc->{format});
1430     runcmd qw(sh -ec), 'dpkg-parsechangelog >../changelog.tmp';
1431     my $clogp = parsecontrol('../changelog.tmp',"commit's changelog");
1432     my $authline = clogp_authline $clogp;
1433     my $changes = getfield $clogp, 'Changes';
1434     open C, ">../commit.tmp" or die $!;
1435     print C <<END or die $!;
1436 tree $tree
1437 author $authline
1438 committer $authline
1439
1440 $changes
1441
1442 # imported from the archive
1443 END
1444     close C or die $!;
1445     my $outputhash = make_commit qw(../commit.tmp);
1446     my $cversion = getfield $clogp, 'Version';
1447     progress "synthesised git commit from .dsc $cversion";
1448     if ($lastpush_hash) {
1449         runcmd @git, qw(reset --hard), $lastpush_hash;
1450         runcmd qw(sh -ec), 'dpkg-parsechangelog >>../changelogold.tmp';
1451         my $oldclogp = parsecontrol('../changelogold.tmp','previous changelog');
1452         my $oversion = getfield $oldclogp, 'Version';
1453         my $vcmp =
1454             version_compare($oversion, $cversion);
1455         if ($vcmp < 0) {
1456             # git upload/ is earlier vsn than archive, use archive
1457             open C, ">../commit2.tmp" or die $!;
1458             print C <<END or die $!;
1459 tree $tree
1460 parent $lastpush_hash
1461 parent $outputhash
1462 author $authline
1463 committer $authline
1464
1465 Record $package ($cversion) in archive suite $csuite
1466 END
1467             $outputhash = make_commit qw(../commit2.tmp);
1468         } elsif ($vcmp > 0) {
1469             print STDERR <<END or die $!;
1470
1471 Version actually in archive:    $cversion (older)
1472 Last allegedly pushed/uploaded: $oversion (newer or same)
1473 $later_warning_msg
1474 END
1475             $outputhash = $lastpush_hash;
1476         } else {
1477             $outputhash = $lastpush_hash;
1478         }
1479     }
1480     changedir '../../../..';
1481     runcmd @git, qw(update-ref -m),"dgit fetch import $cversion",
1482             'DGIT_ARCHIVE', $outputhash;
1483     cmdoutput @git, qw(log -n2), $outputhash;
1484     # ... gives git a chance to complain if our commit is malformed
1485     rmtree($ud);
1486     return $outputhash;
1487 }
1488
1489 sub complete_file_from_dsc ($$) {
1490     our ($dstdir, $fi) = @_;
1491     # Ensures that we have, in $dir, the file $fi, with the correct
1492     # contents.  (Downloading it from alongside $dscurl if necessary.)
1493
1494     my $f = $fi->{Filename};
1495     my $tf = "$dstdir/$f";
1496     my $downloaded = 0;
1497
1498     if (stat_exists $tf) {
1499         progress "using existing $f";
1500     } else {
1501         my $furl = $dscurl;
1502         $furl =~ s{/[^/]+$}{};
1503         $furl .= "/$f";
1504         die "$f ?" unless $f =~ m/^\Q${package}\E_/;
1505         die "$f ?" if $f =~ m#/#;
1506         runcmd_ordryrun_local @curl,qw(-o),$tf,'--',"$furl";
1507         return 0 if !act_local();
1508         $downloaded = 1;
1509     }
1510
1511     open F, "<", "$tf" or die "$tf: $!";
1512     $fi->{Digester}->reset();
1513     $fi->{Digester}->addfile(*F);
1514     F->error and die $!;
1515     my $got = $fi->{Digester}->hexdigest();
1516     $got eq $fi->{Hash} or
1517         fail "file $f has hash $got but .dsc".
1518             " demands hash $fi->{Hash} ".
1519             ($downloaded ? "(got wrong file from archive!)"
1520              : "(perhaps you should delete this file?)");
1521
1522     return 1;
1523 }
1524
1525 sub ensure_we_have_orig () {
1526     foreach my $fi (dsc_files_info()) {
1527         my $f = $fi->{Filename};
1528         next unless is_orig_file($f);
1529         complete_file_from_dsc('..', $fi)
1530             or next;
1531     }
1532 }
1533
1534 sub git_fetch_us () {
1535     my @specs = (fetchspec());
1536     push @specs,
1537         map { "+refs/$_/*:".lrfetchrefs."/$_/*" }
1538         qw(tags heads);
1539     runcmd_ordryrun_local @git, qw(fetch -p -n -q), access_giturl(), @specs;
1540
1541     my %here;
1542     my $tagpat = debiantag('*',access_basedistro);
1543
1544     git_for_each_ref("refs/tags/".$tagpat, sub {
1545         my ($objid,$objtype,$fullrefname,$reftail) = @_;
1546         printdebug "currently $fullrefname=$objid\n";
1547         $here{$fullrefname} = $objid;
1548     });
1549     git_for_each_ref(lrfetchrefs."/tags/".$tagpat, sub {
1550         my ($objid,$objtype,$fullrefname,$reftail) = @_;
1551         my $lref = "refs".substr($fullrefname, length lrfetchrefs);
1552         printdebug "offered $lref=$objid\n";
1553         if (!defined $here{$lref}) {
1554             my @upd = (@git, qw(update-ref), $lref, $objid, '');
1555             runcmd_ordryrun_local @upd;
1556         } elsif ($here{$lref} eq $objid) {
1557         } else {
1558             print STDERR \
1559                 "Not updateting $lref from $here{$lref} to $objid.\n";
1560         }
1561     });
1562 }
1563
1564 sub fetch_from_archive () {
1565     # ensures that lrref() is what is actually in the archive,
1566     #  one way or another
1567     get_archive_dsc();
1568
1569     if ($dsc) {
1570         foreach my $field (@ourdscfield) {
1571             $dsc_hash = $dsc->{$field};
1572             last if defined $dsc_hash;
1573         }
1574         if (defined $dsc_hash) {
1575             $dsc_hash =~ m/\w+/ or fail "invalid hash in .dsc \`$dsc_hash'";
1576             $dsc_hash = $&;
1577             progress "last upload to archive specified git hash";
1578         } else {
1579             progress "last upload to archive has NO git hash";
1580         }
1581     } else {
1582         progress "no version available from the archive";
1583     }
1584
1585     $lastpush_hash = git_get_ref(lrref());
1586     printdebug "previous reference hash=$lastpush_hash\n";
1587     my $hash;
1588     if (defined $dsc_hash) {
1589         fail "missing remote git history even though dsc has hash -".
1590             " could not find ref ".lrref().
1591             " (should have been fetched from ".access_giturl()."#".rrref().")"
1592             unless $lastpush_hash;
1593         $hash = $dsc_hash;
1594         ensure_we_have_orig();
1595         if ($dsc_hash eq $lastpush_hash) {
1596         } elsif (is_fast_fwd($dsc_hash,$lastpush_hash)) {
1597             print STDERR <<END or die $!;
1598
1599 Git commit in archive is behind the last version allegedly pushed/uploaded.
1600 Commit referred to by archive:  $dsc_hash
1601 Last allegedly pushed/uploaded: $lastpush_hash
1602 $later_warning_msg
1603 END
1604             $hash = $lastpush_hash;
1605         } else {
1606             fail "git head (".lrref()."=$lastpush_hash) is not a ".
1607                 "descendant of archive's .dsc hash ($dsc_hash)";
1608         }
1609     } elsif ($dsc) {
1610         $hash = generate_commit_from_dsc();
1611     } elsif ($lastpush_hash) {
1612         # only in git, not in the archive yet
1613         $hash = $lastpush_hash;
1614         print STDERR <<END or die $!;
1615
1616 Package not found in the archive, but has allegedly been pushed using dgit.
1617 $later_warning_msg
1618 END
1619     } else {
1620         printdebug "nothing found!\n";
1621         if (defined $skew_warning_vsn) {
1622             print STDERR <<END or die $!;
1623
1624 Warning: relevant archive skew detected.
1625 Archive allegedly contains $skew_warning_vsn
1626 But we were not able to obtain any version from the archive or git.
1627
1628 END
1629         }
1630         return 0;
1631     }
1632     printdebug "current hash=$hash\n";
1633     if ($lastpush_hash) {
1634         fail "not fast forward on last upload branch!".
1635             " (archive's version left in DGIT_ARCHIVE)"
1636             unless is_fast_fwd($lastpush_hash, $hash);
1637     }
1638     if (defined $skew_warning_vsn) {
1639         mkpath '.git/dgit';
1640         printdebug "SKEW CHECK WANT $skew_warning_vsn\n";
1641         my $clogf = ".git/dgit/changelog.tmp";
1642         runcmd shell_cmd "exec >$clogf",
1643             @git, qw(cat-file blob), "$hash:debian/changelog";
1644         my $gotclogp = parsechangelog("-l$clogf");
1645         my $got_vsn = getfield $gotclogp, 'Version';
1646         printdebug "SKEW CHECK GOT $got_vsn\n";
1647         if (version_compare($got_vsn, $skew_warning_vsn) < 0) {
1648             print STDERR <<END or die $!;
1649
1650 Warning: archive skew detected.  Using the available version:
1651 Archive allegedly contains    $skew_warning_vsn
1652 We were able to obtain only   $got_vsn
1653
1654 END
1655         }
1656     }
1657     if ($lastpush_hash ne $hash) {
1658         my @upd_cmd = (@git, qw(update-ref -m), 'dgit fetch', lrref(), $hash);
1659         if (act_local()) {
1660             cmdoutput @upd_cmd;
1661         } else {
1662             dryrun_report @upd_cmd;
1663         }
1664     }
1665     return 1;
1666 }
1667
1668 sub set_local_git_config ($$) {
1669     my ($k, $v) = @_;
1670     runcmd @git, qw(config), $k, $v;
1671 }
1672
1673 sub setup_mergechangelogs (;$) {
1674     my ($always) = @_;
1675     return unless $always || access_cfg_bool(1, 'setup-mergechangelogs');
1676
1677     my $driver = 'dpkg-mergechangelogs';
1678     my $cb = "merge.$driver";
1679     my $attrs = '.git/info/attributes';
1680     ensuredir '.git/info';
1681
1682     open NATTRS, ">", "$attrs.new" or die "$attrs.new $!";
1683     if (!open ATTRS, "<", $attrs) {
1684         $!==ENOENT or die "$attrs: $!";
1685     } else {
1686         while (<ATTRS>) {
1687             chomp;
1688             next if m{^debian/changelog\s};
1689             print NATTRS $_, "\n" or die $!;
1690         }
1691         ATTRS->error and die $!;
1692         close ATTRS;
1693     }
1694     print NATTRS "debian/changelog merge=$driver\n" or die $!;
1695     close NATTRS;
1696
1697     set_local_git_config "$cb.name", 'debian/changelog merge driver';
1698     set_local_git_config "$cb.driver", 'dpkg-mergechangelogs -m %O %A %B %A';
1699
1700     rename "$attrs.new", "$attrs" or die "$attrs: $!";
1701 }
1702
1703 sub setup_useremail (;$) {
1704     my ($always) = @_;
1705     return unless $always || access_cfg_bool(1, 'setup-useremail');
1706
1707     my $setup = sub {
1708         my ($k, $envvar) = @_;
1709         my $v = access_cfg("user-$k", 'RETURN-UNDEF') // $ENV{$envvar};
1710         return unless defined $v;
1711         set_local_git_config "user.$k", $v;
1712     };
1713
1714     $setup->('email', 'DEBEMAIL');
1715     $setup->('name', 'DEBFULLNAME');
1716 }
1717
1718 sub setup_new_tree () {
1719     setup_mergechangelogs();
1720     setup_useremail();
1721 }
1722
1723 sub clone ($) {
1724     my ($dstdir) = @_;
1725     canonicalise_suite();
1726     badusage "dry run makes no sense with clone" unless act_local();
1727     my $hasgit = check_for_git();
1728     mkdir $dstdir or fail "create \`$dstdir': $!";
1729     changedir $dstdir;
1730     runcmd @git, qw(init -q);
1731     my $giturl = access_giturl(1);
1732     if (defined $giturl) {
1733         set_local_git_config "remote.$remotename.fetch", fetchspec();
1734         open H, "> .git/HEAD" or die $!;
1735         print H "ref: ".lref()."\n" or die $!;
1736         close H or die $!;
1737         runcmd @git, qw(remote add), 'origin', $giturl;
1738     }
1739     if ($hasgit) {
1740         progress "fetching existing git history";
1741         git_fetch_us();
1742         runcmd_ordryrun_local @git, qw(fetch origin);
1743     } else {
1744         progress "starting new git history";
1745     }
1746     fetch_from_archive() or no_such_package;
1747     my $vcsgiturl = $dsc->{'Vcs-Git'};
1748     if (length $vcsgiturl) {
1749         $vcsgiturl =~ s/\s+-b\s+\S+//g;
1750         runcmd @git, qw(remote add vcs-git), $vcsgiturl;
1751     }
1752     setup_new_tree();
1753     runcmd @git, qw(reset --hard), lrref();
1754     printdone "ready for work in $dstdir";
1755 }
1756
1757 sub fetch () {
1758     if (check_for_git()) {
1759         git_fetch_us();
1760     }
1761     fetch_from_archive() or no_such_package();
1762     printdone "fetched into ".lrref();
1763 }
1764
1765 sub pull () {
1766     fetch();
1767     runcmd_ordryrun_local @git, qw(merge -m),"Merge from $csuite [dgit]",
1768         lrref();
1769     printdone "fetched to ".lrref()." and merged into HEAD";
1770 }
1771
1772 sub check_not_dirty () {
1773     foreach my $f (qw(local-options local-patch-header)) {
1774         if (stat_exists "debian/source/$f") {
1775             fail "git tree contains debian/source/$f";
1776         }
1777     }
1778
1779     return if $ignoredirty;
1780
1781     my @cmd = (@git, qw(diff --quiet HEAD));
1782     debugcmd "+",@cmd;
1783     $!=0; $?=0; system @cmd;
1784     return if !$! && !$?;
1785     if (!$! && $?==256) {
1786         fail "working tree is dirty (does not match HEAD)";
1787     } else {
1788         failedcmd @cmd;
1789     }
1790 }
1791
1792 sub commit_admin ($) {
1793     my ($m) = @_;
1794     progress "$m";
1795     runcmd_ordryrun_local @git, qw(commit -m), $m;
1796 }
1797
1798 sub commit_quilty_patch () {
1799     my $output = cmdoutput @git, qw(status --porcelain);
1800     my %adds;
1801     foreach my $l (split /\n/, $output) {
1802         next unless $l =~ m/\S/;
1803         if ($l =~ m{^(?:\?\?| M) (.pc|debian/patches)}) {
1804             $adds{$1}++;
1805         }
1806     }
1807     delete $adds{'.pc'}; # if there wasn't one before, don't add it
1808     if (!%adds) {
1809         progress "nothing quilty to commit, ok.";
1810         return;
1811     }
1812     my @adds = map { s/[][*?\\]/\\$&/g; $_; } sort keys %adds;
1813     runcmd_ordryrun_local @git, qw(add -f), @adds;
1814     commit_admin "Commit Debian 3.0 (quilt) metadata";
1815 }
1816
1817 sub get_source_format () {
1818     my %options;
1819     if (open F, "debian/source/options") {
1820         while (<F>) {
1821             next if m/^\s*\#/;
1822             next unless m/\S/;
1823             s/\s+$//; # ignore missing final newline
1824             if (m/\s*\#\s*/) {
1825                 my ($k, $v) = ($`, $'); #');
1826                 $v =~ s/^"(.*)"$/$1/;
1827                 $options{$k} = $v;
1828             } else {
1829                 $options{$_} = 1;
1830             }
1831         }
1832         F->error and die $!;
1833         close F;
1834     } else {
1835         die $! unless $!==&ENOENT;
1836     }
1837
1838     if (!open F, "debian/source/format") {
1839         die $! unless $!==&ENOENT;
1840         return '';
1841     }
1842     $_ = <F>;
1843     F->error and die $!;
1844     chomp;
1845     return ($_, \%options);
1846 }
1847
1848 sub madformat ($) {
1849     my ($format) = @_;
1850     return 0 unless $format eq '3.0 (quilt)';
1851     if ($quilt_mode eq 'nocheck') {
1852         progress "Not doing any fixup of \`$format' due to --no-quilt-fixup";
1853         return 0;
1854     }
1855     progress "Format \`$format', checking/updating patch stack";
1856     return 1;
1857 }
1858
1859 sub push_parse_changelog ($) {
1860     my ($clogpfn) = @_;
1861
1862     my $clogp = Dpkg::Control::Hash->new();
1863     $clogp->load($clogpfn) or die;
1864
1865     $package = getfield $clogp, 'Source';
1866     my $cversion = getfield $clogp, 'Version';
1867     my $tag = debiantag($cversion, access_basedistro);
1868     runcmd @git, qw(check-ref-format), $tag;
1869
1870     my $dscfn = dscfn($cversion);
1871
1872     return ($clogp, $cversion, $tag, $dscfn);
1873 }
1874
1875 sub push_parse_dsc ($$$) {
1876     my ($dscfn,$dscfnwhat, $cversion) = @_;
1877     $dsc = parsecontrol($dscfn,$dscfnwhat);
1878     my $dversion = getfield $dsc, 'Version';
1879     my $dscpackage = getfield $dsc, 'Source';
1880     ($dscpackage eq $package && $dversion eq $cversion) or
1881         fail "$dscfn is for $dscpackage $dversion".
1882             " but debian/changelog is for $package $cversion";
1883 }
1884
1885 sub push_mktag ($$$$$$$) {
1886     my ($head,$clogp,$tag,
1887         $dscfn,
1888         $changesfile,$changesfilewhat,
1889         $tfn) = @_;
1890
1891     $dsc->{$ourdscfield[0]} = $head;
1892     $dsc->save("$dscfn.tmp") or die $!;
1893
1894     my $changes = parsecontrol($changesfile,$changesfilewhat);
1895     foreach my $field (qw(Source Distribution Version)) {
1896         $changes->{$field} eq $clogp->{$field} or
1897             fail "changes field $field \`$changes->{$field}'".
1898                 " does not match changelog \`$clogp->{$field}'";
1899     }
1900
1901     my $cversion = getfield $clogp, 'Version';
1902     my $clogsuite = getfield $clogp, 'Distribution';
1903
1904     # We make the git tag by hand because (a) that makes it easier
1905     # to control the "tagger" (b) we can do remote signing
1906     my $authline = clogp_authline $clogp;
1907     my $delibs = join(" ", "",@deliberatelies);
1908     my $declaredistro = access_basedistro();
1909     open TO, '>', $tfn->('.tmp') or die $!;
1910     print TO <<END or die $!;
1911 object $head
1912 type commit
1913 tag $tag
1914 tagger $authline
1915
1916 $package release $cversion for $clogsuite ($csuite) [dgit]
1917 [dgit distro=$declaredistro$delibs]
1918 END
1919     foreach my $ref (sort keys %previously) {
1920                     print TO <<END or die $!;
1921 [dgit previously:$ref=$previously{$ref}]
1922 END
1923     }
1924
1925     close TO or die $!;
1926
1927     my $tagobjfn = $tfn->('.tmp');
1928     if ($sign) {
1929         if (!defined $keyid) {
1930             $keyid = access_cfg('keyid','RETURN-UNDEF');
1931         }
1932         if (!defined $keyid) {
1933             $keyid = getfield $clogp, 'Maintainer';
1934         }
1935         unlink $tfn->('.tmp.asc') or $!==&ENOENT or die $!;
1936         my @sign_cmd = (@gpg, qw(--detach-sign --armor));
1937         push @sign_cmd, qw(-u),$keyid if defined $keyid;
1938         push @sign_cmd, $tfn->('.tmp');
1939         runcmd_ordryrun @sign_cmd;
1940         if (act_scary()) {
1941             $tagobjfn = $tfn->('.signed.tmp');
1942             runcmd shell_cmd "exec >$tagobjfn", qw(cat --),
1943                 $tfn->('.tmp'), $tfn->('.tmp.asc');
1944         }
1945     }
1946
1947     return ($tagobjfn);
1948 }
1949
1950 sub sign_changes ($) {
1951     my ($changesfile) = @_;
1952     if ($sign) {
1953         my @debsign_cmd = @debsign;
1954         push @debsign_cmd, "-k$keyid" if defined $keyid;
1955         push @debsign_cmd, "-p$gpg[0]" if $gpg[0] ne 'gpg';
1956         push @debsign_cmd, $changesfile;
1957         runcmd_ordryrun @debsign_cmd;
1958     }
1959 }
1960
1961 sub dopush ($) {
1962     my ($forceflag) = @_;
1963     printdebug "actually entering push\n";
1964     supplementary_message(<<'END');
1965 Push failed, while preparing your push.
1966 You can retry the push, after fixing the problem, if you like.
1967 END
1968     prep_ud();
1969
1970     access_giturl(); # check that success is vaguely likely
1971
1972     my $clogpfn = ".git/dgit/changelog.822.tmp";
1973     runcmd shell_cmd "exec >$clogpfn", qw(dpkg-parsechangelog);
1974
1975     responder_send_file('parsed-changelog', $clogpfn);
1976
1977     my ($clogp, $cversion, $tag, $dscfn) =
1978         push_parse_changelog("$clogpfn");
1979
1980     my $dscpath = "$buildproductsdir/$dscfn";
1981     stat_exists $dscpath or
1982         fail "looked for .dsc $dscfn, but $!;".
1983             " maybe you forgot to build";
1984
1985     responder_send_file('dsc', $dscpath);
1986
1987     push_parse_dsc($dscpath, $dscfn, $cversion);
1988
1989     my $format = getfield $dsc, 'Format';
1990     printdebug "format $format\n";
1991
1992     if (madformat($format)) {
1993         # user might have not used dgit build, so maybe do this now:
1994         commit_quilty_patch();
1995     }
1996
1997     die 'xxx fast forward (should not depend on quilt mode, but will always be needed if we did $split_brain)' if $split_brain;
1998
1999     check_not_dirty();
2000     changedir $ud;
2001     progress "checking that $dscfn corresponds to HEAD";
2002     runcmd qw(dpkg-source -x --),
2003         $dscpath =~ m#^/# ? $dscpath : "../../../$dscpath";
2004     my ($tree,$dir) = mktree_in_ud_from_only_subdir();
2005     check_for_vendor_patches() if madformat($dsc->{format});
2006     changedir '../../../..';
2007     my $diffopt = $debuglevel>0 ? '--exit-code' : '--quiet';
2008     my @diffcmd = (@git, qw(diff), $diffopt, $tree);
2009     debugcmd "+",@diffcmd;
2010     $!=0; $?=0;
2011     my $r = system @diffcmd;
2012     if ($r) {
2013         if ($r==256) {
2014             fail "$dscfn specifies a different tree to your HEAD commit;".
2015                 " perhaps you forgot to build".
2016                 ($diffopt eq '--exit-code' ? "" :
2017                  " (run with -D to see full diff output)");
2018         } else {
2019             failedcmd @diffcmd;
2020         }
2021     }
2022     my $head = git_rev_parse('HEAD');
2023     if (!$changesfile) {
2024         my $pat = changespat $cversion;
2025         my @cs = glob "$buildproductsdir/$pat";
2026         fail "failed to find unique changes file".
2027             " (looked for $pat in $buildproductsdir);".
2028             " perhaps you need to use dgit -C"
2029             unless @cs==1;
2030         ($changesfile) = @cs;
2031     } else {
2032         $changesfile = "$buildproductsdir/$changesfile";
2033     }
2034
2035     responder_send_file('changes',$changesfile);
2036     responder_send_command("param head $head");
2037     responder_send_command("param csuite $csuite");
2038
2039     if (deliberately_not_fast_forward) {
2040         git_for_each_ref(lrfetchrefs, sub {
2041             my ($objid,$objtype,$lrfetchrefname,$reftail) = @_;
2042             my $rrefname= substr($lrfetchrefname, length(lrfetchrefs) + 1);
2043             responder_send_command("previously $rrefname=$objid");
2044             $previously{$rrefname} = $objid;
2045         });
2046     }
2047
2048     my $tfn = sub { ".git/dgit/tag$_[0]"; };
2049     my $tagobjfn;
2050
2051     supplementary_message(<<'END');
2052 Push failed, while signing the tag.
2053 You can retry the push, after fixing the problem, if you like.
2054 END
2055     # If we manage to sign but fail to record it anywhere, it's fine.
2056     if ($we_are_responder) {
2057         $tagobjfn = $tfn->('.signed.tmp');
2058         responder_receive_files('signed-tag', $tagobjfn);
2059     } else {
2060         $tagobjfn =
2061             push_mktag($head,$clogp,$tag,
2062                        $dscpath,
2063                        $changesfile,$changesfile,
2064                        $tfn);
2065     }
2066     supplementary_message(<<'END');
2067 Push failed, *after* signing the tag.
2068 If you want to try again, you should use a new version number.
2069 END
2070
2071     my $tag_obj_hash = cmdoutput @git, qw(hash-object -w -t tag), $tagobjfn;
2072     runcmd_ordryrun @git, qw(verify-tag), $tag_obj_hash;
2073     runcmd_ordryrun_local @git, qw(update-ref), "refs/tags/$tag", $tag_obj_hash;
2074
2075     supplementary_message(<<'END');
2076 Push failed, while updating the remote git repository - see messages above.
2077 If you want to try again, you should use a new version number.
2078 END
2079     if (!check_for_git()) {
2080         create_remote_git_repo();
2081     }
2082     runcmd_ordryrun @git, qw(push),access_giturl(),
2083         $forceflag."HEAD:".rrref(), $forceflag."refs/tags/$tag";
2084     runcmd_ordryrun @git, qw(update-ref -m), 'dgit push', lrref(), 'HEAD';
2085
2086     supplementary_message(<<'END');
2087 Push failed, after updating the remote git repository.
2088 If you want to try again, you must use a new version number.
2089 END
2090     if ($we_are_responder) {
2091         my $dryrunsuffix = act_local() ? "" : ".tmp";
2092         responder_receive_files('signed-dsc-changes',
2093                                 "$dscpath$dryrunsuffix",
2094                                 "$changesfile$dryrunsuffix");
2095     } else {
2096         if (act_local()) {
2097             rename "$dscpath.tmp",$dscpath or die "$dscfn $!";
2098         } else {
2099             progress "[new .dsc left in $dscpath.tmp]";
2100         }
2101         sign_changes $changesfile;
2102     }
2103
2104     supplementary_message(<<END);
2105 Push failed, while uploading package(s) to the archive server.
2106 You can retry the upload of exactly these same files with dput of:
2107   $changesfile
2108 If that .changes file is broken, you will need to use a new version
2109 number for your next attempt at the upload.
2110 END
2111     my $host = access_cfg('upload-host','RETURN-UNDEF');
2112     my @hostarg = defined($host) ? ($host,) : ();
2113     runcmd_ordryrun @dput, @hostarg, $changesfile;
2114     printdone "pushed and uploaded $cversion";
2115
2116     supplementary_message('');
2117     responder_send_command("complete");
2118 }
2119
2120 sub cmd_clone {
2121     parseopts();
2122     notpushing();
2123     my $dstdir;
2124     badusage "-p is not allowed with clone; specify as argument instead"
2125         if defined $package;
2126     if (@ARGV==1) {
2127         ($package) = @ARGV;
2128     } elsif (@ARGV==2 && $ARGV[1] =~ m#^\w#) {
2129         ($package,$isuite) = @ARGV;
2130     } elsif (@ARGV==2 && $ARGV[1] =~ m#^[./]#) {
2131         ($package,$dstdir) = @ARGV;
2132     } elsif (@ARGV==3) {
2133         ($package,$isuite,$dstdir) = @ARGV;
2134     } else {
2135         badusage "incorrect arguments to dgit clone";
2136     }
2137     $dstdir ||= "$package";
2138
2139     if (stat_exists $dstdir) {
2140         fail "$dstdir already exists";
2141     }
2142
2143     my $cwd_remove;
2144     if ($rmonerror && !$dryrun_level) {
2145         $cwd_remove= getcwd();
2146         unshift @end, sub { 
2147             return unless defined $cwd_remove;
2148             if (!chdir "$cwd_remove") {
2149                 return if $!==&ENOENT;
2150                 die "chdir $cwd_remove: $!";
2151             }
2152             if (stat $dstdir) {
2153                 rmtree($dstdir) or die "remove $dstdir: $!\n";
2154             } elsif (!grep { $! == $_ }
2155                      (ENOENT, ENOTDIR, EACCES, EPERM, ELOOP)) {
2156             } else {
2157                 print STDERR "check whether to remove $dstdir: $!\n";
2158             }
2159         };
2160     }
2161
2162     clone($dstdir);
2163     $cwd_remove = undef;
2164 }
2165
2166 sub branchsuite () {
2167     my $branch = cmdoutput_errok @git, qw(symbolic-ref HEAD);
2168     if ($branch =~ m#$lbranch_re#o) {
2169         return $1;
2170     } else {
2171         return undef;
2172     }
2173 }
2174
2175 sub fetchpullargs () {
2176     notpushing();
2177     if (!defined $package) {
2178         my $sourcep = parsecontrol('debian/control','debian/control');
2179         $package = getfield $sourcep, 'Source';
2180     }
2181     if (@ARGV==0) {
2182 #       $isuite = branchsuite();  # this doesn't work because dak hates canons
2183         if (!$isuite) {
2184             my $clogp = parsechangelog();
2185             $isuite = getfield $clogp, 'Distribution';
2186         }
2187         canonicalise_suite();
2188         progress "fetching from suite $csuite";
2189     } elsif (@ARGV==1) {
2190         ($isuite) = @ARGV;
2191         canonicalise_suite();
2192     } else {
2193         badusage "incorrect arguments to dgit fetch or dgit pull";
2194     }
2195 }
2196
2197 sub cmd_fetch {
2198     parseopts();
2199     fetchpullargs();
2200     fetch();
2201 }
2202
2203 sub cmd_pull {
2204     parseopts();
2205     fetchpullargs();
2206     pull();
2207 }
2208
2209 sub cmd_push {
2210     parseopts();
2211     pushing();
2212     badusage "-p is not allowed with dgit push" if defined $package;
2213     check_not_dirty();
2214     my $clogp = parsechangelog();
2215     $package = getfield $clogp, 'Source';
2216     my $specsuite;
2217     if (@ARGV==0) {
2218     } elsif (@ARGV==1) {
2219         ($specsuite) = (@ARGV);
2220     } else {
2221         badusage "incorrect arguments to dgit push";
2222     }
2223     $isuite = getfield $clogp, 'Distribution';
2224     if ($new_package) {
2225         local ($package) = $existing_package; # this is a hack
2226         canonicalise_suite();
2227     } else {
2228         canonicalise_suite();
2229     }
2230     if (defined $specsuite &&
2231         $specsuite ne $isuite &&
2232         $specsuite ne $csuite) {
2233             fail "dgit push: changelog specifies $isuite ($csuite)".
2234                 " but command line specifies $specsuite";
2235     }
2236     supplementary_message(<<'END');
2237 Push failed, while checking state of the archive.
2238 You can retry the push, after fixing the problem, if you like.
2239 END
2240     if (check_for_git()) {
2241         git_fetch_us();
2242     }
2243     my $forceflag = '';
2244     if (fetch_from_archive()) {
2245         if (is_fast_fwd(lrref(), 'HEAD')) {
2246             # ok
2247         } elsif (deliberately_not_fast_forward) {
2248             $forceflag = '+';
2249         } else {
2250             fail "dgit push: HEAD is not a descendant".
2251                 " of the archive's version.\n".
2252                 "dgit: To overwrite its contents,".
2253                 " use git merge -s ours ".lrref().".\n".
2254                 "dgit: To rewind history, if permitted by the archive,".
2255                 " use --deliberately-not-fast-forward";
2256         }
2257     } else {
2258         $new_package or
2259             fail "package appears to be new in this suite;".
2260                 " if this is intentional, use --new";
2261     }
2262     dopush($forceflag);
2263 }
2264
2265 #---------- remote commands' implementation ----------
2266
2267 sub cmd_remote_push_build_host {
2268     my ($nrargs) = shift @ARGV;
2269     my (@rargs) = @ARGV[0..$nrargs-1];
2270     @ARGV = @ARGV[$nrargs..$#ARGV];
2271     die unless @rargs;
2272     my ($dir,$vsnwant) = @rargs;
2273     # vsnwant is a comma-separated list; we report which we have
2274     # chosen in our ready response (so other end can tell if they
2275     # offered several)
2276     $debugprefix = ' ';
2277     $we_are_responder = 1;
2278     $us .= " (build host)";
2279
2280     pushing();
2281
2282     open PI, "<&STDIN" or die $!;
2283     open STDIN, "/dev/null" or die $!;
2284     open PO, ">&STDOUT" or die $!;
2285     autoflush PO 1;
2286     open STDOUT, ">&STDERR" or die $!;
2287     autoflush STDOUT 1;
2288
2289     $vsnwant //= 1;
2290     ($protovsn) = grep {
2291         $vsnwant =~ m{^(?:.*,)?$_(?:,.*)?$}
2292     } @rpushprotovsn_support;
2293
2294     fail "build host has dgit rpush protocol versions ".
2295         (join ",", @rpushprotovsn_support).
2296         " but invocation host has $vsnwant"
2297         unless defined $protovsn;
2298
2299     responder_send_command("dgit-remote-push-ready $protovsn");
2300
2301     changedir $dir;
2302     &cmd_push;
2303 }
2304
2305 sub cmd_remote_push_responder { cmd_remote_push_build_host(); }
2306 # ... for compatibility with proto vsn.1 dgit (just so that user gets
2307 #     a good error message)
2308
2309 our $i_tmp;
2310
2311 sub i_cleanup {
2312     local ($@, $?);
2313     my $report = i_child_report();
2314     if (defined $report) {
2315         printdebug "($report)\n";
2316     } elsif ($i_child_pid) {
2317         printdebug "(killing build host child $i_child_pid)\n";
2318         kill 15, $i_child_pid;
2319     }
2320     if (defined $i_tmp && !defined $initiator_tempdir) {
2321         changedir "/";
2322         eval { rmtree $i_tmp; };
2323     }
2324 }
2325
2326 END { i_cleanup(); }
2327
2328 sub i_method {
2329     my ($base,$selector,@args) = @_;
2330     $selector =~ s/\-/_/g;
2331     { no strict qw(refs); &{"${base}_${selector}"}(@args); }
2332 }
2333
2334 sub cmd_rpush {
2335     pushing();
2336     my $host = nextarg;
2337     my $dir;
2338     if ($host =~ m/^((?:[^][]|\[[^][]*\])*)\:/) {
2339         $host = $1;
2340         $dir = $'; #';
2341     } else {
2342         $dir = nextarg;
2343     }
2344     $dir =~ s{^-}{./-};
2345     my @rargs = ($dir);
2346     push @rargs, join ",", @rpushprotovsn_support;
2347     my @rdgit;
2348     push @rdgit, @dgit;
2349     push @rdgit, @ropts;
2350     push @rdgit, qw(remote-push-build-host), (scalar @rargs), @rargs;
2351     push @rdgit, @ARGV;
2352     my @cmd = (@ssh, $host, shellquote @rdgit);
2353     debugcmd "+",@cmd;
2354
2355     if (defined $initiator_tempdir) {
2356         rmtree $initiator_tempdir;
2357         mkdir $initiator_tempdir, 0700 or die "$initiator_tempdir: $!";
2358         $i_tmp = $initiator_tempdir;
2359     } else {
2360         $i_tmp = tempdir();
2361     }
2362     $i_child_pid = open2(\*RO, \*RI, @cmd);
2363     changedir $i_tmp;
2364     ($protovsn) = initiator_expect { m/^dgit-remote-push-ready (\S+)/ };
2365     die "$protovsn ?" unless grep { $_ eq $protovsn } @rpushprotovsn_support;
2366     $supplementary_message = '' unless $protovsn >= 3;
2367     for (;;) {
2368         my ($icmd,$iargs) = initiator_expect {
2369             m/^(\S+)(?: (.*))?$/;
2370             ($1,$2);
2371         };
2372         i_method "i_resp", $icmd, $iargs;
2373     }
2374 }
2375
2376 sub i_resp_progress ($) {
2377     my ($rhs) = @_;
2378     my $msg = protocol_read_bytes \*RO, $rhs;
2379     progress $msg;
2380 }
2381
2382 sub i_resp_supplementary_message ($) {
2383     my ($rhs) = @_;
2384     $supplementary_message = protocol_read_bytes \*RO, $rhs;
2385 }
2386
2387 sub i_resp_complete {
2388     my $pid = $i_child_pid;
2389     $i_child_pid = undef; # prevents killing some other process with same pid
2390     printdebug "waiting for build host child $pid...\n";
2391     my $got = waitpid $pid, 0;
2392     die $! unless $got == $pid;
2393     die "build host child failed $?" if $?;
2394
2395     i_cleanup();
2396     printdebug "all done\n";
2397     exit 0;
2398 }
2399
2400 sub i_resp_file ($) {
2401     my ($keyword) = @_;
2402     my $localname = i_method "i_localname", $keyword;
2403     my $localpath = "$i_tmp/$localname";
2404     stat_exists $localpath and
2405         badproto \*RO, "file $keyword ($localpath) twice";
2406     protocol_receive_file \*RO, $localpath;
2407     i_method "i_file", $keyword;
2408 }
2409
2410 our %i_param;
2411
2412 sub i_resp_param ($) {
2413     $_[0] =~ m/^(\S+) (.*)$/ or badproto \*RO, "bad param spec";
2414     $i_param{$1} = $2;
2415 }
2416
2417 sub i_resp_previously ($) {
2418     $_[0] =~ m#^(refs/tags/\S+)=(\w+)$#
2419         or badproto \*RO, "bad previously spec";
2420     my $r = system qw(git check-ref-format), $1;
2421     die "bad previously ref spec ($r)" if $r;
2422     $previously{$1} = $2;
2423 }
2424
2425 our %i_wanted;
2426
2427 sub i_resp_want ($) {
2428     my ($keyword) = @_;
2429     die "$keyword ?" if $i_wanted{$keyword}++;
2430     my @localpaths = i_method "i_want", $keyword;
2431     printdebug "[[  $keyword @localpaths\n";
2432     foreach my $localpath (@localpaths) {
2433         protocol_send_file \*RI, $localpath;
2434     }
2435     print RI "files-end\n" or die $!;
2436 }
2437
2438 our ($i_clogp, $i_version, $i_tag, $i_dscfn, $i_changesfn);
2439
2440 sub i_localname_parsed_changelog {
2441     return "remote-changelog.822";
2442 }
2443 sub i_file_parsed_changelog {
2444     ($i_clogp, $i_version, $i_tag, $i_dscfn) =
2445         push_parse_changelog "$i_tmp/remote-changelog.822";
2446     die if $i_dscfn =~ m#/|^\W#;
2447 }
2448
2449 sub i_localname_dsc {
2450     defined $i_dscfn or badproto \*RO, "dsc (before parsed-changelog)";
2451     return $i_dscfn;
2452 }
2453 sub i_file_dsc { }
2454
2455 sub i_localname_changes {
2456     defined $i_dscfn or badproto \*RO, "dsc (before parsed-changelog)";
2457     $i_changesfn = $i_dscfn;
2458     $i_changesfn =~ s/\.dsc$/_dgit.changes/ or die;
2459     return $i_changesfn;
2460 }
2461 sub i_file_changes { }
2462
2463 sub i_want_signed_tag {
2464     printdebug Dumper(\%i_param, $i_dscfn);
2465     defined $i_param{'head'} && defined $i_dscfn && defined $i_clogp
2466         && defined $i_param{'csuite'}
2467         or badproto \*RO, "premature desire for signed-tag";
2468     my $head = $i_param{'head'};
2469     die if $head =~ m/[^0-9a-f]/ || $head !~ m/^../;
2470
2471     die unless $i_param{'csuite'} =~ m/^$suite_re$/;
2472     $csuite = $&;
2473     push_parse_dsc $i_dscfn, 'remote dsc', $i_version;
2474
2475     my $tagobjfn =
2476         push_mktag $head, $i_clogp, $i_tag,
2477             $i_dscfn,
2478             $i_changesfn, 'remote changes',
2479             sub { "tag$_[0]"; };
2480
2481     return $tagobjfn;
2482 }
2483
2484 sub i_want_signed_dsc_changes {
2485     rename "$i_dscfn.tmp","$i_dscfn" or die "$i_dscfn $!";
2486     sign_changes $i_changesfn;
2487     return ($i_dscfn, $i_changesfn);
2488 }
2489
2490 #---------- building etc. ----------
2491
2492 our $version;
2493 our $sourcechanges;
2494 our $dscfn;
2495
2496 #----- `3.0 (quilt)' handling -----
2497
2498 our $fakeeditorenv = 'DGIT_FAKE_EDITOR_QUILT';
2499
2500 sub quiltify_dpkg_commit ($$$;$) {
2501     my ($patchname,$author,$msg, $xinfo) = @_;
2502     $xinfo //= '';
2503
2504     mkpath '.git/dgit';
2505     my $descfn = ".git/dgit/quilt-description.tmp";
2506     open O, '>', $descfn or die "$descfn: $!";
2507     $msg =~ s/\s+$//g;
2508     $msg =~ s/\n/\n /g;
2509     $msg =~ s/^\s+$/ ./mg;
2510     print O <<END or die $!;
2511 Description: $msg
2512 Author: $author
2513 $xinfo
2514 ---
2515
2516 END
2517     close O or die $!;
2518
2519     {
2520         local $ENV{'EDITOR'} = cmdoutput qw(realpath --), $0;
2521         local $ENV{'VISUAL'} = $ENV{'EDITOR'};
2522         local $ENV{$fakeeditorenv} = cmdoutput qw(realpath --), $descfn;
2523         runcmd @dpkgsource, qw(--commit .), $patchname;
2524     }
2525 }
2526
2527 sub quiltify_trees_differ ($$;$$) {
2528     my ($x,$y,$finegrained,$ignorenamesr) = @_;
2529     # returns true iff the two tree objects differ other than in debian/
2530     # with $finegrained,
2531     # returns bitmask 01 - differ in upstream files except .gitignore
2532     #                 02 - differ in .gitignore
2533     # if $ignorenamesr is defined, $ingorenamesr->{$fn}
2534     #  is set for each modified .gitignore filename $fn
2535     local $/=undef;
2536     my @cmd = (@git, qw(diff-tree --name-only -z));
2537     push @cmd, qw(-r) if $finegrained;
2538     push @cmd, $x, $y;
2539     my $diffs= cmdoutput @cmd;
2540     my $r = 0;
2541     foreach my $f (split /\0/, $diffs) {
2542         next if $f =~ m#^debian(?:/.*)?$#s;
2543         my $isignore = $f =~ m#^(?:.*/)?.gitignore$#s;
2544         $r |= $isignore ? 02 : 01;
2545         $ignorenamesr->{$f}=1 if $ignorenamesr && $isignore;
2546     }
2547     printdebug "quiltify_trees_differ $x $y => $r\n";
2548     return $r;
2549 }
2550
2551 sub quiltify_tree_sentinelfiles ($) {
2552     # lists the `sentinel' files present in the tree
2553     my ($x) = @_;
2554     my $r = cmdoutput @git, qw(ls-tree --name-only), $x,
2555         qw(-- debian/rules debian/control);
2556     $r =~ s/\n/,/g;
2557     return $r;
2558                                  }
2559
2560 sub quiltify_splitbrain_needed () {
2561     if (!$split_brain) {
2562         progress "creating dgit view";
2563         runcmd @git, qw(checkout -q -b dgit-view);
2564         $split_brain = 1;
2565     }
2566 }
2567
2568 sub quiltify_splitbrain ($$$$$$) {
2569     my ($clogp, $unapplied, $headref, $diffbits,
2570         $editedignores, $cachekey) = @_;
2571     if ($quilt_mode !~ m/gbp|dpm/) {
2572         # treat .gitignore just like any other upstream file
2573         $diffbits = { %$diffbits };
2574         $_ = !!$_ foreach values %$diffbits;
2575     }
2576     # We would like any commits we generate to be reproducible
2577     my @authline = clogp_authline($clogp);
2578     local $ENV{GIT_COMMITTER_NAME} =  $authline[0];
2579     local $ENV{GIT_COMMITTER_EMAIL} = $authline[1];
2580     local $ENV{GIT_COMMITTER_DATE} =  $authline[2];
2581     if ($quilt_mode =~ m/gbp|unapplied/ &&
2582         ($diffbits->{O2A} & 01) && # some patches
2583         !($diffbits->{H2O} & 01)) { # but HEAD is like orig
2584         quiltify_splitbrain_needed();
2585         progress "creating patches-applied version using gbp-pq";
2586         open STDOUT, ">/dev/null" or die $!;
2587         runcmd shell_cmd 'exec >/dev/null', @gbppq, qw(import);
2588         # gbp-pq import creates a fresh branch; push back to dgit-view
2589         runcmd @git, qw(update-ref refs/heads/dgit-view HEAD);
2590         runcmd @git, qw(checkout -q dgit-view);
2591     }
2592     if (($diffbits->{H2O} & 02) && # user has modified .gitignore
2593         !($diffbits->{O2A} & 02)) { # patches do not change .gitignore
2594         quiltify_splitbrain_needed();
2595         progress "creating patch to represent .gitignore changes";
2596         ensuredir "debian/patches";
2597         my $gipatch = "debian/patches/auto-gitignore";
2598         open GIPATCH, ">>", "$gipatch" or die "$gipatch: $!";
2599         stat GIPATCH or die "$gipatch: $!";
2600         fail "$gipatch already exists; but want to create it".
2601             " to record .gitignore changes" if (stat _)[7];
2602         print GIPATCH <<END or die "$gipatch: $!";
2603 Subject: Update .gitignore from Debian packaging branch
2604
2605 The Debian packaging git branch contains these updates to the upstream
2606 .gitignore file(s).  This patch is autogenerated, to provide these
2607 updates to users of the official Debian archive view of the package.
2608
2609 [dgit version $our_version]
2610 ---
2611 END
2612         close GIPATCH or die "$gipatch: $!";
2613         runcmd shell_cmd "exec >>$gipatch", @git, qw(diff),
2614             $unapplied, $headref, "--", sort keys %$editedignores;
2615         open SERIES, "+>>", "debian/patches/series" or die $!;
2616         defined seek SERIES, -1, 2 or $!==EINVAL or die $!;
2617         my $newline;
2618         defined read SERIES, $newline, 1 or die $!;
2619         print SERIES "\n" or die $! unless $newline eq "\n";
2620         print SERIES "auto-gitignore\n" or die $!;
2621         close SERIES or die  $!;
2622         runcmd @git, qw(add -- debian/patches/series), $gipatch;
2623         commit_admin "Commit patch to update .gitignore";
2624     }
2625
2626     my $dgitview = git_rev_parse 'refs/heads/dgit-view';
2627
2628     changedir '../../../..';
2629     ensuredir ".git/logs/refs/dgit-intern";
2630     my $makelogfh = new IO::File ".git/logs/refs/$splitbraincache", '>>'
2631       or die $!;
2632     runcmd @git, qw(update-ref -m), $cachekey, "refs/$splitbraincache",
2633         $dgitview;
2634
2635     changedir '.git/dgit/unpack/work';
2636 }
2637
2638 sub quiltify ($$$$) {
2639     my ($clogp,$target,$oldtiptree,$failsuggestion) = @_;
2640
2641     # Quilt patchification algorithm
2642     #
2643     # We search backwards through the history of the main tree's HEAD
2644     # (T) looking for a start commit S whose tree object is identical
2645     # to to the patch tip tree (ie the tree corresponding to the
2646     # current dpkg-committed patch series).  For these purposes
2647     # `identical' disregards anything in debian/ - this wrinkle is
2648     # necessary because dpkg-source treates debian/ specially.
2649     #
2650     # We can only traverse edges where at most one of the ancestors'
2651     # trees differs (in changes outside in debian/).  And we cannot
2652     # handle edges which change .pc/ or debian/patches.  To avoid
2653     # going down a rathole we avoid traversing edges which introduce
2654     # debian/rules or debian/control.  And we set a limit on the
2655     # number of edges we are willing to look at.
2656     #
2657     # If we succeed, we walk forwards again.  For each traversed edge
2658     # PC (with P parent, C child) (starting with P=S and ending with
2659     # C=T) to we do this:
2660     #  - git checkout C
2661     #  - dpkg-source --commit with a patch name and message derived from C
2662     # After traversing PT, we git commit the changes which
2663     # should be contained within debian/patches.
2664
2665     # The search for the path S..T is breadth-first.  We maintain a
2666     # todo list containing search nodes.  A search node identifies a
2667     # commit, and looks something like this:
2668     #  $p = {
2669     #      Commit => $git_commit_id,
2670     #      Child => $c,                          # or undef if P=T
2671     #      Whynot => $reason_edge_PC_unsuitable, # in @nots only
2672     #      Nontrivial => true iff $p..$c has relevant changes
2673     #  };
2674
2675     my @todo;
2676     my @nots;
2677     my $sref_S;
2678     my $max_work=100;
2679     my %considered; # saves being exponential on some weird graphs
2680
2681     my $t_sentinels = quiltify_tree_sentinelfiles $target;
2682
2683     my $not = sub {
2684         my ($search,$whynot) = @_;
2685         printdebug " search NOT $search->{Commit} $whynot\n";
2686         $search->{Whynot} = $whynot;
2687         push @nots, $search;
2688         no warnings qw(exiting);
2689         next;
2690     };
2691
2692     push @todo, {
2693         Commit => $target,
2694     };
2695
2696     while (@todo) {
2697         my $c = shift @todo;
2698         next if $considered{$c->{Commit}}++;
2699
2700         $not->($c, "maximum search space exceeded") if --$max_work <= 0;
2701
2702         printdebug "quiltify investigate $c->{Commit}\n";
2703
2704         # are we done?
2705         if (!quiltify_trees_differ $c->{Commit}, $oldtiptree) {
2706             printdebug " search finished hooray!\n";
2707             $sref_S = $c;
2708             last;
2709         }
2710
2711         if ($quilt_mode eq 'nofix') {
2712             fail "quilt fixup required but quilt mode is \`nofix'\n".
2713                 "HEAD commit $c->{Commit} differs from tree implied by ".
2714                 " debian/patches (tree object $oldtiptree)";
2715         }
2716         if ($quilt_mode eq 'smash') {
2717             printdebug " search quitting smash\n";
2718             last;
2719         }
2720
2721         my $c_sentinels = quiltify_tree_sentinelfiles $c->{Commit};
2722         $not->($c, "has $c_sentinels not $t_sentinels")
2723             if $c_sentinels ne $t_sentinels;
2724
2725         my $commitdata = cmdoutput @git, qw(cat-file commit), $c->{Commit};
2726         $commitdata =~ m/\n\n/;
2727         $commitdata =~ $`;
2728         my @parents = ($commitdata =~ m/^parent (\w+)$/gm);
2729         @parents = map { { Commit => $_, Child => $c } } @parents;
2730
2731         $not->($c, "root commit") if !@parents;
2732
2733         foreach my $p (@parents) {
2734             $p->{Nontrivial}= quiltify_trees_differ $p->{Commit},$c->{Commit};
2735         }
2736         my $ndiffers = grep { $_->{Nontrivial} } @parents;
2737         $not->($c, "merge ($ndiffers nontrivial parents)") if $ndiffers > 1;
2738
2739         foreach my $p (@parents) {
2740             printdebug "considering C=$c->{Commit} P=$p->{Commit}\n";
2741
2742             my @cmd= (@git, qw(diff-tree -r --name-only),
2743                       $p->{Commit},$c->{Commit}, qw(-- debian/patches .pc));
2744             my $patchstackchange = cmdoutput @cmd;
2745             if (length $patchstackchange) {
2746                 $patchstackchange =~ s/\n/,/g;
2747                 $not->($p, "changed $patchstackchange");
2748             }
2749
2750             printdebug " search queue P=$p->{Commit} ",
2751                 ($p->{Nontrivial} ? "NT" : "triv"),"\n";
2752             push @todo, $p;
2753         }
2754     }
2755
2756     if (!$sref_S) {
2757         printdebug "quiltify want to smash\n";
2758
2759         my $abbrev = sub {
2760             my $x = $_[0]{Commit};
2761             $x =~ s/(.*?[0-9a-z]{8})[0-9a-z]*$/$1/;
2762             return $x;
2763         };
2764         my $reportnot = sub {
2765             my ($notp) = @_;
2766             my $s = $abbrev->($notp);
2767             my $c = $notp->{Child};
2768             $s .= "..".$abbrev->($c) if $c;
2769             $s .= ": ".$notp->{Whynot};
2770             return $s;
2771         };
2772         if ($quilt_mode eq 'linear') {
2773             print STDERR "$us: quilt fixup cannot be linear.  Stopped at:\n";
2774             foreach my $notp (@nots) {
2775                 print STDERR "$us:  ", $reportnot->($notp), "\n";
2776             }
2777             print STDERR "$us: $_\n" foreach @$failsuggestion;
2778             fail "quilt fixup naive history linearisation failed.\n".
2779  "Use dpkg-source --commit by hand; or, --quilt=smash for one ugly patch";
2780         } elsif ($quilt_mode eq 'smash') {
2781         } elsif ($quilt_mode eq 'auto') {
2782             progress "quilt fixup cannot be linear, smashing...";
2783         } else {
2784             die "$quilt_mode ?";
2785         }
2786
2787         my $time = time;
2788         my $ncommits = 3;
2789         my $msg = cmdoutput @git, qw(log), "-n$ncommits";
2790
2791         quiltify_dpkg_commit "auto-$version-$target-$time",
2792             (getfield $clogp, 'Maintainer'),
2793             "Automatically generated patch ($clogp->{Version})\n".
2794             "Last (up to) $ncommits git changes, FYI:\n\n". $msg;
2795         return;
2796     }
2797
2798     progress "quiltify linearisation planning successful, executing...";
2799
2800     for (my $p = $sref_S;
2801          my $c = $p->{Child};
2802          $p = $p->{Child}) {
2803         printdebug "quiltify traverse $p->{Commit}..$c->{Commit}\n";
2804         next unless $p->{Nontrivial};
2805
2806         my $cc = $c->{Commit};
2807
2808         my $commitdata = cmdoutput @git, qw(cat-file commit), $cc;
2809         $commitdata =~ m/\n\n/ or die "$c ?";
2810         $commitdata = $`;
2811         my $msg = $'; #';
2812         $commitdata =~ m/^author (.*) \d+ [-+0-9]+$/m or die "$cc ?";
2813         my $author = $1;
2814
2815         $msg =~ s/^(.*)\n*/$1\n/ or die "$cc $msg ?";
2816
2817         my $title = $1;
2818         my $patchname = $title;
2819         $patchname =~ s/[.:]$//;
2820         $patchname =~ y/ A-Z/-a-z/;
2821         $patchname =~ y/-a-z0-9_.+=~//cd;
2822         $patchname =~ s/^\W/x-$&/;
2823         $patchname = substr($patchname,0,40);
2824         my $index;
2825         for ($index='';
2826              stat "debian/patches/$patchname$index";
2827              $index++) { }
2828         $!==ENOENT or die "$patchname$index $!";
2829
2830         runcmd @git, qw(checkout -q), $cc;
2831
2832         # We use the tip's changelog so that dpkg-source doesn't
2833         # produce complaining messages from dpkg-parsechangelog.  None
2834         # of the information dpkg-source gets from the changelog is
2835         # actually relevant - it gets put into the original message
2836         # which dpkg-source provides our stunt editor, and then
2837         # overwritten.
2838         runcmd @git, qw(checkout -q), $target, qw(debian/changelog);
2839
2840         quiltify_dpkg_commit "$patchname$index", $author, $msg,
2841             "X-Dgit-Generated: $clogp->{Version} $cc\n";
2842
2843         runcmd @git, qw(checkout -q), $cc, qw(debian/changelog);
2844     }
2845
2846     runcmd @git, qw(checkout -q master);
2847 }
2848
2849 sub build_maybe_quilt_fixup () {
2850     my ($format,$fopts) = get_source_format;
2851     return unless madformat $format;
2852     # sigh
2853
2854     check_for_vendor_patches();
2855
2856     my $clogp = parsechangelog();
2857     my $headref = git_rev_parse('HEAD');
2858
2859     prep_ud();
2860     changedir $ud;
2861
2862     my $upstreamversion=$version;
2863     $upstreamversion =~ s/-[^-]*$//;
2864
2865     if ($fopts->{'single-debian-patch'}) {
2866         quilt_fixup_singlepatch($clogp, $headref, $upstreamversion);
2867     } else {
2868         quilt_fixup_multipatch($clogp, $headref, $upstreamversion);
2869     }
2870
2871     die 'bug' if $split_brain && !$need_split_build_invocation;
2872
2873     changedir '../../../..';
2874     runcmd_ordryrun_local
2875         @git, qw(pull --ff-only -q .git/dgit/unpack/work master);
2876 }
2877
2878 sub quilt_fixup_mkwork ($) {
2879     my ($headref) = @_;
2880
2881     mkdir "work" or die $!;
2882     changedir "work";
2883     mktree_in_ud_here();
2884     runcmd @git, qw(reset -q --hard), $headref;
2885 }
2886
2887 sub quilt_fixup_linkorigs ($$) {
2888     my ($upstreamversion, $fn) = @_;
2889     # calls $fn->($leafname);
2890
2891     foreach my $f (<../../../../*>) { #/){
2892         my $b=$f; $b =~ s{.*/}{};
2893         {
2894             local ($debuglevel) = $debuglevel-1;
2895             printdebug "QF linkorigs $b, $f ?\n";
2896         }
2897         next unless is_orig_file $b, srcfn $upstreamversion,'';
2898         printdebug "QF linkorigs $b, $f Y\n";
2899         link_ltarget $f, $b or die "$b $!";
2900         $fn->($b);
2901     }
2902 }
2903
2904 sub quilt_fixup_delete_pc () {
2905     runcmd @git, qw(rm -rqf .pc);
2906     commit_admin "Commit removal of .pc (quilt series tracking data)";
2907 }
2908
2909 sub quilt_fixup_singlepatch ($$$) {
2910     my ($clogp, $headref, $upstreamversion) = @_;
2911
2912     progress "starting quiltify (single-debian-patch)";
2913
2914     # dpkg-source --commit generates new patches even if
2915     # single-debian-patch is in debian/source/options.  In order to
2916     # get it to generate debian/patches/debian-changes, it is
2917     # necessary to build the source package.
2918
2919     quilt_fixup_linkorigs($upstreamversion, sub { });
2920     quilt_fixup_mkwork($headref);
2921
2922     rmtree("debian/patches");
2923
2924     runcmd @dpkgsource, qw(-b .);
2925     chdir "..";
2926     runcmd @dpkgsource, qw(-x), (srcfn $version, ".dsc");
2927     rename srcfn("$upstreamversion", "/debian/patches"), 
2928            "work/debian/patches";
2929
2930     chdir "work";
2931     commit_quilty_patch();
2932
2933     
2934 }
2935
2936 sub quilt_fixup_multipatch ($$$) {
2937     my ($clogp, $headref, $upstreamversion) = @_;
2938
2939     progress "starting quiltify (multiple patches, $quilt_mode mode)";
2940
2941     # Our objective is:
2942     #  - honour any existing .pc in case it has any strangeness
2943     #  - determine the git commit corresponding to the tip of
2944     #    the patch stack (if there is one)
2945     #  - if there is such a git commit, convert each subsequent
2946     #    git commit into a quilt patch with dpkg-source --commit
2947     #  - otherwise convert all the differences in the tree into
2948     #    a single git commit
2949     #
2950     # To do this we:
2951
2952     # Our git tree doesn't necessarily contain .pc.  (Some versions of
2953     # dgit would include the .pc in the git tree.)  If there isn't
2954     # one, we need to generate one by unpacking the patches that we
2955     # have.
2956     #
2957     # We first look for a .pc in the git tree.  If there is one, we
2958     # will use it.  (This is not the normal case.)
2959     #
2960     # Otherwise need to regenerate .pc so that dpkg-source --commit
2961     # can work.  We do this as follows:
2962     #     1. Collect all relevant .orig from parent directory
2963     #     2. Generate a debian.tar.gz out of
2964     #         debian/{patches,rules,source/format,source/options}
2965     #     3. Generate a fake .dsc containing just these fields:
2966     #          Format Source Version Files
2967     #     4. Extract the fake .dsc
2968     #        Now the fake .dsc has a .pc directory.
2969     # (In fact we do this in every case, because in future we will
2970     # want to search for a good base commit for generating patches.)
2971     #
2972     # Then we can actually do the dpkg-source --commit
2973     #     1. Make a new working tree with the same object
2974     #        store as our main tree and check out the main
2975     #        tree's HEAD.
2976     #     2. Copy .pc from the fake's extraction, if necessary
2977     #     3. Run dpkg-source --commit
2978     #     4. If the result has changes to debian/, then
2979     #          - git-add them them
2980     #          - git-add .pc if we had a .pc in-tree
2981     #          - git-commit
2982     #     5. If we had a .pc in-tree, delete it, and git-commit
2983     #     6. Back in the main tree, fast forward to the new HEAD
2984
2985     # Another situation we may have to cope with is gbp-style
2986     # patches-unapplied trees.
2987     #
2988     # We would want to detect these, so we know to escape into
2989     # quilt_fixup_gbp.  However, this is in general not possible.
2990     # Consider a package with a one patch which the dgit user reverts
2991     # (with git-revert or the moral equivalent).
2992     #
2993     # That is indistinguishable in contents from a patches-unapplied
2994     # tree.  And looking at the history to distinguish them is not
2995     # useful because the user might have made a confusing-looking git
2996     # history structure (which ought to produce an error if dgit can't
2997     # cope, not a silent reintroduction of an unwanted patch).
2998     #
2999     # So gbp users will have to pass an option.  But we can usually
3000     # detect their failure to do so: if the tree is not a clean
3001     # patches-applied tree, quilt linearisation fails, but the tree
3002     # _is_ a clean patches-unapplied tree, we can suggest that maybe
3003     # they want --quilt=unapplied.
3004     #
3005     # To help detect this, when we are extracting the fake dsc, we
3006     # first extract it with --skip-patches, and then apply the patches
3007     # afterwards with dpkg-source --before-build.  That lets us save a
3008     # tree object corresponding to .origs.
3009
3010     my $fakeversion="$upstreamversion-~~DGITFAKE";
3011
3012     my $fakedsc=new IO::File 'fake.dsc', '>' or die $!;
3013     print $fakedsc <<END or die $!;
3014 Format: 3.0 (quilt)
3015 Source: $package
3016 Version: $fakeversion
3017 Files:
3018 END
3019
3020     my $dscaddfile=sub {
3021         my ($b) = @_;
3022         
3023         my $md = new Digest::MD5;
3024
3025         my $fh = new IO::File $b, '<' or die "$b $!";
3026         stat $fh or die $!;
3027         my $size = -s _;
3028
3029         $md->addfile($fh);
3030         print $fakedsc " ".$md->hexdigest." $size $b\n" or die $!;
3031     };
3032
3033     quilt_fixup_linkorigs($upstreamversion, $dscaddfile);
3034
3035     my @files=qw(debian/source/format debian/rules
3036                  debian/control debian/changelog);
3037     foreach my $maybe (qw(debian/patches debian/source/options
3038                           debian/tests/control)) {
3039         next unless stat_exists "../../../$maybe";
3040         push @files, $maybe;
3041     }
3042
3043     my $debtar= srcfn $fakeversion,'.debian.tar.gz';
3044     runcmd qw(env GZIP=-1 tar -zcf), "./$debtar", qw(-C ../../..), @files;
3045
3046     $dscaddfile->($debtar);
3047     close $fakedsc or die $!;
3048
3049     my $splitbrain_cachekey;
3050     if (quiltmode_splitbrain()) {
3051         # we look in the reflog of dgit-intern/quilt-cache
3052         # we look for an entry whose message is the key for the cache lookup
3053         my @cachekey = (qw(dgit), $our_version);
3054         push @cachekey, $upstreamversion;
3055         push @cachekey, $headref;
3056
3057         push @cachekey, hashfile('fake.dsc');
3058
3059         my $srcshash = Digest::SHA->new(256);
3060         my %sfs = ( %INC, '$0(dgit)' => $0 );
3061         foreach my $sfk (sort keys %sfs) {
3062             $srcshash->add($sfk,"  ");
3063             $srcshash->add(hashfile($sfs{$sfk}));
3064             $srcshash->add("\n");
3065         }
3066         push @cachekey, $srcshash->hexdigest();
3067         $splitbrain_cachekey = "@cachekey";
3068
3069         my @cmd = (@git, qw(reflog), '--pretty=format:%H %gs',
3070                    $splitbraincache);
3071         printdebug "splitbrain cachekey $splitbrain_cachekey\n";
3072         debugcmd "|(probably)",@cmd;
3073         my $child = open GC, "-|";  defined $child or die $!;
3074         if (!$child) {
3075             chdir '../../..' or die $!;
3076             if (!stat ".git/logs/refs/$splitbraincache") {
3077                 $! == ENOENT or die $!;
3078                 printdebug ">(no reflog)\n";
3079                 exit 0;
3080             }
3081             exec @cmd; die $!;
3082         }
3083         while (<GC>) {
3084             chomp;
3085             printdebug ">| ", $_, "\n" if $debuglevel > 1;
3086             next unless m/^(\w+) (\S.*\S)$/ && $2 eq $splitbrain_cachekey;
3087             
3088             my $cachehit = $1;
3089             quilt_fixup_mkwork($headref);
3090             if ($cachehit ne $headref) {
3091                 progress "quilt fixup ($quilt_mode mode) found cached tree";
3092                 runcmd @git, qw(checkout -q -b dgit-view), $cachehit;
3093                 $split_brain = 1;
3094                 return;
3095             }
3096             progress "quilt fixup ($quilt_mode mode)".
3097               " found cached indication that no changes needed";
3098             return;
3099         }
3100         die $! if GC->error;
3101         failedcmd unless close GC;
3102
3103         printdebug "splitbrain cache miss\n";
3104     }
3105
3106     runcmd qw(sh -ec),
3107         'exec dpkg-source --no-check --skip-patches -x fake.dsc >/dev/null';
3108
3109     my $fakexdir= $package.'-'.(stripepoch $upstreamversion);
3110     rename $fakexdir, "fake" or die "$fakexdir $!";
3111
3112     changedir 'fake';
3113
3114     remove_stray_gits();
3115     mktree_in_ud_here();
3116
3117     rmtree '.pc';
3118
3119     runcmd @git, qw(add -Af .);
3120     my $unapplied=git_write_tree();
3121     printdebug "fake orig tree object $unapplied\n";
3122
3123     ensuredir '.pc';
3124
3125     runcmd qw(sh -ec),
3126         'exec dpkg-source --before-build . >/dev/null';
3127
3128     changedir '..';
3129
3130     quilt_fixup_mkwork($headref);
3131
3132     my $mustdeletepc=0;
3133     if (stat_exists ".pc") {
3134         -d _ or die;
3135         progress "Tree already contains .pc - will use it then delete it.";
3136         $mustdeletepc=1;
3137     } else {
3138         rename '../fake/.pc','.pc' or die $!;
3139     }
3140
3141     changedir '../fake';
3142     rmtree '.pc';
3143     runcmd @git, qw(add -Af .);
3144     my $oldtiptree=git_write_tree();
3145     printdebug "fake o+d/p tree object $unapplied\n";
3146     changedir '../work';
3147
3148
3149     # We calculate some guesswork now about what kind of tree this might
3150     # be.  This is mostly for error reporting.
3151
3152     my %editedignores;
3153     my $diffbits = {
3154         # H = user's HEAD
3155         # O = orig, without patches applied
3156         # A = "applied", ie orig with H's debian/patches applied
3157         H2O => quiltify_trees_differ($headref,  $unapplied, 1,\%editedignores),
3158         H2A => quiltify_trees_differ($headref,  $oldtiptree,1),
3159         O2A => quiltify_trees_differ($unapplied,$oldtiptree,1),
3160     };
3161
3162     my @dl;
3163     foreach my $b (qw(01 02)) {
3164         foreach my $v (qw(H2O O2A H2A)) {
3165             push @dl, ($diffbits->{$v} & $b) ? '##' : '==';
3166         }
3167     }
3168     printdebug "differences \@dl @dl.\n";
3169
3170     progress sprintf
3171 "$us: quilt differences: src:  %s orig %s     gitignores:  %s orig %s\n".
3172 "$us: quilt differences:      HEAD %s o+d/p               HEAD %s o+d/p",
3173                              $dl[0], $dl[1],              $dl[3], $dl[4],
3174                                  $dl[2],                     $dl[5];
3175
3176     my @failsuggestion;
3177     if (!($diffbits->{H2O} & $diffbits->{O2A})) {
3178         push @failsuggestion, "This might be a patches-unapplied branch.";
3179     }  elsif (!($diffbits->{H2A} & $diffbits->{O2A})) {
3180         push @failsuggestion, "This might be a patches-applied branch.";
3181     }
3182     push @failsuggestion, "Maybe you need to specify one of".
3183         " --quilt=gbp --quilt=dpm --quilt=unapplied ?";
3184
3185     if (quiltmode_splitbrain()) {
3186         quiltify_splitbrain($clogp, $unapplied, $headref,
3187                             $diffbits, \%editedignores,
3188                             $splitbrain_cachekey);
3189         return;
3190     }
3191
3192     quiltify($clogp,$headref,$oldtiptree,\@failsuggestion);
3193
3194     if (!open P, '>>', ".pc/applied-patches") {
3195         $!==&ENOENT or die $!;
3196     } else {
3197         close P;
3198     }
3199
3200     commit_quilty_patch();
3201
3202     if ($mustdeletepc) {
3203         quilt_fixup_delete_pc();
3204     }
3205 }
3206
3207 sub quilt_fixup_editor () {
3208     my $descfn = $ENV{$fakeeditorenv};
3209     my $editing = $ARGV[$#ARGV];
3210     open I1, '<', $descfn or die "$descfn: $!";
3211     open I2, '<', $editing or die "$editing: $!";
3212     unlink $editing or die "$editing: $!";
3213     open O, '>', $editing or die "$editing: $!";
3214     while (<I1>) { print O or die $!; } I1->error and die $!;
3215     my $copying = 0;
3216     while (<I2>) {
3217         $copying ||= m/^\-\-\- /;
3218         next unless $copying;
3219         print O or die $!;
3220     }
3221     I2->error and die $!;
3222     close O or die $1;
3223     exit 0;
3224 }
3225
3226 #----- other building -----
3227
3228 our $clean_using_builder;
3229 # ^ tree is to be cleaned by dpkg-source's builtin idea that it should
3230 #   clean the tree before building (perhaps invoked indirectly by
3231 #   whatever we are using to run the build), rather than separately
3232 #   and explicitly by us.
3233
3234 sub clean_tree () {
3235     return if $clean_using_builder;
3236     if ($cleanmode eq 'dpkg-source') {
3237         runcmd_ordryrun_local @dpkgbuildpackage, qw(-T clean);
3238     } elsif ($cleanmode eq 'dpkg-source-d') {
3239         runcmd_ordryrun_local @dpkgbuildpackage, qw(-d -T clean);
3240     } elsif ($cleanmode eq 'git') {
3241         runcmd_ordryrun_local @git, qw(clean -xdf);
3242     } elsif ($cleanmode eq 'git-ff') {
3243         runcmd_ordryrun_local @git, qw(clean -xdff);
3244     } elsif ($cleanmode eq 'check') {
3245         my $leftovers = cmdoutput @git, qw(clean -xdn);
3246         if (length $leftovers) {
3247             print STDERR $leftovers, "\n" or die $!;
3248             fail "tree contains uncommitted files and --clean=check specified";
3249         }
3250     } elsif ($cleanmode eq 'none') {
3251     } else {
3252         die "$cleanmode ?";
3253     }
3254 }
3255
3256 sub cmd_clean () {
3257     badusage "clean takes no additional arguments" if @ARGV;
3258     notpushing();
3259     clean_tree();
3260 }
3261
3262 sub build_prep () {
3263     notpushing();
3264     badusage "-p is not allowed when building" if defined $package;
3265     check_not_dirty();
3266     clean_tree();
3267     my $clogp = parsechangelog();
3268     $isuite = getfield $clogp, 'Distribution';
3269     $package = getfield $clogp, 'Source';
3270     $version = getfield $clogp, 'Version';
3271     build_maybe_quilt_fixup();
3272     if ($rmchanges) {
3273         my $pat = changespat $version;
3274         foreach my $f (glob "$buildproductsdir/$pat") {
3275             if (act_local()) {
3276                 unlink $f or fail "remove old changes file $f: $!";
3277             } else {
3278                 progress "would remove $f";
3279             }
3280         }
3281     }
3282 }
3283
3284 sub changesopts_initial () {
3285     my @opts =@changesopts[1..$#changesopts];
3286 }
3287
3288 sub changesopts_version () {
3289     if (!defined $changes_since_version) {
3290         my @vsns = archive_query('archive_query');
3291         my @quirk = access_quirk();
3292         if ($quirk[0] eq 'backports') {
3293             local $isuite = $quirk[2];
3294             local $csuite;
3295             canonicalise_suite();
3296             push @vsns, archive_query('archive_query');
3297         }
3298         if (@vsns) {
3299             @vsns = map { $_->[0] } @vsns;
3300             @vsns = sort { -version_compare($a, $b) } @vsns;
3301             $changes_since_version = $vsns[0];
3302             progress "changelog will contain changes since $vsns[0]";
3303         } else {
3304             $changes_since_version = '_';
3305             progress "package seems new, not specifying -v<version>";
3306         }
3307     }
3308     if ($changes_since_version ne '_') {
3309         return ("-v$changes_since_version");
3310     } else {
3311         return ();
3312     }
3313 }
3314
3315 sub changesopts () {
3316     return (changesopts_initial(), changesopts_version());
3317 }
3318
3319 sub massage_dbp_args ($;$) {
3320     my ($cmd,$xargs) = @_;
3321     # We need to:
3322     #
3323     #  - if we're going to split the source build out so we can
3324     #    do strange things to it, massage the arguments to dpkg-buildpackage
3325     #    so that the main build doessn't build source (or add an argument
3326     #    to stop it building source by default).
3327     #
3328     #  - add -nc to stop dpkg-source cleaning the source tree,
3329     #    unless we're not doing a split build and want dpkg-source
3330     #    as cleanmode, in which case we can do nothing
3331     #
3332     # return values:
3333     #    0 - source will NOT need to be built separately by caller
3334     #   +1 - source will need to be built separately by caller
3335     #   +2 - source will need to be built separately by caller AND
3336     #        dpkg-buildpackage should not in fact be run at all!
3337     debugcmd '#massaging#', @$cmd if $debuglevel>1;
3338 #print STDERR "MASS0 ",Dumper($cmd, $xargs, $need_split_build_invocation);
3339     if ($cleanmode eq 'dpkg-source' && !$need_split_build_invocation) {
3340         $clean_using_builder = 1;
3341         return 0;
3342     }
3343     # -nc has the side effect of specifying -b if nothing else specified
3344     # and some combinations of -S, -b, et al, are errors, rather than
3345     # later simply overriding earlie.  So we need to:
3346     #  - search the command line for these options
3347     #  - pick the last one
3348     #  - perhaps add our own as a default
3349     #  - perhaps adjust it to the corresponding non-source-building version
3350     my $dmode = '-F';
3351     foreach my $l ($cmd, $xargs) {
3352         next unless $l;
3353         @$l = grep { !(m/^-[SgGFABb]$/s and $dmode=$_) } @$l;
3354     }
3355     push @$cmd, '-nc';
3356 #print STDERR "MASS1 ",Dumper($cmd, $xargs, $dmode);
3357     my $r = 0;
3358     if ($need_split_build_invocation) {
3359         $r = $dmode =~ m/[S]/     ? +2 :
3360              $dmode =~ y/gGF/ABb/ ? +1 :
3361              $dmode =~ m/[ABb]/   ?  0 :
3362              die "$dmode ?";
3363     }
3364     push @$cmd, $dmode;
3365 #print STDERR "MASS2 ",Dumper($cmd, $xargs, $r);
3366     return $r;
3367 }
3368
3369 sub cmd_build {
3370     my @dbp = (@dpkgbuildpackage, qw(-us -uc), changesopts_initial(), @ARGV);
3371     my $wantsrc = massage_dbp_args \@dbp;
3372     if ($wantsrc > 0) {
3373         build_source();
3374     } else {
3375         build_prep();
3376     }
3377     if ($wantsrc < 2) {
3378         push @dbp, changesopts_version();
3379         runcmd_ordryrun_local @dbp;
3380     }
3381     printdone "build successful\n";
3382 }
3383
3384 sub cmd_gbp_build {
3385     my @dbp = @dpkgbuildpackage;
3386
3387     my $wantsrc = massage_dbp_args \@dbp, \@ARGV;
3388
3389     my @cmd;
3390     if (length executable_on_path('git-buildpackage')) {
3391         @cmd = qw(git-buildpackage);
3392     } else {
3393         @cmd = qw(gbp buildpackage);
3394     }
3395     push @cmd, (qw(-us -uc --git-no-sign-tags), "--git-builder=@dbp");
3396
3397     if ($wantsrc > 0) {
3398         build_source();
3399     } else {
3400         if (!$clean_using_builder) {
3401             push @cmd, '--git-cleaner=true';
3402         }
3403         build_prep();
3404     }
3405     if ($wantsrc < 2) {
3406         unless (grep { m/^--git-debian-branch|^--git-ignore-branch/ } @ARGV) {
3407             canonicalise_suite();
3408             push @cmd, "--git-debian-branch=".lbranch();
3409         }
3410         push @cmd, changesopts();
3411         runcmd_ordryrun_local @cmd, @ARGV;
3412     }
3413     printdone "build successful\n";
3414 }
3415 sub cmd_git_build { cmd_gbp_build(); } # compatibility with <= 1.0
3416
3417 sub build_source {
3418     if ($cleanmode =~ m/^dpkg-source/) {
3419         # dpkg-source will clean, so we shouldn't
3420         $clean_using_builder = 1;
3421     }
3422     build_prep();
3423     $sourcechanges = changespat $version,'source';
3424     if (act_local()) {
3425         unlink "../$sourcechanges" or $!==ENOENT
3426             or fail "remove $sourcechanges: $!";
3427     }
3428     $dscfn = dscfn($version);
3429     if ($cleanmode eq 'dpkg-source') {
3430         runcmd_ordryrun_local @dpkgbuildpackage, qw(-us -uc -S),
3431                                changesopts();
3432     } elsif ($cleanmode eq 'dpkg-source-d') {
3433         runcmd_ordryrun_local @dpkgbuildpackage, qw(-us -uc -S -d),
3434                                changesopts();
3435     } else {
3436         my $pwd = must_getcwd();
3437         my $leafdir = basename $pwd;
3438         changedir "..";
3439         runcmd_ordryrun_local @dpkgsource, qw(-b --), $leafdir;
3440         changedir $pwd;
3441         runcmd_ordryrun_local qw(sh -ec),
3442             'exec >$1; shift; exec "$@"','x',
3443             "../$sourcechanges",
3444             @dpkggenchanges, qw(-S), changesopts();
3445     }
3446 }
3447
3448 sub cmd_build_source {
3449     badusage "build-source takes no additional arguments" if @ARGV;
3450     build_source();
3451     printdone "source built, results in $dscfn and $sourcechanges";
3452 }
3453
3454 sub cmd_sbuild {
3455     build_source();
3456     my $pat = changespat $version;
3457     if (!$rmchanges) {
3458         my @unwanted = map { s#^\.\./##; $_; } glob "../$pat";
3459         @unwanted = grep { $_ ne changespat $version,'source' } @unwanted;
3460         fail "changes files other than source matching $pat".
3461             " already present (@unwanted);".
3462             " building would result in ambiguity about the intended results"
3463             if @unwanted;
3464     }
3465     changedir "..";
3466     if (act_local()) {
3467         stat_exists $dscfn or fail "$dscfn (in parent directory): $!";
3468         stat_exists $sourcechanges
3469             or fail "$sourcechanges (in parent directory): $!";
3470     }
3471     runcmd_ordryrun_local @sbuild, qw(-d), $isuite, @ARGV, $dscfn;
3472     my @changesfiles = glob $pat;
3473     @changesfiles = sort {
3474         ($b =~ m/_source\.changes$/ <=> $a =~ m/_source\.changes$/)
3475             or $a cmp $b
3476     } @changesfiles;
3477     fail "wrong number of different changes files (@changesfiles)"
3478         unless @changesfiles==2;
3479     my $binchanges = parsecontrol($changesfiles[1], "binary changes file");
3480     foreach my $l (split /\n/, getfield $binchanges, 'Files') {
3481         fail "$l found in binaries changes file $binchanges"
3482             if $l =~ m/\.dsc$/;
3483     }
3484     runcmd_ordryrun_local @mergechanges, @changesfiles;
3485     my $multichanges = changespat $version,'multi';
3486     if (act_local()) {
3487         stat_exists $multichanges or fail "$multichanges: $!";
3488         foreach my $cf (glob $pat) {
3489             next if $cf eq $multichanges;
3490             rename "$cf", "$cf.inmulti" or fail "$cf\{,.inmulti}: $!";
3491         }
3492     }
3493     printdone "build successful, results in $multichanges\n" or die $!;
3494 }    
3495
3496 sub cmd_quilt_fixup {
3497     badusage "incorrect arguments to dgit quilt-fixup" if @ARGV;
3498     my $clogp = parsechangelog();
3499     $version = getfield $clogp, 'Version';
3500     $package = getfield $clogp, 'Source';
3501     check_not_dirty();
3502     clean_tree();
3503     build_maybe_quilt_fixup();
3504 }
3505
3506 sub cmd_archive_api_query {
3507     badusage "need only 1 subpath argument" unless @ARGV==1;
3508     my ($subpath) = @ARGV;
3509     my @cmd = archive_api_query_cmd($subpath);
3510     debugcmd ">",@cmd;
3511     exec @cmd or fail "exec curl: $!\n";
3512 }
3513
3514 sub cmd_clone_dgit_repos_server {
3515     badusage "need destination argument" unless @ARGV==1;
3516     my ($destdir) = @ARGV;
3517     $package = '_dgit-repos-server';
3518     my @cmd = (@git, qw(clone), access_giturl(), $destdir);
3519     debugcmd ">",@cmd;
3520     exec @cmd or fail "exec git clone: $!\n";
3521 }
3522
3523 sub cmd_setup_mergechangelogs {
3524     badusage "no arguments allowed to dgit setup-mergechangelogs" if @ARGV;
3525     setup_mergechangelogs(1);
3526 }
3527
3528 sub cmd_setup_useremail {
3529     badusage "no arguments allowed to dgit setup-mergechangelogs" if @ARGV;
3530     setup_useremail(1);
3531 }
3532
3533 sub cmd_setup_new_tree {
3534     badusage "no arguments allowed to dgit setup-tree" if @ARGV;
3535     setup_new_tree();
3536 }
3537
3538 #---------- argument parsing and main program ----------
3539
3540 sub cmd_version {
3541     print "dgit version $our_version\n" or die $!;
3542     exit 0;
3543 }
3544
3545 our (%valopts_long, %valopts_short);
3546 our @rvalopts;
3547
3548 sub defvalopt ($$$$) {
3549     my ($long,$short,$val_re,$how) = @_;
3550     my $oi = { Long => $long, Short => $short, Re => $val_re, How => $how };
3551     $valopts_long{$long} = $oi;
3552     $valopts_short{$short} = $oi;
3553     # $how subref should:
3554     #   do whatever assignemnt or thing it likes with $_[0]
3555     #   if the option should not be passed on to remote, @rvalopts=()
3556     # or $how can be a scalar ref, meaning simply assign the value
3557 }
3558
3559 defvalopt '--since-version', '-v', '[^_]+|_', \$changes_since_version;
3560 defvalopt '--distro',        '-d', '.+',      \$idistro;
3561 defvalopt '',                '-k', '.+',      \$keyid;
3562 defvalopt '--existing-package','', '.*',      \$existing_package;
3563 defvalopt '--build-products-dir','','.*',     \$buildproductsdir;
3564 defvalopt '--clean',       '', $cleanmode_re, \$cleanmode;
3565 defvalopt '--quilt',     '', $quilt_modes_re, \$quilt_mode;
3566
3567 defvalopt '', '-c', '.*=.*', sub { push @git, '-c', @_; };
3568
3569 defvalopt '', '-C', '.+', sub {
3570     ($changesfile) = (@_);
3571     if ($changesfile =~ s#^(.*)/##) {
3572         $buildproductsdir = $1;
3573     }
3574 };
3575
3576 defvalopt '--initiator-tempdir','','.*', sub {
3577     ($initiator_tempdir) = (@_);
3578     $initiator_tempdir =~ m#^/# or
3579         badusage "--initiator-tempdir must be used specify an".
3580         " absolute, not relative, directory."
3581 };
3582
3583 sub parseopts () {
3584     my $om;
3585
3586     if (defined $ENV{'DGIT_SSH'}) {
3587         @ssh = string_to_ssh $ENV{'DGIT_SSH'};
3588     } elsif (defined $ENV{'GIT_SSH'}) {
3589         @ssh = ($ENV{'GIT_SSH'});
3590     }
3591
3592     my $oi;
3593     my $val;
3594     my $valopt = sub {
3595         my ($what) = @_;
3596         @rvalopts = ($_);
3597         if (!defined $val) {
3598             badusage "$what needs a value" unless @ARGV;
3599             $val = shift @ARGV;
3600             push @rvalopts, $val;
3601         }
3602         badusage "bad value \`$val' for $what" unless
3603             $val =~ m/^$oi->{Re}$(?!\n)/s;
3604         my $how = $oi->{How};
3605         if (ref($how) eq 'SCALAR') {
3606             $$how = $val;
3607         } else {
3608             $how->($val);
3609         }
3610         push @ropts, @rvalopts;
3611     };
3612
3613     while (@ARGV) {
3614         last unless $ARGV[0] =~ m/^-/;
3615         $_ = shift @ARGV;
3616         last if m/^--?$/;
3617         if (m/^--/) {
3618             if (m/^--dry-run$/) {
3619                 push @ropts, $_;
3620                 $dryrun_level=2;
3621             } elsif (m/^--damp-run$/) {
3622                 push @ropts, $_;
3623                 $dryrun_level=1;
3624             } elsif (m/^--no-sign$/) {
3625                 push @ropts, $_;
3626                 $sign=0;
3627             } elsif (m/^--help$/) {
3628                 cmd_help();
3629             } elsif (m/^--version$/) {
3630                 cmd_version();
3631             } elsif (m/^--new$/) {
3632                 push @ropts, $_;
3633                 $new_package=1;
3634             } elsif (m/^--([-0-9a-z]+)=(.+)/s &&
3635                      ($om = $opts_opt_map{$1}) &&
3636                      length $om->[0]) {
3637                 push @ropts, $_;
3638                 $om->[0] = $2;
3639             } elsif (m/^--([-0-9a-z]+):(.*)/s &&
3640                      !$opts_opt_cmdonly{$1} &&
3641                      ($om = $opts_opt_map{$1})) {
3642                 push @ropts, $_;
3643                 push @$om, $2;
3644             } elsif (m/^--ignore-dirty$/s) {
3645                 push @ropts, $_;
3646                 $ignoredirty = 1;
3647             } elsif (m/^--no-quilt-fixup$/s) {
3648                 push @ropts, $_;
3649                 $quilt_mode = 'nocheck';
3650             } elsif (m/^--no-rm-on-error$/s) {
3651                 push @ropts, $_;
3652                 $rmonerror = 0;
3653             } elsif (m/^--(no-)?rm-old-changes$/s) {
3654                 push @ropts, $_;
3655                 $rmchanges = !$1;
3656             } elsif (m/^--deliberately-($deliberately_re)$/s) {
3657                 push @ropts, $_;
3658                 push @deliberatelies, $&;
3659             } elsif (m/^--always-split-source-build$/s) {
3660                 # undocumented, for testing
3661                 push @ropts, $_;
3662                 $need_split_build_invocation = 1;
3663             } elsif (m/^(--[-0-9a-z]+)(=|$)/ && ($oi = $valopts_long{$1})) {
3664                 $val = $2 ? $' : undef; #';
3665                 $valopt->($oi->{Long});
3666             } else {
3667                 badusage "unknown long option \`$_'";
3668             }
3669         } else {
3670             while (m/^-./s) {
3671                 if (s/^-n/-/) {
3672                     push @ropts, $&;
3673                     $dryrun_level=2;
3674                 } elsif (s/^-L/-/) {
3675                     push @ropts, $&;
3676                     $dryrun_level=1;
3677                 } elsif (s/^-h/-/) {
3678                     cmd_help();
3679                 } elsif (s/^-D/-/) {
3680                     push @ropts, $&;
3681                     $debuglevel++;
3682                     enabledebug();
3683                 } elsif (s/^-N/-/) {
3684                     push @ropts, $&;
3685                     $new_package=1;
3686                 } elsif (m/^-m/) {
3687                     push @ropts, $&;
3688                     push @changesopts, $_;
3689                     $_ = '';
3690                 } elsif (s/^-wn$//s) {
3691                     push @ropts, $&;
3692                     $cleanmode = 'none';
3693                 } elsif (s/^-wg$//s) {
3694                     push @ropts, $&;
3695                     $cleanmode = 'git';
3696                 } elsif (s/^-wgf$//s) {
3697                     push @ropts, $&;
3698                     $cleanmode = 'git-ff';
3699                 } elsif (s/^-wd$//s) {
3700                     push @ropts, $&;
3701                     $cleanmode = 'dpkg-source';
3702                 } elsif (s/^-wdd$//s) {
3703                     push @ropts, $&;
3704                     $cleanmode = 'dpkg-source-d';
3705                 } elsif (s/^-wc$//s) {
3706                     push @ropts, $&;
3707                     $cleanmode = 'check';
3708                 } elsif (m/^-[a-zA-Z]/ && ($oi = $valopts_short{$&})) {
3709                     $val = $'; #';
3710                     $val = undef unless length $val;
3711                     $valopt->($oi->{Short});
3712                     $_ = '';
3713                 } else {
3714                     badusage "unknown short option \`$_'";
3715                 }
3716             }
3717         }
3718     }
3719 }
3720
3721 sub finalise_opts_opts () {
3722     foreach my $k (keys %opts_opt_map) {
3723         my $om = $opts_opt_map{$k};
3724
3725         my $v = access_cfg("cmd-$k", 'RETURN-UNDEF');
3726         if (defined $v) {
3727             badcfg "cannot set command for $k"
3728                 unless length $om->[0];
3729             $om->[0] = $v;
3730         }
3731
3732         foreach my $c (access_cfg_cfgs("opts-$k")) {
3733             my $vl = $gitcfg{$c};
3734             printdebug "CL $c ",
3735                 ($vl ? join " ", map { shellquote } @$vl : ""),
3736                 "\n" if $debuglevel >= 4;
3737             next unless $vl;
3738             badcfg "cannot configure options for $k"
3739                 if $opts_opt_cmdonly{$k};
3740             my $insertpos = $opts_cfg_insertpos{$k};
3741             @$om = ( @$om[0..$insertpos-1],
3742                      @$vl,
3743                      @$om[$insertpos..$#$om] );
3744         }
3745     }