3 # Integration between git and Debian-style archives
5 # Copyright (C)2013-2017 Ian Jackson
6 # Copyright (C)2017 Sean Whitton
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 END { $? = $Debian::Dgit::ExitStatus::desired // -1; };
22 use Debian::Dgit::ExitStatus;
26 use Debian::Dgit qw(:DEFAULT :playground);
32 use Dpkg::Control::Hash;
34 use File::Temp qw(tempdir);
37 use Dpkg::Compression;
38 use Dpkg::Compression::Process;
43 use List::MoreUtils qw(pairwise);
44 use Text::Glob qw(match_glob);
45 use Fcntl qw(:DEFAULT :flock);
50 our $our_version = 'UNRELEASED'; ###substituted###
51 our $absurdity = undef; ###substituted###
53 our @rpushprotovsn_support = qw(4 3 2); # 4 is new tag format
64 our $dryrun_level = 0;
66 our $buildproductsdir = '..';
72 our $existing_package = 'dpkg';
74 our $changes_since_version;
76 our $overwrite_version; # undef: not specified; '': check changelog
78 our $quilt_modes_re = 'linear|smash|auto|nofix|nocheck|gbp|dpm|unapplied';
80 our $split_brain_save;
81 our $we_are_responder;
82 our $we_are_initiator;
83 our $initiator_tempdir;
84 our $patches_applied_dirtily = 00;
88 our $chase_dsc_distro=1;
90 our %forceopts = map { $_=>0 }
91 qw(unrepresentable unsupported-source-format
92 dsc-changes-mismatch changes-origs-exactly
93 uploading-binaries uploading-source-only
94 import-gitapply-absurd
95 import-gitapply-no-absurd
96 import-dsc-with-dgit-field);
98 our %format_ok = map { $_=>1 } ("1.0","3.0 (native)","3.0 (quilt)");
100 our $suite_re = '[-+.0-9a-z]+';
101 our $cleanmode_re = 'dpkg-source(?:-d)?|git|git-ff|check|none';
102 our $orig_f_comp_re = qr{orig(?:-$extra_orig_namepart_re)?};
103 our $orig_f_sig_re = '\\.(?:asc|gpg|pgp)';
104 our $orig_f_tail_re = "$orig_f_comp_re\\.tar(?:\\.\\w+)?(?:$orig_f_sig_re)?";
106 our $git_authline_re = '^([^<>]+) \<(\S+)\> (\d+ [-+]\d+)$';
107 our $splitbraincache = 'dgit-intern/quilt-cache';
108 our $rewritemap = 'dgit-rewrite/map';
110 our @dpkg_source_ignores = qw(-i(?:^|/)\.git(?:/|$) -I.git);
112 our (@git) = qw(git);
113 our (@dget) = qw(dget);
114 our (@curl) = (qw(curl --proto-redir), '-all,http,https', qw(-L));
115 our (@dput) = qw(dput);
116 our (@debsign) = qw(debsign);
117 our (@gpg) = qw(gpg);
118 our (@sbuild) = qw(sbuild);
120 our (@dgit) = qw(dgit);
121 our (@git_debrebase) = qw(git-debrebase);
122 our (@aptget) = qw(apt-get);
123 our (@aptcache) = qw(apt-cache);
124 our (@dpkgbuildpackage) = (qw(dpkg-buildpackage), @dpkg_source_ignores);
125 our (@dpkgsource) = (qw(dpkg-source), @dpkg_source_ignores);
126 our (@dpkggenchanges) = qw(dpkg-genchanges);
127 our (@mergechanges) = qw(mergechanges -f);
128 our (@gbp_build) = ('');
129 our (@gbp_pq) = ('gbp pq');
130 our (@changesopts) = ('');
132 our %opts_opt_map = ('dget' => \@dget, # accept for compatibility
135 'debsign' => \@debsign,
137 'sbuild' => \@sbuild,
141 'git-debrebase' => \@git_debrebase,
142 'apt-get' => \@aptget,
143 'apt-cache' => \@aptcache,
144 'dpkg-source' => \@dpkgsource,
145 'dpkg-buildpackage' => \@dpkgbuildpackage,
146 'dpkg-genchanges' => \@dpkggenchanges,
147 'gbp-build' => \@gbp_build,
148 'gbp-pq' => \@gbp_pq,
149 'ch' => \@changesopts,
150 'mergechanges' => \@mergechanges);
152 our %opts_opt_cmdonly = ('gpg' => 1, 'git' => 1);
153 our %opts_cfg_insertpos = map {
155 scalar @{ $opts_opt_map{$_} }
156 } keys %opts_opt_map;
158 sub parseopts_late_defaults();
159 sub setup_gitattrs(;$);
160 sub check_gitattrs($$);
167 our $supplementary_message = '';
168 our $need_split_build_invocation = 0;
169 our $split_brain = 0;
173 return unless forkcheck_mainprocess();
174 print STDERR "! $_\n" foreach $supplementary_message =~ m/^.+$/mg;
177 our $remotename = 'dgit';
178 our @ourdscfield = qw(Dgit Vcs-Dgit-Master);
182 if (!defined $absurdity) {
184 $absurdity =~ s{/[^/]+$}{/absurd} or die;
188 my ($v,$distro) = @_;
189 return $tagformatfn->($v, $distro);
192 sub debiantag_maintview ($$) {
193 my ($v,$distro) = @_;
194 return "$distro/".dep14_version_mangle $v;
197 sub madformat ($) { $_[0] eq '3.0 (quilt)' }
199 sub lbranch () { return "$branchprefix/$csuite"; }
200 my $lbranch_re = '^refs/heads/'.$branchprefix.'/([^/.]+)$';
201 sub lref () { return "refs/heads/".lbranch(); }
202 sub lrref () { return "refs/remotes/$remotename/".server_branch($csuite); }
203 sub rrref () { return server_ref($csuite); }
213 return "${package}_".(stripepoch $vsn).$sfx
218 return srcfn($vsn,".dsc");
221 sub changespat ($;$) {
222 my ($vsn, $arch) = @_;
223 return "${package}_".(stripepoch $vsn)."_".($arch//'*').".changes";
226 sub upstreamversion ($) {
238 return unless forkcheck_mainprocess();
239 foreach my $f (@end) {
241 print STDERR "$us: cleanup: $@" if length $@;
245 sub badcfg { print STDERR "$us: invalid configuration: @_\n"; finish 12; }
247 sub forceable_fail ($$) {
248 my ($forceoptsl, $msg) = @_;
249 fail $msg unless grep { $forceopts{$_} } @$forceoptsl;
250 print STDERR "warning: overriding problem due to --force:\n". $msg;
254 my ($forceoptsl) = @_;
255 my @got = grep { $forceopts{$_} } @$forceoptsl;
256 return 0 unless @got;
258 "warning: skipping checks or functionality due to --force-$got[0]\n";
261 sub no_such_package () {
262 print STDERR "$us: package $package does not exist in suite $isuite\n";
266 sub deliberately ($) {
268 return !!grep { $_ eq "--deliberately-$enquiry" } @deliberatelies;
271 sub deliberately_not_fast_forward () {
272 foreach (qw(not-fast-forward fresh-repo)) {
273 return 1 if deliberately($_) || deliberately("TEST-dgit-only-$_");
277 sub quiltmode_splitbrain () {
278 $quilt_mode =~ m/gbp|dpm|unapplied/;
281 sub opts_opt_multi_cmd {
283 push @cmd, split /\s+/, shift @_;
289 return opts_opt_multi_cmd @gbp_pq;
292 sub dgit_privdir () {
293 our $dgit_privdir_made //= ensure_a_playground 'dgit';
296 sub branch_gdr_info ($$) {
297 my ($symref, $head) = @_;
298 my ($status, $msg, $current, $ffq_prev, $gdrlast) =
299 gdr_ffq_prev_branchinfo($symref);
300 return () unless $status eq 'branch';
301 $ffq_prev = git_get_ref $ffq_prev;
302 $gdrlast = git_get_ref $gdrlast;
303 $gdrlast &&= is_fast_fwd $gdrlast, $head;
304 return ($ffq_prev, $gdrlast);
307 sub branch_is_gdr ($$) {
308 my ($symref, $head) = @_;
309 my ($ffq_prev, $gdrlast) = branch_gdr_info($symref, $head);
310 return 0 unless $ffq_prev || $gdrlast;
314 sub branch_is_gdr_unstitched_ff ($$$) {
315 my ($symref, $head, $ancestor) = @_;
316 my ($ffq_prev, $gdrlast) = branch_gdr_info($symref, $head);
317 return 0 unless $ffq_prev;
318 return 0 unless is_fast_fwd $ancestor, $ffq_prev;
322 #---------- remote protocol support, common ----------
324 # remote push initiator/responder protocol:
325 # $ dgit remote-push-build-host <n-rargs> <rargs>... <push-args>...
326 # where <rargs> is <push-host-dir> <supported-proto-vsn>,... ...
327 # < dgit-remote-push-ready <actual-proto-vsn>
334 # > supplementary-message NBYTES # $protovsn >= 3
339 # > file parsed-changelog
340 # [indicates that output of dpkg-parsechangelog follows]
341 # > data-block NBYTES
342 # > [NBYTES bytes of data (no newline)]
343 # [maybe some more blocks]
352 # > param head DGIT-VIEW-HEAD
353 # > param csuite SUITE
354 # > param tagformat old|new
355 # > param maint-view MAINT-VIEW-HEAD
357 # > param buildinfo-filename P_V_X.buildinfo # zero or more times
358 # > file buildinfo # for buildinfos to sign
360 # > previously REFNAME=OBJNAME # if --deliberately-not-fast-forward
361 # # goes into tag, for replay prevention
364 # [indicates that signed tag is wanted]
365 # < data-block NBYTES
366 # < [NBYTES bytes of data (no newline)]
367 # [maybe some more blocks]
371 # > want signed-dsc-changes
372 # < data-block NBYTES [transfer of signed dsc]
374 # < data-block NBYTES [transfer of signed changes]
376 # < data-block NBYTES [transfer of each signed buildinfo
377 # [etc] same number and order as "file buildinfo"]
385 sub i_child_report () {
386 # Sees if our child has died, and reap it if so. Returns a string
387 # describing how it died if it failed, or undef otherwise.
388 return undef unless $i_child_pid;
389 my $got = waitpid $i_child_pid, WNOHANG;
390 return undef if $got <= 0;
391 die unless $got == $i_child_pid;
392 $i_child_pid = undef;
393 return undef unless $?;
394 return "build host child ".waitstatusmsg();
399 fail "connection lost: $!" if $fh->error;
400 fail "protocol violation; $m not expected";
403 sub badproto_badread ($$) {
405 fail "connection lost: $!" if $!;
406 my $report = i_child_report();
407 fail $report if defined $report;
408 badproto $fh, "eof (reading $wh)";
411 sub protocol_expect (&$) {
412 my ($match, $fh) = @_;
415 defined && chomp or badproto_badread $fh, "protocol message";
423 badproto $fh, "\`$_'";
426 sub protocol_send_file ($$) {
427 my ($fh, $ourfn) = @_;
428 open PF, "<", $ourfn or die "$ourfn: $!";
431 my $got = read PF, $d, 65536;
432 die "$ourfn: $!" unless defined $got;
434 print $fh "data-block ".length($d)."\n" or die $!;
435 print $fh $d or die $!;
437 PF->error and die "$ourfn $!";
438 print $fh "data-end\n" or die $!;
442 sub protocol_read_bytes ($$) {
443 my ($fh, $nbytes) = @_;
444 $nbytes =~ m/^[1-9]\d{0,5}$|^0$/ or badproto \*RO, "bad byte count";
446 my $got = read $fh, $d, $nbytes;
447 $got==$nbytes or badproto_badread $fh, "data block";
451 sub protocol_receive_file ($$) {
452 my ($fh, $ourfn) = @_;
453 printdebug "() $ourfn\n";
454 open PF, ">", $ourfn or die "$ourfn: $!";
456 my ($y,$l) = protocol_expect {
457 m/^data-block (.*)$/ ? (1,$1) :
458 m/^data-end$/ ? (0,) :
462 my $d = protocol_read_bytes $fh, $l;
463 print PF $d or die $!;
468 #---------- remote protocol support, responder ----------
470 sub responder_send_command ($) {
472 return unless $we_are_responder;
473 # called even without $we_are_responder
474 printdebug ">> $command\n";
475 print PO $command, "\n" or die $!;
478 sub responder_send_file ($$) {
479 my ($keyword, $ourfn) = @_;
480 return unless $we_are_responder;
481 printdebug "]] $keyword $ourfn\n";
482 responder_send_command "file $keyword";
483 protocol_send_file \*PO, $ourfn;
486 sub responder_receive_files ($@) {
487 my ($keyword, @ourfns) = @_;
488 die unless $we_are_responder;
489 printdebug "[[ $keyword @ourfns\n";
490 responder_send_command "want $keyword";
491 foreach my $fn (@ourfns) {
492 protocol_receive_file \*PI, $fn;
495 protocol_expect { m/^files-end$/ } \*PI;
498 #---------- remote protocol support, initiator ----------
500 sub initiator_expect (&) {
502 protocol_expect { &$match } \*RO;
505 #---------- end remote code ----------
508 if ($we_are_responder) {
510 responder_send_command "progress ".length($m) or die $!;
511 print PO $m or die $!;
521 $ua = LWP::UserAgent->new();
525 progress "downloading $what...";
526 my $r = $ua->get(@_) or die $!;
527 return undef if $r->code == 404;
528 $r->is_success or fail "failed to fetch $what: ".$r->status_line;
529 return $r->decoded_content(charset => 'none');
532 our ($dscdata,$dscurl,$dsc,$dsc_checked,$skew_warning_vsn);
534 sub act_local () { return $dryrun_level <= 1; }
535 sub act_scary () { return !$dryrun_level; }
538 if (!$dryrun_level) {
539 progress "$us ok: @_";
541 progress "would be ok: @_ (but dry run only)";
546 printcmd(\*STDERR,$debugprefix."#",@_);
549 sub runcmd_ordryrun {
557 sub runcmd_ordryrun_local {
565 our $helpmsg = <<END;
567 dgit [dgit-opts] clone [dgit-opts] package [suite] [./dir|/dir]
568 dgit [dgit-opts] fetch|pull [dgit-opts] [suite]
569 dgit [dgit-opts] build [dpkg-buildpackage-opts]
570 dgit [dgit-opts] sbuild [sbuild-opts]
571 dgit [dgit-opts] push [dgit-opts] [suite]
572 dgit [dgit-opts] push-source [dgit-opts] [suite]
573 dgit [dgit-opts] rpush build-host:build-dir ...
574 important dgit options:
575 -k<keyid> sign tag and package with <keyid> instead of default
576 --dry-run -n do not change anything, but go through the motions
577 --damp-run -L like --dry-run but make local changes, without signing
578 --new -N allow introducing a new package
579 --debug -D increase debug level
580 -c<name>=<value> set git config option (used directly by dgit too)
583 our $later_warning_msg = <<END;
584 Perhaps the upload is stuck in incoming. Using the version from git.
588 print STDERR "$us: @_\n", $helpmsg or die $!;
593 @ARGV or badusage "too few arguments";
594 return scalar shift @ARGV;
598 not_necessarily_a_tree();
601 print $helpmsg or die $!;
605 our $td = $ENV{DGIT_TEST_DUMMY_DIR} || "DGIT_TEST_DUMMY_DIR-unset";
607 our %defcfg = ('dgit.default.distro' => 'debian',
608 'dgit.default.default-suite' => 'unstable',
609 'dgit.default.old-dsc-distro' => 'debian',
610 'dgit-suite.*-security.distro' => 'debian-security',
611 'dgit.default.username' => '',
612 'dgit.default.archive-query-default-component' => 'main',
613 'dgit.default.ssh' => 'ssh',
614 'dgit.default.archive-query' => 'madison:',
615 'dgit.default.sshpsql-dbname' => 'service=projectb',
616 'dgit.default.aptget-components' => 'main',
617 'dgit.default.dgit-tag-format' => 'new,old,maint',
618 'dgit.default.source-only-uploads' => 'ok',
619 'dgit.dsc-url-proto-ok.http' => 'true',
620 'dgit.dsc-url-proto-ok.https' => 'true',
621 'dgit.dsc-url-proto-ok.git' => 'true',
622 'dgit.vcs-git.suites', => 'sid', # ;-separated
623 'dgit.default.dsc-url-proto-ok' => 'false',
624 # old means "repo server accepts pushes with old dgit tags"
625 # new means "repo server accepts pushes with new dgit tags"
626 # maint means "repo server accepts split brain pushes"
627 # hist means "repo server may have old pushes without new tag"
628 # ("hist" is implied by "old")
629 'dgit-distro.debian.archive-query' => 'ftpmasterapi:',
630 'dgit-distro.debian.git-check' => 'url',
631 'dgit-distro.debian.git-check-suffix' => '/info/refs',
632 'dgit-distro.debian.new-private-pushers' => 't',
633 'dgit-distro.debian.source-only-uploads' => 'not-wholly-new',
634 'dgit-distro.debian/push.git-url' => '',
635 'dgit-distro.debian/push.git-host' => 'push.dgit.debian.org',
636 'dgit-distro.debian/push.git-user-force' => 'dgit',
637 'dgit-distro.debian/push.git-proto' => 'git+ssh://',
638 'dgit-distro.debian/push.git-path' => '/dgit/debian/repos',
639 'dgit-distro.debian/push.git-create' => 'true',
640 'dgit-distro.debian/push.git-check' => 'ssh-cmd',
641 'dgit-distro.debian.archive-query-url', 'https://api.ftp-master.debian.org/',
642 # 'dgit-distro.debian.archive-query-tls-key',
643 # '/etc/ssl/certs/%HOST%.pem:/etc/dgit/%HOST%.pem',
644 # ^ this does not work because curl is broken nowadays
645 # Fixing #790093 properly will involve providing providing the key
646 # in some pacagke and maybe updating these paths.
648 # 'dgit-distro.debian.archive-query-tls-curl-args',
649 # '--ca-path=/etc/ssl/ca-debian',
650 # ^ this is a workaround but works (only) on DSA-administered machines
651 'dgit-distro.debian.git-url' => 'https://git.dgit.debian.org',
652 'dgit-distro.debian.git-url-suffix' => '',
653 'dgit-distro.debian.upload-host' => 'ftp-master', # for dput
654 'dgit-distro.debian.mirror' => 'http://ftp.debian.org/debian/',
655 'dgit-distro.debian-security.archive-query' => 'aptget:',
656 'dgit-distro.debian-security.mirror' => 'http://security.debian.org/debian-security/',
657 'dgit-distro.debian-security.aptget-suite-map' => 's#-security$#/updates#',
658 'dgit-distro.debian-security.aptget-suite-rmap' => 's#$#-security#',
659 'dgit-distro.debian-security.nominal-distro' => 'debian',
660 'dgit-distro.debian.backports-quirk' => '(squeeze)-backports*',
661 'dgit-distro.debian-backports.mirror' => 'http://backports.debian.org/debian-backports/',
662 'dgit-distro.ubuntu.git-check' => 'false',
663 'dgit-distro.ubuntu.mirror' => 'http://archive.ubuntu.com/ubuntu',
664 'dgit-distro.test-dummy.ssh' => "$td/ssh",
665 'dgit-distro.test-dummy.username' => "alice",
666 'dgit-distro.test-dummy.git-check' => "ssh-cmd",
667 'dgit-distro.test-dummy.git-create' => "ssh-cmd",
668 'dgit-distro.test-dummy.git-url' => "$td/git",
669 'dgit-distro.test-dummy.git-host' => "git",
670 'dgit-distro.test-dummy.git-path' => "$td/git",
671 'dgit-distro.test-dummy.archive-query' => "dummycatapi:",
672 'dgit-distro.test-dummy.archive-query-url' => "file://$td/aq/",
673 'dgit-distro.test-dummy.mirror' => "file://$td/mirror/",
674 'dgit-distro.test-dummy.upload-host' => 'test-dummy',
678 our @gitcfgsources = qw(cmdline local global system);
679 our $invoked_in_git_tree = 1;
681 sub git_slurp_config () {
682 # This algoritm is a bit subtle, but this is needed so that for
683 # options which we want to be single-valued, we allow the
684 # different config sources to override properly. See #835858.
685 foreach my $src (@gitcfgsources) {
686 next if $src eq 'cmdline';
687 # we do this ourselves since git doesn't handle it
689 $gitcfgs{$src} = git_slurp_config_src $src;
693 sub git_get_config ($) {
695 foreach my $src (@gitcfgsources) {
696 my $l = $gitcfgs{$src}{$c};
697 confess "internal error ($l $c)" if $l && !ref $l;
698 printdebug"C $c ".(defined $l ?
699 join " ", map { messagequote "'$_'" } @$l :
703 @$l==1 or badcfg "multiple values for $c".
704 " (in $src git config)" if @$l > 1;
712 return undef if $c =~ /RETURN-UNDEF/;
713 printdebug "C? $c\n" if $debuglevel >= 5;
714 my $v = git_get_config($c);
715 return $v if defined $v;
716 my $dv = $defcfg{$c};
718 printdebug "CD $c $dv\n" if $debuglevel >= 4;
722 badcfg "need value for one of: @_\n".
723 "$us: distro or suite appears not to be (properly) supported";
726 sub not_necessarily_a_tree () {
727 # needs to be called from pre_*
728 @gitcfgsources = grep { $_ ne 'local' } @gitcfgsources;
729 $invoked_in_git_tree = 0;
732 sub access_basedistro__noalias () {
733 if (defined $idistro) {
736 my $def = cfg("dgit-suite.$isuite.distro", 'RETURN-UNDEF');
737 return $def if defined $def;
738 foreach my $src (@gitcfgsources, 'internal') {
739 my $kl = $src eq 'internal' ? \%defcfg : $gitcfgs{$src};
741 foreach my $k (keys %$kl) {
742 next unless $k =~ m#^dgit-suite\.(.*)\.distro$#;
744 next unless match_glob $dpat, $isuite;
748 return cfg("dgit.default.distro");
752 sub access_basedistro () {
753 my $noalias = access_basedistro__noalias();
754 my $canon = cfg("dgit-distro.$noalias.alias-canon",'RETURN-UNDEF');
755 return $canon // $noalias;
758 sub access_nomdistro () {
759 my $base = access_basedistro();
760 my $r = cfg("dgit-distro.$base.nominal-distro",'RETURN-UNDEF') // $base;
761 $r =~ m/^$distro_re$/ or badcfg
762 "bad syntax for (nominal) distro \`$r' (does not match /^$distro_re$/)";
766 sub access_quirk () {
767 # returns (quirk name, distro to use instead or undef, quirk-specific info)
768 my $basedistro = access_basedistro();
769 my $backports_quirk = cfg("dgit-distro.$basedistro.backports-quirk",
771 if (defined $backports_quirk) {
772 my $re = $backports_quirk;
773 $re =~ s/[^-0-9a-z_\%*()]/\\$&/ig;
775 $re =~ s/\%/([-0-9a-z_]+)/
776 or $re =~ m/[()]/ or badcfg "backports-quirk needs \% or ( )";
777 if ($isuite =~ m/^$re$/) {
778 return ('backports',"$basedistro-backports",$1);
781 return ('none',undef);
786 sub parse_cfg_bool ($$$) {
787 my ($what,$def,$v) = @_;
790 $v =~ m/^[ty1]/ ? 1 :
791 $v =~ m/^[fn0]/ ? 0 :
792 badcfg "$what needs t (true, y, 1) or f (false, n, 0) not \`$v'";
795 sub access_forpush_config () {
796 my $d = access_basedistro();
800 parse_cfg_bool('new-private-pushers', 0,
801 cfg("dgit-distro.$d.new-private-pushers",
804 my $v = cfg("dgit-distro.$d.readonly", 'RETURN-UNDEF');
807 $v =~ m/^[ty1]/ ? 0 : # force readonly, forpush = 0
808 $v =~ m/^[fn0]/ ? 1 : # force nonreadonly, forpush = 1
809 $v =~ m/^[a]/ ? '' : # auto, forpush = ''
810 badcfg "readonly needs t (true, y, 1) or f (false, n, 0) or a (auto)";
813 sub access_forpush () {
814 $access_forpush //= access_forpush_config();
815 return $access_forpush;
819 die "$access_forpush ?" if ($access_forpush // 1) ne 1;
820 badcfg "pushing but distro is configured readonly"
821 if access_forpush_config() eq '0';
823 $supplementary_message = <<'END' unless $we_are_responder;
824 Push failed, before we got started.
825 You can retry the push, after fixing the problem, if you like.
827 parseopts_late_defaults();
831 parseopts_late_defaults();
834 sub supplementary_message ($) {
836 if (!$we_are_responder) {
837 $supplementary_message = $msg;
839 } elsif ($protovsn >= 3) {
840 responder_send_command "supplementary-message ".length($msg)
842 print PO $msg or die $!;
846 sub access_distros () {
847 # Returns list of distros to try, in order
850 # 0. `instead of' distro name(s) we have been pointed to
851 # 1. the access_quirk distro, if any
852 # 2a. the user's specified distro, or failing that } basedistro
853 # 2b. the distro calculated from the suite }
854 my @l = access_basedistro();
856 my (undef,$quirkdistro) = access_quirk();
857 unshift @l, $quirkdistro;
858 unshift @l, $instead_distro;
859 @l = grep { defined } @l;
861 push @l, access_nomdistro();
863 if (access_forpush()) {
864 @l = map { ("$_/push", $_) } @l;
869 sub access_cfg_cfgs (@) {
872 # The nesting of these loops determines the search order. We put
873 # the key loop on the outside so that we search all the distros
874 # for each key, before going on to the next key. That means that
875 # if access_cfg is called with a more specific, and then a less
876 # specific, key, an earlier distro can override the less specific
877 # without necessarily overriding any more specific keys. (If the
878 # distro wants to override the more specific keys it can simply do
879 # so; whereas if we did the loop the other way around, it would be
880 # impossible to for an earlier distro to override a less specific
881 # key but not the more specific ones without restating the unknown
882 # values of the more specific keys.
885 # We have to deal with RETURN-UNDEF specially, so that we don't
886 # terminate the search prematurely.
888 if (m/RETURN-UNDEF/) { push @rundef, $_; last; }
891 foreach my $d (access_distros()) {
892 push @cfgs, map { "dgit-distro.$d.$_" } @realkeys;
894 push @cfgs, map { "dgit.default.$_" } @realkeys;
901 my (@cfgs) = access_cfg_cfgs(@keys);
902 my $value = cfg(@cfgs);
906 sub access_cfg_bool ($$) {
907 my ($def, @keys) = @_;
908 parse_cfg_bool($keys[0], $def, access_cfg(@keys, 'RETURN-UNDEF'));
911 sub string_to_ssh ($) {
913 if ($spec =~ m/\s/) {
914 return qw(sh -ec), 'exec '.$spec.' "$@"', 'x';
920 sub access_cfg_ssh () {
921 my $gitssh = access_cfg('ssh', 'RETURN-UNDEF');
922 if (!defined $gitssh) {
925 return string_to_ssh $gitssh;
929 sub access_runeinfo ($) {
931 return ": dgit ".access_basedistro()." $info ;";
934 sub access_someuserhost ($) {
936 my $user = access_cfg("$some-user-force", 'RETURN-UNDEF');
937 defined($user) && length($user) or
938 $user = access_cfg("$some-user",'username');
939 my $host = access_cfg("$some-host");
940 return length($user) ? "$user\@$host" : $host;
943 sub access_gituserhost () {
944 return access_someuserhost('git');
947 sub access_giturl (;$) {
949 my $url = access_cfg('git-url','RETURN-UNDEF');
952 my $proto = access_cfg('git-proto', 'RETURN-UNDEF');
953 return undef unless defined $proto;
956 access_gituserhost().
957 access_cfg('git-path');
959 $suffix = access_cfg('git-url-suffix','RETURN-UNDEF');
962 return "$url/$package$suffix";
965 sub parsecontrolfh ($$;$) {
966 my ($fh, $desc, $allowsigned) = @_;
967 our $dpkgcontrolhash_noissigned;
970 my %opts = ('name' => $desc);
971 $opts{allow_pgp}= $allowsigned || !$dpkgcontrolhash_noissigned;
972 $c = Dpkg::Control::Hash->new(%opts);
974 print STDERR "DCHn\n", Dumper(\%opts);
975 $c->parse($fh,$desc) or die "parsing of $desc failed";
976 print STDERR "parse\n";
979 last if $allowsigned;
980 last if $dpkgcontrolhash_noissigned;
981 my $issigned= $c->get_option('is_pgp_signed');
982 if (!defined $issigned) {
983 $dpkgcontrolhash_noissigned= 1;
984 seek $fh, 0,0 or die "seek $desc: $!";
985 } elsif ($issigned) {
986 fail "control file $desc is (already) PGP-signed. ".
987 " Note that dgit push needs to modify the .dsc and then".
988 " do the signature itself";
997 my ($file, $desc, $allowsigned) = @_;
998 my $fh = new IO::Handle;
999 open $fh, '<', $file or die "$file: $!";
1000 my $c = parsecontrolfh($fh,$desc,$allowsigned);
1001 $fh->error and die $!;
1007 my ($dctrl,$field) = @_;
1008 my $v = $dctrl->{$field};
1009 return $v if defined $v;
1010 fail "missing field $field in ".$dctrl->get_option('name');
1013 sub parsechangelog {
1014 my $c = Dpkg::Control::Hash->new(name => 'parsed changelog');
1015 my $p = new IO::Handle;
1016 my @cmd = (qw(dpkg-parsechangelog), @_);
1017 open $p, '-|', @cmd or die $!;
1019 $?=0; $!=0; close $p or failedcmd @cmd;
1023 sub commit_getclogp ($) {
1024 # Returns the parsed changelog hashref for a particular commit
1026 our %commit_getclogp_memo;
1027 my $memo = $commit_getclogp_memo{$objid};
1028 return $memo if $memo;
1030 my $mclog = dgit_privdir()."clog";
1031 runcmd shell_cmd "exec >$mclog", @git, qw(cat-file blob),
1032 "$objid:debian/changelog";
1033 $commit_getclogp_memo{$objid} = parsechangelog("-l$mclog");
1036 sub parse_dscdata () {
1037 my $dscfh = new IO::File \$dscdata, '<' or die $!;
1038 printdebug Dumper($dscdata) if $debuglevel>1;
1039 $dsc = parsecontrolfh($dscfh,$dscurl,1);
1040 printdebug Dumper($dsc) if $debuglevel>1;
1045 sub archive_query ($;@) {
1046 my ($method) = shift @_;
1047 fail "this operation does not support multiple comma-separated suites"
1049 my $query = access_cfg('archive-query','RETURN-UNDEF');
1050 $query =~ s/^(\w+):// or badcfg "invalid archive-query method \`$query'";
1053 { no strict qw(refs); &{"${method}_${proto}"}($proto,$data,@_); }
1056 sub archive_query_prepend_mirror {
1057 my $m = access_cfg('mirror');
1058 return map { [ $_->[0], $m.$_->[1], @$_[2..$#$_] ] } @_;
1061 sub pool_dsc_subpath ($$) {
1062 my ($vsn,$component) = @_; # $package is implict arg
1063 my $prefix = substr($package, 0, $package =~ m/^l/ ? 4 : 1);
1064 return "/pool/$component/$prefix/$package/".dscfn($vsn);
1067 sub cfg_apply_map ($$$) {
1068 my ($varref, $what, $mapspec) = @_;
1069 return unless $mapspec;
1071 printdebug "config $what EVAL{ $mapspec; }\n";
1073 eval "package Dgit::Config; $mapspec;";
1078 #---------- `ftpmasterapi' archive query method (nascent) ----------
1080 sub archive_api_query_cmd ($) {
1082 my @cmd = (@curl, qw(-sS));
1083 my $url = access_cfg('archive-query-url');
1084 if ($url =~ m#^https://([-.0-9a-z]+)/#) {
1086 my $keys = access_cfg('archive-query-tls-key','RETURN-UNDEF') //'';
1087 foreach my $key (split /\:/, $keys) {
1088 $key =~ s/\%HOST\%/$host/g;
1090 fail "for $url: stat $key: $!" unless $!==ENOENT;
1093 fail "config requested specific TLS key but do not know".
1094 " how to get curl to use exactly that EE key ($key)";
1095 # push @cmd, "--cacert", $key, "--capath", "/dev/enoent";
1096 # # Sadly the above line does not work because of changes
1097 # # to gnutls. The real fix for #790093 may involve
1098 # # new curl options.
1101 # Fixing #790093 properly will involve providing a value
1102 # for this on clients.
1103 my $kargs = access_cfg('archive-query-tls-curl-ca-args','RETURN-UNDEF');
1104 push @cmd, split / /, $kargs if defined $kargs;
1106 push @cmd, $url.$subpath;
1110 sub api_query ($$;$) {
1112 my ($data, $subpath, $ok404) = @_;
1113 badcfg "ftpmasterapi archive query method takes no data part"
1115 my @cmd = archive_api_query_cmd($subpath);
1116 my $url = $cmd[$#cmd];
1117 push @cmd, qw(-w %{http_code});
1118 my $json = cmdoutput @cmd;
1119 unless ($json =~ s/\d+\d+\d$//) {
1120 failedcmd_report_cmd undef, @cmd;
1121 fail "curl failed to print 3-digit HTTP code";
1124 return undef if $code eq '404' && $ok404;
1125 fail "fetch of $url gave HTTP code $code"
1126 unless $url =~ m#^file://# or $code =~ m/^2/;
1127 return decode_json($json);
1130 sub canonicalise_suite_ftpmasterapi {
1131 my ($proto,$data) = @_;
1132 my $suites = api_query($data, 'suites');
1134 foreach my $entry (@$suites) {
1136 my $v = $entry->{$_};
1137 defined $v && $v eq $isuite;
1138 } qw(codename name);
1139 push @matched, $entry;
1141 fail "unknown suite $isuite" unless @matched;
1144 @matched==1 or die "multiple matches for suite $isuite\n";
1145 $cn = "$matched[0]{codename}";
1146 defined $cn or die "suite $isuite info has no codename\n";
1147 $cn =~ m/^$suite_re$/ or die "suite $isuite maps to bad codename\n";
1149 die "bad ftpmaster api response: $@\n".Dumper(\@matched)
1154 sub archive_query_ftpmasterapi {
1155 my ($proto,$data) = @_;
1156 my $info = api_query($data, "dsc_in_suite/$isuite/$package");
1158 my $digester = Digest::SHA->new(256);
1159 foreach my $entry (@$info) {
1161 my $vsn = "$entry->{version}";
1162 my ($ok,$msg) = version_check $vsn;
1163 die "bad version: $msg\n" unless $ok;
1164 my $component = "$entry->{component}";
1165 $component =~ m/^$component_re$/ or die "bad component";
1166 my $filename = "$entry->{filename}";
1167 $filename && $filename !~ m#[^-+:._~0-9a-zA-Z/]|^[/.]|/[/.]#
1168 or die "bad filename";
1169 my $sha256sum = "$entry->{sha256sum}";
1170 $sha256sum =~ m/^[0-9a-f]+$/ or die "bad sha256sum";
1171 push @rows, [ $vsn, "/pool/$component/$filename",
1172 $digester, $sha256sum ];
1174 die "bad ftpmaster api response: $@\n".Dumper($entry)
1177 @rows = sort { -version_compare($a->[0],$b->[0]) } @rows;
1178 return archive_query_prepend_mirror @rows;
1181 sub file_in_archive_ftpmasterapi {
1182 my ($proto,$data,$filename) = @_;
1183 my $pat = $filename;
1186 $pat =~ s#[^-+_.0-9a-z/]# sprintf '%%%02x', ord $& #ge;
1187 my $info = api_query($data, "file_in_archive/$pat", 1);
1190 sub package_not_wholly_new_ftpmasterapi {
1191 my ($proto,$data,$pkg) = @_;
1192 my $info = api_query($data,"madison?package=${pkg}&f=json");
1196 #---------- `aptget' archive query method ----------
1199 our $aptget_releasefile;
1200 our $aptget_configpath;
1202 sub aptget_aptget () { return @aptget, qw(-c), $aptget_configpath; }
1203 sub aptget_aptcache () { return @aptcache, qw(-c), $aptget_configpath; }
1205 sub aptget_cache_clean {
1206 runcmd_ordryrun_local qw(sh -ec),
1207 'cd "$1"; find -atime +30 -type f -print0 | xargs -0r rm --',
1211 sub aptget_lock_acquire () {
1212 my $lockfile = "$aptget_base/lock";
1213 open APTGET_LOCK, '>', $lockfile or die "open $lockfile: $!";
1214 flock APTGET_LOCK, LOCK_EX or die "lock $lockfile: $!";
1217 sub aptget_prep ($) {
1219 return if defined $aptget_base;
1221 badcfg "aptget archive query method takes no data part"
1224 my $cache = $ENV{XDG_CACHE_DIR} // "$ENV{HOME}/.cache";
1227 ensuredir "$cache/dgit";
1229 access_cfg('aptget-cachekey','RETURN-UNDEF')
1230 // access_nomdistro();
1232 $aptget_base = "$cache/dgit/aptget";
1233 ensuredir $aptget_base;
1235 my $quoted_base = $aptget_base;
1236 die "$quoted_base contains bad chars, cannot continue"
1237 if $quoted_base =~ m/["\\]/; # apt.conf(5) says no escaping :-/
1239 ensuredir $aptget_base;
1241 aptget_lock_acquire();
1243 aptget_cache_clean();
1245 $aptget_configpath = "$aptget_base/apt.conf#$cachekey";
1246 my $sourceslist = "source.list#$cachekey";
1248 my $aptsuites = $isuite;
1249 cfg_apply_map(\$aptsuites, 'suite map',
1250 access_cfg('aptget-suite-map', 'RETURN-UNDEF'));
1252 open SRCS, ">", "$aptget_base/$sourceslist" or die $!;
1253 printf SRCS "deb-src %s %s %s\n",
1254 access_cfg('mirror'),
1256 access_cfg('aptget-components')
1259 ensuredir "$aptget_base/cache";
1260 ensuredir "$aptget_base/lists";
1262 open CONF, ">", $aptget_configpath or die $!;
1264 Debug::NoLocking "true";
1265 APT::Get::List-Cleanup "false";
1266 #clear APT::Update::Post-Invoke-Success;
1267 Dir::Etc::SourceList "$quoted_base/$sourceslist";
1268 Dir::State::Lists "$quoted_base/lists";
1269 Dir::Etc::preferences "$quoted_base/preferences";
1270 Dir::Cache::srcpkgcache "$quoted_base/cache/srcs#$cachekey";
1271 Dir::Cache::pkgcache "$quoted_base/cache/pkgs#$cachekey";
1274 foreach my $key (qw(
1277 Dir::Cache::Archives
1278 Dir::Etc::SourceParts
1279 Dir::Etc::preferencesparts
1281 ensuredir "$aptget_base/$key";
1282 print CONF "$key \"$quoted_base/$key\";\n" or die $!;
1285 my $oldatime = (time // die $!) - 1;
1286 foreach my $oldlist (<$aptget_base/lists/*Release>) {
1287 next unless stat_exists $oldlist;
1288 my ($mtime) = (stat _)[9];
1289 utime $oldatime, $mtime, $oldlist or die "$oldlist $!";
1292 runcmd_ordryrun_local aptget_aptget(), qw(update);
1295 foreach my $oldlist (<$aptget_base/lists/*Release>) {
1296 next unless stat_exists $oldlist;
1297 my ($atime) = (stat _)[8];
1298 next if $atime == $oldatime;
1299 push @releasefiles, $oldlist;
1301 my @inreleasefiles = grep { m#/InRelease$# } @releasefiles;
1302 @releasefiles = @inreleasefiles if @inreleasefiles;
1303 die "apt updated wrong number of Release files (@releasefiles), erk"
1304 unless @releasefiles == 1;
1306 ($aptget_releasefile) = @releasefiles;
1309 sub canonicalise_suite_aptget {
1310 my ($proto,$data) = @_;
1313 my $release = parsecontrol $aptget_releasefile, "Release file", 1;
1315 foreach my $name (qw(Codename Suite)) {
1316 my $val = $release->{$name};
1318 printdebug "release file $name: $val\n";
1319 $val =~ m/^$suite_re$/o or fail
1320 "Release file ($aptget_releasefile) specifies intolerable $name";
1321 cfg_apply_map(\$val, 'suite rmap',
1322 access_cfg('aptget-suite-rmap', 'RETURN-UNDEF'));
1329 sub archive_query_aptget {
1330 my ($proto,$data) = @_;
1333 ensuredir "$aptget_base/source";
1334 foreach my $old (<$aptget_base/source/*.dsc>) {
1335 unlink $old or die "$old: $!";
1338 my $showsrc = cmdoutput aptget_aptcache(), qw(showsrc), $package;
1339 return () unless $showsrc =~ m/^package:\s*\Q$package\E\s*$/mi;
1340 # avoids apt-get source failing with ambiguous error code
1342 runcmd_ordryrun_local
1343 shell_cmd 'cd "$1"/source; shift', $aptget_base,
1344 aptget_aptget(), qw(--download-only --only-source source), $package;
1346 my @dscs = <$aptget_base/source/*.dsc>;
1347 fail "apt-get source did not produce a .dsc" unless @dscs;
1348 fail "apt-get source produced several .dscs (@dscs)" unless @dscs==1;
1350 my $pre_dsc = parsecontrol $dscs[0], $dscs[0], 1;
1353 my $uri = "file://". uri_escape $dscs[0];
1354 $uri =~ s{\%2f}{/}gi;
1355 return [ (getfield $pre_dsc, 'Version'), $uri ];
1358 sub file_in_archive_aptget () { return undef; }
1359 sub package_not_wholly_new_aptget () { return undef; }
1361 #---------- `dummyapicat' archive query method ----------
1363 sub archive_query_dummycatapi { archive_query_ftpmasterapi @_; }
1364 sub canonicalise_suite_dummycatapi { canonicalise_suite_ftpmasterapi @_; }
1366 sub dummycatapi_run_in_mirror ($@) {
1367 # runs $fn with FIA open onto rune
1368 my ($rune, $argl, $fn) = @_;
1370 my $mirror = access_cfg('mirror');
1371 $mirror =~ s#^file://#/# or die "$mirror ?";
1372 my @cmd = (qw(sh -ec), 'cd "$1"; shift'."\n".$rune,
1373 qw(x), $mirror, @$argl);
1374 debugcmd "-|", @cmd;
1375 open FIA, "-|", @cmd or die $!;
1377 close FIA or ($!==0 && $?==141) or die failedcmd @cmd;
1381 sub file_in_archive_dummycatapi ($$$) {
1382 my ($proto,$data,$filename) = @_;
1384 dummycatapi_run_in_mirror '
1385 find -name "$1" -print0 |
1387 ', [$filename], sub {
1390 printdebug "| $_\n";
1391 m/^(\w+) (\S+)$/ or die "$_ ?";
1392 push @out, { sha256sum => $1, filename => $2 };
1398 sub package_not_wholly_new_dummycatapi {
1399 my ($proto,$data,$pkg) = @_;
1400 dummycatapi_run_in_mirror "
1401 find -name ${pkg}_*.dsc
1408 #---------- `madison' archive query method ----------
1410 sub archive_query_madison {
1411 return archive_query_prepend_mirror
1412 map { [ @$_[0..1] ] } madison_get_parse(@_);
1415 sub madison_get_parse {
1416 my ($proto,$data) = @_;
1417 die unless $proto eq 'madison';
1418 if (!length $data) {
1419 $data= access_cfg('madison-distro','RETURN-UNDEF');
1420 $data //= access_basedistro();
1422 $rmad{$proto,$data,$package} ||= cmdoutput
1423 qw(rmadison -asource),"-s$isuite","-u$data",$package;
1424 my $rmad = $rmad{$proto,$data,$package};
1427 foreach my $l (split /\n/, $rmad) {
1428 $l =~ m{^ \s*( [^ \t|]+ )\s* \|
1429 \s*( [^ \t|]+ )\s* \|
1430 \s*( [^ \t|/]+ )(?:/([^ \t|/]+))? \s* \|
1431 \s*( [^ \t|]+ )\s* }x or die "$rmad ?";
1432 $1 eq $package or die "$rmad $package ?";
1439 $component = access_cfg('archive-query-default-component');
1441 $5 eq 'source' or die "$rmad ?";
1442 push @out, [$vsn,pool_dsc_subpath($vsn,$component),$newsuite];
1444 return sort { -version_compare($a->[0],$b->[0]); } @out;
1447 sub canonicalise_suite_madison {
1448 # madison canonicalises for us
1449 my @r = madison_get_parse(@_);
1451 "unable to canonicalise suite using package $package".
1452 " which does not appear to exist in suite $isuite;".
1453 " --existing-package may help";
1457 sub file_in_archive_madison { return undef; }
1458 sub package_not_wholly_new_madison { return undef; }
1460 #---------- `sshpsql' archive query method ----------
1463 my ($data,$runeinfo,$sql) = @_;
1464 if (!length $data) {
1465 $data= access_someuserhost('sshpsql').':'.
1466 access_cfg('sshpsql-dbname');
1468 $data =~ m/:/ or badcfg "invalid sshpsql method string \`$data'";
1469 my ($userhost,$dbname) = ($`,$'); #';
1471 my @cmd = (access_cfg_ssh, $userhost,
1472 access_runeinfo("ssh-psql $runeinfo").
1473 " export LC_MESSAGES=C; export LC_CTYPE=C;".
1474 " ".shellquote qw(psql -A), $dbname, qw(-c), $sql);
1476 open P, "-|", @cmd or die $!;
1479 printdebug(">|$_|\n");
1482 $!=0; $?=0; close P or failedcmd @cmd;
1484 my $nrows = pop @rows;
1485 $nrows =~ s/^\((\d+) rows?\)$/$1/ or die "$nrows ?";
1486 @rows == $nrows+1 or die "$nrows ".(scalar @rows)." ?";
1487 @rows = map { [ split /\|/, $_ ] } @rows;
1488 my $ncols = scalar @{ shift @rows };
1489 die if grep { scalar @$_ != $ncols } @rows;
1493 sub sql_injection_check {
1494 foreach (@_) { die "$_ $& ?" if m{[^-+=:_.,/0-9a-zA-Z]}; }
1497 sub archive_query_sshpsql ($$) {
1498 my ($proto,$data) = @_;
1499 sql_injection_check $isuite, $package;
1500 my @rows = sshpsql($data, "archive-query $isuite $package", <<END);
1501 SELECT source.version, component.name, files.filename, files.sha256sum
1503 JOIN src_associations ON source.id = src_associations.source
1504 JOIN suite ON suite.id = src_associations.suite
1505 JOIN dsc_files ON dsc_files.source = source.id
1506 JOIN files_archive_map ON files_archive_map.file_id = dsc_files.file
1507 JOIN component ON component.id = files_archive_map.component_id
1508 JOIN files ON files.id = dsc_files.file
1509 WHERE ( suite.suite_name='$isuite' OR suite.codename='$isuite' )
1510 AND source.source='$package'
1511 AND files.filename LIKE '%.dsc';
1513 @rows = sort { -version_compare($a->[0],$b->[0]) } @rows;
1514 my $digester = Digest::SHA->new(256);
1516 my ($vsn,$component,$filename,$sha256sum) = @$_;
1517 [ $vsn, "/pool/$component/$filename",$digester,$sha256sum ];
1519 return archive_query_prepend_mirror @rows;
1522 sub canonicalise_suite_sshpsql ($$) {
1523 my ($proto,$data) = @_;
1524 sql_injection_check $isuite;
1525 my @rows = sshpsql($data, "canonicalise-suite $isuite", <<END);
1526 SELECT suite.codename
1527 FROM suite where suite_name='$isuite' or codename='$isuite';
1529 @rows = map { $_->[0] } @rows;
1530 fail "unknown suite $isuite" unless @rows;
1531 die "ambiguous $isuite: @rows ?" if @rows>1;
1535 sub file_in_archive_sshpsql ($$$) { return undef; }
1536 sub package_not_wholly_new_sshpsql ($$$) { return undef; }
1538 #---------- `dummycat' archive query method ----------
1540 sub canonicalise_suite_dummycat ($$) {
1541 my ($proto,$data) = @_;
1542 my $dpath = "$data/suite.$isuite";
1543 if (!open C, "<", $dpath) {
1544 $!==ENOENT or die "$dpath: $!";
1545 printdebug "dummycat canonicalise_suite $isuite $dpath ENOENT\n";
1549 chomp or die "$dpath: $!";
1551 printdebug "dummycat canonicalise_suite $isuite $dpath = $_\n";
1555 sub archive_query_dummycat ($$) {
1556 my ($proto,$data) = @_;
1557 canonicalise_suite();
1558 my $dpath = "$data/package.$csuite.$package";
1559 if (!open C, "<", $dpath) {
1560 $!==ENOENT or die "$dpath: $!";
1561 printdebug "dummycat query $csuite $package $dpath ENOENT\n";
1569 printdebug "dummycat query $csuite $package $dpath | $_\n";
1570 my @row = split /\s+/, $_;
1571 @row==2 or die "$dpath: $_ ?";
1574 C->error and die "$dpath: $!";
1576 return archive_query_prepend_mirror
1577 sort { -version_compare($a->[0],$b->[0]); } @rows;
1580 sub file_in_archive_dummycat () { return undef; }
1581 sub package_not_wholly_new_dummycat () { return undef; }
1583 #---------- tag format handling ----------
1585 sub access_cfg_tagformats () {
1586 split /\,/, access_cfg('dgit-tag-format');
1589 sub access_cfg_tagformats_can_splitbrain () {
1590 my %y = map { $_ => 1 } access_cfg_tagformats;
1591 foreach my $needtf (qw(new maint)) {
1592 next if $y{$needtf};
1598 sub need_tagformat ($$) {
1599 my ($fmt, $why) = @_;
1600 fail "need to use tag format $fmt ($why) but also need".
1601 " to use tag format $tagformat_want->[0] ($tagformat_want->[1])".
1602 " - no way to proceed"
1603 if $tagformat_want && $tagformat_want->[0] ne $fmt;
1604 $tagformat_want = [$fmt, $why, $tagformat_want->[2] // 0];
1607 sub select_tagformat () {
1609 return if $tagformatfn && !$tagformat_want;
1610 die 'bug' if $tagformatfn && $tagformat_want;
1611 # ... $tagformat_want assigned after previous select_tagformat
1613 my (@supported) = grep { $_ =~ m/^(?:old|new)$/ } access_cfg_tagformats();
1614 printdebug "select_tagformat supported @supported\n";
1616 $tagformat_want //= [ $supported[0], "distro access configuration", 0 ];
1617 printdebug "select_tagformat specified @$tagformat_want\n";
1619 my ($fmt,$why,$override) = @$tagformat_want;
1621 fail "target distro supports tag formats @supported".
1622 " but have to use $fmt ($why)"
1624 or grep { $_ eq $fmt } @supported;
1626 $tagformat_want = undef;
1628 $tagformatfn = ${*::}{"debiantag_$fmt"};
1630 fail "trying to use unknown tag format \`$fmt' ($why) !"
1631 unless $tagformatfn;
1634 #---------- archive query entrypoints and rest of program ----------
1636 sub canonicalise_suite () {
1637 return if defined $csuite;
1638 fail "cannot operate on $isuite suite" if $isuite eq 'UNRELEASED';
1639 $csuite = archive_query('canonicalise_suite');
1640 if ($isuite ne $csuite) {
1641 progress "canonical suite name for $isuite is $csuite";
1643 progress "canonical suite name is $csuite";
1647 sub get_archive_dsc () {
1648 canonicalise_suite();
1649 my @vsns = archive_query('archive_query');
1650 foreach my $vinfo (@vsns) {
1651 my ($vsn,$vsn_dscurl,$digester,$digest) = @$vinfo;
1652 $dscurl = $vsn_dscurl;
1653 $dscdata = url_get($dscurl);
1655 $skew_warning_vsn = $vsn if !defined $skew_warning_vsn;
1660 $digester->add($dscdata);
1661 my $got = $digester->hexdigest();
1663 fail "$dscurl has hash $got but".
1664 " archive told us to expect $digest";
1667 my $fmt = getfield $dsc, 'Format';
1668 $format_ok{$fmt} or forceable_fail [qw(unsupported-source-format)],
1669 "unsupported source format $fmt, sorry";
1671 $dsc_checked = !!$digester;
1672 printdebug "get_archive_dsc: Version ".(getfield $dsc, 'Version')."\n";
1676 printdebug "get_archive_dsc: nothing in archive, returning undef\n";
1679 sub check_for_git ();
1680 sub check_for_git () {
1682 my $how = access_cfg('git-check');
1683 if ($how eq 'ssh-cmd') {
1685 (access_cfg_ssh, access_gituserhost(),
1686 access_runeinfo("git-check $package").
1687 " set -e; cd ".access_cfg('git-path').";".
1688 " if test -d $package.git; then echo 1; else echo 0; fi");
1689 my $r= cmdoutput @cmd;
1690 if (defined $r and $r =~ m/^divert (\w+)$/) {
1692 my ($usedistro,) = access_distros();
1693 # NB that if we are pushing, $usedistro will be $distro/push
1694 $instead_distro= cfg("dgit-distro.$usedistro.diverts.$divert");
1695 $instead_distro =~ s{^/}{ access_basedistro()."/" }e;
1696 progress "diverting to $divert (using config for $instead_distro)";
1697 return check_for_git();
1699 failedcmd @cmd unless defined $r and $r =~ m/^[01]$/;
1701 } elsif ($how eq 'url') {
1702 my $prefix = access_cfg('git-check-url','git-url');
1703 my $suffix = access_cfg('git-check-suffix','git-suffix',
1704 'RETURN-UNDEF') // '.git';
1705 my $url = "$prefix/$package$suffix";
1706 my @cmd = (@curl, qw(-sS -I), $url);
1707 my $result = cmdoutput @cmd;
1708 $result =~ s/^\S+ 200 .*\n\r?\n//;
1709 # curl -sS -I with https_proxy prints
1710 # HTTP/1.0 200 Connection established
1711 $result =~ m/^\S+ (404|200) /s or
1712 fail "unexpected results from git check query - ".
1713 Dumper($prefix, $result);
1715 if ($code eq '404') {
1717 } elsif ($code eq '200') {
1722 } elsif ($how eq 'true') {
1724 } elsif ($how eq 'false') {
1727 badcfg "unknown git-check \`$how'";
1731 sub create_remote_git_repo () {
1732 my $how = access_cfg('git-create');
1733 if ($how eq 'ssh-cmd') {
1735 (access_cfg_ssh, access_gituserhost(),
1736 access_runeinfo("git-create $package").
1737 "set -e; cd ".access_cfg('git-path').";".
1738 " cp -a _template $package.git");
1739 } elsif ($how eq 'true') {
1742 badcfg "unknown git-create \`$how'";
1746 our ($dsc_hash,$lastpush_mergeinput);
1747 our ($dsc_distro, $dsc_hint_tag, $dsc_hint_url);
1751 dgit_privdir(); # ensures that $dgit_privdir_made is based on $maindir
1752 $playground = fresh_playground 'dgit/unpack';
1755 sub mktree_in_ud_here () {
1756 playtree_setup $gitcfgs{local};
1759 sub git_write_tree () {
1760 my $tree = cmdoutput @git, qw(write-tree);
1761 $tree =~ m/^\w+$/ or die "$tree ?";
1765 sub git_add_write_tree () {
1766 runcmd @git, qw(add -Af .);
1767 return git_write_tree();
1770 sub remove_stray_gits ($) {
1772 my @gitscmd = qw(find -name .git -prune -print0);
1773 debugcmd "|",@gitscmd;
1774 open GITS, "-|", @gitscmd or die $!;
1779 print STDERR "$us: warning: removing from $what: ",
1780 (messagequote $_), "\n";
1784 $!=0; $?=0; close GITS or failedcmd @gitscmd;
1787 sub mktree_in_ud_from_only_subdir ($;$) {
1788 my ($what,$raw) = @_;
1789 # changes into the subdir
1792 die "expected one subdir but found @dirs ?" unless @dirs==1;
1793 $dirs[0] =~ m#^([^/]+)/\.$# or die;
1797 remove_stray_gits($what);
1798 mktree_in_ud_here();
1800 my ($format, $fopts) = get_source_format();
1801 if (madformat($format)) {
1806 my $tree=git_add_write_tree();
1807 return ($tree,$dir);
1810 our @files_csum_info_fields =
1811 (['Checksums-Sha256','Digest::SHA', 'new(256)', 'sha256sum'],
1812 ['Checksums-Sha1', 'Digest::SHA', 'new(1)', 'sha1sum'],
1813 ['Files', 'Digest::MD5', 'new()', 'md5sum']);
1815 sub dsc_files_info () {
1816 foreach my $csumi (@files_csum_info_fields) {
1817 my ($fname, $module, $method) = @$csumi;
1818 my $field = $dsc->{$fname};
1819 next unless defined $field;
1820 eval "use $module; 1;" or die $@;
1822 foreach (split /\n/, $field) {
1824 m/^(\w+) (\d+) (\S+)$/ or
1825 fail "could not parse .dsc $fname line \`$_'";
1826 my $digester = eval "$module"."->$method;" or die $@;
1831 Digester => $digester,
1836 fail "missing any supported Checksums-* or Files field in ".
1837 $dsc->get_option('name');
1841 map { $_->{Filename} } dsc_files_info();
1844 sub files_compare_inputs (@) {
1849 my $showinputs = sub {
1850 return join "; ", map { $_->get_option('name') } @$inputs;
1853 foreach my $in (@$inputs) {
1855 my $in_name = $in->get_option('name');
1857 printdebug "files_compare_inputs $in_name\n";
1859 foreach my $csumi (@files_csum_info_fields) {
1860 my ($fname) = @$csumi;
1861 printdebug "files_compare_inputs $in_name $fname\n";
1863 my $field = $in->{$fname};
1864 next unless defined $field;
1867 foreach (split /\n/, $field) {
1870 my ($info, $f) = m/^(\w+ \d+) (?:\S+ \S+ )?(\S+)$/ or
1871 fail "could not parse $in_name $fname line \`$_'";
1873 printdebug "files_compare_inputs $in_name $fname $f\n";
1877 my $re = \ $record{$f}{$fname};
1879 $fchecked{$f}{$in_name} = 1;
1881 fail "hash or size of $f varies in $fname fields".
1882 " (between: ".$showinputs->().")";
1887 @files = sort @files;
1888 $expected_files //= \@files;
1889 "@$expected_files" eq "@files" or
1890 fail "file list in $in_name varies between hash fields!";
1893 fail "$in_name has no files list field(s)";
1895 printdebug "files_compare_inputs ".Dumper(\%fchecked, \%record)
1898 grep { keys %$_ == @$inputs-1 } values %fchecked
1899 or fail "no file appears in all file lists".
1900 " (looked in: ".$showinputs->().")";
1903 sub is_orig_file_in_dsc ($$) {
1904 my ($f, $dsc_files_info) = @_;
1905 return 0 if @$dsc_files_info <= 1;
1906 # One file means no origs, and the filename doesn't have a "what
1907 # part of dsc" component. (Consider versions ending `.orig'.)
1908 return 0 unless $f =~ m/\.$orig_f_tail_re$/o;
1912 sub is_orig_file_of_vsn ($$) {
1913 my ($f, $upstreamvsn) = @_;
1914 my $base = srcfn $upstreamvsn, '';
1915 return 0 unless $f =~ m/^\Q$base\E\.$orig_f_tail_re$/;
1919 # This function determines whether a .changes file is source-only from
1920 # the point of view of dak. Thus, it permits *_source.buildinfo
1923 # It does not, however, permit any other buildinfo files. After a
1924 # source-only upload, the buildds will try to upload files like
1925 # foo_1.2.3_amd64.buildinfo. If the package maintainer included files
1926 # named like this in their (otherwise) source-only upload, the uploads
1927 # of the buildd can be rejected by dak. Fixing the resultant
1928 # situation can require manual intervention. So we block such
1929 # .buildinfo files when the user tells us to perform a source-only
1930 # upload (such as when using the push-source subcommand with the -C
1931 # option, which calls this function).
1933 # Note, though, that when dgit is told to prepare a source-only
1934 # upload, such as when subcommands like build-source and push-source
1935 # without -C are used, dgit has a more restrictive notion of
1936 # source-only .changes than dak: such uploads will never include
1937 # *_source.buildinfo files. This is because there is no use for such
1938 # files when using a tool like dgit to produce the source package, as
1939 # dgit ensures the source is identical to git HEAD.
1940 sub test_source_only_changes ($) {
1942 foreach my $l (split /\n/, getfield $changes, 'Files') {
1943 $l =~ m/\S+$/ or next;
1944 # \.tar\.[a-z0-9]+ covers orig.tar and the tarballs in native packages
1945 unless ($& =~ m/(?:\.dsc|\.diff\.gz|\.tar\.[a-z0-9]+|_source\.buildinfo)$/) {
1946 print "purportedly source-only changes polluted by $&\n";
1953 sub changes_update_origs_from_dsc ($$$$) {
1954 my ($dsc, $changes, $upstreamvsn, $changesfile) = @_;
1956 printdebug "checking origs needed ($upstreamvsn)...\n";
1957 $_ = getfield $changes, 'Files';
1958 m/^\w+ \d+ (\S+ \S+) \S+$/m or
1959 fail "cannot find section/priority from .changes Files field";
1960 my $placementinfo = $1;
1962 printdebug "checking origs needed placement '$placementinfo'...\n";
1963 foreach my $l (split /\n/, getfield $dsc, 'Files') {
1964 $l =~ m/\S+$/ or next;
1966 printdebug "origs $file | $l\n";
1967 next unless is_orig_file_of_vsn $file, $upstreamvsn;
1968 printdebug "origs $file is_orig\n";
1969 my $have = archive_query('file_in_archive', $file);
1970 if (!defined $have) {
1972 archive does not support .orig check; hope you used --ch:--sa/-sd if needed
1978 printdebug "origs $file \$#\$have=$#$have\n";
1979 foreach my $h (@$have) {
1982 foreach my $csumi (@files_csum_info_fields) {
1983 my ($fname, $module, $method, $archivefield) = @$csumi;
1984 next unless defined $h->{$archivefield};
1985 $_ = $dsc->{$fname};
1986 next unless defined;
1987 m/^(\w+) .* \Q$file\E$/m or
1988 fail ".dsc $fname missing entry for $file";
1989 if ($h->{$archivefield} eq $1) {
1993 "$archivefield: $h->{$archivefield} (archive) != $1 (local .dsc)";
1996 die "$file ".Dumper($h)." ?!" if $same && @differ;
1999 push @found_differ, "archive $h->{filename}: ".join "; ", @differ
2002 printdebug "origs $file f.same=$found_same".
2003 " #f._differ=$#found_differ\n";
2004 if (@found_differ && !$found_same) {
2006 "archive contains $file with different checksum",
2009 # Now we edit the changes file to add or remove it
2010 foreach my $csumi (@files_csum_info_fields) {
2011 my ($fname, $module, $method, $archivefield) = @$csumi;
2012 next unless defined $changes->{$fname};
2014 # in archive, delete from .changes if it's there
2015 $changed{$file} = "removed" if
2016 $changes->{$fname} =~ s/\n.* \Q$file\E$(?:)$//m;
2017 } elsif ($changes->{$fname} =~ m/^.* \Q$file\E$(?:)$/m) {
2018 # not in archive, but it's here in the .changes
2020 my $dsc_data = getfield $dsc, $fname;
2021 $dsc_data =~ m/^(.* \Q$file\E$)$/m or die "$dsc_data $file ?";
2023 $extra =~ s/ \d+ /$&$placementinfo /
2024 or die "$fname $extra >$dsc_data< ?"
2025 if $fname eq 'Files';
2026 $changes->{$fname} .= "\n". $extra;
2027 $changed{$file} = "added";
2032 foreach my $file (keys %changed) {
2034 "edited .changes for archive .orig contents: %s %s",
2035 $changed{$file}, $file;
2037 my $chtmp = "$changesfile.tmp";
2038 $changes->save($chtmp);
2040 rename $chtmp,$changesfile or die "$changesfile $!";
2042 progress "[new .changes left in $changesfile]";
2045 progress "$changesfile already has appropriate .orig(s) (if any)";
2049 sub make_commit ($) {
2051 return cmdoutput @git, qw(hash-object -w -t commit), $file;
2054 sub make_commit_text ($) {
2057 my @cmd = (@git, qw(hash-object -w -t commit --stdin));
2059 print Dumper($text) if $debuglevel > 1;
2060 my $child = open2($out, $in, @cmd) or die $!;
2063 print $in $text or die $!;
2064 close $in or die $!;
2066 $h =~ m/^\w+$/ or die;
2068 printdebug "=> $h\n";
2071 waitpid $child, 0 == $child or die "$child $!";
2072 $? and failedcmd @cmd;
2076 sub clogp_authline ($) {
2078 my $author = getfield $clogp, 'Maintainer';
2079 if ($author =~ m/^[^"\@]+\,/) {
2080 # single entry Maintainer field with unquoted comma
2081 $author = ($& =~ y/,//rd).$'; # strip the comma
2083 # git wants a single author; any remaining commas in $author
2084 # are by now preceded by @ (or "). It seems safer to punt on
2085 # "..." for now rather than attempting to dequote or something.
2086 $author =~ s#,.*##ms unless $author =~ m/"/;
2087 my $date = cmdoutput qw(date), '+%s %z', qw(-d), getfield($clogp,'Date');
2088 my $authline = "$author $date";
2089 $authline =~ m/$git_authline_re/o or
2090 fail "unexpected commit author line format \`$authline'".
2091 " (was generated from changelog Maintainer field)";
2092 return ($1,$2,$3) if wantarray;
2096 sub vendor_patches_distro ($$) {
2097 my ($checkdistro, $what) = @_;
2098 return unless defined $checkdistro;
2100 my $series = "debian/patches/\L$checkdistro\E.series";
2101 printdebug "checking for vendor-specific $series ($what)\n";
2103 if (!open SERIES, "<", $series) {
2104 die "$series $!" unless $!==ENOENT;
2113 Unfortunately, this source package uses a feature of dpkg-source where
2114 the same source package unpacks to different source code on different
2115 distros. dgit cannot safely operate on such packages on affected
2116 distros, because the meaning of source packages is not stable.
2118 Please ask the distro/maintainer to remove the distro-specific series
2119 files and use a different technique (if necessary, uploading actually
2120 different packages, if different distros are supposed to have
2124 fail "Found active distro-specific series file for".
2125 " $checkdistro ($what): $series, cannot continue";
2127 die "$series $!" if SERIES->error;
2131 sub check_for_vendor_patches () {
2132 # This dpkg-source feature doesn't seem to be documented anywhere!
2133 # But it can be found in the changelog (reformatted):
2135 # commit 4fa01b70df1dc4458daee306cfa1f987b69da58c
2136 # Author: Raphael Hertzog <hertzog@debian.org>
2137 # Date: Sun Oct 3 09:36:48 2010 +0200
2139 # dpkg-source: correctly create .pc/.quilt_series with alternate
2142 # If you have debian/patches/ubuntu.series and you were
2143 # unpacking the source package on ubuntu, quilt was still
2144 # directed to debian/patches/series instead of
2145 # debian/patches/ubuntu.series.
2147 # debian/changelog | 3 +++
2148 # scripts/Dpkg/Source/Package/V3/quilt.pm | 4 +++-
2149 # 2 files changed, 6 insertions(+), 1 deletion(-)
2152 vendor_patches_distro($ENV{DEB_VENDOR}, "DEB_VENDOR");
2153 vendor_patches_distro(Dpkg::Vendor::get_current_vendor(),
2154 "Dpkg::Vendor \`current vendor'");
2155 vendor_patches_distro(access_basedistro(),
2156 "(base) distro being accessed");
2157 vendor_patches_distro(access_nomdistro(),
2158 "(nominal) distro being accessed");
2161 sub generate_commits_from_dsc () {
2162 # See big comment in fetch_from_archive, below.
2163 # See also README.dsc-import.
2165 changedir $playground;
2167 my @dfi = dsc_files_info();
2168 foreach my $fi (@dfi) {
2169 my $f = $fi->{Filename};
2170 die "$f ?" if $f =~ m#/|^\.|\.dsc$|\.tmp$#;
2171 my $upper_f = "$maindir/../$f";
2173 printdebug "considering reusing $f: ";
2175 if (link_ltarget "$upper_f,fetch", $f) {
2176 printdebug "linked (using ...,fetch).\n";
2177 } elsif ((printdebug "($!) "),
2179 fail "accessing ../$f,fetch: $!";
2180 } elsif (link_ltarget $upper_f, $f) {
2181 printdebug "linked.\n";
2182 } elsif ((printdebug "($!) "),
2184 fail "accessing ../$f: $!";
2186 printdebug "absent.\n";
2190 complete_file_from_dsc('.', $fi, \$refetched)
2193 printdebug "considering saving $f: ";
2195 if (link $f, $upper_f) {
2196 printdebug "linked.\n";
2197 } elsif ((printdebug "($!) "),
2199 fail "saving ../$f: $!";
2200 } elsif (!$refetched) {
2201 printdebug "no need.\n";
2202 } elsif (link $f, "$upper_f,fetch") {
2203 printdebug "linked (using ...,fetch).\n";
2204 } elsif ((printdebug "($!) "),
2206 fail "saving ../$f,fetch: $!";
2208 printdebug "cannot.\n";
2212 # We unpack and record the orig tarballs first, so that we only
2213 # need disk space for one private copy of the unpacked source.
2214 # But we can't make them into commits until we have the metadata
2215 # from the debian/changelog, so we record the tree objects now and
2216 # make them into commits later.
2218 my $upstreamv = upstreamversion $dsc->{version};
2219 my $orig_f_base = srcfn $upstreamv, '';
2221 foreach my $fi (@dfi) {
2222 # We actually import, and record as a commit, every tarball
2223 # (unless there is only one file, in which case there seems
2226 my $f = $fi->{Filename};
2227 printdebug "import considering $f ";
2228 (printdebug "only one dfi\n"), next if @dfi == 1;
2229 (printdebug "not tar\n"), next unless $f =~ m/\.tar(\.\w+)?$/;
2230 (printdebug "signature\n"), next if $f =~ m/$orig_f_sig_re$/o;
2234 $f =~ m/^\Q$orig_f_base\E\.([^._]+)?\.tar(?:\.\w+)?$/;
2236 printdebug "Y ", (join ' ', map { $_//"(none)" }
2237 $compr_ext, $orig_f_part
2240 my $input = new IO::File $f, '<' or die "$f $!";
2244 if (defined $compr_ext) {
2246 Dpkg::Compression::compression_guess_from_filename $f;
2247 fail "Dpkg::Compression cannot handle file $f in source package"
2248 if defined $compr_ext && !defined $cname;
2250 new Dpkg::Compression::Process compression => $cname;
2251 @compr_cmd = $compr_proc->get_uncompress_cmdline();
2252 my $compr_fh = new IO::Handle;
2253 my $compr_pid = open $compr_fh, "-|" // die $!;
2255 open STDIN, "<&", $input or die $!;
2257 die "dgit (child): exec $compr_cmd[0]: $!\n";
2262 rmtree "_unpack-tar";
2263 mkdir "_unpack-tar" or die $!;
2264 my @tarcmd = qw(tar -x -f -
2265 --no-same-owner --no-same-permissions
2266 --no-acls --no-xattrs --no-selinux);
2267 my $tar_pid = fork // die $!;
2269 chdir "_unpack-tar" or die $!;
2270 open STDIN, "<&", $input or die $!;
2272 die "dgit (child): exec $tarcmd[0]: $!";
2274 $!=0; (waitpid $tar_pid, 0) == $tar_pid or die $!;
2275 !$? or failedcmd @tarcmd;
2278 (@compr_cmd ? ($?==SIGPIPE || failedcmd @compr_cmd)
2280 # finally, we have the results in "tarball", but maybe
2281 # with the wrong permissions
2283 runcmd qw(chmod -R +rwX _unpack-tar);
2284 changedir "_unpack-tar";
2285 remove_stray_gits($f);
2286 mktree_in_ud_here();
2288 my ($tree) = git_add_write_tree();
2289 my $tentries = cmdoutput @git, qw(ls-tree -z), $tree;
2290 if ($tentries =~ m/^\d+ tree (\w+)\t[^\000]+\000$/s) {
2292 printdebug "one subtree $1\n";
2294 printdebug "multiple subtrees\n";
2297 rmtree "_unpack-tar";
2299 my $ent = [ $f, $tree ];
2301 Orig => !!$orig_f_part,
2302 Sort => (!$orig_f_part ? 2 :
2303 $orig_f_part =~ m/-/g ? 1 :
2311 # put any without "_" first (spec is not clear whether files
2312 # are always in the usual order). Tarballs without "_" are
2313 # the main orig or the debian tarball.
2314 $a->{Sort} <=> $b->{Sort} or
2318 my $any_orig = grep { $_->{Orig} } @tartrees;
2320 my $dscfn = "$package.dsc";
2322 my $treeimporthow = 'package';
2324 open D, ">", $dscfn or die "$dscfn: $!";
2325 print D $dscdata or die "$dscfn: $!";
2326 close D or die "$dscfn: $!";
2327 my @cmd = qw(dpkg-source);
2328 push @cmd, '--no-check' if $dsc_checked;
2329 if (madformat $dsc->{format}) {
2330 push @cmd, '--skip-patches';
2331 $treeimporthow = 'unpatched';
2333 push @cmd, qw(-x --), $dscfn;
2336 my ($tree,$dir) = mktree_in_ud_from_only_subdir("source package");
2337 if (madformat $dsc->{format}) {
2338 check_for_vendor_patches();
2342 if (madformat $dsc->{format}) {
2343 my @pcmd = qw(dpkg-source --before-build .);
2344 runcmd shell_cmd 'exec >/dev/null', @pcmd;
2346 $dappliedtree = git_add_write_tree();
2349 my @clogcmd = qw(dpkg-parsechangelog --format rfc822 --all);
2350 debugcmd "|",@clogcmd;
2351 open CLOGS, "-|", @clogcmd or die $!;
2356 printdebug "import clog search...\n";
2359 my $stanzatext = do { local $/=""; <CLOGS>; };
2360 printdebug "import clogp ".Dumper($stanzatext) if $debuglevel>1;
2361 last if !defined $stanzatext;
2363 my $desc = "package changelog, entry no.$.";
2364 open my $stanzafh, "<", \$stanzatext or die;
2365 my $thisstanza = parsecontrolfh $stanzafh, $desc, 1;
2366 $clogp //= $thisstanza;
2368 printdebug "import clog $thisstanza->{version} $desc...\n";
2370 last if !$any_orig; # we don't need $r1clogp
2372 # We look for the first (most recent) changelog entry whose
2373 # version number is lower than the upstream version of this
2374 # package. Then the last (least recent) previous changelog
2375 # entry is treated as the one which introduced this upstream
2376 # version and used for the synthetic commits for the upstream
2379 # One might think that a more sophisticated algorithm would be
2380 # necessary. But: we do not want to scan the whole changelog
2381 # file. Stopping when we see an earlier version, which
2382 # necessarily then is an earlier upstream version, is the only
2383 # realistic way to do that. Then, either the earliest
2384 # changelog entry we have seen so far is indeed the earliest
2385 # upload of this upstream version; or there are only changelog
2386 # entries relating to later upstream versions (which is not
2387 # possible unless the changelog and .dsc disagree about the
2388 # version). Then it remains to choose between the physically
2389 # last entry in the file, and the one with the lowest version
2390 # number. If these are not the same, we guess that the
2391 # versions were created in a non-monotic order rather than
2392 # that the changelog entries have been misordered.
2394 printdebug "import clog $thisstanza->{version} vs $upstreamv...\n";
2396 last if version_compare($thisstanza->{version}, $upstreamv) < 0;
2397 $r1clogp = $thisstanza;
2399 printdebug "import clog $r1clogp->{version} becomes r1\n";
2401 die $! if CLOGS->error;
2402 close CLOGS or $?==SIGPIPE or failedcmd @clogcmd;
2404 $clogp or fail "package changelog has no entries!";
2406 my $authline = clogp_authline $clogp;
2407 my $changes = getfield $clogp, 'Changes';
2408 $changes =~ s/^\n//; # Changes: \n
2409 my $cversion = getfield $clogp, 'Version';
2412 $r1clogp //= $clogp; # maybe there's only one entry;
2413 my $r1authline = clogp_authline $r1clogp;
2414 # Strictly, r1authline might now be wrong if it's going to be
2415 # unused because !$any_orig. Whatever.
2417 printdebug "import tartrees authline $authline\n";
2418 printdebug "import tartrees r1authline $r1authline\n";
2420 foreach my $tt (@tartrees) {
2421 printdebug "import tartree $tt->{F} $tt->{Tree}\n";
2423 $tt->{Commit} = make_commit_text($tt->{Orig} ? <<END_O : <<END_T);
2426 committer $r1authline
2430 [dgit import orig $tt->{F}]
2438 [dgit import tarball $package $cversion $tt->{F}]
2443 printdebug "import main commit\n";
2445 open C, ">../commit.tmp" or die $!;
2446 print C <<END or die $!;
2449 print C <<END or die $! foreach @tartrees;
2452 print C <<END or die $!;
2458 [dgit import $treeimporthow $package $cversion]
2462 my $rawimport_hash = make_commit qw(../commit.tmp);
2464 if (madformat $dsc->{format}) {
2465 printdebug "import apply patches...\n";
2467 # regularise the state of the working tree so that
2468 # the checkout of $rawimport_hash works nicely.
2469 my $dappliedcommit = make_commit_text(<<END);
2476 runcmd @git, qw(checkout -q -b dapplied), $dappliedcommit;
2478 runcmd @git, qw(checkout -q -b unpa), $rawimport_hash;
2480 # We need the answers to be reproducible
2481 my @authline = clogp_authline($clogp);
2482 local $ENV{GIT_COMMITTER_NAME} = $authline[0];
2483 local $ENV{GIT_COMMITTER_EMAIL} = $authline[1];
2484 local $ENV{GIT_COMMITTER_DATE} = $authline[2];
2485 local $ENV{GIT_AUTHOR_NAME} = $authline[0];
2486 local $ENV{GIT_AUTHOR_EMAIL} = $authline[1];
2487 local $ENV{GIT_AUTHOR_DATE} = $authline[2];
2489 my $path = $ENV{PATH} or die;
2491 # we use ../../gbp-pq-output, which (given that we are in
2492 # $playground/PLAYTREE, and $playground is .git/dgit/unpack,
2495 foreach my $use_absurd (qw(0 1)) {
2496 runcmd @git, qw(checkout -q unpa);
2497 runcmd @git, qw(update-ref -d refs/heads/patch-queue/unpa);
2498 local $ENV{PATH} = $path;
2501 progress "warning: $@";
2502 $path = "$absurdity:$path";
2503 progress "$us: trying slow absurd-git-apply...";
2504 rename "../../gbp-pq-output","../../gbp-pq-output.0"
2509 die "forbid absurd git-apply\n" if $use_absurd
2510 && forceing [qw(import-gitapply-no-absurd)];
2511 die "only absurd git-apply!\n" if !$use_absurd
2512 && forceing [qw(import-gitapply-absurd)];
2514 local $ENV{DGIT_ABSURD_DEBUG} = $debuglevel if $use_absurd;
2515 local $ENV{PATH} = $path if $use_absurd;
2517 my @showcmd = (gbp_pq, qw(import));
2518 my @realcmd = shell_cmd
2519 'exec >/dev/null 2>>../../gbp-pq-output', @showcmd;
2520 debugcmd "+",@realcmd;
2521 if (system @realcmd) {
2522 die +(shellquote @showcmd).
2524 failedcmd_waitstatus()."\n";
2527 my $gapplied = git_rev_parse('HEAD');
2528 my $gappliedtree = cmdoutput @git, qw(rev-parse HEAD:);
2529 $gappliedtree eq $dappliedtree or
2531 gbp-pq import and dpkg-source disagree!
2532 gbp-pq import gave commit $gapplied
2533 gbp-pq import gave tree $gappliedtree
2534 dpkg-source --before-build gave tree $dappliedtree
2536 $rawimport_hash = $gapplied;
2541 { local $@; eval { runcmd qw(cat ../../gbp-pq-output); }; }
2546 progress "synthesised git commit from .dsc $cversion";
2548 my $rawimport_mergeinput = {
2549 Commit => $rawimport_hash,
2550 Info => "Import of source package",
2552 my @output = ($rawimport_mergeinput);
2554 if ($lastpush_mergeinput) {
2555 my $oldclogp = mergeinfo_getclogp($lastpush_mergeinput);
2556 my $oversion = getfield $oldclogp, 'Version';
2558 version_compare($oversion, $cversion);
2560 @output = ($rawimport_mergeinput, $lastpush_mergeinput,
2561 { Message => <<END, ReverseParents => 1 });
2562 Record $package ($cversion) in archive suite $csuite
2564 } elsif ($vcmp > 0) {
2565 print STDERR <<END or die $!;
2567 Version actually in archive: $cversion (older)
2568 Last version pushed with dgit: $oversion (newer or same)
2571 @output = $lastpush_mergeinput;
2573 # Same version. Use what's in the server git branch,
2574 # discarding our own import. (This could happen if the
2575 # server automatically imports all packages into git.)
2576 @output = $lastpush_mergeinput;
2584 sub complete_file_from_dsc ($$;$) {
2585 our ($dstdir, $fi, $refetched) = @_;
2586 # Ensures that we have, in $dstdir, the file $fi, with the correct
2587 # contents. (Downloading it from alongside $dscurl if necessary.)
2588 # If $refetched is defined, can overwrite "$dstdir/$fi->{Filename}"
2589 # and will set $$refetched=1 if it did so (or tried to).
2591 my $f = $fi->{Filename};
2592 my $tf = "$dstdir/$f";
2596 my $checkhash = sub {
2597 open F, "<", "$tf" or die "$tf: $!";
2598 $fi->{Digester}->reset();
2599 $fi->{Digester}->addfile(*F);
2600 F->error and die $!;
2601 $got = $fi->{Digester}->hexdigest();
2602 return $got eq $fi->{Hash};
2605 if (stat_exists $tf) {
2606 if ($checkhash->()) {
2607 progress "using existing $f";
2611 fail "file $f has hash $got but .dsc".
2612 " demands hash $fi->{Hash} ".
2613 "(perhaps you should delete this file?)";
2615 progress "need to fetch correct version of $f";
2616 unlink $tf or die "$tf $!";
2619 printdebug "$tf does not exist, need to fetch\n";
2623 $furl =~ s{/[^/]+$}{};
2625 die "$f ?" unless $f =~ m/^\Q${package}\E_/;
2626 die "$f ?" if $f =~ m#/#;
2627 runcmd_ordryrun_local @curl,qw(-f -o),$tf,'--',"$furl";
2628 return 0 if !act_local();
2631 fail "file $f has hash $got but .dsc".
2632 " demands hash $fi->{Hash} ".
2633 "(got wrong file from archive!)";
2638 sub ensure_we_have_orig () {
2639 my @dfi = dsc_files_info();
2640 foreach my $fi (@dfi) {
2641 my $f = $fi->{Filename};
2642 next unless is_orig_file_in_dsc($f, \@dfi);
2643 complete_file_from_dsc('..', $fi)
2648 #---------- git fetch ----------
2650 sub lrfetchrefs () { return "refs/dgit-fetch/".access_basedistro(); }
2651 sub lrfetchref () { return lrfetchrefs.'/'.server_branch($csuite); }
2653 # We fetch some parts of lrfetchrefs/*. Ideally we delete these
2654 # locally fetched refs because they have unhelpful names and clutter
2655 # up gitk etc. So we track whether we have "used up" head ref (ie,
2656 # whether we have made another local ref which refers to this object).
2658 # (If we deleted them unconditionally, then we might end up
2659 # re-fetching the same git objects each time dgit fetch was run.)
2661 # So, each use of lrfetchrefs needs to be accompanied by arrangements
2662 # in git_fetch_us to fetch the refs in question, and possibly a call
2663 # to lrfetchref_used.
2665 our (%lrfetchrefs_f, %lrfetchrefs_d);
2666 # $lrfetchrefs_X{lrfetchrefs."/heads/whatever"} = $objid
2668 sub lrfetchref_used ($) {
2669 my ($fullrefname) = @_;
2670 my $objid = $lrfetchrefs_f{$fullrefname};
2671 $lrfetchrefs_d{$fullrefname} = $objid if defined $objid;
2674 sub git_lrfetch_sane {
2675 my ($url, $supplementary, @specs) = @_;
2676 # Make a 'refs/'.lrfetchrefs.'/*' be just like on server,
2677 # at least as regards @specs. Also leave the results in
2678 # %lrfetchrefs_f, and arrange for lrfetchref_used to be
2679 # able to clean these up.
2681 # With $supplementary==1, @specs must not contain wildcards
2682 # and we add to our previous fetches (non-atomically).
2684 # This is rather miserable:
2685 # When git fetch --prune is passed a fetchspec ending with a *,
2686 # it does a plausible thing. If there is no * then:
2687 # - it matches subpaths too, even if the supplied refspec
2688 # starts refs, and behaves completely madly if the source
2689 # has refs/refs/something. (See, for example, Debian #NNNN.)
2690 # - if there is no matching remote ref, it bombs out the whole
2692 # We want to fetch a fixed ref, and we don't know in advance
2693 # if it exists, so this is not suitable.
2695 # Our workaround is to use git ls-remote. git ls-remote has its
2696 # own qairks. Notably, it has the absurd multi-tail-matching
2697 # behaviour: git ls-remote R refs/foo can report refs/foo AND
2698 # refs/refs/foo etc.
2700 # Also, we want an idempotent snapshot, but we have to make two
2701 # calls to the remote: one to git ls-remote and to git fetch. The
2702 # solution is use git ls-remote to obtain a target state, and
2703 # git fetch to try to generate it. If we don't manage to generate
2704 # the target state, we try again.
2706 printdebug "git_lrfetch_sane suppl=$supplementary specs @specs\n";
2708 my $specre = join '|', map {
2711 my $wildcard = $x =~ s/\\\*$/.*/;
2712 die if $wildcard && $supplementary;
2715 printdebug "git_lrfetch_sane specre=$specre\n";
2716 my $wanted_rref = sub {
2718 return m/^(?:$specre)$/;
2721 my $fetch_iteration = 0;
2724 printdebug "git_lrfetch_sane iteration $fetch_iteration\n";
2725 if (++$fetch_iteration > 10) {
2726 fail "too many iterations trying to get sane fetch!";
2729 my @look = map { "refs/$_" } @specs;
2730 my @lcmd = (@git, qw(ls-remote -q --refs), $url, @look);
2734 open GITLS, "-|", @lcmd or die $!;
2736 printdebug "=> ", $_;
2737 m/^(\w+)\s+(\S+)\n/ or die "ls-remote $_ ?";
2738 my ($objid,$rrefname) = ($1,$2);
2739 if (!$wanted_rref->($rrefname)) {
2741 warning: git ls-remote @look reported $rrefname; this is silly, ignoring it.
2745 $wantr{$rrefname} = $objid;
2748 close GITLS or failedcmd @lcmd;
2750 # OK, now %want is exactly what we want for refs in @specs
2752 !m/\*$/ && !exists $wantr{"refs/$_"} ? () :
2753 "+refs/$_:".lrfetchrefs."/$_";
2756 printdebug "git_lrfetch_sane fspecs @fspecs\n";
2758 my @fcmd = (@git, qw(fetch -p -n -q), $url, @fspecs);
2759 runcmd_ordryrun_local @fcmd if @fspecs;
2761 if (!$supplementary) {
2762 %lrfetchrefs_f = ();
2766 git_for_each_ref(lrfetchrefs, sub {
2767 my ($objid,$objtype,$lrefname,$reftail) = @_;
2768 $lrfetchrefs_f{$lrefname} = $objid;
2769 $objgot{$objid} = 1;
2772 if ($supplementary) {
2776 foreach my $lrefname (sort keys %lrfetchrefs_f) {
2777 my $rrefname = 'refs'.substr($lrefname, length lrfetchrefs);
2778 if (!exists $wantr{$rrefname}) {
2779 if ($wanted_rref->($rrefname)) {
2781 git-fetch @fspecs created $lrefname which git ls-remote @look didn't list.
2785 warning: git fetch @fspecs created $lrefname; this is silly, deleting it.
2788 runcmd_ordryrun_local @git, qw(update-ref -d), $lrefname;
2789 delete $lrfetchrefs_f{$lrefname};
2793 foreach my $rrefname (sort keys %wantr) {
2794 my $lrefname = lrfetchrefs.substr($rrefname, 4);
2795 my $got = $lrfetchrefs_f{$lrefname} // '<none>';
2796 my $want = $wantr{$rrefname};
2797 next if $got eq $want;
2798 if (!defined $objgot{$want}) {
2799 fail <<END unless act_local();
2800 --dry-run specified but we actually wanted the results of git fetch,
2801 so this is not going to work. Try running dgit fetch first,
2802 or using --damp-run instead of --dry-run.
2805 warning: git ls-remote suggests we want $lrefname
2806 warning: and it should refer to $want
2807 warning: but git fetch didn't fetch that object to any relevant ref.
2808 warning: This may be due to a race with someone updating the server.
2809 warning: Will try again...
2811 next FETCH_ITERATION;
2814 git-fetch @fspecs made $lrefname=$got but want git ls-remote @look says $want
2816 runcmd_ordryrun_local @git, qw(update-ref -m),
2817 "dgit fetch git fetch fixup", $lrefname, $want;
2818 $lrfetchrefs_f{$lrefname} = $want;
2823 if (defined $csuite) {
2824 printdebug "git_lrfetch_sane: tidying any old suite lrfetchrefs\n";
2825 git_for_each_ref("refs/dgit-fetch/$csuite", sub {
2826 my ($objid,$objtype,$lrefname,$reftail) = @_;
2827 next if $lrfetchrefs_f{$lrefname}; # $csuite eq $distro ?
2828 runcmd_ordryrun_local @git, qw(update-ref -d), $lrefname;
2832 printdebug "git_lrfetch_sane: git fetch --no-insane emulation complete\n",
2833 Dumper(\%lrfetchrefs_f);
2836 sub git_fetch_us () {
2837 # Want to fetch only what we are going to use, unless
2838 # deliberately-not-ff, in which case we must fetch everything.
2840 my @specs = deliberately_not_fast_forward ? qw(tags/*) :
2842 (quiltmode_splitbrain
2843 ? (map { $_->('*',access_nomdistro) }
2844 \&debiantag_new, \&debiantag_maintview)
2845 : debiantags('*',access_nomdistro));
2846 push @specs, server_branch($csuite);
2847 push @specs, $rewritemap;
2848 push @specs, qw(heads/*) if deliberately_not_fast_forward;
2850 my $url = access_giturl();
2851 git_lrfetch_sane $url, 0, @specs;
2854 my @tagpats = debiantags('*',access_nomdistro);
2856 git_for_each_ref([map { "refs/tags/$_" } @tagpats], sub {
2857 my ($objid,$objtype,$fullrefname,$reftail) = @_;
2858 printdebug "currently $fullrefname=$objid\n";
2859 $here{$fullrefname} = $objid;
2861 git_for_each_ref([map { lrfetchrefs."/tags/".$_ } @tagpats], sub {
2862 my ($objid,$objtype,$fullrefname,$reftail) = @_;
2863 my $lref = "refs".substr($fullrefname, length(lrfetchrefs));
2864 printdebug "offered $lref=$objid\n";
2865 if (!defined $here{$lref}) {
2866 my @upd = (@git, qw(update-ref), $lref, $objid, '');
2867 runcmd_ordryrun_local @upd;
2868 lrfetchref_used $fullrefname;
2869 } elsif ($here{$lref} eq $objid) {
2870 lrfetchref_used $fullrefname;
2873 "Not updating $lref from $here{$lref} to $objid.\n";
2878 #---------- dsc and archive handling ----------
2880 sub mergeinfo_getclogp ($) {
2881 # Ensures thit $mi->{Clogp} exists and returns it
2883 $mi->{Clogp} = commit_getclogp($mi->{Commit});
2886 sub mergeinfo_version ($) {
2887 return getfield( (mergeinfo_getclogp $_[0]), 'Version' );
2890 sub fetch_from_archive_record_1 ($) {
2892 runcmd git_update_ref_cmd "dgit fetch $csuite", 'DGIT_ARCHIVE', $hash;
2893 cmdoutput @git, qw(log -n2), $hash;
2894 # ... gives git a chance to complain if our commit is malformed
2897 sub fetch_from_archive_record_2 ($) {
2899 my @upd_cmd = (git_update_ref_cmd 'dgit fetch', lrref(), $hash);
2903 dryrun_report @upd_cmd;
2907 sub parse_dsc_field_def_dsc_distro () {
2908 $dsc_distro //= cfg qw(dgit.default.old-dsc-distro
2909 dgit.default.distro);
2912 sub parse_dsc_field ($$) {
2913 my ($dsc, $what) = @_;
2915 foreach my $field (@ourdscfield) {
2916 $f = $dsc->{$field};
2921 progress "$what: NO git hash";
2922 parse_dsc_field_def_dsc_distro();
2923 } elsif (($dsc_hash, $dsc_distro, $dsc_hint_tag, $dsc_hint_url)
2924 = $f =~ m/^(\w+)\s+($distro_re)\s+($versiontag_re)\s+(\S+)(?:\s|$)/) {
2925 progress "$what: specified git info ($dsc_distro)";
2926 $dsc_hint_tag = [ $dsc_hint_tag ];
2927 } elsif ($f =~ m/^\w+\s*$/) {
2929 parse_dsc_field_def_dsc_distro();
2930 $dsc_hint_tag = [ debiantags +(getfield $dsc, 'Version'),
2932 progress "$what: specified git hash";
2934 fail "$what: invalid Dgit info";
2938 sub resolve_dsc_field_commit ($$) {
2939 my ($already_distro, $already_mapref) = @_;
2941 return unless defined $dsc_hash;
2944 defined $already_mapref &&
2945 ($already_distro eq $dsc_distro || !$chase_dsc_distro)
2946 ? $already_mapref : undef;
2950 my ($what, @fetch) = @_;
2952 local $idistro = $dsc_distro;
2953 my $lrf = lrfetchrefs;
2955 if (!$chase_dsc_distro) {
2957 "not chasing .dsc distro $dsc_distro: not fetching $what";
2962 ".dsc names distro $dsc_distro: fetching $what";
2964 my $url = access_giturl();
2965 if (!defined $url) {
2966 defined $dsc_hint_url or fail <<END;
2967 .dsc Dgit metadata is in context of distro $dsc_distro
2968 for which we have no configured url and .dsc provides no hint
2971 $dsc_hint_url =~ m#^([-+0-9a-zA-Z]+):# ? $1 :
2972 $dsc_hint_url =~ m#^/# ? 'file' : 'bad-syntax';
2973 parse_cfg_bool "dsc-url-proto-ok", 'false',
2974 cfg("dgit.dsc-url-proto-ok.$proto",
2975 "dgit.default.dsc-url-proto-ok")
2977 .dsc Dgit metadata is in context of distro $dsc_distro
2978 for which we have no configured url;
2979 .dsc provides hinted url with protocol $proto which is unsafe.
2980 (can be overridden by config - consult documentation)
2982 $url = $dsc_hint_url;
2985 git_lrfetch_sane $url, 1, @fetch;
2990 my $rewrite_enable = do {
2991 local $idistro = $dsc_distro;
2992 access_cfg('rewrite-map-enable', 'RETURN-UNDEF');
2995 if (parse_cfg_bool 'rewrite-map-enable', 'true', $rewrite_enable) {
2996 if (!defined $mapref) {
2997 my $lrf = $do_fetch->("rewrite map", $rewritemap) or return;
2998 $mapref = $lrf.'/'.$rewritemap;
3000 my $rewritemapdata = git_cat_file $mapref.':map';
3001 if (defined $rewritemapdata
3002 && $rewritemapdata =~ m/^$dsc_hash(?:[ \t](\w+))/m) {
3004 "server's git history rewrite map contains a relevant entry!";
3007 if (defined $dsc_hash) {
3008 progress "using rewritten git hash in place of .dsc value";
3010 progress "server data says .dsc hash is to be disregarded";
3015 if (!defined git_cat_file $dsc_hash) {
3016 my @tags = map { "tags/".$_ } @$dsc_hint_tag;
3017 my $lrf = $do_fetch->("additional commits", @tags) &&
3018 defined git_cat_file $dsc_hash
3020 .dsc Dgit metadata requires commit $dsc_hash
3021 but we could not obtain that object anywhere.
3023 foreach my $t (@tags) {
3024 my $fullrefname = $lrf.'/'.$t;
3025 # print STDERR "CHK $t $fullrefname ".Dumper(\%lrfetchrefs_f);
3026 next unless $lrfetchrefs_f{$fullrefname};
3027 next unless is_fast_fwd "$fullrefname~0", $dsc_hash;
3028 lrfetchref_used $fullrefname;
3033 sub fetch_from_archive () {
3034 ensure_setup_existing_tree();
3036 # Ensures that lrref() is what is actually in the archive, one way
3037 # or another, according to us - ie this client's
3038 # appropritaely-updated archive view. Also returns the commit id.
3039 # If there is nothing in the archive, leaves lrref alone and
3040 # returns undef. git_fetch_us must have already been called.
3044 parse_dsc_field($dsc, 'last upload to archive');
3045 resolve_dsc_field_commit access_basedistro,
3046 lrfetchrefs."/".$rewritemap
3048 progress "no version available from the archive";
3051 # If the archive's .dsc has a Dgit field, there are three
3052 # relevant git commitids we need to choose between and/or merge
3054 # 1. $dsc_hash: the Dgit field from the archive
3055 # 2. $lastpush_hash: the suite branch on the dgit git server
3056 # 3. $lastfetch_hash: our local tracking brach for the suite
3058 # These may all be distinct and need not be in any fast forward
3061 # If the dsc was pushed to this suite, then the server suite
3062 # branch will have been updated; but it might have been pushed to
3063 # a different suite and copied by the archive. Conversely a more
3064 # recent version may have been pushed with dgit but not appeared
3065 # in the archive (yet).
3067 # $lastfetch_hash may be awkward because archive imports
3068 # (particularly, imports of Dgit-less .dscs) are performed only as
3069 # needed on individual clients, so different clients may perform a
3070 # different subset of them - and these imports are only made
3071 # public during push. So $lastfetch_hash may represent a set of
3072 # imports different to a subsequent upload by a different dgit
3075 # Our approach is as follows:
3077 # As between $dsc_hash and $lastpush_hash: if $lastpush_hash is a
3078 # descendant of $dsc_hash, then it was pushed by a dgit user who
3079 # had based their work on $dsc_hash, so we should prefer it.
3080 # Otherwise, $dsc_hash was installed into this suite in the
3081 # archive other than by a dgit push, and (necessarily) after the
3082 # last dgit push into that suite (since a dgit push would have
3083 # been descended from the dgit server git branch); thus, in that
3084 # case, we prefer the archive's version (and produce a
3085 # pseudo-merge to overwrite the dgit server git branch).
3087 # (If there is no Dgit field in the archive's .dsc then
3088 # generate_commit_from_dsc uses the version numbers to decide
3089 # whether the suite branch or the archive is newer. If the suite
3090 # branch is newer it ignores the archive's .dsc; otherwise it
3091 # generates an import of the .dsc, and produces a pseudo-merge to
3092 # overwrite the suite branch with the archive contents.)
3094 # The outcome of that part of the algorithm is the `public view',
3095 # and is same for all dgit clients: it does not depend on any
3096 # unpublished history in the local tracking branch.
3098 # As between the public view and the local tracking branch: The
3099 # local tracking branch is only updated by dgit fetch, and
3100 # whenever dgit fetch runs it includes the public view in the
3101 # local tracking branch. Therefore if the public view is not
3102 # descended from the local tracking branch, the local tracking
3103 # branch must contain history which was imported from the archive
3104 # but never pushed; and, its tip is now out of date. So, we make
3105 # a pseudo-merge to overwrite the old imports and stitch the old
3108 # Finally: we do not necessarily reify the public view (as
3109 # described above). This is so that we do not end up stacking two
3110 # pseudo-merges. So what we actually do is figure out the inputs
3111 # to any public view pseudo-merge and put them in @mergeinputs.
3114 # $mergeinputs[]{Commit}
3115 # $mergeinputs[]{Info}
3116 # $mergeinputs[0] is the one whose tree we use
3117 # @mergeinputs is in the order we use in the actual commit)
3120 # $mergeinputs[]{Message} is a commit message to use
3121 # $mergeinputs[]{ReverseParents} if def specifies that parent
3122 # list should be in opposite order
3123 # Such an entry has no Commit or Info. It applies only when found
3124 # in the last entry. (This ugliness is to support making
3125 # identical imports to previous dgit versions.)
3127 my $lastpush_hash = git_get_ref(lrfetchref());
3128 printdebug "previous reference hash=$lastpush_hash\n";
3129 $lastpush_mergeinput = $lastpush_hash && {
3130 Commit => $lastpush_hash,
3131 Info => "dgit suite branch on dgit git server",
3134 my $lastfetch_hash = git_get_ref(lrref());
3135 printdebug "fetch_from_archive: lastfetch=$lastfetch_hash\n";
3136 my $lastfetch_mergeinput = $lastfetch_hash && {
3137 Commit => $lastfetch_hash,
3138 Info => "dgit client's archive history view",
3141 my $dsc_mergeinput = $dsc_hash && {
3142 Commit => $dsc_hash,
3143 Info => "Dgit field in .dsc from archive",
3147 my $del_lrfetchrefs = sub {
3150 printdebug "del_lrfetchrefs...\n";
3151 foreach my $fullrefname (sort keys %lrfetchrefs_d) {
3152 my $objid = $lrfetchrefs_d{$fullrefname};
3153 printdebug "del_lrfetchrefs: $objid $fullrefname\n";
3155 $gur ||= new IO::Handle;
3156 open $gur, "|-", qw(git update-ref --stdin) or die $!;
3158 printf $gur "delete %s %s\n", $fullrefname, $objid;
3161 close $gur or failedcmd "git update-ref delete lrfetchrefs";
3165 if (defined $dsc_hash) {
3166 ensure_we_have_orig();
3167 if (!$lastpush_hash || $dsc_hash eq $lastpush_hash) {
3168 @mergeinputs = $dsc_mergeinput
3169 } elsif (is_fast_fwd($dsc_hash,$lastpush_hash)) {
3170 print STDERR <<END or die $!;
3172 Git commit in archive is behind the last version allegedly pushed/uploaded.
3173 Commit referred to by archive: $dsc_hash
3174 Last version pushed with dgit: $lastpush_hash
3177 @mergeinputs = ($lastpush_mergeinput);
3179 # Archive has .dsc which is not a descendant of the last dgit
3180 # push. This can happen if the archive moves .dscs about.
3181 # Just follow its lead.
3182 if (is_fast_fwd($lastpush_hash,$dsc_hash)) {
3183 progress "archive .dsc names newer git commit";
3184 @mergeinputs = ($dsc_mergeinput);
3186 progress "archive .dsc names other git commit, fixing up";
3187 @mergeinputs = ($dsc_mergeinput, $lastpush_mergeinput);
3191 @mergeinputs = generate_commits_from_dsc();
3192 # We have just done an import. Now, our import algorithm might
3193 # have been improved. But even so we do not want to generate
3194 # a new different import of the same package. So if the
3195 # version numbers are the same, just use our existing version.
3196 # If the version numbers are different, the archive has changed
3197 # (perhaps, rewound).
3198 if ($lastfetch_mergeinput &&
3199 !version_compare( (mergeinfo_version $lastfetch_mergeinput),
3200 (mergeinfo_version $mergeinputs[0]) )) {
3201 @mergeinputs = ($lastfetch_mergeinput);
3203 } elsif ($lastpush_hash) {
3204 # only in git, not in the archive yet
3205 @mergeinputs = ($lastpush_mergeinput);
3206 print STDERR <<END or die $!;
3208 Package not found in the archive, but has allegedly been pushed using dgit.
3212 printdebug "nothing found!\n";
3213 if (defined $skew_warning_vsn) {
3214 print STDERR <<END or die $!;
3216 Warning: relevant archive skew detected.
3217 Archive allegedly contains $skew_warning_vsn
3218 But we were not able to obtain any version from the archive or git.
3222 unshift @end, $del_lrfetchrefs;
3226 if ($lastfetch_hash &&
3228 my $h = $_->{Commit};
3229 $h and is_fast_fwd($lastfetch_hash, $h);
3230 # If true, one of the existing parents of this commit
3231 # is a descendant of the $lastfetch_hash, so we'll
3232 # be ff from that automatically.
3236 push @mergeinputs, $lastfetch_mergeinput;
3239 printdebug "fetch mergeinfos:\n";
3240 foreach my $mi (@mergeinputs) {
3242 printdebug " commit $mi->{Commit} $mi->{Info}\n";
3244 printdebug sprintf " ReverseParents=%d Message=%s",
3245 $mi->{ReverseParents}, $mi->{Message};
3249 my $compat_info= pop @mergeinputs
3250 if $mergeinputs[$#mergeinputs]{Message};
3252 @mergeinputs = grep { defined $_->{Commit} } @mergeinputs;
3255 if (@mergeinputs > 1) {
3257 my $tree_commit = $mergeinputs[0]{Commit};
3259 my $tree = cmdoutput @git, qw(cat-file commit), $tree_commit;
3260 $tree =~ m/\n\n/; $tree = $`;
3261 $tree =~ m/^tree (\w+)$/m or die "$dsc_hash tree ?";
3264 # We use the changelog author of the package in question the
3265 # author of this pseudo-merge. This is (roughly) correct if
3266 # this commit is simply representing aa non-dgit upload.
3267 # (Roughly because it does not record sponsorship - but we
3268 # don't have sponsorship info because that's in the .changes,
3269 # which isn't in the archivw.)
3271 # But, it might be that we are representing archive history
3272 # updates (including in-archive copies). These are not really
3273 # the responsibility of the person who created the .dsc, but
3274 # there is no-one whose name we should better use. (The
3275 # author of the .dsc-named commit is clearly worse.)
3277 my $useclogp = mergeinfo_getclogp $mergeinputs[0];
3278 my $author = clogp_authline $useclogp;
3279 my $cversion = getfield $useclogp, 'Version';
3281 my $mcf = dgit_privdir()."/mergecommit";
3282 open MC, ">", $mcf or die "$mcf $!";
3283 print MC <<END or die $!;
3287 my @parents = grep { $_->{Commit} } @mergeinputs;
3288 @parents = reverse @parents if $compat_info->{ReverseParents};
3289 print MC <<END or die $! foreach @parents;
3293 print MC <<END or die $!;
3299 if (defined $compat_info->{Message}) {
3300 print MC $compat_info->{Message} or die $!;
3302 print MC <<END or die $!;
3303 Record $package ($cversion) in archive suite $csuite
3307 my $message_add_info = sub {
3309 my $mversion = mergeinfo_version $mi;
3310 printf MC " %-20s %s\n", $mversion, $mi->{Info}
3314 $message_add_info->($mergeinputs[0]);
3315 print MC <<END or die $!;
3316 should be treated as descended from
3318 $message_add_info->($_) foreach @mergeinputs[1..$#mergeinputs];
3322 $hash = make_commit $mcf;
3324 $hash = $mergeinputs[0]{Commit};
3326 printdebug "fetch hash=$hash\n";
3329 my ($lasth, $what) = @_;
3330 return unless $lasth;
3331 die "$lasth $hash $what ?" unless is_fast_fwd($lasth, $hash);
3334 $chkff->($lastpush_hash, 'dgit repo server tip (last push)')
3336 $chkff->($lastfetch_hash, 'local tracking tip (last fetch)');
3338 fetch_from_archive_record_1($hash);
3340 if (defined $skew_warning_vsn) {
3341 printdebug "SKEW CHECK WANT $skew_warning_vsn\n";
3342 my $gotclogp = commit_getclogp($hash);
3343 my $got_vsn = getfield $gotclogp, 'Version';
3344 printdebug "SKEW CHECK GOT $got_vsn\n";
3345 if (version_compare($got_vsn, $skew_warning_vsn) < 0) {
3346 print STDERR <<END or die $!;
3348 Warning: archive skew detected. Using the available version:
3349 Archive allegedly contains $skew_warning_vsn
3350 We were able to obtain only $got_vsn
3356 if ($lastfetch_hash ne $hash) {
3357 fetch_from_archive_record_2($hash);
3360 lrfetchref_used lrfetchref();
3362 check_gitattrs($hash, "fetched source tree");
3364 unshift @end, $del_lrfetchrefs;
3368 sub set_local_git_config ($$) {
3370 runcmd @git, qw(config), $k, $v;
3373 sub setup_mergechangelogs (;$) {
3375 return unless $always || access_cfg_bool(1, 'setup-mergechangelogs');
3377 my $driver = 'dpkg-mergechangelogs';
3378 my $cb = "merge.$driver";
3379 confess unless defined $maindir;
3380 my $attrs = "$maindir_gitcommon/info/attributes";
3381 ensuredir "$maindir_gitcommon/info";
3383 open NATTRS, ">", "$attrs.new" or die "$attrs.new $!";
3384 if (!open ATTRS, "<", $attrs) {
3385 $!==ENOENT or die "$attrs: $!";
3389 next if m{^debian/changelog\s};
3390 print NATTRS $_, "\n" or die $!;
3392 ATTRS->error and die $!;
3395 print NATTRS "debian/changelog merge=$driver\n" or die $!;
3398 set_local_git_config "$cb.name", 'debian/changelog merge driver';
3399 set_local_git_config "$cb.driver", 'dpkg-mergechangelogs -m %O %A %B %A';
3401 rename "$attrs.new", "$attrs" or die "$attrs: $!";
3404 sub setup_useremail (;$) {
3406 return unless $always || access_cfg_bool(1, 'setup-useremail');
3409 my ($k, $envvar) = @_;
3410 my $v = access_cfg("user-$k", 'RETURN-UNDEF') // $ENV{$envvar};
3411 return unless defined $v;
3412 set_local_git_config "user.$k", $v;
3415 $setup->('email', 'DEBEMAIL');
3416 $setup->('name', 'DEBFULLNAME');
3419 sub ensure_setup_existing_tree () {
3420 my $k = "remote.$remotename.skipdefaultupdate";
3421 my $c = git_get_config $k;
3422 return if defined $c;
3423 set_local_git_config $k, 'true';
3426 sub open_main_gitattrs () {
3427 confess 'internal error no maindir' unless defined $maindir;
3428 my $gai = new IO::File "$maindir_gitcommon/info/attributes"
3430 or die "open $maindir_gitcommon/info/attributes: $!";
3434 our $gitattrs_ourmacro_re = qr{^\[attr\]dgit-defuse-attrs\s};
3436 sub is_gitattrs_setup () {
3439 # 1: gitattributes set up and should be left alone
3441 # 0: there is a dgit-defuse-attrs but it needs fixing
3442 # undef: there is none
3443 my $gai = open_main_gitattrs();
3444 return 0 unless $gai;
3446 next unless m{$gitattrs_ourmacro_re};
3447 return 1 if m{\s-working-tree-encoding\s};
3448 printdebug "is_gitattrs_setup: found old macro\n";
3451 $gai->error and die $!;
3452 printdebug "is_gitattrs_setup: found nothing\n";
3456 sub setup_gitattrs (;$) {
3458 return unless $always || access_cfg_bool(1, 'setup-gitattributes');
3460 my $already = is_gitattrs_setup();
3463 [attr]dgit-defuse-attrs already found, and proper, in .git/info/attributes
3464 not doing further gitattributes setup
3468 my $new = "[attr]dgit-defuse-attrs $negate_harmful_gitattrs";
3469 my $af = "$maindir_gitcommon/info/attributes";
3470 ensuredir "$maindir_gitcommon/info";
3472 open GAO, "> $af.new" or die $!;
3473 print GAO <<END or die $! unless defined $already;
3476 # ^ see GITATTRIBUTES in dgit(7) and dgit setup-new-tree in dgit(1)
3478 my $gai = open_main_gitattrs();
3481 if (m{$gitattrs_ourmacro_re}) {
3482 die unless defined $already;
3486 print GAO $_, "\n" or die $!;
3488 $gai->error and die $!;
3490 close GAO or die $!;
3491 rename "$af.new", "$af" or die "install $af: $!";
3494 sub setup_new_tree () {
3495 setup_mergechangelogs();
3500 sub check_gitattrs ($$) {
3501 my ($treeish, $what) = @_;
3503 return if is_gitattrs_setup;
3506 my @cmd = (@git, qw(ls-tree -lrz --), "${treeish}:");
3508 my $gafl = new IO::File;
3509 open $gafl, "-|", @cmd or die $!;
3512 s/^\d+\s+\w+\s+\w+\s+(\d+)\t// or die;
3514 next unless m{(?:^|/)\.gitattributes$};
3516 # oh dear, found one
3518 dgit: warning: $what contains .gitattributes
3519 dgit: .gitattributes not (fully) defused. Recommended: dgit setup-new-tree.
3524 # tree contains no .gitattributes files
3525 $?=0; $!=0; close $gafl or failedcmd @cmd;
3529 sub multisuite_suite_child ($$$) {
3530 my ($tsuite, $merginputs, $fn) = @_;
3531 # in child, sets things up, calls $fn->(), and returns undef
3532 # in parent, returns canonical suite name for $tsuite
3533 my $canonsuitefh = IO::File::new_tmpfile;
3534 my $pid = fork // die $!;
3538 $us .= " [$isuite]";
3539 $debugprefix .= " ";
3540 progress "fetching $tsuite...";
3541 canonicalise_suite();
3542 print $canonsuitefh $csuite, "\n" or die $!;
3543 close $canonsuitefh or die $!;
3547 waitpid $pid,0 == $pid or die $!;
3548 fail "failed to obtain $tsuite: ".waitstatusmsg() if $? && $?!=256*4;
3549 seek $canonsuitefh,0,0 or die $!;
3550 local $csuite = <$canonsuitefh>;
3551 die $! unless defined $csuite && chomp $csuite;
3553 printdebug "multisuite $tsuite missing\n";
3556 printdebug "multisuite $tsuite ok (canon=$csuite)\n";
3557 push @$merginputs, {
3564 sub fork_for_multisuite ($) {
3565 my ($before_fetch_merge) = @_;
3566 # if nothing unusual, just returns ''
3569 # returns 0 to caller in child, to do first of the specified suites
3570 # in child, $csuite is not yet set
3572 # returns 1 to caller in parent, to finish up anything needed after
3573 # in parent, $csuite is set to canonicalised portmanteau
3575 my $org_isuite = $isuite;
3576 my @suites = split /\,/, $isuite;
3577 return '' unless @suites > 1;
3578 printdebug "fork_for_multisuite: @suites\n";
3582 my $cbasesuite = multisuite_suite_child($suites[0], \@mergeinputs,
3584 return 0 unless defined $cbasesuite;
3586 fail "package $package missing in (base suite) $cbasesuite"
3587 unless @mergeinputs;
3589 my @csuites = ($cbasesuite);
3591 $before_fetch_merge->();
3593 foreach my $tsuite (@suites[1..$#suites]) {
3594 $tsuite =~ s/^-/$cbasesuite-/;
3595 my $csubsuite = multisuite_suite_child($tsuite, \@mergeinputs,
3601 # xxx collecte the ref here
3603 $csubsuite =~ s/^\Q$cbasesuite\E-/-/;
3604 push @csuites, $csubsuite;
3607 foreach my $mi (@mergeinputs) {
3608 my $ref = git_get_ref $mi->{Ref};
3609 die "$mi->{Ref} ?" unless length $ref;
3610 $mi->{Commit} = $ref;
3613 $csuite = join ",", @csuites;
3615 my $previous = git_get_ref lrref;
3617 unshift @mergeinputs, {
3618 Commit => $previous,
3619 Info => "local combined tracking branch",
3621 "archive seems to have rewound: local tracking branch is ahead!",
3625 foreach my $ix (0..$#mergeinputs) {
3626 $mergeinputs[$ix]{Index} = $ix;
3629 @mergeinputs = sort {
3630 -version_compare(mergeinfo_version $a,
3631 mergeinfo_version $b) # highest version first
3633 $a->{Index} <=> $b->{Index}; # earliest in spec first
3639 foreach my $mi (@mergeinputs) {
3640 printdebug "multisuite merge check $mi->{Info}\n";
3641 foreach my $previous (@needed) {
3642 next unless is_fast_fwd $mi->{Commit}, $previous->{Commit};
3643 printdebug "multisuite merge un-needed $previous->{Info}\n";
3647 printdebug "multisuite merge this-needed\n";
3648 $mi->{Character} = '+';
3651 $needed[0]{Character} = '*';
3653 my $output = $needed[0]{Commit};
3656 printdebug "multisuite merge nontrivial\n";
3657 my $tree = cmdoutput qw(git rev-parse), $needed[0]{Commit}.':';
3659 my $commit = "tree $tree\n";
3660 my $msg = "Combine archive branches $csuite [dgit]\n\n".
3661 "Input branches:\n";
3663 foreach my $mi (sort { $a->{Index} <=> $b->{Index} } @mergeinputs) {
3664 printdebug "multisuite merge include $mi->{Info}\n";
3665 $mi->{Character} //= ' ';
3666 $commit .= "parent $mi->{Commit}\n";
3667 $msg .= sprintf " %s %-25s %s\n",
3669 (mergeinfo_version $mi),
3672 my $authline = clogp_authline mergeinfo_getclogp $needed[0];
3674 " * marks the highest version branch, which choose to use\n".
3675 " + marks each branch which was not already an ancestor\n\n".
3676 "[dgit multi-suite $csuite]\n";
3678 "author $authline\n".
3679 "committer $authline\n\n";
3680 $output = make_commit_text $commit.$msg;
3681 printdebug "multisuite merge generated $output\n";
3684 fetch_from_archive_record_1($output);
3685 fetch_from_archive_record_2($output);
3687 progress "calculated combined tracking suite $csuite";
3692 sub clone_set_head () {
3693 open H, "> .git/HEAD" or die $!;
3694 print H "ref: ".lref()."\n" or die $!;
3697 sub clone_finish ($) {
3699 runcmd @git, qw(reset --hard), lrref();
3700 runcmd qw(bash -ec), <<'END';
3702 git ls-tree -r --name-only -z HEAD | \
3703 xargs -0r touch -h -r . --
3705 printdone "ready for work in $dstdir";
3709 # in multisuite, returns twice!
3710 # once in parent after first suite fetched,
3711 # and then again in child after everything is finished
3713 badusage "dry run makes no sense with clone" unless act_local();
3715 my $multi_fetched = fork_for_multisuite(sub {
3716 printdebug "multi clone before fetch merge\n";
3720 if ($multi_fetched) {
3721 printdebug "multi clone after fetch merge\n";
3723 clone_finish($dstdir);
3726 printdebug "clone main body\n";
3728 canonicalise_suite();
3729 my $hasgit = check_for_git();
3730 mkdir $dstdir or fail "create \`$dstdir': $!";
3732 runcmd @git, qw(init -q);
3736 my $giturl = access_giturl(1);
3737 if (defined $giturl) {
3738 runcmd @git, qw(remote add), 'origin', $giturl;
3741 progress "fetching existing git history";
3743 runcmd_ordryrun_local @git, qw(fetch origin);
3745 progress "starting new git history";
3747 fetch_from_archive() or no_such_package;
3748 my $vcsgiturl = $dsc->{'Vcs-Git'};
3749 if (length $vcsgiturl) {
3750 $vcsgiturl =~ s/\s+-b\s+\S+//g;
3751 runcmd @git, qw(remote add vcs-git), $vcsgiturl;
3753 clone_finish($dstdir);
3757 canonicalise_suite();
3758 if (check_for_git()) {
3761 fetch_from_archive() or no_such_package();
3763 my $vcsgiturl = $dsc && $dsc->{'Vcs-Git'};
3764 if (length $vcsgiturl and
3765 (grep { $csuite eq $_ }
3767 cfg 'dgit.vcs-git.suites')) {
3768 my $current = cfg 'remote.vcs-git.url', 'RETURN-UNDEF';
3769 if (defined $current && $current ne $vcsgiturl) {
3771 FYI: Vcs-Git in $csuite has different url to your vcs-git remote.
3772 Your vcs-git remote url may be out of date. Use dgit update-vcs-git ?
3776 printdone "fetched into ".lrref();
3780 my $multi_fetched = fork_for_multisuite(sub { });
3781 fetch_one() unless $multi_fetched; # parent
3782 finish 0 if $multi_fetched eq '0'; # child
3787 runcmd_ordryrun_local @git, qw(merge -m),"Merge from $csuite [dgit]",
3789 printdone "fetched to ".lrref()." and merged into HEAD";
3792 sub check_not_dirty () {
3793 foreach my $f (qw(local-options local-patch-header)) {
3794 if (stat_exists "debian/source/$f") {
3795 fail "git tree contains debian/source/$f";
3799 return if $ignoredirty;
3801 git_check_unmodified();
3804 sub commit_admin ($) {
3807 runcmd_ordryrun_local @git, qw(commit -m), $m;
3810 sub quiltify_nofix_bail ($$) {
3811 my ($headinfo, $xinfo) = @_;
3812 if ($quilt_mode eq 'nofix') {
3813 fail "quilt fixup required but quilt mode is \`nofix'\n".
3814 "HEAD commit".$headinfo." differs from tree implied by ".
3815 " debian/patches".$xinfo;
3819 sub commit_quilty_patch () {
3820 my $output = cmdoutput @git, qw(status --porcelain);
3822 foreach my $l (split /\n/, $output) {
3823 next unless $l =~ m/\S/;
3824 if ($l =~ m{^(?:\?\?| [MADRC]) (.pc|debian/patches)}) {
3828 delete $adds{'.pc'}; # if there wasn't one before, don't add it
3830 progress "nothing quilty to commit, ok.";
3833 quiltify_nofix_bail "", " (wanted to commit patch update)";
3834 my @adds = map { s/[][*?\\]/\\$&/g; $_; } sort keys %adds;
3835 runcmd_ordryrun_local @git, qw(add -f), @adds;
3837 Commit Debian 3.0 (quilt) metadata
3839 [dgit ($our_version) quilt-fixup]
3843 sub get_source_format () {
3845 if (open F, "debian/source/options") {
3849 s/\s+$//; # ignore missing final newline
3851 my ($k, $v) = ($`, $'); #');
3852 $v =~ s/^"(.*)"$/$1/;
3858 F->error and die $!;
3861 die $! unless $!==&ENOENT;
3864 if (!open F, "debian/source/format") {
3865 die $! unless $!==&ENOENT;
3869 F->error and die $!;
3871 return ($_, \%options);
3874 sub madformat_wantfixup ($) {
3876 return 0 unless $format eq '3.0 (quilt)';
3877 our $quilt_mode_warned;
3878 if ($quilt_mode eq 'nocheck') {
3879 progress "Not doing any fixup of \`$format' due to".
3880 " ----no-quilt-fixup or --quilt=nocheck"
3881 unless $quilt_mode_warned++;
3884 progress "Format \`$format', need to check/update patch stack"
3885 unless $quilt_mode_warned++;
3889 sub maybe_split_brain_save ($$$) {
3890 my ($headref, $dgitview, $msg) = @_;
3891 # => message fragment "$saved" describing disposition of $dgitview
3892 return "commit id $dgitview" unless defined $split_brain_save;
3893 my @cmd = (shell_cmd 'cd "$1"; shift', $maindir,
3895 "dgit --dgit-view-save $msg HEAD=$headref",
3896 $split_brain_save, $dgitview);
3898 return "and left in $split_brain_save";
3901 # An "infopair" is a tuple [ $thing, $what ]
3902 # (often $thing is a commit hash; $what is a description)
3904 sub infopair_cond_equal ($$) {
3906 $x->[0] eq $y->[0] or fail <<END;
3907 $x->[1] ($x->[0]) not equal to $y->[1] ($y->[0])
3911 sub infopair_lrf_tag_lookup ($$) {
3912 my ($tagnames, $what) = @_;
3913 # $tagname may be an array ref
3914 my @tagnames = ref $tagnames ? @$tagnames : ($tagnames);
3915 printdebug "infopair_lrfetchref_tag_lookup $what @tagnames\n";
3916 foreach my $tagname (@tagnames) {
3917 my $lrefname = lrfetchrefs."/tags/$tagname";
3918 my $tagobj = $lrfetchrefs_f{$lrefname};
3919 next unless defined $tagobj;
3920 printdebug "infopair_lrfetchref_tag_lookup $tagobj $tagname $what\n";
3921 return [ git_rev_parse($tagobj), $what ];
3923 fail @tagnames==1 ? <<END : <<END;
3924 Wanted tag $what (@tagnames) on dgit server, but not found
3926 Wanted tag $what (one of: @tagnames) on dgit server, but not found
3930 sub infopair_cond_ff ($$) {
3931 my ($anc,$desc) = @_;
3932 is_fast_fwd($anc->[0], $desc->[0]) or fail <<END;
3933 $anc->[1] ($anc->[0]) .. $desc->[1] ($desc->[0]) is not fast forward
3937 sub pseudomerge_version_check ($$) {
3938 my ($clogp, $archive_hash) = @_;
3940 my $arch_clogp = commit_getclogp $archive_hash;
3941 my $i_arch_v = [ (getfield $arch_clogp, 'Version'),
3942 'version currently in archive' ];
3943 if (defined $overwrite_version) {
3944 if (length $overwrite_version) {
3945 infopair_cond_equal([ $overwrite_version,
3946 '--overwrite= version' ],
3949 my $v = $i_arch_v->[0];
3950 progress "Checking package changelog for archive version $v ...";
3953 my @xa = ("-f$v", "-t$v");
3954 my $vclogp = parsechangelog @xa;
3957 [ (getfield $vclogp, $fn),
3958 "$fn field from dpkg-parsechangelog @xa" ];
3960 my $cv = $gf->('Version');
3961 infopair_cond_equal($i_arch_v, $cv);
3962 $cd = $gf->('Distribution');
3965 $@ =~ s/^dgit: //gm;
3967 "Perhaps debian/changelog does not mention $v ?";
3969 fail <<END if $cd->[0] =~ m/UNRELEASED/;
3970 $cd->[1] is $cd->[0]
3971 Your tree seems to based on earlier (not uploaded) $v.
3976 printdebug "pseudomerge_version_check i_arch_v @$i_arch_v\n";
3980 sub pseudomerge_make_commit ($$$$ $$) {
3981 my ($clogp, $dgitview, $archive_hash, $i_arch_v,
3982 $msg_cmd, $msg_msg) = @_;
3983 progress "Declaring that HEAD inciudes all changes in $i_arch_v->[0]...";
3985 my $tree = cmdoutput qw(git rev-parse), "${dgitview}:";
3986 my $authline = clogp_authline $clogp;
3990 !defined $overwrite_version ? ""
3991 : !length $overwrite_version ? " --overwrite"
3992 : " --overwrite=".$overwrite_version;
3994 # Contributing parent is the first parent - that makes
3995 # git rev-list --first-parent DTRT.
3996 my $pmf = dgit_privdir()."/pseudomerge";
3997 open MC, ">", $pmf or die "$pmf $!";
3998 print MC <<END or die $!;
4001 parent $archive_hash
4011 return make_commit($pmf);
4014 sub splitbrain_pseudomerge ($$$$) {
4015 my ($clogp, $maintview, $dgitview, $archive_hash) = @_;
4016 # => $merged_dgitview
4017 printdebug "splitbrain_pseudomerge...\n";
4019 # We: debian/PREVIOUS HEAD($maintview)
4020 # expect: o ----------------- o
4023 # a/d/PREVIOUS $dgitview
4026 # we do: `------------------ o
4030 return $dgitview unless defined $archive_hash;
4031 return $dgitview if deliberately_not_fast_forward();
4033 printdebug "splitbrain_pseudomerge...\n";
4035 my $i_arch_v = pseudomerge_version_check($clogp, $archive_hash);
4037 if (!defined $overwrite_version) {
4038 progress "Checking that HEAD inciudes all changes in archive...";
4041 return $dgitview if is_fast_fwd $archive_hash, $dgitview;
4043 if (defined $overwrite_version) {
4045 my $t_dep14 = debiantag_maintview $i_arch_v->[0], access_nomdistro;
4046 my $i_dep14 = infopair_lrf_tag_lookup($t_dep14, "maintainer view tag");
4047 my $t_dgit = debiantag_new $i_arch_v->[0], access_nomdistro;
4048 my $i_dgit = infopair_lrf_tag_lookup($t_dgit, "dgit view tag");
4049 my $i_archive = [ $archive_hash, "current archive contents" ];
4051 printdebug "splitbrain_pseudomerge i_archive @$i_archive\n";
4053 infopair_cond_equal($i_dgit, $i_archive);
4054 infopair_cond_ff($i_dep14, $i_dgit);
4055 infopair_cond_ff($i_dep14, [ $maintview, 'HEAD' ]);
4058 $@ =~ s/^\n//; chomp $@;
4061 | Not fast forward; maybe --overwrite is needed, see dgit(1)
4066 my $r = pseudomerge_make_commit
4067 $clogp, $dgitview, $archive_hash, $i_arch_v,
4068 "dgit --quilt=$quilt_mode",
4069 (defined $overwrite_version ? <<END_OVERWR : <<END_MAKEFF);
4070 Declare fast forward from $i_arch_v->[0]
4072 Make fast forward from $i_arch_v->[0]
4075 maybe_split_brain_save $maintview, $r, "pseudomerge";
4077 progress "Made pseudo-merge of $i_arch_v->[0] into dgit view.";
4081 sub plain_overwrite_pseudomerge ($$$) {
4082 my ($clogp, $head, $archive_hash) = @_;
4084 printdebug "plain_overwrite_pseudomerge...";
4086 my $i_arch_v = pseudomerge_version_check($clogp, $archive_hash);
4088 return $head if is_fast_fwd $archive_hash, $head;
4090 my $m = "Declare fast forward from $i_arch_v->[0]";
4092 my $r = pseudomerge_make_commit
4093 $clogp, $head, $archive_hash, $i_arch_v,
4096 runcmd git_update_ref_cmd $m, 'HEAD', $r, $head;
4098 progress "Make pseudo-merge of $i_arch_v->[0] into your HEAD.";
4102 sub push_parse_changelog ($) {
4105 my $clogp = Dpkg::Control::Hash->new();
4106 $clogp->load($clogpfn) or die;
4108 my $clogpackage = getfield $clogp, 'Source';
4109 $package //= $clogpackage;
4110 fail "-p specified $package but changelog specified $clogpackage"
4111 unless $package eq $clogpackage;
4112 my $cversion = getfield $clogp, 'Version';
4114 if (!$we_are_initiator) {
4115 # rpush initiator can't do this because it doesn't have $isuite yet
4116 my $tag = debiantag($cversion, access_nomdistro);
4117 runcmd @git, qw(check-ref-format), $tag;
4120 my $dscfn = dscfn($cversion);
4122 return ($clogp, $cversion, $dscfn);
4125 sub push_parse_dsc ($$$) {
4126 my ($dscfn,$dscfnwhat, $cversion) = @_;
4127 $dsc = parsecontrol($dscfn,$dscfnwhat);
4128 my $dversion = getfield $dsc, 'Version';
4129 my $dscpackage = getfield $dsc, 'Source';
4130 ($dscpackage eq $package && $dversion eq $cversion) or
4131 fail "$dscfn is for $dscpackage $dversion".
4132 " but debian/changelog is for $package $cversion";
4135 sub push_tagwants ($$$$) {
4136 my ($cversion, $dgithead, $maintviewhead, $tfbase) = @_;
4139 TagFn => \&debiantag,
4144 if (defined $maintviewhead) {
4146 TagFn => \&debiantag_maintview,
4147 Objid => $maintviewhead,
4148 TfSuffix => '-maintview',
4151 } elsif ($dodep14tag eq 'no' ? 0
4152 : $dodep14tag eq 'want' ? access_cfg_tagformats_can_splitbrain
4153 : $dodep14tag eq 'always'
4154 ? (access_cfg_tagformats_can_splitbrain or fail <<END)
4155 --dep14tag-always (or equivalent in config) means server must support
4156 both "new" and "maint" tag formats, but config says it doesn't.
4158 : die "$dodep14tag ?") {
4160 TagFn => \&debiantag_maintview,
4162 TfSuffix => '-dgit',
4166 foreach my $tw (@tagwants) {
4167 $tw->{Tag} = $tw->{TagFn}($cversion, access_nomdistro);
4168 $tw->{Tfn} = sub { $tfbase.$tw->{TfSuffix}.$_[0]; };
4170 printdebug 'push_tagwants: ', Dumper(\@_, \@tagwants);
4174 sub push_mktags ($$ $$ $) {
4176 $changesfile,$changesfilewhat,
4179 die unless $tagwants->[0]{View} eq 'dgit';
4181 my $declaredistro = access_nomdistro();
4182 my $reader_giturl = do { local $access_forpush=0; access_giturl(); };
4183 $dsc->{$ourdscfield[0]} = join " ",
4184 $tagwants->[0]{Objid}, $declaredistro, $tagwants->[0]{Tag},
4186 $dsc->save("$dscfn.tmp") or die $!;
4188 my $changes = parsecontrol($changesfile,$changesfilewhat);
4189 foreach my $field (qw(Source Distribution Version)) {
4190 confess Dumper($changes,$clogp) unless $changes->{$field} &&
4192 $changes->{$field} eq $clogp->{$field} or
4193 fail "changes field $field \`$changes->{$field}'".
4194 " does not match changelog \`$clogp->{$field}'";
4197 my $cversion = getfield $clogp, 'Version';
4198 my $clogsuite = getfield $clogp, 'Distribution';
4200 # We make the git tag by hand because (a) that makes it easier
4201 # to control the "tagger" (b) we can do remote signing
4202 my $authline = clogp_authline $clogp;
4203 my $delibs = join(" ", "",@deliberatelies);
4207 my $tfn = $tw->{Tfn};
4208 my $head = $tw->{Objid};
4209 my $tag = $tw->{Tag};
4211 open TO, '>', $tfn->('.tmp') or die $!;
4212 print TO <<END or die $!;
4219 if ($tw->{View} eq 'dgit') {
4220 print TO <<END or die $!;
4221 $package release $cversion for $clogsuite ($csuite) [dgit]
4222 [dgit distro=$declaredistro$delibs]
4224 foreach my $ref (sort keys %previously) {
4225 print TO <<END or die $!;
4226 [dgit previously:$ref=$previously{$ref}]
4229 } elsif ($tw->{View} eq 'maint') {
4230 print TO <<END or die $!;
4231 $package release $cversion for $clogsuite ($csuite)
4232 (maintainer view tag generated by dgit --quilt=$quilt_mode)
4235 die Dumper($tw)."?";
4240 my $tagobjfn = $tfn->('.tmp');
4242 if (!defined $keyid) {
4243 $keyid = access_cfg('keyid','RETURN-UNDEF');
4245 if (!defined $keyid) {
4246 $keyid = getfield $clogp, 'Maintainer';
4248 unlink $tfn->('.tmp.asc') or $!==&ENOENT or die $!;
4249 my @sign_cmd = (@gpg, qw(--detach-sign --armor));
4250 push @sign_cmd, qw(-u),$keyid if defined $keyid;
4251 push @sign_cmd, $tfn->('.tmp');
4252 runcmd_ordryrun @sign_cmd;
4254 $tagobjfn = $tfn->('.signed.tmp');
4255 runcmd shell_cmd "exec >$tagobjfn", qw(cat --),
4256 $tfn->('.tmp'), $tfn->('.tmp.asc');
4262 my @r = map { $mktag->($_); } @$tagwants;
4266 sub sign_changes ($) {
4267 my ($changesfile) = @_;
4269 my @debsign_cmd = @debsign;
4270 push @debsign_cmd, "-k$keyid" if defined $keyid;
4271 push @debsign_cmd, "-p$gpg[0]" if $gpg[0] ne 'gpg';
4272 push @debsign_cmd, $changesfile;
4273 runcmd_ordryrun @debsign_cmd;
4278 printdebug "actually entering push\n";
4280 supplementary_message(<<'END');
4281 Push failed, while checking state of the archive.
4282 You can retry the push, after fixing the problem, if you like.
4284 if (check_for_git()) {
4287 my $archive_hash = fetch_from_archive();
4288 if (!$archive_hash) {
4290 fail "package appears to be new in this suite;".
4291 " if this is intentional, use --new";
4294 supplementary_message(<<'END');
4295 Push failed, while preparing your push.
4296 You can retry the push, after fixing the problem, if you like.
4299 need_tagformat 'new', "quilt mode $quilt_mode"
4300 if quiltmode_splitbrain;
4304 access_giturl(); # check that success is vaguely likely
4305 rpush_handle_protovsn_bothends() if $we_are_initiator;
4308 my $clogpfn = dgit_privdir()."/changelog.822.tmp";
4309 runcmd shell_cmd "exec >$clogpfn", qw(dpkg-parsechangelog);
4311 responder_send_file('parsed-changelog', $clogpfn);
4313 my ($clogp, $cversion, $dscfn) =
4314 push_parse_changelog("$clogpfn");
4316 my $dscpath = "$buildproductsdir/$dscfn";
4317 stat_exists $dscpath or
4318 fail "looked for .dsc $dscpath, but $!;".
4319 " maybe you forgot to build";
4321 responder_send_file('dsc', $dscpath);
4323 push_parse_dsc($dscpath, $dscfn, $cversion);
4325 my $format = getfield $dsc, 'Format';
4326 printdebug "format $format\n";
4328 my $symref = git_get_symref();
4329 my $actualhead = git_rev_parse('HEAD');
4331 if (branch_is_gdr_unstitched_ff($symref, $actualhead, $archive_hash)) {
4332 runcmd_ordryrun_local @git_debrebase, 'stitch';
4333 $actualhead = git_rev_parse('HEAD');
4336 my $dgithead = $actualhead;
4337 my $maintviewhead = undef;
4339 my $upstreamversion = upstreamversion $clogp->{Version};
4341 if (madformat_wantfixup($format)) {
4342 # user might have not used dgit build, so maybe do this now:
4343 if (quiltmode_splitbrain()) {
4344 changedir $playground;
4345 quilt_make_fake_dsc($upstreamversion);
4347 ($dgithead, $cachekey) =
4348 quilt_check_splitbrain_cache($actualhead, $upstreamversion);
4350 "--quilt=$quilt_mode but no cached dgit view:
4351 perhaps HEAD changed since dgit build[-source] ?";
4353 $dgithead = splitbrain_pseudomerge($clogp,
4354 $actualhead, $dgithead,
4356 $maintviewhead = $actualhead;
4358 prep_ud(); # so _only_subdir() works, below
4360 commit_quilty_patch();
4364 if (defined $overwrite_version && !defined $maintviewhead
4366 $dgithead = plain_overwrite_pseudomerge($clogp,
4374 if ($archive_hash) {
4375 if (is_fast_fwd($archive_hash, $dgithead)) {
4377 } elsif (deliberately_not_fast_forward) {
4380 fail "dgit push: HEAD is not a descendant".
4381 " of the archive's version.\n".
4382 "To overwrite the archive's contents,".
4383 " pass --overwrite[=VERSION].\n".
4384 "To rewind history, if permitted by the archive,".
4385 " use --deliberately-not-fast-forward.";
4389 changedir $playground;
4390 progress "checking that $dscfn corresponds to HEAD";
4391 runcmd qw(dpkg-source -x --),
4392 $dscpath =~ m#^/# ? $dscpath : "$maindir/$dscpath";
4393 my ($tree,$dir) = mktree_in_ud_from_only_subdir("source package");
4394 check_for_vendor_patches() if madformat($dsc->{format});
4396 my @diffcmd = (@git, qw(diff --quiet), $tree, $dgithead);
4397 debugcmd "+",@diffcmd;
4399 my $r = system @diffcmd;
4402 my $referent = $split_brain ? $dgithead : 'HEAD';
4403 my $diffs = cmdoutput @git, qw(diff --stat), $tree, $dgithead;
4406 my $raw = cmdoutput @git,
4407 qw(diff --no-renames -z -r --raw), $tree, $dgithead;
4409 foreach (split /\0/, $raw) {
4410 if (defined $changed) {
4411 push @mode_changes, "$changed: $_\n" if $changed;
4414 } elsif (m/^:0+ 0+ /) {
4416 } elsif (m/^:(?:10*)?(\d+) (?:10*)?(\d+) /) {
4417 $changed = "Mode change from $1 to $2"
4422 if (@mode_changes) {
4423 fail <<END.(join '', @mode_changes).<<END;
4424 HEAD specifies a different tree to $dscfn:
4427 There is a problem with your source tree (see dgit(7) for some hints).
4428 To see a full diff, run git diff $tree $referent
4433 HEAD specifies a different tree to $dscfn:
4435 Perhaps you forgot to build. Or perhaps there is a problem with your
4436 source tree (see dgit(7) for some hints). To see a full diff, run
4437 git diff $tree $referent
4443 if (!$changesfile) {
4444 my $pat = changespat $cversion;
4445 my @cs = glob "$buildproductsdir/$pat";
4446 fail "failed to find unique changes file".
4447 " (looked for $pat in $buildproductsdir);".
4448 " perhaps you need to use dgit -C"
4450 ($changesfile) = @cs;
4452 $changesfile = "$buildproductsdir/$changesfile";
4455 # Check that changes and .dsc agree enough
4456 $changesfile =~ m{[^/]*$};
4457 my $changes = parsecontrol($changesfile,$&);
4458 files_compare_inputs($dsc, $changes)
4459 unless forceing [qw(dsc-changes-mismatch)];
4461 # Check whether this is a source only upload
4462 my $hasdebs = $changes->{Files} =~ m{\.deb$}m;
4463 my $sourceonlypolicy = access_cfg 'source-only-uploads';
4464 if ($sourceonlypolicy eq 'ok') {
4465 } elsif ($sourceonlypolicy eq 'always') {
4466 forceable_fail [qw(uploading-binaries)],
4467 "uploading binaries, although distroy policy is source only"
4469 } elsif ($sourceonlypolicy eq 'never') {
4470 forceable_fail [qw(uploading-source-only)],
4471 "source-only upload, although distroy policy requires .debs"
4473 } elsif ($sourceonlypolicy eq 'not-wholly-new') {
4474 forceable_fail [qw(uploading-source-only)],
4475 "source-only upload, even though package is entirely NEW\n".
4476 "(this is contrary to policy in ".(access_nomdistro()).")"
4479 && !archive_query('package_not_wholly_new', $package);
4481 badcfg "unknown source-only-uploads policy \`$sourceonlypolicy'";
4484 # Perhaps adjust .dsc to contain right set of origs
4485 changes_update_origs_from_dsc($dsc, $changes, $upstreamversion,
4487 unless forceing [qw(changes-origs-exactly)];
4489 # Checks complete, we're going to try and go ahead:
4491 responder_send_file('changes',$changesfile);
4492 responder_send_command("param head $dgithead");
4493 responder_send_command("param csuite $csuite");
4494 responder_send_command("param isuite $isuite");
4495 responder_send_command("param tagformat $tagformat");
4496 if (defined $maintviewhead) {
4497 die unless ($protovsn//4) >= 4;
4498 responder_send_command("param maint-view $maintviewhead");
4501 # Perhaps send buildinfo(s) for signing
4502 my $changes_files = getfield $changes, 'Files';
4503 my @buildinfos = ($changes_files =~ m/ .* (\S+\.buildinfo)$/mg);
4504 foreach my $bi (@buildinfos) {
4505 responder_send_command("param buildinfo-filename $bi");
4506 responder_send_file('buildinfo', "$buildproductsdir/$bi");
4509 if (deliberately_not_fast_forward) {
4510 git_for_each_ref(lrfetchrefs, sub {
4511 my ($objid,$objtype,$lrfetchrefname,$reftail) = @_;
4512 my $rrefname= substr($lrfetchrefname, length(lrfetchrefs) + 1);
4513 responder_send_command("previously $rrefname=$objid");
4514 $previously{$rrefname} = $objid;
4518 my @tagwants = push_tagwants($cversion, $dgithead, $maintviewhead,
4519 dgit_privdir()."/tag");
4522 supplementary_message(<<'END');
4523 Push failed, while signing the tag.
4524 You can retry the push, after fixing the problem, if you like.
4526 # If we manage to sign but fail to record it anywhere, it's fine.
4527 if ($we_are_responder) {
4528 @tagobjfns = map { $_->{Tfn}('.signed-tmp') } @tagwants;
4529 responder_receive_files('signed-tag', @tagobjfns);
4531 @tagobjfns = push_mktags($clogp,$dscpath,
4532 $changesfile,$changesfile,
4535 supplementary_message(<<'END');
4536 Push failed, *after* signing the tag.
4537 If you want to try again, you should use a new version number.
4540 pairwise { $a->{TagObjFn} = $b } @tagwants, @tagobjfns;
4542 foreach my $tw (@tagwants) {
4543 my $tag = $tw->{Tag};
4544 my $tagobjfn = $tw->{TagObjFn};
4546 cmdoutput @git, qw(hash-object -w -t tag), $tagobjfn;
4547 runcmd_ordryrun @git, qw(verify-tag), $tag_obj_hash;
4548 runcmd_ordryrun_local
4549 @git, qw(update-ref), "refs/tags/$tag", $tag_obj_hash;
4552 supplementary_message(<<'END');
4553 Push failed, while updating the remote git repository - see messages above.
4554 If you want to try again, you should use a new version number.
4556 if (!check_for_git()) {
4557 create_remote_git_repo();
4560 my @pushrefs = $forceflag.$dgithead.":".rrref();
4561 foreach my $tw (@tagwants) {
4562 push @pushrefs, $forceflag."refs/tags/$tw->{Tag}";
4565 runcmd_ordryrun @git,
4566 qw(-c push.followTags=false push), access_giturl(), @pushrefs;
4567 runcmd_ordryrun git_update_ref_cmd 'dgit push', lrref(), $dgithead;
4569 supplementary_message(<<'END');
4570 Push failed, while obtaining signatures on the .changes and .dsc.
4571 If it was just that the signature failed, you may try again by using
4572 debsign by hand to sign the changes
4574 and then dput to complete the upload.
4575 If you need to change the package, you must use a new version number.
4577 if ($we_are_responder) {
4578 my $dryrunsuffix = act_local() ? "" : ".tmp";
4579 my @rfiles = ($dscpath, $changesfile);
4580 push @rfiles, map { "$buildproductsdir/$_" } @buildinfos;
4581 responder_receive_files('signed-dsc-changes',
4582 map { "$_$dryrunsuffix" } @rfiles);
4585 rename "$dscpath.tmp",$dscpath or die "$dscfn $!";
4587 progress "[new .dsc left in $dscpath.tmp]";
4589 sign_changes $changesfile;
4592 supplementary_message(<<END);
4593 Push failed, while uploading package(s) to the archive server.
4594 You can retry the upload of exactly these same files with dput of:
4596 If that .changes file is broken, you will need to use a new version
4597 number for your next attempt at the upload.
4599 my $host = access_cfg('upload-host','RETURN-UNDEF');
4600 my @hostarg = defined($host) ? ($host,) : ();
4601 runcmd_ordryrun @dput, @hostarg, $changesfile;
4602 printdone "pushed and uploaded $cversion";
4604 supplementary_message('');
4605 responder_send_command("complete");
4609 not_necessarily_a_tree();
4614 badusage "-p is not allowed with clone; specify as argument instead"
4615 if defined $package;
4618 } elsif (@ARGV==2 && $ARGV[1] =~ m#^\w#) {
4619 ($package,$isuite) = @ARGV;
4620 } elsif (@ARGV==2 && $ARGV[1] =~ m#^[./]#) {
4621 ($package,$dstdir) = @ARGV;
4622 } elsif (@ARGV==3) {
4623 ($package,$isuite,$dstdir) = @ARGV;
4625 badusage "incorrect arguments to dgit clone";
4629 $dstdir ||= "$package";
4630 if (stat_exists $dstdir) {
4631 fail "$dstdir already exists";
4635 if ($rmonerror && !$dryrun_level) {
4636 $cwd_remove= getcwd();
4638 return unless defined $cwd_remove;
4639 if (!chdir "$cwd_remove") {
4640 return if $!==&ENOENT;
4641 die "chdir $cwd_remove: $!";
4643 printdebug "clone rmonerror removing $dstdir\n";
4645 rmtree($dstdir) or die "remove $dstdir: $!\n";
4646 } elsif (grep { $! == $_ }
4647 (ENOENT, ENOTDIR, EACCES, EPERM, ELOOP)) {
4649 print STDERR "check whether to remove $dstdir: $!\n";
4655 $cwd_remove = undef;
4658 sub branchsuite () {
4659 my $branch = git_get_symref();
4660 if (defined $branch && $branch =~ m#$lbranch_re#o) {
4667 sub package_from_d_control () {
4668 if (!defined $package) {
4669 my $sourcep = parsecontrol('debian/control','debian/control');
4670 $package = getfield $sourcep, 'Source';
4674 sub fetchpullargs () {
4675 package_from_d_control();
4677 $isuite = branchsuite();
4679 my $clogp = parsechangelog();
4680 my $clogsuite = getfield $clogp, 'Distribution';
4681 $isuite= $clogsuite if $clogsuite ne 'UNRELEASED';
4683 } elsif (@ARGV==1) {
4686 badusage "incorrect arguments to dgit fetch or dgit pull";
4700 if (quiltmode_splitbrain()) {
4701 my ($format, $fopts) = get_source_format();
4702 madformat($format) and fail <<END
4703 dgit pull not yet supported in split view mode (--quilt=$quilt_mode)
4711 package_from_d_control();
4712 @ARGV==1 or badusage "dgit checkout needs a suite argument";
4716 foreach my $canon (qw(0 1)) {
4721 canonicalise_suite();
4723 if (length git_get_ref lref()) {
4724 # local branch already exists, yay
4727 if (!length git_get_ref lrref()) {
4735 runcmd (@git, qw(update-ref), lref(), lrref(), '');
4738 local $ENV{GIT_REFLOG_ACTION} = git_reflog_action_msg
4739 "dgit checkout $isuite";
4740 runcmd (@git, qw(checkout), lref());
4743 sub cmd_update_vcs_git () {
4745 if (@ARGV==0 || $ARGV[0] =~ m/^-/) {
4746 ($specsuite,) = split /\;/, cfg 'dgit.vcs-git.suites';
4748 ($specsuite) = (@ARGV);
4753 if ($ARGV[0] eq '-') {
4755 } elsif ($ARGV[0] eq '-') {
4760 package_from_d_control();
4762 if ($specsuite eq '.') {
4763 $ctrl = parsecontrol 'debian/control', 'debian/control';
4765 $isuite = $specsuite;
4769 my $url = getfield $ctrl, 'Vcs-Git';
4772 my $orgurl = cfg 'remote.vcs-git.url', 'RETURN-UNDEF';
4773 if (!defined $orgurl) {
4774 print STDERR "setting up vcs-git: $url\n";
4775 @cmd = (@git, qw(remote add vcs-git), $url);
4776 } elsif ($orgurl eq $url) {
4777 print STDERR "vcs git already configured: $url\n";
4779 print STDERR "changing vcs-git url to: $url\n";
4780 @cmd = (@git, qw(remote set-url vcs-git), $url);
4782 runcmd_ordryrun_local @cmd;
4784 print "fetching (@ARGV)\n";
4785 runcmd_ordryrun_local @git, qw(fetch vcs-git), @ARGV;
4791 build_or_push_prep_early();
4796 } elsif (@ARGV==1) {
4797 ($specsuite) = (@ARGV);
4799 badusage "incorrect arguments to dgit $subcommand";
4802 local ($package) = $existing_package; # this is a hack
4803 canonicalise_suite();
4805 canonicalise_suite();
4807 if (defined $specsuite &&
4808 $specsuite ne $isuite &&
4809 $specsuite ne $csuite) {
4810 fail "dgit $subcommand: changelog specifies $isuite ($csuite)".
4811 " but command line specifies $specsuite";
4820 sub cmd_push_source {
4823 my $changes = parsecontrol("$buildproductsdir/$changesfile",
4824 "source changes file");
4825 unless (test_source_only_changes($changes)) {
4826 fail "user-specified changes file is not source-only";
4829 # Building a source package is very fast, so just do it
4830 build_source_for_push();
4835 #---------- remote commands' implementation ----------
4837 sub pre_remote_push_build_host {
4838 my ($nrargs) = shift @ARGV;
4839 my (@rargs) = @ARGV[0..$nrargs-1];
4840 @ARGV = @ARGV[$nrargs..$#ARGV];
4842 my ($dir,$vsnwant) = @rargs;
4843 # vsnwant is a comma-separated list; we report which we have
4844 # chosen in our ready response (so other end can tell if they
4847 $we_are_responder = 1;
4848 $us .= " (build host)";
4850 open PI, "<&STDIN" or die $!;
4851 open STDIN, "/dev/null" or die $!;
4852 open PO, ">&STDOUT" or die $!;
4854 open STDOUT, ">&STDERR" or die $!;
4858 ($protovsn) = grep {
4859 $vsnwant =~ m{^(?:.*,)?$_(?:,.*)?$}
4860 } @rpushprotovsn_support;
4862 fail "build host has dgit rpush protocol versions ".
4863 (join ",", @rpushprotovsn_support).
4864 " but invocation host has $vsnwant"
4865 unless defined $protovsn;
4869 sub cmd_remote_push_build_host {
4870 responder_send_command("dgit-remote-push-ready $protovsn");
4874 sub pre_remote_push_responder { pre_remote_push_build_host(); }
4875 sub cmd_remote_push_responder { cmd_remote_push_build_host(); }
4876 # ... for compatibility with proto vsn.1 dgit (just so that user gets
4877 # a good error message)
4879 sub rpush_handle_protovsn_bothends () {
4880 if ($protovsn < 4) {
4881 need_tagformat 'old', "rpush negotiated protocol $protovsn";
4890 my $report = i_child_report();
4891 if (defined $report) {
4892 printdebug "($report)\n";
4893 } elsif ($i_child_pid) {
4894 printdebug "(killing build host child $i_child_pid)\n";
4895 kill 15, $i_child_pid;
4897 if (defined $i_tmp && !defined $initiator_tempdir) {
4899 eval { rmtree $i_tmp; };
4904 return unless forkcheck_mainprocess();
4909 my ($base,$selector,@args) = @_;
4910 $selector =~ s/\-/_/g;
4911 { no strict qw(refs); &{"${base}_${selector}"}(@args); }
4915 not_necessarily_a_tree();
4920 if ($host =~ m/^((?:[^][]|\[[^][]*\])*)\:/) {
4928 push @rargs, join ",", @rpushprotovsn_support;
4931 push @rdgit, @ropts;
4932 push @rdgit, qw(remote-push-build-host), (scalar @rargs), @rargs;
4934 my @cmd = (@ssh, $host, shellquote @rdgit);
4937 $we_are_initiator=1;
4939 if (defined $initiator_tempdir) {
4940 rmtree $initiator_tempdir;
4941 mkdir $initiator_tempdir, 0700 or die "$initiator_tempdir: $!";
4942 $i_tmp = $initiator_tempdir;
4946 $i_child_pid = open2(\*RO, \*RI, @cmd);
4948 ($protovsn) = initiator_expect { m/^dgit-remote-push-ready (\S+)/ };
4949 die "$protovsn ?" unless grep { $_ eq $protovsn } @rpushprotovsn_support;
4950 $supplementary_message = '' unless $protovsn >= 3;
4953 my ($icmd,$iargs) = initiator_expect {
4954 m/^(\S+)(?: (.*))?$/;
4957 i_method "i_resp", $icmd, $iargs;
4961 sub i_resp_progress ($) {
4963 my $msg = protocol_read_bytes \*RO, $rhs;
4967 sub i_resp_supplementary_message ($) {
4969 $supplementary_message = protocol_read_bytes \*RO, $rhs;
4972 sub i_resp_complete {
4973 my $pid = $i_child_pid;
4974 $i_child_pid = undef; # prevents killing some other process with same pid
4975 printdebug "waiting for build host child $pid...\n";
4976 my $got = waitpid $pid, 0;
4977 die $! unless $got == $pid;
4978 die "build host child failed $?" if $?;
4981 printdebug "all done\n";
4985 sub i_resp_file ($) {
4987 my $localname = i_method "i_localname", $keyword;
4988 my $localpath = "$i_tmp/$localname";
4989 stat_exists $localpath and
4990 badproto \*RO, "file $keyword ($localpath) twice";
4991 protocol_receive_file \*RO, $localpath;
4992 i_method "i_file", $keyword;
4997 sub i_resp_param ($) {
4998 $_[0] =~ m/^(\S+) (.*)$/ or badproto \*RO, "bad param spec";
5002 sub i_resp_previously ($) {
5003 $_[0] =~ m#^(refs/tags/\S+)=(\w+)$#
5004 or badproto \*RO, "bad previously spec";
5005 my $r = system qw(git check-ref-format), $1;
5006 die "bad previously ref spec ($r)" if $r;
5007 $previously{$1} = $2;
5012 sub i_resp_want ($) {
5014 die "$keyword ?" if $i_wanted{$keyword}++;
5016 defined $i_param{'csuite'} or badproto \*RO, "premature desire, no csuite";
5017 $isuite = $i_param{'isuite'} // $i_param{'csuite'};
5018 die unless $isuite =~ m/^$suite_re$/;
5021 rpush_handle_protovsn_bothends();
5023 fail "rpush negotiated protocol version $protovsn".
5024 " which does not support quilt mode $quilt_mode"
5025 if quiltmode_splitbrain;
5027 my @localpaths = i_method "i_want", $keyword;
5028 printdebug "[[ $keyword @localpaths\n";
5029 foreach my $localpath (@localpaths) {
5030 protocol_send_file \*RI, $localpath;
5032 print RI "files-end\n" or die $!;
5035 our ($i_clogp, $i_version, $i_dscfn, $i_changesfn, @i_buildinfos);
5037 sub i_localname_parsed_changelog {
5038 return "remote-changelog.822";
5040 sub i_file_parsed_changelog {
5041 ($i_clogp, $i_version, $i_dscfn) =
5042 push_parse_changelog "$i_tmp/remote-changelog.822";
5043 die if $i_dscfn =~ m#/|^\W#;
5046 sub i_localname_dsc {
5047 defined $i_dscfn or badproto \*RO, "dsc (before parsed-changelog)";
5052 sub i_localname_buildinfo ($) {
5053 my $bi = $i_param{'buildinfo-filename'};
5054 defined $bi or badproto \*RO, "buildinfo before filename";
5055 defined $i_changesfn or badproto \*RO, "buildinfo before changes";
5056 $bi =~ m{^\Q$package\E_[!-.0-~]*\.buildinfo$}s
5057 or badproto \*RO, "improper buildinfo filename";
5060 sub i_file_buildinfo {
5061 my $bi = $i_param{'buildinfo-filename'};
5062 my $bd = parsecontrol "$i_tmp/$bi", $bi;
5063 my $ch = parsecontrol "$i_tmp/$i_changesfn", 'changes';
5064 if (!forceing [qw(buildinfo-changes-mismatch)]) {
5065 files_compare_inputs($bd, $ch);
5066 (getfield $bd, $_) eq (getfield $ch, $_) or
5067 fail "buildinfo mismatch $_"
5068 foreach qw(Source Version);
5069 !defined $bd->{$_} or
5070 fail "buildinfo contains $_"
5071 foreach qw(Changes Changed-by Distribution);
5073 push @i_buildinfos, $bi;
5074 delete $i_param{'buildinfo-filename'};
5077 sub i_localname_changes {
5078 defined $i_dscfn or badproto \*RO, "dsc (before parsed-changelog)";
5079 $i_changesfn = $i_dscfn;
5080 $i_changesfn =~ s/\.dsc$/_dgit.changes/ or die;
5081 return $i_changesfn;
5083 sub i_file_changes { }
5085 sub i_want_signed_tag {
5086 printdebug Dumper(\%i_param, $i_dscfn);
5087 defined $i_param{'head'} && defined $i_dscfn && defined $i_clogp
5088 && defined $i_param{'csuite'}
5089 or badproto \*RO, "premature desire for signed-tag";
5090 my $head = $i_param{'head'};
5091 die if $head =~ m/[^0-9a-f]/ || $head !~ m/^../;
5093 my $maintview = $i_param{'maint-view'};
5094 die if defined $maintview && $maintview =~ m/[^0-9a-f]/;
5097 if ($protovsn >= 4) {
5098 my $p = $i_param{'tagformat'} // '<undef>';
5100 or badproto \*RO, "tag format mismatch: $p vs. $tagformat";
5103 die unless $i_param{'csuite'} =~ m/^$suite_re$/;
5105 push_parse_dsc $i_dscfn, 'remote dsc', $i_version;
5107 my @tagwants = push_tagwants $i_version, $head, $maintview, "tag";
5110 push_mktags $i_clogp, $i_dscfn,
5111 $i_changesfn, 'remote changes',
5115 sub i_want_signed_dsc_changes {
5116 rename "$i_dscfn.tmp","$i_dscfn" or die "$i_dscfn $!";
5117 sign_changes $i_changesfn;
5118 return ($i_dscfn, $i_changesfn, @i_buildinfos);
5121 #---------- building etc. ----------
5127 #----- `3.0 (quilt)' handling -----
5129 our $fakeeditorenv = 'DGIT_FAKE_EDITOR_QUILT';
5131 sub quiltify_dpkg_commit ($$$;$) {
5132 my ($patchname,$author,$msg, $xinfo) = @_;
5135 mkpath '.git/dgit'; # we are in playtree
5136 my $descfn = ".git/dgit/quilt-description.tmp";
5137 open O, '>', $descfn or die "$descfn: $!";
5138 $msg =~ s/\n+/\n\n/;
5139 print O <<END or die $!;
5141 ${xinfo}Subject: $msg
5148 local $ENV{'EDITOR'} = cmdoutput qw(realpath --), $0;
5149 local $ENV{'VISUAL'} = $ENV{'EDITOR'};
5150 local $ENV{$fakeeditorenv} = cmdoutput qw(realpath --), $descfn;
5151 runcmd @dpkgsource, qw(--commit --include-removal .), $patchname;
5155 sub quiltify_trees_differ ($$;$$$) {
5156 my ($x,$y,$finegrained,$ignorenamesr,$unrepres) = @_;
5157 # returns true iff the two tree objects differ other than in debian/
5158 # with $finegrained,
5159 # returns bitmask 01 - differ in upstream files except .gitignore
5160 # 02 - differ in .gitignore
5161 # if $ignorenamesr is defined, $ingorenamesr->{$fn}
5162 # is set for each modified .gitignore filename $fn
5163 # if $unrepres is defined, array ref to which is appeneded
5164 # a list of unrepresentable changes (removals of upstream files
5167 my @cmd = (@git, qw(diff-tree -z --no-renames));
5168 push @cmd, qw(--name-only) unless $unrepres;
5169 push @cmd, qw(-r) if $finegrained || $unrepres;
5171 my $diffs= cmdoutput @cmd;
5174 foreach my $f (split /\0/, $diffs) {
5175 if ($unrepres && !@lmodes) {
5176 @lmodes = $f =~ m/^\:(\w+) (\w+) \w+ \w+ / or die "$_ ?";
5179 my ($oldmode,$newmode) = @lmodes;
5182 next if $f =~ m#^debian(?:/.*)?$#s;
5186 die "not a plain file or symlink\n"
5187 unless $newmode =~ m/^(?:10|12)\d{4}$/ ||
5188 $oldmode =~ m/^(?:10|12)\d{4}$/;
5189 if ($oldmode =~ m/[^0]/ &&
5190 $newmode =~ m/[^0]/) {
5191 # both old and new files exist
5192 die "mode or type changed\n" if $oldmode ne $newmode;
5193 die "modified symlink\n" unless $newmode =~ m/^10/;
5194 } elsif ($oldmode =~ m/[^0]/) {
5196 die "deletion of symlink\n"
5197 unless $oldmode =~ m/^10/;
5200 die "creation with non-default mode\n"
5201 unless $newmode =~ m/^100644$/ or
5202 $newmode =~ m/^120000$/;
5206 local $/="\n"; chomp $@;
5207 push @$unrepres, [ $f, "$@ ($oldmode->$newmode)" ];
5211 my $isignore = $f =~ m#^(?:.*/)?.gitignore$#s;
5212 $r |= $isignore ? 02 : 01;
5213 $ignorenamesr->{$f}=1 if $ignorenamesr && $isignore;
5215 printdebug "quiltify_trees_differ $x $y => $r\n";
5219 sub quiltify_tree_sentinelfiles ($) {
5220 # lists the `sentinel' files present in the tree
5222 my $r = cmdoutput @git, qw(ls-tree --name-only), $x,
5223 qw(-- debian/rules debian/control);
5228 sub quiltify_splitbrain_needed () {
5229 if (!$split_brain) {
5230 progress "dgit view: changes are required...";
5231 runcmd @git, qw(checkout -q -b dgit-view);
5236 sub quiltify_splitbrain ($$$$$$$) {
5237 my ($clogp, $unapplied, $headref, $oldtiptree, $diffbits,
5238 $editedignores, $cachekey) = @_;
5239 my $gitignore_special = 1;
5240 if ($quilt_mode !~ m/gbp|dpm/) {
5241 # treat .gitignore just like any other upstream file
5242 $diffbits = { %$diffbits };
5243 $_ = !!$_ foreach values %$diffbits;
5244 $gitignore_special = 0;
5246 # We would like any commits we generate to be reproducible
5247 my @authline = clogp_authline($clogp);
5248 local $ENV{GIT_COMMITTER_NAME} = $authline[0];
5249 local $ENV{GIT_COMMITTER_EMAIL} = $authline[1];
5250 local $ENV{GIT_COMMITTER_DATE} = $authline[2];
5251 local $ENV{GIT_AUTHOR_NAME} = $authline[0];
5252 local $ENV{GIT_AUTHOR_EMAIL} = $authline[1];
5253 local $ENV{GIT_AUTHOR_DATE} = $authline[2];
5255 my $fulldiffhint = sub {
5257 my $cmd = "git diff $x $y -- :/ ':!debian'";
5258 $cmd .= " ':!/.gitignore' ':!*/.gitignore'" if $gitignore_special;
5259 return "\nFor full diff showing the problem(s), type:\n $cmd\n";
5262 if ($quilt_mode =~ m/gbp|unapplied/ &&
5263 ($diffbits->{O2H} & 01)) {
5265 "--quilt=$quilt_mode specified, implying patches-unapplied git tree\n".
5266 " but git tree differs from orig in upstream files.";
5267 $msg .= $fulldiffhint->($unapplied, 'HEAD');
5268 if (!stat_exists "debian/patches") {
5270 "\n ... debian/patches is missing; perhaps this is a patch queue branch?";
5274 if ($quilt_mode =~ m/dpm/ &&
5275 ($diffbits->{H2A} & 01)) {
5276 fail <<END. $fulldiffhint->($oldtiptree,'HEAD');
5277 --quilt=$quilt_mode specified, implying patches-applied git tree
5278 but git tree differs from result of applying debian/patches to upstream
5281 if ($quilt_mode =~ m/gbp|unapplied/ &&
5282 ($diffbits->{O2A} & 01)) { # some patches
5283 quiltify_splitbrain_needed();
5284 progress "dgit view: creating patches-applied version using gbp pq";
5285 runcmd shell_cmd 'exec >/dev/null', gbp_pq, qw(import);
5286 # gbp pq import creates a fresh branch; push back to dgit-view
5287 runcmd @git, qw(update-ref refs/heads/dgit-view HEAD);
5288 runcmd @git, qw(checkout -q dgit-view);
5290 if ($quilt_mode =~ m/gbp|dpm/ &&
5291 ($diffbits->{O2A} & 02)) {
5293 --quilt=$quilt_mode specified, implying that HEAD is for use with a
5294 tool which does not create patches for changes to upstream
5295 .gitignores: but, such patches exist in debian/patches.
5298 if (($diffbits->{O2H} & 02) && # user has modified .gitignore
5299 !($diffbits->{O2A} & 02)) { # patches do not change .gitignore
5300 quiltify_splitbrain_needed();
5301 progress "dgit view: creating patch to represent .gitignore changes";
5302 ensuredir "debian/patches";
5303 my $gipatch = "debian/patches/auto-gitignore";
5304 open GIPATCH, ">>", "$gipatch" or die "$gipatch: $!";
5305 stat GIPATCH or die "$gipatch: $!";
5306 fail "$gipatch already exists; but want to create it".
5307 " to record .gitignore changes" if (stat _)[7];
5308 print GIPATCH <<END or die "$gipatch: $!";
5309 Subject: Update .gitignore from Debian packaging branch
5311 The Debian packaging git branch contains these updates to the upstream
5312 .gitignore file(s). This patch is autogenerated, to provide these
5313 updates to users of the official Debian archive view of the package.
5315 [dgit ($our_version) update-gitignore]
5318 close GIPATCH or die "$gipatch: $!";
5319 runcmd shell_cmd "exec >>$gipatch", @git, qw(diff),
5320 $unapplied, $headref, "--", sort keys %$editedignores;
5321 open SERIES, "+>>", "debian/patches/series" or die $!;
5322 defined seek SERIES, -1, 2 or $!==EINVAL or die $!;
5324 defined read SERIES, $newline, 1 or die $!;
5325 print SERIES "\n" or die $! unless $newline eq "\n";
5326 print SERIES "auto-gitignore\n" or die $!;
5327 close SERIES or die $!;
5328 runcmd @git, qw(add -- debian/patches/series), $gipatch;
5330 Commit patch to update .gitignore
5332 [dgit ($our_version) update-gitignore-quilt-fixup]
5336 my $dgitview = git_rev_parse 'HEAD';
5339 # When we no longer need to support squeeze, use --create-reflog
5341 ensuredir "$maindir_gitcommon/logs/refs/dgit-intern";
5342 my $makelogfh = new IO::File "$maindir_gitcommon/logs/refs/$splitbraincache", '>>'
5345 my $oldcache = git_get_ref "refs/$splitbraincache";
5346 if ($oldcache eq $dgitview) {
5347 my $tree = cmdoutput qw(git rev-parse), "$dgitview:";
5348 # git update-ref doesn't always update, in this case. *sigh*
5349 my $dummy = make_commit_text <<END;
5352 author Dgit <dgit\@example.com> 1000000000 +0000
5353 committer Dgit <dgit\@example.com> 1000000000 +0000
5355 Dummy commit - do not use
5357 runcmd @git, qw(update-ref -m), "dgit $our_version - dummy",
5358 "refs/$splitbraincache", $dummy;
5360 runcmd @git, qw(update-ref -m), $cachekey, "refs/$splitbraincache",
5363 changedir "$playground/work";
5365 my $saved = maybe_split_brain_save $headref, $dgitview, "converted";
5366 progress "dgit view: created ($saved)";
5369 sub quiltify ($$$$) {
5370 my ($clogp,$target,$oldtiptree,$failsuggestion) = @_;
5372 # Quilt patchification algorithm
5374 # We search backwards through the history of the main tree's HEAD
5375 # (T) looking for a start commit S whose tree object is identical
5376 # to to the patch tip tree (ie the tree corresponding to the
5377 # current dpkg-committed patch series). For these purposes
5378 # `identical' disregards anything in debian/ - this wrinkle is
5379 # necessary because dpkg-source treates debian/ specially.
5381 # We can only traverse edges where at most one of the ancestors'
5382 # trees differs (in changes outside in debian/). And we cannot
5383 # handle edges which change .pc/ or debian/patches. To avoid
5384 # going down a rathole we avoid traversing edges which introduce
5385 # debian/rules or debian/control. And we set a limit on the
5386 # number of edges we are willing to look at.
5388 # If we succeed, we walk forwards again. For each traversed edge
5389 # PC (with P parent, C child) (starting with P=S and ending with
5390 # C=T) to we do this:
5392 # - dpkg-source --commit with a patch name and message derived from C
5393 # After traversing PT, we git commit the changes which
5394 # should be contained within debian/patches.
5396 # The search for the path S..T is breadth-first. We maintain a
5397 # todo list containing search nodes. A search node identifies a
5398 # commit, and looks something like this:
5400 # Commit => $git_commit_id,
5401 # Child => $c, # or undef if P=T
5402 # Whynot => $reason_edge_PC_unsuitable, # in @nots only
5403 # Nontrivial => true iff $p..$c has relevant changes
5410 my %considered; # saves being exponential on some weird graphs
5412 my $t_sentinels = quiltify_tree_sentinelfiles $target;
5415 my ($search,$whynot) = @_;
5416 printdebug " search NOT $search->{Commit} $whynot\n";
5417 $search->{Whynot} = $whynot;
5418 push @nots, $search;
5419 no warnings qw(exiting);
5428 my $c = shift @todo;
5429 next if $considered{$c->{Commit}}++;
5431 $not->($c, "maximum search space exceeded") if --$max_work <= 0;
5433 printdebug "quiltify investigate $c->{Commit}\n";
5436 if (!quiltify_trees_differ $c->{Commit}, $oldtiptree) {
5437 printdebug " search finished hooray!\n";
5442 quiltify_nofix_bail " $c->{Commit}", " (tree object $oldtiptree)";
5443 if ($quilt_mode eq 'smash') {
5444 printdebug " search quitting smash\n";
5448 my $c_sentinels = quiltify_tree_sentinelfiles $c->{Commit};
5449 $not->($c, "has $c_sentinels not $t_sentinels")
5450 if $c_sentinels ne $t_sentinels;
5452 my $commitdata = cmdoutput @git, qw(cat-file commit), $c->{Commit};
5453 $commitdata =~ m/\n\n/;
5455 my @parents = ($commitdata =~ m/^parent (\w+)$/gm);
5456 @parents = map { { Commit => $_, Child => $c } } @parents;
5458 $not->($c, "root commit") if !@parents;
5460 foreach my $p (@parents) {
5461 $p->{Nontrivial}= quiltify_trees_differ $p->{Commit},$c->{Commit};
5463 my $ndiffers = grep { $_->{Nontrivial} } @parents;
5464 $not->($c, "merge ($ndiffers nontrivial parents)") if $ndiffers > 1;
5466 foreach my $p (@parents) {
5467 printdebug "considering C=$c->{Commit} P=$p->{Commit}\n";
5469 my @cmd= (@git, qw(diff-tree -r --name-only),
5470 $p->{Commit},$c->{Commit}, qw(-- debian/patches .pc));
5471 my $patchstackchange = cmdoutput @cmd;
5472 if (length $patchstackchange) {
5473 $patchstackchange =~ s/\n/,/g;
5474 $not->($p, "changed $patchstackchange");
5477 printdebug " search queue P=$p->{Commit} ",
5478 ($p->{Nontrivial} ? "NT" : "triv"),"\n";
5484 printdebug "quiltify want to smash\n";
5487 my $x = $_[0]{Commit};
5488 $x =~ s/(.*?[0-9a-z]{8})[0-9a-z]*$/$1/;
5491 my $reportnot = sub {
5493 my $s = $abbrev->($notp);
5494 my $c = $notp->{Child};
5495 $s .= "..".$abbrev->($c) if $c;
5496 $s .= ": ".$notp->{Whynot};
5499 if ($quilt_mode eq 'linear') {
5500 print STDERR "\n$us: error: quilt fixup cannot be linear. Stopped at:\n";
5501 foreach my $notp (@nots) {
5502 print STDERR "$us: ", $reportnot->($notp), "\n";
5504 print STDERR "$us: $_\n" foreach @$failsuggestion;
5506 "quilt history linearisation failed. Search \`quilt fixup' in dgit(7).\n".
5507 "Use dpkg-source --commit by hand; or, --quilt=smash for one ugly patch";
5508 } elsif ($quilt_mode eq 'smash') {
5509 } elsif ($quilt_mode eq 'auto') {
5510 progress "quilt fixup cannot be linear, smashing...";
5512 die "$quilt_mode ?";
5515 my $time = $ENV{'GIT_COMMITTER_DATE'} || time;
5516 $time =~ s/\s.*//; # trim timezone from GIT_COMMITTER_DATE
5518 my $msg = cmdoutput @git, qw(log), "-n$ncommits";
5520 quiltify_dpkg_commit "auto-$version-$target-$time",
5521 (getfield $clogp, 'Maintainer'),
5522 "Automatically generated patch ($clogp->{Version})\n".
5523 "Last (up to) $ncommits git changes, FYI:\n\n". $msg;
5527 progress "quiltify linearisation planning successful, executing...";
5529 for (my $p = $sref_S;
5530 my $c = $p->{Child};
5532 printdebug "quiltify traverse $p->{Commit}..$c->{Commit}\n";
5533 next unless $p->{Nontrivial};
5535 my $cc = $c->{Commit};
5537 my $commitdata = cmdoutput @git, qw(cat-file commit), $cc;
5538 $commitdata =~ m/\n\n/ or die "$c ?";
5541 $commitdata =~ m/^author (.*) \d+ [-+0-9]+$/m or die "$cc ?";
5544 my $commitdate = cmdoutput
5545 @git, qw(log -n1 --pretty=format:%aD), $cc;
5547 $msg =~ s/^(.*)\n*/$1\n/ or die "$cc $msg ?";
5549 my $strip_nls = sub { $msg =~ s/\n+$//; $msg .= "\n"; };
5556 my $gbp_check_suitable = sub {
5561 die "contains unexpected slashes\n" if m{//} || m{/$};
5562 die "contains leading punctuation\n" if m{^\W} || m{/\W};
5563 die "contains bad character(s)\n" if m{[^-a-z0-9_.+=~/]}i;
5564 die "is series file\n" if m{$series_filename_re}o;
5565 die "too long" if length > 200;
5567 return $_ unless $@;
5568 print STDERR "quiltifying commit $cc:".
5569 " ignoring/dropping Gbp-Pq $what: $@";
5573 if ($msg =~ s/^ (?: gbp(?:-pq)? : \s* name \s+ |
5575 (\S+) \s* \n //ixm) {
5576 $patchname = $gbp_check_suitable->($1, 'Name');
5578 if ($msg =~ s/^ (?: gbp(?:-pq)? : \s* topic \s+ |
5580 (\S+) \s* \n //ixm) {
5581 $patchdir = $gbp_check_suitable->($1, 'Topic');
5586 if (!defined $patchname) {
5587 $patchname = $title;
5588 $patchname =~ s/[.:]$//;
5591 my $converter = new Text::Iconv qw(UTF-8 ASCII//TRANSLIT);
5592 my $translitname = $converter->convert($patchname);
5593 die unless defined $translitname;
5594 $patchname = $translitname;
5597 "dgit: patch title transliteration error: $@"
5599 $patchname =~ y/ A-Z/-a-z/;
5600 $patchname =~ y/-a-z0-9_.+=~//cd;
5601 $patchname =~ s/^\W/x-$&/;
5602 $patchname = substr($patchname,0,40);
5603 $patchname .= ".patch";
5605 if (!defined $patchdir) {
5608 if (length $patchdir) {
5609 $patchname = "$patchdir/$patchname";
5611 if ($patchname =~ m{^(.*)/}) {
5612 mkpath "debian/patches/$1";
5617 stat "debian/patches/$patchname$index";
5619 $!==ENOENT or die "$patchname$index $!";
5621 runcmd @git, qw(checkout -q), $cc;
5623 # We use the tip's changelog so that dpkg-source doesn't
5624 # produce complaining messages from dpkg-parsechangelog. None
5625 # of the information dpkg-source gets from the changelog is
5626 # actually relevant - it gets put into the original message
5627 # which dpkg-source provides our stunt editor, and then
5629 runcmd @git, qw(checkout -q), $target, qw(debian/changelog);
5631 quiltify_dpkg_commit "$patchname$index", $author, $msg,
5632 "Date: $commitdate\n".
5633 "X-Dgit-Generated: $clogp->{Version} $cc\n";
5635 runcmd @git, qw(checkout -q), $cc, qw(debian/changelog);
5638 runcmd @git, qw(checkout -q master);
5641 sub build_maybe_quilt_fixup () {
5642 my ($format,$fopts) = get_source_format;
5643 return unless madformat_wantfixup $format;
5646 check_for_vendor_patches();
5648 if (quiltmode_splitbrain) {
5649 fail <<END unless access_cfg_tagformats_can_splitbrain;
5650 quilt mode $quilt_mode requires split view so server needs to support
5651 both "new" and "maint" tag formats, but config says it doesn't.
5655 my $clogp = parsechangelog();
5656 my $headref = git_rev_parse('HEAD');
5657 my $symref = git_get_symref();
5659 if ($quilt_mode eq 'linear'
5660 && !$fopts->{'single-debian-patch'}
5661 && branch_is_gdr($symref, $headref)) {
5662 # This is much faster. It also makes patches that gdr
5663 # likes better for future updates without laundering.
5665 # However, it can fail in some casses where we would
5666 # succeed: if there are existing patches, which correspond
5667 # to a prefix of the branch, but are not in gbp/gdr
5668 # format, gdr will fail (exiting status 7), but we might
5669 # be able to figure out where to start linearising. That
5670 # will be slower so hopefully there's not much to do.
5671 my @cmd = (@git_debrebase,
5672 qw(--noop-ok -funclean-mixed -funclean-ordering
5673 make-patches --quiet-would-amend));
5674 # We tolerate soe snags that gdr wouldn't, by default.
5678 failedcmd @cmd if system @cmd and $?!=7*256;
5682 $headref = git_rev_parse('HEAD');
5686 changedir $playground;
5688 my $upstreamversion = upstreamversion $version;
5690 if ($fopts->{'single-debian-patch'}) {
5691 quilt_fixup_singlepatch($clogp, $headref, $upstreamversion);
5693 quilt_fixup_multipatch($clogp, $headref, $upstreamversion);
5696 die 'bug' if $split_brain && !$need_split_build_invocation;
5699 runcmd_ordryrun_local
5700 @git, qw(pull --ff-only -q), "$playground/work", qw(master);
5703 sub quilt_fixup_mkwork ($) {
5706 mkdir "work" or die $!;
5708 mktree_in_ud_here();
5709 runcmd @git, qw(reset -q --hard), $headref;
5712 sub quilt_fixup_linkorigs ($$) {
5713 my ($upstreamversion, $fn) = @_;
5714 # calls $fn->($leafname);
5716 foreach my $f (<$maindir/../*>) { #/){
5717 my $b=$f; $b =~ s{.*/}{};
5719 local ($debuglevel) = $debuglevel-1;
5720 printdebug "QF linkorigs $b, $f ?\n";
5722 next unless is_orig_file_of_vsn $b, $upstreamversion;
5723 printdebug "QF linkorigs $b, $f Y\n";
5724 link_ltarget $f, $b or die "$b $!";
5729 sub quilt_fixup_delete_pc () {
5730 runcmd @git, qw(rm -rqf .pc);
5732 Commit removal of .pc (quilt series tracking data)
5734 [dgit ($our_version) upgrade quilt-remove-pc]
5738 sub quilt_fixup_singlepatch ($$$) {
5739 my ($clogp, $headref, $upstreamversion) = @_;
5741 progress "starting quiltify (single-debian-patch)";
5743 # dpkg-source --commit generates new patches even if
5744 # single-debian-patch is in debian/source/options. In order to
5745 # get it to generate debian/patches/debian-changes, it is
5746 # necessary to build the source package.
5748 quilt_fixup_linkorigs($upstreamversion, sub { });
5749 quilt_fixup_mkwork($headref);
5751 rmtree("debian/patches");
5753 runcmd @dpkgsource, qw(-b .);
5755 runcmd @dpkgsource, qw(-x), (srcfn $version, ".dsc");
5756 rename srcfn("$upstreamversion", "/debian/patches"),
5757 "work/debian/patches";
5760 commit_quilty_patch();
5763 sub quilt_make_fake_dsc ($) {
5764 my ($upstreamversion) = @_;
5766 my $fakeversion="$upstreamversion-~~DGITFAKE";
5768 my $fakedsc=new IO::File 'fake.dsc', '>' or die $!;
5769 print $fakedsc <<END or die $!;
5772 Version: $fakeversion
5776 my $dscaddfile=sub {
5779 my $md = new Digest::MD5;
5781 my $fh = new IO::File $b, '<' or die "$b $!";
5786 print $fakedsc " ".$md->hexdigest." $size $b\n" or die $!;
5789 quilt_fixup_linkorigs($upstreamversion, $dscaddfile);
5791 my @files=qw(debian/source/format debian/rules
5792 debian/control debian/changelog);
5793 foreach my $maybe (qw(debian/patches debian/source/options
5794 debian/tests/control)) {
5795 next unless stat_exists "$maindir/$maybe";
5796 push @files, $maybe;
5799 my $debtar= srcfn $fakeversion,'.debian.tar.gz';
5800 runcmd qw(env GZIP=-1n tar -zcf), "./$debtar", qw(-C), $maindir, @files;
5802 $dscaddfile->($debtar);
5803 close $fakedsc or die $!;
5806 sub quilt_check_splitbrain_cache ($$) {
5807 my ($headref, $upstreamversion) = @_;
5808 # Called only if we are in (potentially) split brain mode.
5809 # Called in playground.
5810 # Computes the cache key and looks in the cache.
5811 # Returns ($dgit_view_commitid, $cachekey) or (undef, $cachekey)
5813 my $splitbrain_cachekey;
5816 "dgit: split brain (separate dgit view) may be needed (--quilt=$quilt_mode).";
5817 # we look in the reflog of dgit-intern/quilt-cache
5818 # we look for an entry whose message is the key for the cache lookup
5819 my @cachekey = (qw(dgit), $our_version);
5820 push @cachekey, $upstreamversion;
5821 push @cachekey, $quilt_mode;
5822 push @cachekey, $headref;
5824 push @cachekey, hashfile('fake.dsc');
5826 my $srcshash = Digest::SHA->new(256);
5827 my %sfs = ( %INC, '$0(dgit)' => $0 );
5828 foreach my $sfk (sort keys %sfs) {
5829 next unless $sfk =~ m/^\$0\b/ || $sfk =~ m{^Debian/Dgit\b};
5830 $srcshash->add($sfk," ");
5831 $srcshash->add(hashfile($sfs{$sfk}));
5832 $srcshash->add("\n");
5834 push @cachekey, $srcshash->hexdigest();
5835 $splitbrain_cachekey = "@cachekey";
5837 my @cmd = (@git, qw(log -g), '--pretty=format:%H %gs',
5839 printdebug "splitbrain cachekey $splitbrain_cachekey\n";
5840 debugcmd "|(probably)",@cmd;
5841 my $child = open GC, "-|"; defined $child or die $!;
5843 chdir $maindir or die $!;
5844 if (!stat "$maindir_gitcommon/logs/refs/$splitbraincache") {
5845 $! == ENOENT or die $!;
5846 printdebug ">(no reflog)\n";
5853 printdebug ">| ", $_, "\n" if $debuglevel > 1;
5854 next unless m/^(\w+) (\S.*\S)$/ && $2 eq $splitbrain_cachekey;
5857 quilt_fixup_mkwork($headref);
5858 my $saved = maybe_split_brain_save $headref, $cachehit, "cache-hit";
5859 if ($cachehit ne $headref) {
5860 progress "dgit view: found cached ($saved)";
5861 runcmd @git, qw(checkout -q -b dgit-view), $cachehit;
5863 return ($cachehit, $splitbrain_cachekey);
5865 progress "dgit view: found cached, no changes required";
5866 return ($headref, $splitbrain_cachekey);
5868 die $! if GC->error;
5869 failedcmd unless close GC;
5871 printdebug "splitbrain cache miss\n";
5872 return (undef, $splitbrain_cachekey);
5875 sub quilt_fixup_multipatch ($$$) {
5876 my ($clogp, $headref, $upstreamversion) = @_;
5878 progress "examining quilt state (multiple patches, $quilt_mode mode)";
5881 # - honour any existing .pc in case it has any strangeness
5882 # - determine the git commit corresponding to the tip of
5883 # the patch stack (if there is one)
5884 # - if there is such a git commit, convert each subsequent
5885 # git commit into a quilt patch with dpkg-source --commit
5886 # - otherwise convert all the differences in the tree into
5887 # a single git commit
5891 # Our git tree doesn't necessarily contain .pc. (Some versions of
5892 # dgit would include the .pc in the git tree.) If there isn't
5893 # one, we need to generate one by unpacking the patches that we
5896 # We first look for a .pc in the git tree. If there is one, we
5897 # will use it. (This is not the normal case.)
5899 # Otherwise need to regenerate .pc so that dpkg-source --commit
5900 # can work. We do this as follows:
5901 # 1. Collect all relevant .orig from parent directory
5902 # 2. Generate a debian.tar.gz out of
5903 # debian/{patches,rules,source/format,source/options}
5904 # 3. Generate a fake .dsc containing just these fields:
5905 # Format Source Version Files
5906 # 4. Extract the fake .dsc
5907 # Now the fake .dsc has a .pc directory.
5908 # (In fact we do this in every case, because in future we will
5909 # want to search for a good base commit for generating patches.)
5911 # Then we can actually do the dpkg-source --commit
5912 # 1. Make a new working tree with the same object
5913 # store as our main tree and check out the main
5915 # 2. Copy .pc from the fake's extraction, if necessary
5916 # 3. Run dpkg-source --commit
5917 # 4. If the result has changes to debian/, then
5918 # - git add them them
5919 # - git add .pc if we had a .pc in-tree
5921 # 5. If we had a .pc in-tree, delete it, and git commit
5922 # 6. Back in the main tree, fast forward to the new HEAD
5924 # Another situation we may have to cope with is gbp-style
5925 # patches-unapplied trees.
5927 # We would want to detect these, so we know to escape into
5928 # quilt_fixup_gbp. However, this is in general not possible.
5929 # Consider a package with a one patch which the dgit user reverts
5930 # (with git revert or the moral equivalent).
5932 # That is indistinguishable in contents from a patches-unapplied
5933 # tree. And looking at the history to distinguish them is not
5934 # useful because the user might have made a confusing-looking git
5935 # history structure (which ought to produce an error if dgit can't
5936 # cope, not a silent reintroduction of an unwanted patch).
5938 # So gbp users will have to pass an option. But we can usually
5939 # detect their failure to do so: if the tree is not a clean
5940 # patches-applied tree, quilt linearisation fails, but the tree
5941 # _is_ a clean patches-unapplied tree, we can suggest that maybe
5942 # they want --quilt=unapplied.
5944 # To help detect this, when we are extracting the fake dsc, we
5945 # first extract it with --skip-patches, and then apply the patches
5946 # afterwards with dpkg-source --before-build. That lets us save a
5947 # tree object corresponding to .origs.
5949 my $splitbrain_cachekey;
5951 quilt_make_fake_dsc($upstreamversion);
5953 if (quiltmode_splitbrain()) {
5955 ($cachehit, $splitbrain_cachekey) =
5956 quilt_check_splitbrain_cache($headref, $upstreamversion);
5957 return if $cachehit;
5961 'exec dpkg-source --no-check --skip-patches -x fake.dsc >/dev/null';
5963 my $fakexdir= $package.'-'.(stripepoch $upstreamversion);
5964 rename $fakexdir, "fake" or die "$fakexdir $!";
5968 remove_stray_gits("source package");
5969 mktree_in_ud_here();
5973 rmtree 'debian'; # git checkout commitish paths does not delete!
5974 runcmd @git, qw(checkout -f), $headref, qw(-- debian);
5975 my $unapplied=git_add_write_tree();
5976 printdebug "fake orig tree object $unapplied\n";
5980 my @bbcmd = (qw(sh -ec), 'exec dpkg-source --before-build . >/dev/null');
5982 if (system @bbcmd) {
5983 failedcmd @bbcmd if $? < 0;
5985 failed to apply your git tree's patch stack (from debian/patches/) to
5986 the corresponding upstream tarball(s). Your source tree and .orig
5987 are probably too inconsistent. dgit can only fix up certain kinds of
5988 anomaly (depending on the quilt mode). See --quilt= in dgit(1).
5994 quilt_fixup_mkwork($headref);
5997 if (stat_exists ".pc") {
5999 progress "Tree already contains .pc - will use it then delete it.";
6002 rename '../fake/.pc','.pc' or die $!;
6005 changedir '../fake';
6007 my $oldtiptree=git_add_write_tree();
6008 printdebug "fake o+d/p tree object $unapplied\n";
6009 changedir '../work';
6012 # We calculate some guesswork now about what kind of tree this might
6013 # be. This is mostly for error reporting.
6019 # O = orig, without patches applied
6020 # A = "applied", ie orig with H's debian/patches applied
6021 O2H => quiltify_trees_differ($unapplied,$headref, 1,
6022 \%editedignores, \@unrepres),
6023 H2A => quiltify_trees_differ($headref, $oldtiptree,1),
6024 O2A => quiltify_trees_differ($unapplied,$oldtiptree,1),
6028 foreach my $b (qw(01 02)) {
6029 foreach my $v (qw(O2H O2A H2A)) {
6030 push @dl, ($diffbits->{$v} & $b) ? '##' : '==';
6033 printdebug "differences \@dl @dl.\n";
6036 "$us: base trees orig=%.20s o+d/p=%.20s",
6037 $unapplied, $oldtiptree;
6039 "$us: quilt differences: src: %s orig %s gitignores: %s orig %s\n".
6040 "$us: quilt differences: HEAD %s o+d/p HEAD %s o+d/p",
6041 $dl[0], $dl[1], $dl[3], $dl[4],
6045 print STDERR "dgit: cannot represent change: $_->[1]: $_->[0]\n"
6047 forceable_fail [qw(unrepresentable)], <<END;
6048 HEAD has changes to .orig[s] which are not representable by `3.0 (quilt)'
6053 if (!($diffbits->{O2H} & $diffbits->{O2A})) {
6054 push @failsuggestion, "This might be a patches-unapplied branch.";
6055 } elsif (!($diffbits->{H2A} & $diffbits->{O2A})) {
6056 push @failsuggestion, "This might be a patches-applied branch.";
6058 push @failsuggestion, "Maybe you need to specify one of".
6059 " --[quilt=]gbp --[quilt=]dpm --quilt=unapplied ?";
6061 if (quiltmode_splitbrain()) {
6062 quiltify_splitbrain($clogp, $unapplied, $headref, $oldtiptree,
6063 $diffbits, \%editedignores,
6064 $splitbrain_cachekey);
6068 progress "starting quiltify (multiple patches, $quilt_mode mode)";
6069 quiltify($clogp,$headref,$oldtiptree,\@failsuggestion);
6071 if (!open P, '>>', ".pc/applied-patches") {
6072 $!==&ENOENT or die $!;
6077 commit_quilty_patch();
6079 if ($mustdeletepc) {
6080 quilt_fixup_delete_pc();
6084 sub quilt_fixup_editor () {
6085 my $descfn = $ENV{$fakeeditorenv};
6086 my $editing = $ARGV[$#ARGV];
6087 open I1, '<', $descfn or die "$descfn: $!";
6088 open I2, '<', $editing or die "$editing: $!";
6089 unlink $editing or die "$editing: $!";
6090 open O, '>', $editing or die "$editing: $!";
6091 while (<I1>) { print O or die $!; } I1->error and die $!;
6094 $copying ||= m/^\-\-\- /;
6095 next unless $copying;
6098 I2->error and die $!;
6103 sub maybe_apply_patches_dirtily () {
6104 return unless $quilt_mode =~ m/gbp|unapplied/;
6105 print STDERR <<END or die $!;
6107 dgit: Building, or cleaning with rules target, in patches-unapplied tree.
6108 dgit: Have to apply the patches - making the tree dirty.
6109 dgit: (Consider specifying --clean=git and (or) using dgit sbuild.)
6112 $patches_applied_dirtily = 01;
6113 $patches_applied_dirtily |= 02 unless stat_exists '.pc';
6114 runcmd qw(dpkg-source --before-build .);
6117 sub maybe_unapply_patches_again () {
6118 progress "dgit: Unapplying patches again to tidy up the tree."
6119 if $patches_applied_dirtily;
6120 runcmd qw(dpkg-source --after-build .)
6121 if $patches_applied_dirtily & 01;
6123 if $patches_applied_dirtily & 02;
6124 $patches_applied_dirtily = 0;
6127 #----- other building -----
6129 our $clean_using_builder;
6130 # ^ tree is to be cleaned by dpkg-source's builtin idea that it should
6131 # clean the tree before building (perhaps invoked indirectly by
6132 # whatever we are using to run the build), rather than separately
6133 # and explicitly by us.
6136 return if $clean_using_builder;
6137 if ($cleanmode eq 'dpkg-source') {
6138 maybe_apply_patches_dirtily();
6139 runcmd_ordryrun_local @dpkgbuildpackage, qw(-T clean);
6140 } elsif ($cleanmode eq 'dpkg-source-d') {
6141 maybe_apply_patches_dirtily();
6142 runcmd_ordryrun_local @dpkgbuildpackage, qw(-d -T clean);
6143 } elsif ($cleanmode eq 'git') {
6144 runcmd_ordryrun_local @git, qw(clean -xdf);
6145 } elsif ($cleanmode eq 'git-ff') {
6146 runcmd_ordryrun_local @git, qw(clean -xdff);
6147 } elsif ($cleanmode eq 'check') {
6148 my $leftovers = cmdoutput @git, qw(clean -xdn);
6149 if (length $leftovers) {
6150 print STDERR $leftovers, "\n" or die $!;
6151 fail "tree contains uncommitted files and --clean=check specified";
6153 } elsif ($cleanmode eq 'none') {
6160 badusage "clean takes no additional arguments" if @ARGV;
6163 maybe_unapply_patches_again();
6166 sub build_or_push_prep_early () {
6167 our $build_or_push_prep_early_done //= 0;
6168 return if $build_or_push_prep_early_done++;
6169 badusage "-p is not allowed with dgit $subcommand" if defined $package;
6170 my $clogp = parsechangelog();
6171 $isuite = getfield $clogp, 'Distribution';
6172 $package = getfield $clogp, 'Source';
6173 $version = getfield $clogp, 'Version';
6176 sub build_prep_early () {
6177 build_or_push_prep_early();
6185 build_maybe_quilt_fixup();
6187 my $pat = changespat $version;
6188 foreach my $f (glob "$buildproductsdir/$pat") {
6190 unlink $f or fail "remove old changes file $f: $!";
6192 progress "would remove $f";
6198 sub changesopts_initial () {
6199 my @opts =@changesopts[1..$#changesopts];
6202 sub changesopts_version () {
6203 if (!defined $changes_since_version) {
6206 @vsns = archive_query('archive_query');
6207 my @quirk = access_quirk();
6208 if ($quirk[0] eq 'backports') {
6209 local $isuite = $quirk[2];
6211 canonicalise_suite();
6212 push @vsns, archive_query('archive_query');
6218 "archive query failed (queried because --since-version not specified)";
6221 @vsns = map { $_->[0] } @vsns;
6222 @vsns = sort { -version_compare($a, $b) } @vsns;
6223 $changes_since_version = $vsns[0];
6224 progress "changelog will contain changes since $vsns[0]";
6226 $changes_since_version = '_';
6227 progress "package seems new, not specifying -v<version>";
6230 if ($changes_since_version ne '_') {
6231 return ("-v$changes_since_version");
6237 sub changesopts () {
6238 return (changesopts_initial(), changesopts_version());
6241 sub massage_dbp_args ($;$) {
6242 my ($cmd,$xargs) = @_;
6245 # - if we're going to split the source build out so we can
6246 # do strange things to it, massage the arguments to dpkg-buildpackage
6247 # so that the main build doessn't build source (or add an argument
6248 # to stop it building source by default).
6250 # - add -nc to stop dpkg-source cleaning the source tree,
6251 # unless we're not doing a split build and want dpkg-source
6252 # as cleanmode, in which case we can do nothing
6255 # 0 - source will NOT need to be built separately by caller
6256 # +1 - source will need to be built separately by caller
6257 # +2 - source will need to be built separately by caller AND
6258 # dpkg-buildpackage should not in fact be run at all!
6259 debugcmd '#massaging#', @$cmd if $debuglevel>1;
6260 #print STDERR "MASS0 ",Dumper($cmd, $xargs, $need_split_build_invocation);
6261 if ($cleanmode eq 'dpkg-source' && !$need_split_build_invocation) {
6262 $clean_using_builder = 1;
6265 # -nc has the side effect of specifying -b if nothing else specified
6266 # and some combinations of -S, -b, et al, are errors, rather than
6267 # later simply overriding earlie. So we need to:
6268 # - search the command line for these options
6269 # - pick the last one
6270 # - perhaps add our own as a default
6271 # - perhaps adjust it to the corresponding non-source-building version
6273 foreach my $l ($cmd, $xargs) {
6275 @$l = grep { !(m/^-[SgGFABb]$/s and $dmode=$_) } @$l;
6278 #print STDERR "MASS1 ",Dumper($cmd, $xargs, $dmode);
6280 if ($need_split_build_invocation) {
6281 printdebug "massage split $dmode.\n";
6282 $r = $dmode =~ m/[S]/ ? +2 :
6283 $dmode =~ y/gGF/ABb/ ? +1 :
6284 $dmode =~ m/[ABb]/ ? 0 :
6287 printdebug "massage done $r $dmode.\n";
6289 #print STDERR "MASS2 ",Dumper($cmd, $xargs, $r);
6295 my $wasdir = must_getcwd();
6301 sub postbuild_mergechanges ($) { # must run with CWD=.. (eg in in_parent)
6302 my ($msg_if_onlyone) = @_;
6303 # If there is only one .changes file, fail with $msg_if_onlyone,
6304 # or if that is undef, be a no-op.
6305 # Returns the changes file to report to the user.
6306 my $pat = changespat $version;
6307 my @changesfiles = glob $pat;
6308 @changesfiles = sort {
6309 ($b =~ m/_source\.changes$/ <=> $a =~ m/_source\.changes$/)
6313 if (@changesfiles==1) {
6314 fail <<END.$msg_if_onlyone if defined $msg_if_onlyone;
6315 only one changes file from build (@changesfiles)
6317 $result = $changesfiles[0];
6318 } elsif (@changesfiles==2) {
6319 my $binchanges = parsecontrol($changesfiles[1], "binary changes file");
6320 foreach my $l (split /\n/, getfield $binchanges, 'Files') {
6321 fail "$l found in binaries changes file $binchanges"
6324 runcmd_ordryrun_local @mergechanges, @changesfiles;
6325 my $multichanges = changespat $version,'multi';
6327 stat_exists $multichanges or fail "$multichanges: $!";
6328 foreach my $cf (glob $pat) {
6329 next if $cf eq $multichanges;
6330 rename "$cf", "$cf.inmulti" or fail "$cf\{,.inmulti}: $!";
6333 $result = $multichanges;
6335 fail "wrong number of different changes files (@changesfiles)";
6337 printdone "build successful, results in $result\n" or die $!;
6340 sub midbuild_checkchanges () {
6341 my $pat = changespat $version;
6342 return if $rmchanges;
6343 my @unwanted = map { s#^\.\./##; $_; } glob "../$pat";
6344 @unwanted = grep { $_ ne changespat $version,'source' } @unwanted;
6346 changes files other than source matching $pat already present; building would result in ambiguity about the intended results.
6347 Suggest you delete @unwanted.
6352 sub midbuild_checkchanges_vanilla ($) {
6354 midbuild_checkchanges() if $wantsrc == 1;
6357 sub postbuild_mergechanges_vanilla ($) {
6359 if ($wantsrc == 1) {
6361 postbuild_mergechanges(undef);
6364 printdone "build successful\n";
6370 my @dbp = (@dpkgbuildpackage, qw(-us -uc), changesopts_initial(), @ARGV);
6371 my $wantsrc = massage_dbp_args \@dbp;
6374 midbuild_checkchanges_vanilla $wantsrc;
6379 push @dbp, changesopts_version();
6380 maybe_apply_patches_dirtily();
6381 runcmd_ordryrun_local @dbp;
6383 maybe_unapply_patches_again();
6384 postbuild_mergechanges_vanilla $wantsrc;
6388 $quilt_mode //= 'gbp';
6394 # gbp can make .origs out of thin air. In my tests it does this
6395 # even for a 1.0 format package, with no origs present. So I
6396 # guess it keys off just the version number. We don't know
6397 # exactly what .origs ought to exist, but let's assume that we
6398 # should run gbp if: the version has an upstream part and the main
6400 my $upstreamversion = upstreamversion $version;
6401 my $origfnpat = srcfn $upstreamversion, '.orig.tar.*';
6402 my $gbp_make_orig = $version =~ m/-/ && !(() = glob "../$origfnpat");
6404 if ($gbp_make_orig) {
6406 $cleanmode = 'none'; # don't do it again
6407 $need_split_build_invocation = 1;
6410 my @dbp = @dpkgbuildpackage;
6412 my $wantsrc = massage_dbp_args \@dbp, \@ARGV;
6414 if (!length $gbp_build[0]) {
6415 if (length executable_on_path('git-buildpackage')) {
6416 $gbp_build[0] = qw(git-buildpackage);
6418 $gbp_build[0] = 'gbp buildpackage';
6421 my @cmd = opts_opt_multi_cmd @gbp_build;
6423 push @cmd, (qw(-us -uc --git-no-sign-tags),
6424 "--git-builder=".(shellquote @dbp));
6426 if ($gbp_make_orig) {
6427 my $priv = dgit_privdir();
6428 my $ok = "$priv/origs-gen-ok";
6429 unlink $ok or $!==&ENOENT or die $!;
6430 my @origs_cmd = @cmd;
6431 push @origs_cmd, qw(--git-cleaner=true);
6432 push @origs_cmd, "--git-prebuild=".
6433 "touch ".(shellquote $ok)." ".(shellquote "$priv/no-such-dir/ok");
6434 push @origs_cmd, @ARGV;
6436 debugcmd @origs_cmd;
6438 do { local $!; stat_exists $ok; }
6439 or failedcmd @origs_cmd;
6441 dryrun_report @origs_cmd;
6447 midbuild_checkchanges_vanilla $wantsrc;
6449 if (!$clean_using_builder) {
6450 push @cmd, '--git-cleaner=true';
6454 maybe_unapply_patches_again();
6456 push @cmd, changesopts();
6457 runcmd_ordryrun_local @cmd, @ARGV;
6459 postbuild_mergechanges_vanilla $wantsrc;
6461 sub cmd_git_build { cmd_gbp_build(); } # compatibility with <= 1.0
6463 sub build_source_for_push {
6465 maybe_unapply_patches_again();
6466 $changesfile = $sourcechanges;
6472 $sourcechanges = changespat $version,'source';
6474 unlink "../$sourcechanges" or $!==ENOENT
6475 or fail "remove $sourcechanges: $!";
6477 $dscfn = dscfn($version);
6478 my @cmd = (@dpkgsource, qw(-b --));
6480 changedir $playground;
6481 runcmd_ordryrun_local @cmd, "work";
6482 my @udfiles = <${package}_*>;
6484 foreach my $f (@udfiles) {
6485 printdebug "source copy, found $f\n";
6488 ($f =~ m/\.debian\.tar(?:\.\w+)$/ &&
6489 $f eq srcfn($version, $&));
6490 printdebug "source copy, found $f - renaming\n";
6491 rename "$playground/$f", "../$f" or $!==ENOENT
6492 or fail "put in place new source file ($f): $!";
6495 my $pwd = must_getcwd();
6496 my $leafdir = basename $pwd;
6498 runcmd_ordryrun_local @cmd, $leafdir;
6501 runcmd_ordryrun_local qw(sh -ec),
6502 'exec >$1; shift; exec "$@"','x',
6503 "../$sourcechanges",
6504 @dpkggenchanges, qw(-S), changesopts();
6507 sub cmd_build_source {
6509 badusage "build-source takes no additional arguments" if @ARGV;
6511 maybe_unapply_patches_again();
6512 printdone "source built, results in $dscfn and $sourcechanges";
6517 midbuild_checkchanges();
6520 stat_exists $dscfn or fail "$dscfn (in parent directory): $!";
6521 stat_exists $sourcechanges
6522 or fail "$sourcechanges (in parent directory): $!";
6524 runcmd_ordryrun_local @sbuild, qw(-d), $isuite, @ARGV, $dscfn;
6526 maybe_unapply_patches_again();
6528 postbuild_mergechanges(<<END);
6529 perhaps you need to pass -A ? (sbuild's default is to build only
6530 arch-specific binaries; dgit 1.4 used to override that.)
6535 sub cmd_quilt_fixup {
6536 badusage "incorrect arguments to dgit quilt-fixup" if @ARGV;
6539 build_maybe_quilt_fixup();
6542 sub import_dsc_result {
6543 my ($dstref, $newhash, $what_log, $what_msg) = @_;
6544 my @cmd = (git_update_ref_cmd $what_log, $dstref, $newhash);
6546 check_gitattrs($newhash, "source tree");
6548 progress "dgit: import-dsc: $what_msg";
6551 sub cmd_import_dsc {
6555 last unless $ARGV[0] =~ m/^-/;
6558 if (m/^--require-valid-signature$/) {
6561 badusage "unknown dgit import-dsc sub-option \`$_'";
6565 badusage "usage: dgit import-dsc .../PATH/TO/.DSC BRANCH" unless @ARGV==2;
6566 my ($dscfn, $dstbranch) = @ARGV;
6568 badusage "dry run makes no sense with import-dsc" unless act_local();
6570 my $force = $dstbranch =~ s/^\+// ? +1 :
6571 $dstbranch =~ s/^\.\.// ? -1 :
6573 my $info = $force ? " $&" : '';
6574 $info = "$dscfn$info";
6576 my $specbranch = $dstbranch;
6577 $dstbranch = "refs/heads/$dstbranch" unless $dstbranch =~ m#^refs/#;
6578 $dstbranch = cmdoutput @git, qw(check-ref-format --normalize), $dstbranch;
6580 my @symcmd = (@git, qw(symbolic-ref -q HEAD));
6581 my $chead = cmdoutput_errok @symcmd;
6582 defined $chead or $?==256 or failedcmd @symcmd;
6584 fail "$dstbranch is checked out - will not update it"
6585 if defined $chead and $chead eq $dstbranch;
6587 my $oldhash = git_get_ref $dstbranch;
6589 open D, "<", $dscfn or fail "open import .dsc ($dscfn): $!";
6590 $dscdata = do { local $/ = undef; <D>; };
6591 D->error and fail "read $dscfn: $!";
6594 # we don't normally need this so import it here
6595 use Dpkg::Source::Package;
6596 my $dp = new Dpkg::Source::Package filename => $dscfn,
6597 require_valid_signature => $needsig;
6599 local $SIG{__WARN__} = sub {
6601 return unless $needsig;
6602 fail "import-dsc signature check failed";
6604 if (!$dp->is_signed()) {
6605 warn "$us: warning: importing unsigned .dsc\n";
6607 my $r = $dp->check_signature();
6608 die "->check_signature => $r" if $needsig && $r;
6614 $package = getfield $dsc, 'Source';
6616 parse_dsc_field($dsc, "Dgit metadata in .dsc")
6617 unless forceing [qw(import-dsc-with-dgit-field)];
6618 parse_dsc_field_def_dsc_distro();
6620 $isuite = 'DGIT-IMPORT-DSC';
6621 $idistro //= $dsc_distro;
6625 if (defined $dsc_hash) {
6626 progress "dgit: import-dsc of .dsc with Dgit field, using git hash";
6627 resolve_dsc_field_commit undef, undef;
6629 if (defined $dsc_hash) {
6630 my @cmd = (qw(sh -ec),
6631 "echo $dsc_hash | git cat-file --batch-check");
6632 my $objgot = cmdoutput @cmd;
6633 if ($objgot =~ m#^\w+ missing\b#) {
6635 .dsc contains Dgit field referring to object $dsc_hash
6636 Your git tree does not have that object. Try `git fetch' from a
6637 plausible server (browse.dgit.d.o? alioth?), and try the import-dsc again.
6640 if ($oldhash && !is_fast_fwd $oldhash, $dsc_hash) {
6642 progress "Not fast forward, forced update.";
6644 fail "Not fast forward to $dsc_hash";
6647 import_dsc_result $dstbranch, $dsc_hash,
6648 "dgit import-dsc (Dgit): $info",
6649 "updated git ref $dstbranch";
6654 Branch $dstbranch already exists
6655 Specify ..$specbranch for a pseudo-merge, binding in existing history
6656 Specify +$specbranch to overwrite, discarding existing history
6658 if $oldhash && !$force;
6660 my @dfi = dsc_files_info();
6661 foreach my $fi (@dfi) {
6662 my $f = $fi->{Filename};
6666 fail "lstat $here works but stat gives $! !";
6668 fail "stat $here: $!" unless $! == ENOENT;
6670 if ($dscfn =~ m#^(?:\./+)?\.\./+#) {
6672 } elsif ($dscfn =~ m#^/#) {
6675 fail "cannot import $dscfn which seems to be inside working tree!";
6677 $there =~ s#/+[^/]+$## or
6678 fail "import $dscfn requires ../$f, but it does not exist";
6680 my $test = $there =~ m{^/} ? $there : "../$there";
6681 stat $test or fail "import $dscfn requires $test, but: $!";
6682 symlink $there, $here or fail "symlink $there to $here: $!";
6683 progress "made symlink $here -> $there";
6684 # print STDERR Dumper($fi);
6686 my @mergeinputs = generate_commits_from_dsc();
6687 die unless @mergeinputs == 1;
6689 my $newhash = $mergeinputs[0]{Commit};
6693 progress "Import, forced update - synthetic orphan git history.";
6694 } elsif ($force < 0) {
6695 progress "Import, merging.";
6696 my $tree = cmdoutput @git, qw(rev-parse), "$newhash:";
6697 my $version = getfield $dsc, 'Version';
6698 my $clogp = commit_getclogp $newhash;
6699 my $authline = clogp_authline $clogp;
6700 $newhash = make_commit_text <<END;
6707 Merge $package ($version) import into $dstbranch
6710 die; # caught earlier
6714 import_dsc_result $dstbranch, $newhash,
6715 "dgit import-dsc: $info",
6716 "results are in in git ref $dstbranch";
6719 sub pre_archive_api_query () {
6720 not_necessarily_a_tree();
6722 sub cmd_archive_api_query {
6723 badusage "need only 1 subpath argument" unless @ARGV==1;
6724 my ($subpath) = @ARGV;
6725 local $isuite = 'DGIT-API-QUERY-CMD';
6726 my @cmd = archive_api_query_cmd($subpath);
6729 exec @cmd or fail "exec curl: $!\n";
6732 sub repos_server_url () {
6733 $package = '_dgit-repos-server';
6734 local $access_forpush = 1;
6735 local $isuite = 'DGIT-REPOS-SERVER';
6736 my $url = access_giturl();
6739 sub pre_clone_dgit_repos_server () {
6740 not_necessarily_a_tree();
6742 sub cmd_clone_dgit_repos_server {
6743 badusage "need destination argument" unless @ARGV==1;
6744 my ($destdir) = @ARGV;
6745 my $url = repos_server_url();
6746 my @cmd = (@git, qw(clone), $url, $destdir);
6748 exec @cmd or fail "exec git clone: $!\n";
6751 sub pre_print_dgit_repos_server_source_url () {
6752 not_necessarily_a_tree();
6754 sub cmd_print_dgit_repos_server_source_url {
6755 badusage "no arguments allowed to dgit print-dgit-repos-server-source-url"
6757 my $url = repos_server_url();
6758 print $url, "\n" or die $!;
6761 sub pre_print_dpkg_source_ignores {
6762 not_necessarily_a_tree();
6764 sub cmd_print_dpkg_source_ignores {
6765 badusage "no arguments allowed to dgit print-dpkg-source-ignores"
6767 print "@dpkg_source_ignores\n" or die $!;
6770 sub cmd_setup_mergechangelogs {
6771 badusage "no arguments allowed to dgit setup-mergechangelogs" if @ARGV;
6772 local $isuite = 'DGIT-SETUP-TREE';
6773 setup_mergechangelogs(1);
6776 sub cmd_setup_useremail {
6777 badusage "no arguments allowed to dgit setup-useremail" if @ARGV;
6778 local $isuite = 'DGIT-SETUP-TREE';
6782 sub cmd_setup_gitattributes {
6783 badusage "no arguments allowed to dgit setup-useremail" if @ARGV;
6784 local $isuite = 'DGIT-SETUP-TREE';
6788 sub cmd_setup_new_tree {
6789 badusage "no arguments allowed to dgit setup-tree" if @ARGV;
6790 local $isuite = 'DGIT-SETUP-TREE';
6794 #---------- argument parsing and main program ----------
6797 print "dgit version $our_version\n" or die $!;
6801 our (%valopts_long, %valopts_short);
6802 our (%funcopts_long);
6804 our (@modeopt_cfgs);
6806 sub defvalopt ($$$$) {
6807 my ($long,$short,$val_re,$how) = @_;
6808 my $oi = { Long => $long, Short => $short, Re => $val_re, How => $how };
6809 $valopts_long{$long} = $oi;
6810 $valopts_short{$short} = $oi;
6811 # $how subref should:
6812 # do whatever assignemnt or thing it likes with $_[0]
6813 # if the option should not be passed on to remote, @rvalopts=()
6814 # or $how can be a scalar ref, meaning simply assign the value
6817 defvalopt '--since-version', '-v', '[^_]+|_', \$changes_since_version;
6818 defvalopt '--distro', '-d', '.+', \$idistro;
6819 defvalopt '', '-k', '.+', \$keyid;
6820 defvalopt '--existing-package','', '.*', \$existing_package;
6821 defvalopt '--build-products-dir','','.*', \$buildproductsdir;
6822 defvalopt '--clean', '', $cleanmode_re, \$cleanmode;
6823 defvalopt '--package', '-p', $package_re, \$package;
6824 defvalopt '--quilt', '', $quilt_modes_re, \$quilt_mode;
6826 defvalopt '', '-C', '.+', sub {
6827 ($changesfile) = (@_);
6828 if ($changesfile =~ s#^(.*)/##) {
6829 $buildproductsdir = $1;
6833 defvalopt '--initiator-tempdir','','.*', sub {
6834 ($initiator_tempdir) = (@_);
6835 $initiator_tempdir =~ m#^/# or
6836 badusage "--initiator-tempdir must be used specify an".
6837 " absolute, not relative, directory."
6840 sub defoptmodes ($@) {
6841 my ($varref, $cfgkey, $default, %optmap) = @_;
6843 while (my ($opt,$val) = each %optmap) {
6844 $funcopts_long{$opt} = sub { $$varref = $val; };
6845 $permit{$val} = $val;
6847 push @modeopt_cfgs, {
6850 Default => $default,
6855 defoptmodes \$dodep14tag, qw( dep14tag want
6858 --always-dep14tag always );
6863 if (defined $ENV{'DGIT_SSH'}) {
6864 @ssh = string_to_ssh $ENV{'DGIT_SSH'};
6865 } elsif (defined $ENV{'GIT_SSH'}) {
6866 @ssh = ($ENV{'GIT_SSH'});
6874 if (!defined $val) {
6875 badusage "$what needs a value" unless @ARGV;
6877 push @rvalopts, $val;
6879 badusage "bad value \`$val' for $what" unless
6880 $val =~ m/^$oi->{Re}$(?!\n)/s;
6881 my $how = $oi->{How};
6882 if (ref($how) eq 'SCALAR') {
6887 push @ropts, @rvalopts;
6891 last unless $ARGV[0] =~ m/^-/;
6895 if (m/^--dry-run$/) {
6898 } elsif (m/^--damp-run$/) {
6901 } elsif (m/^--no-sign$/) {
6904 } elsif (m/^--help$/) {
6906 } elsif (m/^--version$/) {
6908 } elsif (m/^--new$/) {
6911 } elsif (m/^--([-0-9a-z]+)=(.+)/s &&
6912 ($om = $opts_opt_map{$1}) &&
6916 } elsif (m/^--([-0-9a-z]+):(.*)/s &&
6917 !$opts_opt_cmdonly{$1} &&
6918 ($om = $opts_opt_map{$1})) {
6921 } elsif (m/^--(gbp|dpm)$/s) {
6922 push @ropts, "--quilt=$1";
6924 } elsif (m/^--ignore-dirty$/s) {
6927 } elsif (m/^--no-quilt-fixup$/s) {
6929 $quilt_mode = 'nocheck';
6930 } elsif (m/^--no-rm-on-error$/s) {
6933 } elsif (m/^--no-chase-dsc-distro$/s) {
6935 $chase_dsc_distro = 0;
6936 } elsif (m/^--overwrite$/s) {
6938 $overwrite_version = '';
6939 } elsif (m/^--overwrite=(.+)$/s) {
6941 $overwrite_version = $1;
6942 } elsif (m/^--delayed=(\d+)$/s) {
6945 } elsif (m/^--dgit-view-save=(.+)$/s) {
6947 $split_brain_save = $1;
6948 $split_brain_save =~ s#^(?!refs/)#refs/heads/#;
6949 } elsif (m/^--(no-)?rm-old-changes$/s) {
6952 } elsif (m/^--deliberately-($deliberately_re)$/s) {
6954 push @deliberatelies, $&;
6955 } elsif (m/^--force-(.*)/ && defined $forceopts{$1}) {
6959 } elsif (m/^--force-/) {
6961 "$us: warning: ignoring unknown force option $_\n";
6963 } elsif (m/^--dgit-tag-format=(old|new)$/s) {
6964 # undocumented, for testing
6966 $tagformat_want = [ $1, 'command line', 1 ];
6967 # 1 menas overrides distro configuration
6968 } elsif (m/^--always-split-source-build$/s) {
6969 # undocumented, for testing
6971 $need_split_build_invocation = 1;
6972 } elsif (m/^--config-lookup-explode=(.+)$/s) {
6973 # undocumented, for testing
6975 $gitcfgs{cmdline}{$1} = 'CONFIG-LOOKUP-EXPLODE';
6976 # ^ it's supposed to be an array ref
6977 } elsif (m/^(--[-0-9a-z]+)(=|$)/ && ($oi = $valopts_long{$1})) {
6978 $val = $2 ? $' : undef; #';
6979 $valopt->($oi->{Long});
6980 } elsif ($funcopts_long{$_}) {
6982 $funcopts_long{$_}();
6984 badusage "unknown long option \`$_'";
6991 } elsif (s/^-L/-/) {
6994 } elsif (s/^-h/-/) {
6996 } elsif (s/^-D/-/) {
7000 } elsif (s/^-N/-/) {
7005 push @changesopts, $_;
7007 } elsif (s/^-wn$//s) {
7009 $cleanmode = 'none';
7010 } elsif (s/^-wg$//s) {
7013 } elsif (s/^-wgf$//s) {
7015 $cleanmode = 'git-ff';
7016 } elsif (s/^-wd$//s) {
7018 $cleanmode = 'dpkg-source';
7019 } elsif (s/^-wdd$//s) {
7021 $cleanmode = 'dpkg-source-d';
7022 } elsif (s/^-wc$//s) {
7024 $cleanmode = 'check';
7025 } elsif (s/^-c([^=]*)\=(.*)$//s) {
7026 push @git, '-c', $&;
7027 $gitcfgs{cmdline}{$1} = [ $2 ];
7028 } elsif (s/^-c([^=]+)$//s) {
7029 push @git, '-c', $&;
7030 $gitcfgs{cmdline}{$1} = [ 'true' ];
7031 } elsif (m/^-[a-zA-Z]/ && ($oi = $valopts_short{$&})) {
7033 $val = undef unless length $val;
7034 $valopt->($oi->{Short});
7037 badusage "unknown short option \`$_'";
7044 sub check_env_sanity () {
7045 my $blocked = new POSIX::SigSet;
7046 sigprocmask SIG_UNBLOCK, $blocked, $blocked or die $!;
7049 foreach my $name (qw(PIPE CHLD)) {
7050 my $signame = "SIG$name";
7051 my $signum = eval "POSIX::$signame" // die;
7052 ($SIG{$name} // 'DEFAULT') eq 'DEFAULT' or
7053 die "$signame is set to something other than SIG_DFL\n";
7054 $blocked->ismember($signum) and
7055 die "$signame is blocked\n";
7061 On entry to dgit, $@
7062 This is a bug produced by something in in your execution environment.
7068 sub parseopts_late_defaults () {
7069 $isuite //= cfg("dgit-distro.$idistro.default-suite", 'RETURN-UNDEF')
7070 if defined $idistro;
7071 $isuite //= cfg('dgit.default.default-suite');
7073 foreach my $k (keys %opts_opt_map) {
7074 my $om = $opts_opt_map{$k};
7076 my $v = access_cfg("cmd-$k", 'RETURN-UNDEF');
7078 badcfg "cannot set command for $k"
7079 unless length $om->[0];
7083 foreach my $c (access_cfg_cfgs("opts-$k")) {
7085 map { $_ ? @$_ : () }
7086 map { $gitcfgs{$_}{$c} }
7087 reverse @gitcfgsources;
7088 printdebug "CL $c ", (join " ", map { shellquote } @vl),
7089 "\n" if $debuglevel >= 4;
7091 badcfg "cannot configure options for $k"
7092 if $opts_opt_cmdonly{$k};
7093 my $insertpos = $opts_cfg_insertpos{$k};
7094 @$om = ( @$om[0..$insertpos-1],
7096 @$om[$insertpos..$#$om] );
7100 if (!defined $rmchanges) {
7101 local $access_forpush;
7102 $rmchanges = access_cfg_bool(0, 'rm-old-changes');
7105 if (!defined $quilt_mode) {
7106 local $access_forpush;
7107 $quilt_mode = cfg('dgit.force.quilt-mode', 'RETURN-UNDEF')
7108 // access_cfg('quilt-mode', 'RETURN-UNDEF')
7110 $quilt_mode =~ m/^($quilt_modes_re)$/
7111 or badcfg "unknown quilt-mode \`$quilt_mode'";
7115 foreach my $moc (@modeopt_cfgs) {
7116 local $access_forpush;
7117 my $vr = $moc->{Var};
7118 next if defined $$vr;
7119 $$vr = access_cfg($moc->{Key}, 'RETURN-UNDEF') // $moc->{Default};
7120 my $v = $moc->{Vals}{$$vr};
7121 badcfg "unknown $moc->{Key} setting \`$$vr'" unless defined $v;
7125 $need_split_build_invocation ||= quiltmode_splitbrain();
7127 if (!defined $cleanmode) {
7128 local $access_forpush;
7129 $cleanmode = access_cfg('clean-mode', 'RETURN-UNDEF');
7130 $cleanmode //= 'dpkg-source';
7132 badcfg "unknown clean-mode \`$cleanmode'" unless
7133 $cleanmode =~ m/^($cleanmode_re)$(?!\n)/s;
7137 if ($ENV{$fakeeditorenv}) {
7139 quilt_fixup_editor();
7145 print STDERR "DRY RUN ONLY\n" if $dryrun_level > 1;
7146 print STDERR "DAMP RUN - WILL MAKE LOCAL (UNSIGNED) CHANGES\n"
7147 if $dryrun_level == 1;
7149 print STDERR $helpmsg or die $!;
7152 $cmd = $subcommand = shift @ARGV;
7155 my $pre_fn = ${*::}{"pre_$cmd"};
7156 $pre_fn->() if $pre_fn;
7158 record_maindir if $invoked_in_git_tree;
7161 my $fn = ${*::}{"cmd_$cmd"};
7162 $fn or badusage "unknown operation $cmd";