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