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