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