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