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