3 # Integration between git and Debian-style archives
5 # Copyright (C)2013-2019 Ian Jackson
6 # Copyright (C)2017-2019 Sean Whitton
7 # Copyright (C)2019 Matthew Vernon / Genome Research Limited
9 # This program is free software: you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation, either version 3 of the License, or
12 # (at your option) any later version.
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with this program. If not, see <http://www.gnu.org/licenses/>.
22 END { $? = $Debian::Dgit::ExitStatus::desired // -1; };
23 use Debian::Dgit::ExitStatus;
24 use Debian::Dgit::I18n;
28 use Debian::Dgit qw(:DEFAULT :playground);
34 use Dpkg::Control::Hash;
37 use File::Temp qw(tempdir);
40 use Dpkg::Compression;
41 use Dpkg::Compression::Process;
47 use List::MoreUtils qw(pairwise);
48 use Text::Glob qw(match_glob);
50 use Fcntl qw(:DEFAULT :flock);
55 our $our_version = 'UNRELEASED'; ###substituted###
56 our $absurdity = undef; ###substituted###
58 $SIG{INT} = 'DEFAULT'; # work around #932841
60 our @rpushprotovsn_support = qw(6 5 4); # Reverse order!
71 our $dryrun_level = 0;
73 our $buildproductsdir;
76 our $includedirty = 0;
80 our $existing_package = 'dpkg';
82 our $changes_since_version;
84 our $overwrite_version; # undef: not specified; '': check changelog
86 our $quilt_upstream_commitish;
87 our $quilt_upstream_commitish_used;
88 our $quilt_upstream_commitish_message;
89 our $quilt_options_re = 'gbp|dpm|baredebian(?:\+tarball|\+git)?';
90 our $quilt_modes_re = "linear|smash|auto|nofix|nocheck|unapplied|$quilt_options_re";
92 our $splitview_modes_re = qr{auto|always|never};
94 our %internal_object_save;
95 our $we_are_responder;
96 our $we_are_initiator;
97 our $initiator_tempdir;
98 our $patches_applied_dirtily = 00;
99 our $chase_dsc_distro=1;
101 our %forceopts = map { $_=>0 }
102 qw(unrepresentable unsupported-source-format
103 dsc-changes-mismatch changes-origs-exactly
104 uploading-binaries uploading-source-only
106 import-gitapply-absurd
107 import-gitapply-no-absurd
108 import-dsc-with-dgit-field);
110 our %format_ok = map { $_=>1 } ("1.0","3.0 (native)","3.0 (quilt)");
112 our $cleanmode_re = qr{(?: dpkg-source (?: -d )? (?: ,no-check | ,all-check )?
113 | (?: git | git-ff ) (?: ,always )?
114 | check (?: ,ignores )?
118 our $git_authline_re = '^([^<>]+) \<(\S+)\> (\d+ [-+]\d+)$';
119 our $splitbraincache = 'dgit-intern/quilt-cache';
120 our $rewritemap = 'dgit-rewrite/map';
122 our @dpkg_source_ignores = qw(-i(?:^|/)\.git(?:/|$) -I.git);
124 our (@dget) = qw(dget);
125 our (@curl) = (qw(curl --proto-redir), '-all,http,https', qw(-L));
126 our (@dput) = qw(dput);
127 our (@debsign) = qw(debsign);
128 our (@gpg) = qw(gpg);
129 our (@sbuild) = (qw(sbuild --no-source));
131 our (@dgit) = qw(dgit);
132 our (@git_debrebase) = qw(git-debrebase);
133 our (@aptget) = qw(apt-get);
134 our (@aptcache) = qw(apt-cache);
135 our (@dpkgbuildpackage) = (qw(dpkg-buildpackage), @dpkg_source_ignores);
136 our (@dpkgsource) = (qw(dpkg-source), @dpkg_source_ignores);
137 our (@dpkggenchanges) = qw(dpkg-genchanges);
138 our (@mergechanges) = qw(mergechanges -f);
139 our (@gbp_build) = ('');
140 our (@gbp_pq) = ('gbp pq');
141 our (@changesopts) = ('');
142 our (@pbuilder) = ("sudo -E pbuilder","--no-source-only-changes");
143 our (@cowbuilder) = ("sudo -E cowbuilder","--no-source-only-changes");
145 our %opts_opt_map = ('dget' => \@dget, # accept for compatibility
148 'debsign' => \@debsign,
150 'sbuild' => \@sbuild,
154 'git-debrebase' => \@git_debrebase,
155 'apt-get' => \@aptget,
156 'apt-cache' => \@aptcache,
157 'dpkg-source' => \@dpkgsource,
158 'dpkg-buildpackage' => \@dpkgbuildpackage,
159 'dpkg-genchanges' => \@dpkggenchanges,
160 'gbp-build' => \@gbp_build,
161 'gbp-pq' => \@gbp_pq,
162 'ch' => \@changesopts,
163 'mergechanges' => \@mergechanges,
164 'pbuilder' => \@pbuilder,
165 'cowbuilder' => \@cowbuilder);
167 our %opts_opt_cmdonly = ('gpg' => 1, 'git' => 1);
168 our %opts_cfg_insertpos = map {
170 scalar @{ $opts_opt_map{$_} }
171 } keys %opts_opt_map;
173 sub parseopts_late_defaults();
174 sub quiltify_trees_differ ($$;$$$);
175 sub setup_gitattrs(;$);
176 sub check_gitattrs($$);
183 our $supplementary_message = '';
184 our $made_split_brain = 0;
187 # Interactions between quilt mode and split brain
188 # (currently, split brain only implemented iff
189 # madformat_wantfixup && quiltmode_splitting)
191 # source format sane `3.0 (quilt)'
192 # madformat_wantfixup()
194 # quilt mode normal quiltmode
195 # (eg linear) _splitbrain
197 # ------------ ------------------------------------------------
199 # no split no q cache no q cache forbidden,
200 # brain PM on master q fixup on master prevented
201 # !do_split_brain() PM on master
203 # split brain no q cache q fixup cached, to dgit view
204 # PM in dgit view PM in dgit view
206 # PM = pseudomerge to make ff, due to overwrite (or split view)
207 # "no q cache" = do not record in cache on build, do not check cache
208 # `3.0 (quilt)' with --quilt=nocheck is treated as sane format
212 return unless forkcheck_mainprocess();
213 print STDERR "! $_\n" foreach $supplementary_message =~ m/^.+$/mg;
216 our $remotename = 'dgit';
217 our @ourdscfield = qw(Dgit Vcs-Dgit-Master);
221 if (!defined $absurdity) {
223 $absurdity =~ s{/[^/]+$}{/absurd} or die;
226 sub madformat ($) { $_[0] eq '3.0 (quilt)' }
228 sub lbranch () { return "$branchprefix/$csuite"; }
229 my $lbranch_re = '^refs/heads/'.$branchprefix.'/([^/.]+)$';
230 sub lref () { return "refs/heads/".lbranch(); }
231 sub lrref () { return "refs/remotes/$remotename/".server_branch($csuite); }
232 sub rrref () { return server_ref($csuite); }
235 my ($vsn, $sfx) = @_;
236 return &source_file_leafname($package, $vsn, $sfx);
238 sub is_orig_file_of_vsn ($$) {
239 my ($f, $upstreamvsn) = @_;
240 return is_orig_file_of_p_v($f, $package, $upstreamvsn);
245 return srcfn($vsn,".dsc");
248 sub changespat ($;$) {
249 my ($vsn, $arch) = @_;
250 return "${package}_".(stripepoch $vsn)."_".($arch//'*').".changes";
259 return unless forkcheck_mainprocess();
260 foreach my $f (@end) {
262 print STDERR "$us: cleanup: $@" if length $@;
267 print STDERR f_ "%s: invalid configuration: %s\n", $us, "@_";
271 sub forceable_fail ($$) {
272 my ($forceoptsl, $msg) = @_;
273 fail $msg unless grep { $forceopts{$_} } @$forceoptsl;
274 print STDERR +(__ "warning: overriding problem due to --force:\n"). $msg;
278 my ($forceoptsl) = @_;
279 my @got = grep { $forceopts{$_} } @$forceoptsl;
280 return 0 unless @got;
282 "warning: skipping checks or functionality due to --force-%s\n",
286 sub no_such_package () {
287 print STDERR f_ "%s: source package %s does not exist in suite %s\n",
288 $us, $package, $isuite;
292 sub deliberately ($) {
294 return !!grep { $_ eq "--deliberately-$enquiry" } @deliberatelies;
297 sub deliberately_not_fast_forward () {
298 foreach (qw(not-fast-forward fresh-repo)) {
299 return 1 if deliberately($_) || deliberately("TEST-dgit-only-$_");
303 sub quiltmode_splitting () {
304 $quilt_mode =~ m/gbp|dpm|unapplied|baredebian/;
306 sub format_quiltmode_splitting ($) {
308 return madformat_wantfixup($format) && quiltmode_splitting();
311 sub do_split_brain () { !!($do_split_brain // confess) }
313 sub opts_opt_multi_cmd {
316 push @cmd, split /\s+/, shift @_;
323 return opts_opt_multi_cmd [], @gbp_pq;
326 sub dgit_privdir () {
327 our $dgit_privdir_made //= ensure_a_playground 'dgit';
331 my $r = $buildproductsdir;
332 $r = "$maindir/$r" unless $r =~ m{^/};
336 sub get_tree_of_commit ($) {
337 my ($commitish) = @_;
338 my $cdata = cmdoutput @git, qw(cat-file commit), $commitish;
339 $cdata =~ m/\n\n/; $cdata = $`;
340 $cdata =~ m/^tree (\w+)$/m or confess "cdata $cdata ?";
344 sub branch_gdr_info ($$) {
345 my ($symref, $head) = @_;
346 my ($status, $msg, $current, $ffq_prev, $gdrlast) =
347 gdr_ffq_prev_branchinfo($symref);
348 return () unless $status eq 'branch';
349 $ffq_prev = git_get_ref $ffq_prev;
350 $gdrlast = git_get_ref $gdrlast;
351 $gdrlast &&= is_fast_fwd $gdrlast, $head;
352 return ($ffq_prev, $gdrlast);
355 sub branch_is_gdr_unstitched_ff ($$$) {
356 my ($symref, $head, $ancestor) = @_;
357 my ($ffq_prev, $gdrlast) = branch_gdr_info($symref, $head);
358 return 0 unless $ffq_prev;
359 return 0 unless !defined $ancestor or is_fast_fwd $ancestor, $ffq_prev;
363 sub branch_is_gdr ($) {
365 # This is quite like git-debrebase's keycommits.
366 # We have our own implementation because:
367 # - our algorighm can do fewer tests so is faster
368 # - it saves testing to see if gdr is installed
370 # NB we use this jsut for deciding whether to run gdr make-patches
371 # Before reusing this algorithm for somthing else, its
372 # suitability should be reconsidered.
375 local $Debian::Dgit::debugcmd_when_debuglevel = 3;
376 printdebug "branch_is_gdr $head...\n";
377 my $get_patches = sub {
378 my $t = git_cat_file "$_[0]:debian/patches", [qw(missing tree)];
381 my $tip_patches = $get_patches->($head);
384 my $cdata = git_cat_file $walk, 'commit';
385 my ($hdrs,$msg) = $cdata =~ m{\n\n} ? ($`,$') : ($cdata,'');
386 if ($msg =~ m{^\[git-debrebase\ (
387 anchor | changelog | make-patches |
388 merged-breakwater | pseudomerge
390 # no need to analyse this - it's sufficient
391 # (gdr classifications: Anchor, MergedBreakwaters)
392 # (made by gdr: Pseudomerge, Changelog)
393 printdebug "branch_is_gdr $walk gdr $1 YES\n";
396 my @parents = ($hdrs =~ m/^parent (\w+)$/gm);
398 my $walk_tree = get_tree_of_commit $walk;
399 foreach my $p (@parents) {
400 my $p_tree = get_tree_of_commit $p;
401 if ($p_tree eq $walk_tree) { # pseudomerge contriburor
402 # (gdr classification: Pseudomerge; not made by gdr)
403 printdebug "branch_is_gdr $walk unmarked pseudomerge\n"
409 # some other non-gdr merge
410 # (gdr classification: VanillaMerge, DgitImportUnpatched, ?)
411 printdebug "branch_is_gdr $walk ?-2-merge NO\n";
415 # (gdr classification: ?)
416 printdebug "branch_is_gdr $walk ?-octopus NO\n";
420 printdebug "branch_is_gdr $walk origin\n";
423 if ($get_patches->($walk) ne $tip_patches) {
424 # Our parent added, removed, or edited patches, and wasn't
425 # a gdr make-patches commit. gdr make-patches probably
426 # won't do that well, then.
427 # (gdr classification of parent: AddPatches or ?)
428 printdebug "branch_is_gdr $walk ?-patches NO\n";
431 if ($tip_patches eq '' and
432 !defined git_cat_file "$walk~:debian" and
433 !quiltify_trees_differ "$walk~", $walk
435 # (gdr classification of parent: BreakwaterStart
436 printdebug "branch_is_gdr $walk unmarked BreakwaterStart YES\n";
439 # (gdr classification: Upstream Packaging Mixed Changelog)
440 printdebug "branch_is_gdr $walk plain\n"
446 #---------- remote protocol support, common ----------
448 # remote push initiator/responder protocol:
449 # $ dgit remote-push-build-host <n-rargs> <rargs>... <push-args>...
450 # where <rargs> is <push-host-dir> <supported-proto-vsn>,... ...
451 # < dgit-remote-push-ready <actual-proto-vsn>
458 # > supplementary-message NBYTES
463 # > file parsed-changelog
464 # [indicates that output of dpkg-parsechangelog follows]
465 # > data-block NBYTES
466 # > [NBYTES bytes of data (no newline)]
467 # [maybe some more blocks]
476 # > param head DGIT-VIEW-HEAD
477 # > param csuite SUITE
478 # > param tagformat new # $protovsn == 4
479 # > param splitbrain 0|1 # $protovsn >= 6
480 # > param maint-view MAINT-VIEW-HEAD
482 # > param buildinfo-filename P_V_X.buildinfo # zero or more times
483 # > file buildinfo # for buildinfos to sign
485 # > previously REFNAME=OBJNAME # if --deliberately-not-fast-forward
486 # # goes into tag, for replay prevention
489 # [indicates that signed tag is wanted]
490 # < data-block NBYTES
491 # < [NBYTES bytes of data (no newline)]
492 # [maybe some more blocks]
496 # > want signed-dsc-changes
497 # < data-block NBYTES [transfer of signed dsc]
499 # < data-block NBYTES [transfer of signed changes]
501 # < data-block NBYTES [transfer of each signed buildinfo
502 # [etc] same number and order as "file buildinfo"]
510 sub i_child_report () {
511 # Sees if our child has died, and reap it if so. Returns a string
512 # describing how it died if it failed, or undef otherwise.
513 return undef unless $i_child_pid;
514 my $got = waitpid $i_child_pid, WNOHANG;
515 return undef if $got <= 0;
516 die unless $got == $i_child_pid;
517 $i_child_pid = undef;
518 return undef unless $?;
519 return f_ "build host child %s", waitstatusmsg();
524 fail f_ "connection lost: %s", $! if $fh->error;
525 fail f_ "protocol violation; %s not expected", $m;
528 sub badproto_badread ($$) {
530 fail f_ "connection lost: %s", $! if $!;
531 my $report = i_child_report();
532 fail $report if defined $report;
533 badproto $fh, f_ "eof (reading %s)", $wh;
536 sub protocol_expect (&$) {
537 my ($match, $fh) = @_;
540 defined && chomp or badproto_badread $fh, __ "protocol message";
548 badproto $fh, f_ "\`%s'", $_;
551 sub protocol_send_file ($$) {
552 my ($fh, $ourfn) = @_;
553 open PF, "<", $ourfn or die "$ourfn: $!";
556 my $got = read PF, $d, 65536;
557 die "$ourfn: $!" unless defined $got;
559 print $fh "data-block ".length($d)."\n" or confess "$!";
560 print $fh $d or confess "$!";
562 PF->error and die "$ourfn $!";
563 print $fh "data-end\n" or confess "$!";
567 sub protocol_read_bytes ($$) {
568 my ($fh, $nbytes) = @_;
569 $nbytes =~ m/^[1-9]\d{0,5}$|^0$/ or badproto \*RO, __ "bad byte count";
571 my $got = read $fh, $d, $nbytes;
572 $got==$nbytes or badproto_badread $fh, __ "data block";
576 sub protocol_receive_file ($$) {
577 my ($fh, $ourfn) = @_;
578 printdebug "() $ourfn\n";
579 open PF, ">", $ourfn or die "$ourfn: $!";
581 my ($y,$l) = protocol_expect {
582 m/^data-block (.*)$/ ? (1,$1) :
583 m/^data-end$/ ? (0,) :
587 my $d = protocol_read_bytes $fh, $l;
588 print PF $d or confess "$!";
590 close PF or confess "$!";
593 #---------- remote protocol support, responder ----------
595 sub responder_send_command ($) {
597 return unless $we_are_responder;
598 # called even without $we_are_responder
599 printdebug ">> $command\n";
600 print PO $command, "\n" or confess "$!";
603 sub responder_send_file ($$) {
604 my ($keyword, $ourfn) = @_;
605 return unless $we_are_responder;
606 printdebug "]] $keyword $ourfn\n";
607 responder_send_command "file $keyword";
608 protocol_send_file \*PO, $ourfn;
611 sub responder_receive_files ($@) {
612 my ($keyword, @ourfns) = @_;
613 die unless $we_are_responder;
614 printdebug "[[ $keyword @ourfns\n";
615 responder_send_command "want $keyword";
616 foreach my $fn (@ourfns) {
617 protocol_receive_file \*PI, $fn;
620 protocol_expect { m/^files-end$/ } \*PI;
623 #---------- remote protocol support, initiator ----------
625 sub initiator_expect (&) {
627 protocol_expect { &$match } \*RO;
630 #---------- end remote code ----------
633 if ($we_are_responder) {
635 responder_send_command "progress ".length($m) or confess "$!";
636 print PO $m or confess "$!";
644 our ($dscdata,$dscurl,$dsc,$dsc_checked,$skew_warning_vsn);
646 sub act_local () { return $dryrun_level <= 1; }
647 sub act_scary () { return !$dryrun_level; }
650 if (!$dryrun_level) {
651 progress f_ "%s ok: %s", $us, "@_";
653 progress f_ "would be ok: %s (but dry run only)", "@_";
658 printcmd(\*STDERR,$debugprefix."#",@_);
661 sub runcmd_ordryrun {
669 sub runcmd_ordryrun_local {
677 our $helpmsg = i_ <<END;
679 dgit [dgit-opts] clone [dgit-opts] package [suite] [./dir|/dir]
680 dgit [dgit-opts] fetch|pull [dgit-opts] [suite]
681 dgit [dgit-opts] build [dpkg-buildpackage-opts]
682 dgit [dgit-opts] sbuild [sbuild-opts]
683 dgit [dgit-opts] pbuilder|cowbuilder [debbuildopts]
684 dgit [dgit-opts] push [dgit-opts] [suite]
685 dgit [dgit-opts] push-source [dgit-opts] [suite]
686 dgit [dgit-opts] rpush build-host:build-dir ...
687 important dgit options:
688 -k<keyid> sign tag and package with <keyid> instead of default
689 --dry-run -n do not change anything, but go through the motions
690 --damp-run -L like --dry-run but make local changes, without signing
691 --new -N allow introducing a new package
692 --debug -D increase debug level
693 -c<name>=<value> set git config option (used directly by dgit too)
696 our $later_warning_msg = i_ <<END;
697 Perhaps the upload is stuck in incoming. Using the version from git.
701 print STDERR f_ "%s: %s\n%s", $us, "@_", __ $helpmsg or confess "$!";
706 @ARGV or badusage __ "too few arguments";
707 return scalar shift @ARGV;
711 not_necessarily_a_tree();
714 print __ $helpmsg or confess "$!";
718 our $td = $ENV{DGIT_TEST_DUMMY_DIR} || "DGIT_TEST_DUMMY_DIR-unset";
720 our %defcfg = ('dgit.default.distro' => 'debian',
721 'dgit.default.default-suite' => 'unstable',
722 'dgit.default.old-dsc-distro' => 'debian',
723 'dgit-suite.*-security.distro' => 'debian-security',
724 'dgit.default.username' => '',
725 'dgit.default.archive-query-default-component' => 'main',
726 'dgit.default.ssh' => 'ssh',
727 'dgit.default.archive-query' => 'madison:',
728 'dgit.default.sshpsql-dbname' => 'service=projectb',
729 'dgit.default.aptget-components' => 'main',
730 'dgit.default.source-only-uploads' => 'ok',
731 'dgit.dsc-url-proto-ok.http' => 'true',
732 'dgit.dsc-url-proto-ok.https' => 'true',
733 'dgit.dsc-url-proto-ok.git' => 'true',
734 'dgit.vcs-git.suites', => 'sid', # ;-separated
735 'dgit.default.dsc-url-proto-ok' => 'false',
736 # old means "repo server accepts pushes with old dgit tags"
737 # new means "repo server accepts pushes with new dgit tags"
738 # maint means "repo server accepts split brain pushes"
739 # hist means "repo server may have old pushes without new tag"
740 # ("hist" is implied by "old")
741 'dgit-distro.debian.archive-query' => 'ftpmasterapi:',
742 'dgit-distro.debian.git-check' => 'url',
743 'dgit-distro.debian.git-check-suffix' => '/info/refs',
744 'dgit-distro.debian.new-private-pushers' => 't',
745 'dgit-distro.debian.source-only-uploads' => 'not-wholly-new',
746 'dgit-distro.debian/push.git-url' => '',
747 'dgit-distro.debian/push.git-host' => 'push.dgit.debian.org',
748 'dgit-distro.debian/push.git-user-force' => 'dgit',
749 'dgit-distro.debian/push.git-proto' => 'git+ssh://',
750 'dgit-distro.debian/push.git-path' => '/dgit/debian/repos',
751 'dgit-distro.debian/push.git-create' => 'true',
752 'dgit-distro.debian/push.git-check' => 'ssh-cmd',
753 'dgit-distro.debian.archive-query-url', 'https://api.ftp-master.debian.org/',
754 # 'dgit-distro.debian.archive-query-tls-key',
755 # '/etc/ssl/certs/%HOST%.pem:/etc/dgit/%HOST%.pem',
756 # ^ this does not work because curl is broken nowadays
757 # Fixing #790093 properly will involve providing providing the key
758 # in some pacagke and maybe updating these paths.
760 # 'dgit-distro.debian.archive-query-tls-curl-args',
761 # '--ca-path=/etc/ssl/ca-debian',
762 # ^ this is a workaround but works (only) on DSA-administered machines
763 'dgit-distro.debian.git-url' => 'https://git.dgit.debian.org',
764 'dgit-distro.debian.git-url-suffix' => '',
765 'dgit-distro.debian.upload-host' => 'ftp-master', # for dput
766 'dgit-distro.debian.mirror' => 'http://ftp.debian.org/debian/',
767 'dgit-distro.debian-security.archive-query' => 'aptget:',
768 'dgit-distro.debian-security.mirror' => 'http://security.debian.org/debian-security/',
769 'dgit-distro.debian-security.aptget-suite-map' => 's#-security$#/updates#',
770 'dgit-distro.debian-security.aptget-suite-rmap' => 's#$#-security#',
771 'dgit-distro.debian-security.nominal-distro' => 'debian',
772 'dgit-distro.debian.backports-quirk' => '(squeeze)-backports*',
773 'dgit-distro.debian-backports.mirror' => 'http://backports.debian.org/debian-backports/',
774 'dgit-distro.ubuntu.git-check' => 'false',
775 'dgit-distro.ubuntu.mirror' => 'http://archive.ubuntu.com/ubuntu',
776 'dgit-distro.ubuntucloud.git-check' => 'false',
777 'dgit-distro.ubuntucloud.nominal-distro' => 'ubuntu',
778 'dgit-distro.ubuntucloud.archive-query' => 'aptget:',
779 'dgit-distro.ubuntucloud.mirror' => 'http://ubuntu-cloud.archive.canonical.com/ubuntu',
780 'dgit-distro.ubuntucloud.aptget-suite-map' => 's#^([^-]+):([^:]+)$#${1}-updates/$2#; s#^(.+)-(.+):(.+)#$1-$2/$3#;',
781 'dgit-distro.ubuntucloud.aptget-suite-rmap' => 's#/(.+)$#-$1#',
782 'dgit-distro.test-dummy.ssh' => "$td/ssh",
783 'dgit-distro.test-dummy.username' => "alice",
784 'dgit-distro.test-dummy.git-check' => "ssh-cmd",
785 'dgit-distro.test-dummy.git-create' => "ssh-cmd",
786 'dgit-distro.test-dummy.git-url' => "$td/git",
787 'dgit-distro.test-dummy.git-host' => "git",
788 'dgit-distro.test-dummy.git-path' => "$td/git",
789 'dgit-distro.test-dummy.archive-query' => "dummycatapi:",
790 'dgit-distro.test-dummy.archive-query-url' => "file://$td/aq/",
791 'dgit-distro.test-dummy.mirror' => "file://$td/mirror/",
792 'dgit-distro.test-dummy.upload-host' => 'test-dummy',
796 our @gitcfgsources = qw(cmdline local global system);
797 our $invoked_in_git_tree = 1;
799 sub git_slurp_config () {
800 # This algoritm is a bit subtle, but this is needed so that for
801 # options which we want to be single-valued, we allow the
802 # different config sources to override properly. See #835858.
803 foreach my $src (@gitcfgsources) {
804 next if $src eq 'cmdline';
805 # we do this ourselves since git doesn't handle it
807 $gitcfgs{$src} = git_slurp_config_src $src;
811 sub git_get_config ($) {
813 foreach my $src (@gitcfgsources) {
814 my $l = $gitcfgs{$src}{$c};
815 confess "internal error ($l $c)" if $l && !ref $l;
816 printdebug"C $c ".(defined $l ?
817 join " ", map { messagequote "'$_'" } @$l :
822 f_ "multiple values for %s (in %s git config)", $c, $src
824 $l->[0] =~ m/\n/ and badcfg f_
825 "value for config option %s (in %s git config) contains newline(s)!",
834 return undef if $c =~ /RETURN-UNDEF/;
835 printdebug "C? $c\n" if $debuglevel >= 5;
836 my $v = git_get_config($c);
837 return $v if defined $v;
838 my $dv = $defcfg{$c};
840 printdebug "CD $c $dv\n" if $debuglevel >= 4;
845 "need value for one of: %s\n".
846 "%s: distro or suite appears not to be (properly) supported",
850 sub not_necessarily_a_tree () {
851 # needs to be called from pre_*
852 @gitcfgsources = grep { $_ ne 'local' } @gitcfgsources;
853 $invoked_in_git_tree = 0;
856 sub access_basedistro__noalias () {
857 if (defined $idistro) {
860 my $def = cfg("dgit-suite.$isuite.distro", 'RETURN-UNDEF');
861 return $def if defined $def;
862 foreach my $src (@gitcfgsources, 'internal') {
863 my $kl = $src eq 'internal' ? \%defcfg : $gitcfgs{$src};
865 foreach my $k (keys %$kl) {
866 next unless $k =~ m#^dgit-suite\.(.*)\.distro$#;
868 next unless match_glob $dpat, $isuite;
872 foreach my $csvf (</usr/share/distro-info/*.csv>) {
874 $csvf =~ m{/(\w+)\.csv$} ? $1 : do {
875 printdebug "skipping $csvf\n";
878 my $csv = Text::CSV->new({ binary => 1, auto_diag => 2 }) or die;
879 my $fh = new IO::File $csvf, "<:encoding(utf8)"
880 or die "open $csvf: $!";
881 while (my $cols = $csv->getline($fh)) {
882 next unless $cols->[2] eq $isuite;
885 die "$csvf $!" if $fh->error;
888 return cfg("dgit.default.distro");
892 sub access_basedistro () {
893 my $noalias = access_basedistro__noalias();
894 my $canon = cfg("dgit-distro.$noalias.alias-canon",'RETURN-UNDEF');
895 return $canon // $noalias;
898 sub access_nomdistro () {
899 my $base = access_basedistro();
900 my $r = cfg("dgit-distro.$base.nominal-distro",'RETURN-UNDEF') // $base;
901 $r =~ m/^$distro_re$/ or badcfg
902 f_ "bad syntax for (nominal) distro \`%s' (does not match %s)",
903 $r, "/^$distro_re$/";
907 sub access_quirk () {
908 # returns (quirk name, distro to use instead or undef, quirk-specific info)
909 my $basedistro = access_basedistro();
910 my $backports_quirk = cfg("dgit-distro.$basedistro.backports-quirk",
912 if (defined $backports_quirk) {
913 my $re = $backports_quirk;
914 $re =~ s/[^-0-9a-z_\%*()]/\\$&/ig;
916 $re =~ s/\%/([-0-9a-z_]+)/
917 or $re =~ m/[()]/ or badcfg __ "backports-quirk needs \% or ( )";
918 if ($isuite =~ m/^$re$/) {
919 return ('backports',"$basedistro-backports",$1);
922 return ('none',undef);
927 sub parse_cfg_bool ($$$) {
928 my ($what,$def,$v) = @_;
931 $v =~ m/^[ty1]/ ? 1 :
932 $v =~ m/^[fn0]/ ? 0 :
933 badcfg f_ "%s needs t (true, y, 1) or f (false, n, 0) not \`%s'",
937 sub access_forpush_config () {
938 my $d = access_basedistro();
942 parse_cfg_bool('new-private-pushers', 0,
943 cfg("dgit-distro.$d.new-private-pushers",
946 my $v = cfg("dgit-distro.$d.readonly", 'RETURN-UNDEF');
949 $v =~ m/^[ty1]/ ? 0 : # force readonly, forpush = 0
950 $v =~ m/^[fn0]/ ? 1 : # force nonreadonly, forpush = 1
951 $v =~ m/^[a]/ ? '' : # auto, forpush = ''
953 "readonly needs t (true, y, 1) or f (false, n, 0) or a (auto)";
956 sub access_forpush () {
957 $access_forpush //= access_forpush_config();
958 return $access_forpush;
961 sub default_from_access_cfg ($$$;$) {
962 my ($var, $keybase, $defval, $permit_re) = @_;
963 return if defined $$var;
965 $$var = access_cfg("$keybase-newer", 'RETURN-UNDEF');
966 $$var = undef if $$var && $$var !~ m/^$permit_re$/;
968 $$var //= access_cfg($keybase, 'RETURN-UNDEF');
971 badcfg f_ "unknown %s \`%s'", $keybase, $$var
972 if defined $permit_re and $$var !~ m/$permit_re/;
976 confess +(__ 'internal error').' '.Dumper($access_forpush)," ?" if
977 defined $access_forpush and !$access_forpush;
978 badcfg __ "pushing but distro is configured readonly"
979 if access_forpush_config() eq '0';
981 $supplementary_message = __ <<'END' unless $we_are_responder;
982 Push failed, before we got started.
983 You can retry the push, after fixing the problem, if you like.
985 parseopts_late_defaults();
989 parseopts_late_defaults();
992 sub determine_whether_split_brain ($) {
995 local $access_forpush;
996 default_from_access_cfg(\$splitview_mode, 'split-view', 'auto',
997 $splitview_modes_re);
998 $do_split_brain = 1 if $splitview_mode eq 'always';
1001 printdebug "format $format, quilt mode $quilt_mode\n";
1003 if (format_quiltmode_splitting $format) {
1004 $splitview_mode ne 'never' or
1005 fail f_ "dgit: quilt mode \`%s' (for format \`%s')".
1006 " implies split view, but split-view set to \`%s'",
1007 $quilt_mode, $format, $splitview_mode;
1008 $do_split_brain = 1;
1010 $do_split_brain //= 0;
1013 sub supplementary_message ($) {
1015 if (!$we_are_responder) {
1016 $supplementary_message = $msg;
1019 responder_send_command "supplementary-message ".length($msg)
1021 print PO $msg or confess "$!";
1025 sub access_distros () {
1026 # Returns list of distros to try, in order
1029 # 0. `instead of' distro name(s) we have been pointed to
1030 # 1. the access_quirk distro, if any
1031 # 2a. the user's specified distro, or failing that } basedistro
1032 # 2b. the distro calculated from the suite }
1033 my @l = access_basedistro();
1035 my (undef,$quirkdistro) = access_quirk();
1036 unshift @l, $quirkdistro;
1037 unshift @l, $instead_distro;
1038 @l = grep { defined } @l;
1040 push @l, access_nomdistro();
1042 if (access_forpush()) {
1043 @l = map { ("$_/push", $_) } @l;
1048 sub access_cfg_cfgs (@) {
1051 # The nesting of these loops determines the search order. We put
1052 # the key loop on the outside so that we search all the distros
1053 # for each key, before going on to the next key. That means that
1054 # if access_cfg is called with a more specific, and then a less
1055 # specific, key, an earlier distro can override the less specific
1056 # without necessarily overriding any more specific keys. (If the
1057 # distro wants to override the more specific keys it can simply do
1058 # so; whereas if we did the loop the other way around, it would be
1059 # impossible to for an earlier distro to override a less specific
1060 # key but not the more specific ones without restating the unknown
1061 # values of the more specific keys.
1064 # We have to deal with RETURN-UNDEF specially, so that we don't
1065 # terminate the search prematurely.
1067 if (m/RETURN-UNDEF/) { push @rundef, $_; last; }
1070 foreach my $d (access_distros()) {
1071 push @cfgs, map { "dgit-distro.$d.$_" } @realkeys;
1073 push @cfgs, map { "dgit.default.$_" } @realkeys;
1074 push @cfgs, @rundef;
1078 sub access_cfg (@) {
1080 my (@cfgs) = access_cfg_cfgs(@keys);
1081 my $value = cfg(@cfgs);
1085 sub access_cfg_bool ($$) {
1086 my ($def, @keys) = @_;
1087 parse_cfg_bool($keys[0], $def, access_cfg(@keys, 'RETURN-UNDEF'));
1090 sub string_to_ssh ($) {
1092 if ($spec =~ m/\s/) {
1093 return qw(sh -ec), 'exec '.$spec.' "$@"', 'x';
1099 sub access_cfg_ssh () {
1100 my $gitssh = access_cfg('ssh', 'RETURN-UNDEF');
1101 if (!defined $gitssh) {
1104 return string_to_ssh $gitssh;
1108 sub access_runeinfo ($) {
1110 return ": dgit ".access_basedistro()." $info ;";
1113 sub access_someuserhost ($) {
1115 my $user = access_cfg("$some-user-force", 'RETURN-UNDEF');
1116 defined($user) && length($user) or
1117 $user = access_cfg("$some-user",'username');
1118 my $host = access_cfg("$some-host");
1119 return length($user) ? "$user\@$host" : $host;
1122 sub access_gituserhost () {
1123 return access_someuserhost('git');
1126 sub access_giturl (;$) {
1127 my ($optional) = @_;
1128 my $url = access_cfg('git-url','RETURN-UNDEF');
1131 my $proto = access_cfg('git-proto', 'RETURN-UNDEF');
1132 return undef unless defined $proto;
1135 access_gituserhost().
1136 access_cfg('git-path');
1138 $suffix = access_cfg('git-url-suffix','RETURN-UNDEF');
1141 return "$url/$package$suffix";
1144 sub commit_getclogp ($) {
1145 # Returns the parsed changelog hashref for a particular commit
1147 our %commit_getclogp_memo;
1148 my $memo = $commit_getclogp_memo{$objid};
1149 return $memo if $memo;
1151 my $mclog = dgit_privdir()."clog";
1152 runcmd shell_cmd "exec >$mclog", @git, qw(cat-file blob),
1153 "$objid:debian/changelog";
1154 $commit_getclogp_memo{$objid} = parsechangelog("-l$mclog");
1157 sub parse_dscdata () {
1158 my $dscfh = new IO::File \$dscdata, '<' or confess "$!";
1159 printdebug Dumper($dscdata) if $debuglevel>1;
1160 $dsc = parsecontrolfh($dscfh,$dscurl,1);
1161 printdebug Dumper($dsc) if $debuglevel>1;
1166 sub archive_query ($;@) {
1167 my ($method) = shift @_;
1168 fail __ "this operation does not support multiple comma-separated suites"
1170 my $query = access_cfg('archive-query','RETURN-UNDEF');
1171 $query =~ s/^(\w+):// or badcfg "invalid archive-query method \`$query'";
1174 { no strict qw(refs); &{"${method}_${proto}"}($proto,$data,@_); }
1177 sub archive_query_prepend_mirror {
1178 my $m = access_cfg('mirror');
1179 return map { [ $_->[0], $m.$_->[1], @$_[2..$#$_] ] } @_;
1182 sub pool_dsc_subpath ($$) {
1183 my ($vsn,$component) = @_; # $package is implict arg
1184 my $prefix = substr($package, 0, $package =~ m/^l/ ? 4 : 1);
1185 return "/pool/$component/$prefix/$package/".dscfn($vsn);
1188 sub cfg_apply_map ($$$) {
1189 my ($varref, $what, $mapspec) = @_;
1190 return unless $mapspec;
1192 printdebug "config $what EVAL{ $mapspec; }\n";
1194 eval "package Dgit::Config; $mapspec;";
1199 sub url_fetch ($;@) {
1200 my ($url, %xopts) = @_;
1201 # Ok404 => 1 means give undef for 404
1202 # AccessBase => 'archive-query' (eg)
1203 # CurlOpts => { key => value }
1205 my $curl = WWW::Curl::Easy->new;
1208 my $x = $curl->setopt($k, $v);
1209 confess "$k $v ".$curl->strerror($x)." ?" if $x;
1212 my $response_body = '';
1213 $setopt->(CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS|CURLPROTO_HTTP);
1214 $setopt->(CURLOPT_URL, $url);
1215 $setopt->(CURLOPT_NOSIGNAL, 1);
1216 $setopt->(CURLOPT_WRITEDATA, \$response_body);
1218 my $xcurlopts = $xopts{CurlOpts} // { };
1220 while (my ($k,$v) = each %$xcurlopts) { $setopt->($k,$v); }
1222 if ($xopts{AccessBase} && $url =~ m#^https://([-.0-9a-z]+)/#) {
1223 foreach my $k ("$xopts{AccessBase}-tls-key",
1224 "$xopts{AccessBase}-tls-curl-ca-args") {
1225 fail "config option $k is obsolete and no longer supported"
1226 if defined access_cfg($k, 'RETURN-UNDEF');
1230 printdebug "query: fetching $url...\n";
1232 local $SIG{PIPE} = 'IGNORE';
1234 my $x = $curl->perform();
1235 fail f_ "fetch of %s failed (%s): %s",
1236 $url, $curl->strerror($x), $curl->errbuf
1239 my $code = $curl->getinfo(CURLINFO_HTTP_CODE);
1240 if ($code eq '404' && $xopts{Ok404}) { return undef; }
1242 fail f_ "fetch of %s gave HTTP code %s", $url, $code
1243 unless $url =~ m#^file://# or $code =~ m/^2/;
1245 confess unless defined $response_body;
1246 return $response_body;
1249 #---------- `ftpmasterapi' archive query method (nascent) ----------
1251 sub api_query_raw ($;$) {
1252 my ($subpath, $ok404) = @_;
1253 my $url = access_cfg('archive-query-url');
1255 return url_fetch $url,
1257 AccessBase => 'archive-query';
1260 sub api_query ($$;$) {
1261 my ($data, $subpath, $ok404) = @_;
1263 badcfg __ "ftpmasterapi archive query method takes no data part"
1265 my $json = api_query_raw $subpath, $ok404;
1266 return undef unless defined $json;
1267 return decode_json($json);
1270 sub canonicalise_suite_ftpmasterapi {
1271 my ($proto,$data) = @_;
1272 my $suites = api_query($data, 'suites');
1274 foreach my $entry (@$suites) {
1276 my $v = $entry->{$_};
1277 defined $v && $v eq $isuite;
1278 } qw(codename name);
1279 push @matched, $entry;
1281 fail f_ "unknown suite %s, maybe -d would help", $isuite
1285 @matched==1 or die f_ "multiple matches for suite %s\n", $isuite;
1286 $cn = "$matched[0]{codename}";
1287 defined $cn or die f_ "suite %s info has no codename\n", $isuite;
1288 $cn =~ m/^$suite_re$/
1289 or die f_ "suite %s maps to bad codename\n", $isuite;
1291 die +(__ "bad ftpmaster api response: ")."$@\n".Dumper(\@matched)
1296 sub archive_query_ftpmasterapi {
1297 my ($proto,$data) = @_;
1298 my $info = api_query($data, "dsc_in_suite/$isuite/$package");
1300 my $digester = Digest::SHA->new(256);
1301 foreach my $entry (@$info) {
1303 my $vsn = "$entry->{version}";
1304 my ($ok,$msg) = version_check $vsn;
1305 die f_ "bad version: %s\n", $msg unless $ok;
1306 my $component = "$entry->{component}";
1307 $component =~ m/^$component_re$/ or die __ "bad component";
1308 my $filename = "$entry->{filename}";
1309 $filename && $filename !~ m#[^-+:._~0-9a-zA-Z/]|^[/.]|/[/.]#
1310 or die __ "bad filename";
1311 my $sha256sum = "$entry->{sha256sum}";
1312 $sha256sum =~ m/^[0-9a-f]+$/ or die __ "bad sha256sum";
1313 push @rows, [ $vsn, "/pool/$component/$filename",
1314 $digester, $sha256sum ];
1316 die +(__ "bad ftpmaster api response: ")."$@\n".Dumper($entry)
1319 @rows = sort { -version_compare($a->[0],$b->[0]) } @rows;
1320 return archive_query_prepend_mirror @rows;
1323 sub file_in_archive_ftpmasterapi {
1324 my ($proto,$data,$filename) = @_;
1325 my $pat = $filename;
1328 $pat =~ s#[^-+_.0-9a-z/]# sprintf '%%%02x', ord $& #ge;
1329 my $info = api_query($data, "file_in_archive/$pat", 1);
1332 sub package_not_wholly_new_ftpmasterapi {
1333 my ($proto,$data,$pkg) = @_;
1334 my $info = api_query($data,"madison?package=${pkg}&f=json");
1338 #---------- `aptget' archive query method ----------
1341 our $aptget_releasefile;
1342 our $aptget_configpath;
1344 sub aptget_aptget () { return @aptget, qw(-c), $aptget_configpath; }
1345 sub aptget_aptcache () { return @aptcache, qw(-c), $aptget_configpath; }
1347 sub aptget_cache_clean {
1348 runcmd_ordryrun_local qw(sh -ec),
1349 'cd "$1"; find -atime +30 -type f -print0 | xargs -0r rm --',
1353 sub aptget_lock_acquire () {
1354 my $lockfile = "$aptget_base/lock";
1355 open APTGET_LOCK, '>', $lockfile or confess "open $lockfile: $!";
1356 flock APTGET_LOCK, LOCK_EX or confess "lock $lockfile: $!";
1359 sub aptget_prep ($) {
1361 return if defined $aptget_base;
1363 badcfg __ "aptget archive query method takes no data part"
1366 my $cache = $ENV{XDG_CACHE_DIR} // "$ENV{HOME}/.cache";
1369 ensuredir "$cache/dgit";
1371 access_cfg('aptget-cachekey','RETURN-UNDEF')
1372 // access_nomdistro();
1374 $aptget_base = "$cache/dgit/aptget";
1375 ensuredir $aptget_base;
1377 my $quoted_base = $aptget_base;
1378 confess "$quoted_base contains bad chars, cannot continue"
1379 if $quoted_base =~ m/["\\]/; # apt.conf(5) says no escaping :-/
1381 ensuredir $aptget_base;
1383 aptget_lock_acquire();
1385 aptget_cache_clean();
1387 $aptget_configpath = "$aptget_base/apt.conf#$cachekey";
1388 my $sourceslist = "source.list#$cachekey";
1390 my $aptsuites = $isuite;
1391 cfg_apply_map(\$aptsuites, 'suite map',
1392 access_cfg('aptget-suite-map', 'RETURN-UNDEF'));
1394 open SRCS, ">", "$aptget_base/$sourceslist" or confess "$!";
1395 printf SRCS "deb-src %s %s %s\n",
1396 access_cfg('mirror'),
1398 access_cfg('aptget-components')
1401 ensuredir "$aptget_base/cache";
1402 ensuredir "$aptget_base/lists";
1404 open CONF, ">", $aptget_configpath or confess "$!";
1406 Debug::NoLocking "true";
1407 APT::Get::List-Cleanup "false";
1408 #clear APT::Update::Post-Invoke-Success;
1409 Dir::Etc::SourceList "$quoted_base/$sourceslist";
1410 Dir::State::Lists "$quoted_base/lists";
1411 Dir::Etc::preferences "$quoted_base/preferences";
1412 Dir::Cache::srcpkgcache "$quoted_base/cache/srcs#$cachekey";
1413 Dir::Cache::pkgcache "$quoted_base/cache/pkgs#$cachekey";
1416 foreach my $key (qw(
1419 Dir::Cache::Archives
1420 Dir::Etc::SourceParts
1421 Dir::Etc::preferencesparts
1423 ensuredir "$aptget_base/$key";
1424 print CONF "$key \"$quoted_base/$key\";\n" or confess "$!";
1427 my $oldatime = (time // confess "$!") - 1;
1428 foreach my $oldlist (<$aptget_base/lists/*Release>) {
1429 next unless stat_exists $oldlist;
1430 my ($mtime) = (stat _)[9];
1431 utime $oldatime, $mtime, $oldlist or die "$oldlist $!";
1434 runcmd_ordryrun_local aptget_aptget(), qw(update);
1437 foreach my $oldlist (<$aptget_base/lists/*Release>) {
1438 next unless stat_exists $oldlist;
1439 my ($atime) = (stat _)[8];
1440 next if $atime == $oldatime;
1441 push @releasefiles, $oldlist;
1443 my @inreleasefiles = grep { m#/InRelease$# } @releasefiles;
1444 @releasefiles = @inreleasefiles if @inreleasefiles;
1445 if (!@releasefiles) {
1446 fail f_ <<END, $isuite, $cache;
1447 apt seemed to not to update dgit's cached Release files for %s.
1449 is on a filesystem mounted `noatime'; if so, please use `relatime'.)
1452 confess "apt updated too many Release files (@releasefiles), erk"
1453 unless @releasefiles == 1;
1455 ($aptget_releasefile) = @releasefiles;
1458 sub canonicalise_suite_aptget {
1459 my ($proto,$data) = @_;
1462 my $release = parsecontrol $aptget_releasefile, "Release file", 1;
1464 foreach my $name (qw(Codename Suite)) {
1465 my $val = $release->{$name};
1467 printdebug "release file $name: $val\n";
1468 cfg_apply_map(\$val, 'suite rmap',
1469 access_cfg('aptget-suite-rmap', 'RETURN-UNDEF'));
1470 $val =~ m/^$suite_re$/o or fail f_
1471 "Release file (%s) specifies intolerable %s",
1472 $aptget_releasefile, $name;
1479 sub archive_query_aptget {
1480 my ($proto,$data) = @_;
1483 ensuredir "$aptget_base/source";
1484 foreach my $old (<$aptget_base/source/*.dsc>) {
1485 unlink $old or die "$old: $!";
1488 my $showsrc = cmdoutput aptget_aptcache(), qw(showsrc), $package;
1489 return () unless $showsrc =~ m/^package:\s*\Q$package\E\s*$/mi;
1490 # avoids apt-get source failing with ambiguous error code
1492 runcmd_ordryrun_local
1493 shell_cmd 'cd "$1"/source; shift', $aptget_base,
1494 aptget_aptget(), qw(--download-only --only-source source), $package;
1496 my @dscs = <$aptget_base/source/*.dsc>;
1497 fail __ "apt-get source did not produce a .dsc" unless @dscs;
1498 fail f_ "apt-get source produced several .dscs (%s)", "@dscs"
1501 my $pre_dsc = parsecontrol $dscs[0], $dscs[0], 1;
1504 my $uri = "file://". uri_escape $dscs[0];
1505 $uri =~ s{\%2f}{/}gi;
1506 return [ (getfield $pre_dsc, 'Version'), $uri ];
1509 sub file_in_archive_aptget () { return undef; }
1510 sub package_not_wholly_new_aptget () { return undef; }
1512 #---------- `dummyapicat' archive query method ----------
1513 # (untranslated, because this is for testing purposes etc.)
1515 sub archive_query_dummycatapi { archive_query_ftpmasterapi @_; }
1516 sub canonicalise_suite_dummycatapi { canonicalise_suite_ftpmasterapi @_; }
1518 sub dummycatapi_run_in_mirror ($@) {
1519 # runs $fn with FIA open onto rune
1520 my ($rune, $argl, $fn) = @_;
1522 my $mirror = access_cfg('mirror');
1523 $mirror =~ s#^file://#/# or die "$mirror ?";
1524 my @cmd = (qw(sh -ec), 'cd "$1"; shift'."\n".$rune,
1525 qw(x), $mirror, @$argl);
1526 debugcmd "-|", @cmd;
1527 open FIA, "-|", @cmd or confess "$!";
1529 close FIA or ($!==0 && $?==141) or die failedcmd @cmd;
1533 sub file_in_archive_dummycatapi ($$$) {
1534 my ($proto,$data,$filename) = @_;
1536 dummycatapi_run_in_mirror '
1537 find -name "$1" -print0 |
1539 ', [$filename], sub {
1542 printdebug "| $_\n";
1543 m/^(\w+) (\S+)$/ or die "$_ ?";
1544 push @out, { sha256sum => $1, filename => $2 };
1550 sub package_not_wholly_new_dummycatapi {
1551 my ($proto,$data,$pkg) = @_;
1552 dummycatapi_run_in_mirror "
1553 find -name ${pkg}_*.dsc
1560 #---------- `madison' archive query method ----------
1562 sub archive_query_madison {
1563 return archive_query_prepend_mirror
1564 map { [ @$_[0..1] ] } madison_get_parse(@_);
1567 sub madison_get_parse {
1568 my ($proto,$data) = @_;
1569 die unless $proto eq 'madison';
1570 if (!length $data) {
1571 $data= access_cfg('madison-distro','RETURN-UNDEF');
1572 $data //= access_basedistro();
1574 $rmad{$proto,$data,$package} ||= cmdoutput
1575 qw(rmadison -asource),"-s$isuite","-u$data",$package;
1576 my $rmad = $rmad{$proto,$data,$package};
1579 foreach my $l (split /\n/, $rmad) {
1580 $l =~ m{^ \s*( [^ \t|]+ )\s* \|
1581 \s*( [^ \t|]+ )\s* \|
1582 \s*( [^ \t|/]+ )(?:/([^ \t|/]+))? \s* \|
1583 \s*( [^ \t|]+ )\s* }x or die "$rmad ?";
1584 $1 eq $package or die "$rmad $package ?";
1591 $component = access_cfg('archive-query-default-component');
1593 $5 eq 'source' or die "$rmad ?";
1594 push @out, [$vsn,pool_dsc_subpath($vsn,$component),$newsuite];
1596 return sort { -version_compare($a->[0],$b->[0]); } @out;
1599 sub canonicalise_suite_madison {
1600 # madison canonicalises for us
1601 my @r = madison_get_parse(@_);
1603 "unable to canonicalise suite using package %s".
1604 " which does not appear to exist in suite %s;".
1605 " --existing-package may help",
1610 sub file_in_archive_madison { return undef; }
1611 sub package_not_wholly_new_madison { return undef; }
1613 #---------- `sshpsql' archive query method ----------
1614 # (untranslated, because this is obsolete)
1617 my ($data,$runeinfo,$sql) = @_;
1618 if (!length $data) {
1619 $data= access_someuserhost('sshpsql').':'.
1620 access_cfg('sshpsql-dbname');
1622 $data =~ m/:/ or badcfg "invalid sshpsql method string \`$data'";
1623 my ($userhost,$dbname) = ($`,$'); #';
1625 my @cmd = (access_cfg_ssh, $userhost,
1626 access_runeinfo("ssh-psql $runeinfo").
1627 " export LC_MESSAGES=C; export LC_CTYPE=C;".
1628 " ".shellquote qw(psql -A), $dbname, qw(-c), $sql);
1630 open P, "-|", @cmd or confess "$!";
1633 printdebug(">|$_|\n");
1636 $!=0; $?=0; close P or failedcmd @cmd;
1638 my $nrows = pop @rows;
1639 $nrows =~ s/^\((\d+) rows?\)$/$1/ or die "$nrows ?";
1640 @rows == $nrows+1 or die "$nrows ".(scalar @rows)." ?";
1641 @rows = map { [ split /\|/, $_ ] } @rows;
1642 my $ncols = scalar @{ shift @rows };
1643 die if grep { scalar @$_ != $ncols } @rows;
1647 sub sql_injection_check {
1648 foreach (@_) { die "$_ $& ?" if m{[^-+=:_.,/0-9a-zA-Z]}; }
1651 sub archive_query_sshpsql ($$) {
1652 my ($proto,$data) = @_;
1653 sql_injection_check $isuite, $package;
1654 my @rows = sshpsql($data, "archive-query $isuite $package", <<END);
1655 SELECT source.version, component.name, files.filename, files.sha256sum
1657 JOIN src_associations ON source.id = src_associations.source
1658 JOIN suite ON suite.id = src_associations.suite
1659 JOIN dsc_files ON dsc_files.source = source.id
1660 JOIN files_archive_map ON files_archive_map.file_id = dsc_files.file
1661 JOIN component ON component.id = files_archive_map.component_id
1662 JOIN files ON files.id = dsc_files.file
1663 WHERE ( suite.suite_name='$isuite' OR suite.codename='$isuite' )
1664 AND source.source='$package'
1665 AND files.filename LIKE '%.dsc';
1667 @rows = sort { -version_compare($a->[0],$b->[0]) } @rows;
1668 my $digester = Digest::SHA->new(256);
1670 my ($vsn,$component,$filename,$sha256sum) = @$_;
1671 [ $vsn, "/pool/$component/$filename",$digester,$sha256sum ];
1673 return archive_query_prepend_mirror @rows;
1676 sub canonicalise_suite_sshpsql ($$) {
1677 my ($proto,$data) = @_;
1678 sql_injection_check $isuite;
1679 my @rows = sshpsql($data, "canonicalise-suite $isuite", <<END);
1680 SELECT suite.codename
1681 FROM suite where suite_name='$isuite' or codename='$isuite';
1683 @rows = map { $_->[0] } @rows;
1684 fail "unknown suite $isuite" unless @rows;
1685 die "ambiguous $isuite: @rows ?" if @rows>1;
1689 sub file_in_archive_sshpsql ($$$) { return undef; }
1690 sub package_not_wholly_new_sshpsql ($$$) { return undef; }
1692 #---------- `dummycat' archive query method ----------
1693 # (untranslated, because this is for testing purposes etc.)
1695 sub canonicalise_suite_dummycat ($$) {
1696 my ($proto,$data) = @_;
1697 my $dpath = "$data/suite.$isuite";
1698 if (!open C, "<", $dpath) {
1699 $!==ENOENT or die "$dpath: $!";
1700 printdebug "dummycat canonicalise_suite $isuite $dpath ENOENT\n";
1704 chomp or die "$dpath: $!";
1706 printdebug "dummycat canonicalise_suite $isuite $dpath = $_\n";
1710 sub archive_query_dummycat ($$) {
1711 my ($proto,$data) = @_;
1712 canonicalise_suite();
1713 my $dpath = "$data/package.$csuite.$package";
1714 if (!open C, "<", $dpath) {
1715 $!==ENOENT or die "$dpath: $!";
1716 printdebug "dummycat query $csuite $package $dpath ENOENT\n";
1724 printdebug "dummycat query $csuite $package $dpath | $_\n";
1725 my @row = split /\s+/, $_;
1726 @row==2 or die "$dpath: $_ ?";
1729 C->error and die "$dpath: $!";
1731 return archive_query_prepend_mirror
1732 sort { -version_compare($a->[0],$b->[0]); } @rows;
1735 sub file_in_archive_dummycat () { return undef; }
1736 sub package_not_wholly_new_dummycat () { return undef; }
1738 #---------- archive query entrypoints and rest of program ----------
1740 sub canonicalise_suite () {
1741 return if defined $csuite;
1742 fail f_ "cannot operate on %s suite", $isuite if $isuite eq 'UNRELEASED';
1743 $csuite = archive_query('canonicalise_suite');
1744 if ($isuite ne $csuite) {
1745 progress f_ "canonical suite name for %s is %s", $isuite, $csuite;
1747 progress f_ "canonical suite name is %s", $csuite;
1751 sub get_archive_dsc () {
1752 canonicalise_suite();
1753 my @vsns = archive_query('archive_query');
1754 foreach my $vinfo (@vsns) {
1755 my ($vsn,$vsn_dscurl,$digester,$digest) = @$vinfo;
1756 $dscurl = $vsn_dscurl;
1757 $dscdata = url_fetch($dscurl);
1759 $skew_warning_vsn = $vsn if !defined $skew_warning_vsn;
1764 $digester->add($dscdata);
1765 my $got = $digester->hexdigest();
1767 fail f_ "%s has hash %s but archive told us to expect %s",
1768 $dscurl, $got, $digest;
1771 my $fmt = getfield $dsc, 'Format';
1772 $format_ok{$fmt} or forceable_fail [qw(unsupported-source-format)],
1773 f_ "unsupported source format %s, sorry", $fmt;
1775 $dsc_checked = !!$digester;
1776 printdebug "get_archive_dsc: Version ".(getfield $dsc, 'Version')."\n";
1780 printdebug "get_archive_dsc: nothing in archive, returning undef\n";
1783 sub check_for_git ();
1784 sub check_for_git () {
1786 my $how = access_cfg('git-check');
1787 if ($how eq 'ssh-cmd') {
1789 (access_cfg_ssh, access_gituserhost(),
1790 access_runeinfo("git-check $package").
1791 " set -e; cd ".access_cfg('git-path').";".
1792 " if test -d $package.git; then echo 1; else echo 0; fi");
1793 my $r= cmdoutput @cmd;
1794 if (defined $r and $r =~ m/^divert (\w+)$/) {
1796 my ($usedistro,) = access_distros();
1797 # NB that if we are pushing, $usedistro will be $distro/push
1798 $instead_distro= cfg("dgit-distro.$usedistro.diverts.$divert");
1799 $instead_distro =~ s{^/}{ access_basedistro()."/" }e;
1800 progress f_ "diverting to %s (using config for %s)",
1801 $divert, $instead_distro;
1802 return check_for_git();
1804 failedcmd @cmd unless defined $r and $r =~ m/^[01]$/;
1806 } elsif ($how eq 'url') {
1807 my $prefix = access_cfg('git-check-url','git-url');
1808 my $suffix = access_cfg('git-check-suffix','git-suffix',
1809 'RETURN-UNDEF') // '.git';
1810 my $url = "$prefix/$package$suffix";
1811 my $result = url_fetch $url,
1812 CurlOpts => { CURLOPT_NOBODY() => 1 },
1814 AccessBase => 'git-check';
1815 $result = defined $result;
1816 printdebug "dgit-repos check_for_git => $result.\n";
1818 } elsif ($how eq 'true') {
1820 } elsif ($how eq 'false') {
1823 badcfg f_ "unknown git-check \`%s'", $how;
1827 sub create_remote_git_repo () {
1828 my $how = access_cfg('git-create');
1829 if ($how eq 'ssh-cmd') {
1831 (access_cfg_ssh, access_gituserhost(),
1832 access_runeinfo("git-create $package").
1833 "set -e; cd ".access_cfg('git-path').";".
1834 " cp -a _template $package.git");
1835 } elsif ($how eq 'true') {
1838 badcfg f_ "unknown git-create \`%s'", $how;
1842 our ($dsc_hash,$lastpush_mergeinput);
1843 our ($dsc_distro, $dsc_hint_tag, $dsc_hint_url);
1847 dgit_privdir(); # ensures that $dgit_privdir_made is based on $maindir
1848 $playground = fresh_playground 'dgit/unpack';
1851 sub mktree_in_ud_here () {
1855 sub git_write_tree () {
1856 my $tree = cmdoutput @git, qw(write-tree);
1857 $tree =~ m/^\w+$/ or die "$tree ?";
1861 sub git_add_write_tree () {
1862 runcmd @git, qw(add -Af .);
1863 return git_write_tree();
1866 sub remove_stray_gits ($) {
1868 my @gitscmd = qw(find -name .git -prune -print0);
1869 debugcmd "|",@gitscmd;
1870 open GITS, "-|", @gitscmd or confess "$!";
1875 print STDERR f_ "%s: warning: removing from %s: %s\n",
1876 $us, $what, (messagequote $_);
1880 $!=0; $?=0; close GITS or failedcmd @gitscmd;
1883 sub mktree_in_ud_from_only_subdir ($;$) {
1884 my ($what,$raw) = @_;
1885 # changes into the subdir
1888 confess "expected one subdir but found @dirs ?" unless @dirs==1;
1889 $dirs[0] =~ m#^([^/]+)/\.$# or die;
1893 remove_stray_gits($what);
1894 mktree_in_ud_here();
1896 my ($format, $fopts) = get_source_format();
1897 if (madformat($format)) {
1902 my $tree=git_add_write_tree();
1903 return ($tree,$dir);
1906 our @files_csum_info_fields =
1907 (['Checksums-Sha256','Digest::SHA', 'new(256)', 'sha256sum'],
1908 ['Checksums-Sha1', 'Digest::SHA', 'new(1)', 'sha1sum'],
1909 ['Files', 'Digest::MD5', 'new()', 'md5sum']);
1911 sub dsc_files_info () {
1912 foreach my $csumi (@files_csum_info_fields) {
1913 my ($fname, $module, $method) = @$csumi;
1914 my $field = $dsc->{$fname};
1915 next unless defined $field;
1916 eval "use $module; 1;" or die $@;
1918 foreach (split /\n/, $field) {
1920 m/^(\w+) (\d+) (\S+)$/ or
1921 fail f_ "could not parse .dsc %s line \`%s'", $fname, $_;
1922 my $digester = eval "$module"."->$method;" or die $@;
1927 Digester => $digester,
1932 fail f_ "missing any supported Checksums-* or Files field in %s",
1933 $dsc->get_option('name');
1937 map { $_->{Filename} } dsc_files_info();
1940 sub files_compare_inputs (@) {
1945 my $showinputs = sub {
1946 return join "; ", map { $_->get_option('name') } @$inputs;
1949 foreach my $in (@$inputs) {
1951 my $in_name = $in->get_option('name');
1953 printdebug "files_compare_inputs $in_name\n";
1955 foreach my $csumi (@files_csum_info_fields) {
1956 my ($fname) = @$csumi;
1957 printdebug "files_compare_inputs $in_name $fname\n";
1959 my $field = $in->{$fname};
1960 next unless defined $field;
1963 foreach (split /\n/, $field) {
1966 my ($info, $f) = m/^(\w+ \d+) (?:\S+ \S+ )?(\S+)$/ or
1967 fail "could not parse $in_name $fname line \`$_'";
1969 printdebug "files_compare_inputs $in_name $fname $f\n";
1973 my $re = \ $record{$f}{$fname};
1975 $fchecked{$f}{$in_name} = 1;
1978 "hash or size of %s varies in %s fields (between: %s)",
1979 $f, $fname, $showinputs->();
1984 @files = sort @files;
1985 $expected_files //= \@files;
1986 "@$expected_files" eq "@files" or
1987 fail f_ "file list in %s varies between hash fields!",
1991 fail f_ "%s has no files list field(s)", $in_name;
1993 printdebug "files_compare_inputs ".Dumper(\%fchecked, \%record)
1996 grep { keys %$_ == @$inputs-1 } values %fchecked
1997 or fail f_ "no file appears in all file lists (looked in: %s)",
2001 sub is_orig_file_in_dsc ($$) {
2002 my ($f, $dsc_files_info) = @_;
2003 return 0 if @$dsc_files_info <= 1;
2004 # One file means no origs, and the filename doesn't have a "what
2005 # part of dsc" component. (Consider versions ending `.orig'.)
2006 return 0 unless $f =~ m/\.$orig_f_tail_re$/o;
2010 # This function determines whether a .changes file is source-only from
2011 # the point of view of dak. Thus, it permits *_source.buildinfo
2014 # It does not, however, permit any other buildinfo files. After a
2015 # source-only upload, the buildds will try to upload files like
2016 # foo_1.2.3_amd64.buildinfo. If the package maintainer included files
2017 # named like this in their (otherwise) source-only upload, the uploads
2018 # of the buildd can be rejected by dak. Fixing the resultant
2019 # situation can require manual intervention. So we block such
2020 # .buildinfo files when the user tells us to perform a source-only
2021 # upload (such as when using the push-source subcommand with the -C
2022 # option, which calls this function).
2024 # Note, though, that when dgit is told to prepare a source-only
2025 # upload, such as when subcommands like build-source and push-source
2026 # without -C are used, dgit has a more restrictive notion of
2027 # source-only .changes than dak: such uploads will never include
2028 # *_source.buildinfo files. This is because there is no use for such
2029 # files when using a tool like dgit to produce the source package, as
2030 # dgit ensures the source is identical to git HEAD.
2031 sub test_source_only_changes ($) {
2033 foreach my $l (split /\n/, getfield $changes, 'Files') {
2034 $l =~ m/\S+$/ or next;
2035 # \.tar\.[a-z0-9]+ covers orig.tar and the tarballs in native packages
2036 unless ($& =~ m/(?:\.dsc|\.diff\.gz|$tarball_f_ext_re|_source\.buildinfo)$/) {
2037 print f_ "purportedly source-only changes polluted by %s\n", $&;
2044 sub changes_update_origs_from_dsc ($$$$) {
2045 my ($dsc, $changes, $upstreamvsn, $changesfile) = @_;
2047 printdebug "checking origs needed ($upstreamvsn)...\n";
2048 $_ = getfield $changes, 'Files';
2049 m/^\w+ \d+ (\S+ \S+) \S+$/m or
2050 fail __ "cannot find section/priority from .changes Files field";
2051 my $placementinfo = $1;
2053 printdebug "checking origs needed placement '$placementinfo'...\n";
2054 foreach my $l (split /\n/, getfield $dsc, 'Files') {
2055 $l =~ m/\S+$/ or next;
2057 printdebug "origs $file | $l\n";
2058 next unless is_orig_file_of_vsn $file, $upstreamvsn;
2059 printdebug "origs $file is_orig\n";
2060 my $have = archive_query('file_in_archive', $file);
2061 if (!defined $have) {
2062 print STDERR __ <<END;
2063 archive does not support .orig check; hope you used --ch:--sa/-sd if needed
2069 printdebug "origs $file \$#\$have=$#$have\n";
2070 foreach my $h (@$have) {
2073 foreach my $csumi (@files_csum_info_fields) {
2074 my ($fname, $module, $method, $archivefield) = @$csumi;
2075 next unless defined $h->{$archivefield};
2076 $_ = $dsc->{$fname};
2077 next unless defined;
2078 m/^(\w+) .* \Q$file\E$/m or
2079 fail f_ ".dsc %s missing entry for %s", $fname, $file;
2080 if ($h->{$archivefield} eq $1) {
2084 "%s: %s (archive) != %s (local .dsc)",
2085 $archivefield, $h->{$archivefield}, $1;
2088 confess "$file ".Dumper($h)." ?!" if $same && @differ;
2092 f_ "archive %s: %s", $h->{filename}, join "; ", @differ
2095 printdebug "origs $file f.same=$found_same".
2096 " #f._differ=$#found_differ\n";
2097 if (@found_differ && !$found_same) {
2099 (f_ "archive contains %s with different checksum", $file),
2102 # Now we edit the changes file to add or remove it
2103 foreach my $csumi (@files_csum_info_fields) {
2104 my ($fname, $module, $method, $archivefield) = @$csumi;
2105 next unless defined $changes->{$fname};
2107 # in archive, delete from .changes if it's there
2108 $changed{$file} = "removed" if
2109 $changes->{$fname} =~ s/\n.* \Q$file\E$(?:)$//m;
2110 } elsif ($changes->{$fname} =~ m/^.* \Q$file\E$(?:)$/m) {
2111 # not in archive, but it's here in the .changes
2113 my $dsc_data = getfield $dsc, $fname;
2114 $dsc_data =~ m/^(.* \Q$file\E$)$/m or die "$dsc_data $file ?";
2116 $extra =~ s/ \d+ /$&$placementinfo /
2117 or confess "$fname $extra >$dsc_data< ?"
2118 if $fname eq 'Files';
2119 $changes->{$fname} .= "\n". $extra;
2120 $changed{$file} = "added";
2125 foreach my $file (keys %changed) {
2127 "edited .changes for archive .orig contents: %s %s",
2128 $changed{$file}, $file;
2130 my $chtmp = "$changesfile.tmp";
2131 $changes->save($chtmp);
2133 rename $chtmp,$changesfile or die "$changesfile $!";
2135 progress f_ "[new .changes left in %s]", $changesfile;
2138 progress f_ "%s already has appropriate .orig(s) (if any)",
2143 sub clogp_authline ($) {
2145 my $author = getfield $clogp, 'Maintainer';
2146 if ($author =~ m/^[^"\@]+\,/) {
2147 # single entry Maintainer field with unquoted comma
2148 $author = ($& =~ y/,//rd).$'; # strip the comma
2150 # git wants a single author; any remaining commas in $author
2151 # are by now preceded by @ (or "). It seems safer to punt on
2152 # "..." for now rather than attempting to dequote or something.
2153 $author =~ s#,.*##ms unless $author =~ m/"/;
2154 my $date = cmdoutput qw(date), '+%s %z', qw(-d), getfield($clogp,'Date');
2155 my $authline = "$author $date";
2156 $authline =~ m/$git_authline_re/o or
2157 fail f_ "unexpected commit author line format \`%s'".
2158 " (was generated from changelog Maintainer field)",
2160 return ($1,$2,$3) if wantarray;
2164 sub vendor_patches_distro ($$) {
2165 my ($checkdistro, $what) = @_;
2166 return unless defined $checkdistro;
2168 my $series = "debian/patches/\L$checkdistro\E.series";
2169 printdebug "checking for vendor-specific $series ($what)\n";
2171 if (!open SERIES, "<", $series) {
2172 confess "$series $!" unless $!==ENOENT;
2179 print STDERR __ <<END;
2181 Unfortunately, this source package uses a feature of dpkg-source where
2182 the same source package unpacks to different source code on different
2183 distros. dgit cannot safely operate on such packages on affected
2184 distros, because the meaning of source packages is not stable.
2186 Please ask the distro/maintainer to remove the distro-specific series
2187 files and use a different technique (if necessary, uploading actually
2188 different packages, if different distros are supposed to have
2192 fail f_ "Found active distro-specific series file for".
2193 " %s (%s): %s, cannot continue",
2194 $checkdistro, $what, $series;
2196 die "$series $!" if SERIES->error;
2200 sub check_for_vendor_patches () {
2201 # This dpkg-source feature doesn't seem to be documented anywhere!
2202 # But it can be found in the changelog (reformatted):
2204 # commit 4fa01b70df1dc4458daee306cfa1f987b69da58c
2205 # Author: Raphael Hertzog <hertzog@debian.org>
2206 # Date: Sun Oct 3 09:36:48 2010 +0200
2208 # dpkg-source: correctly create .pc/.quilt_series with alternate
2211 # If you have debian/patches/ubuntu.series and you were
2212 # unpacking the source package on ubuntu, quilt was still
2213 # directed to debian/patches/series instead of
2214 # debian/patches/ubuntu.series.
2216 # debian/changelog | 3 +++
2217 # scripts/Dpkg/Source/Package/V3/quilt.pm | 4 +++-
2218 # 2 files changed, 6 insertions(+), 1 deletion(-)
2221 vendor_patches_distro($ENV{DEB_VENDOR}, "DEB_VENDOR");
2222 vendor_patches_distro(Dpkg::Vendor::get_current_vendor(),
2223 __ "Dpkg::Vendor \`current vendor'");
2224 vendor_patches_distro(access_basedistro(),
2225 __ "(base) distro being accessed");
2226 vendor_patches_distro(access_nomdistro(),
2227 __ "(nominal) distro being accessed");
2230 sub check_bpd_exists () {
2231 stat $buildproductsdir
2232 or fail f_ "build-products-dir %s is not accessible: %s\n",
2233 $buildproductsdir, $!;
2236 sub dotdot_bpd_transfer_origs ($$$) {
2237 my ($bpd_abs, $upstreamversion, $wanted) = @_;
2238 # checks is_orig_file_of_vsn and if
2239 # calls $wanted->{$leaf} and expects boolish
2241 return if $buildproductsdir eq '..';
2244 my $dotdot = $maindir;
2245 $dotdot =~ s{/[^/]+$}{};
2246 opendir DD, $dotdot or fail "opendir .. ($dotdot): $!";
2247 while ($!=0, defined(my $leaf = readdir DD)) {
2249 local ($debuglevel) = $debuglevel-1;
2250 printdebug "DD_BPD $leaf ?\n";
2252 next unless is_orig_file_of_vsn $leaf, $upstreamversion;
2253 next unless $wanted->($leaf);
2254 next if lstat "$bpd_abs/$leaf";
2257 "%s: found orig(s) in .. missing from build-products-dir, transferring:\n",
2260 $! == &ENOENT or fail f_
2261 "check orig file %s in bpd %s: %s", $leaf, $bpd_abs, $!;
2262 lstat "$dotdot/$leaf" or fail f_
2263 "check orig file %s in ..: %s", $leaf, $!;
2265 stat "$dotdot/$leaf" or fail f_
2266 "check target of orig symlink %s in ..: %s", $leaf, $!;
2267 my $ltarget = readlink "$dotdot/$leaf" or
2268 die "readlink $dotdot/$leaf: $!";
2269 if ($ltarget !~ m{^/}) {
2270 $ltarget = "$dotdot/$ltarget";
2272 symlink $ltarget, "$bpd_abs/$leaf"
2273 or die "$ltarget $bpd_abs $leaf: $!";
2275 "%s: cloned orig symlink from ..: %s\n",
2277 } elsif (link "$dotdot/$leaf", "$bpd_abs/$leaf") {
2279 "%s: hardlinked orig from ..: %s\n",
2281 } elsif ($! != EXDEV) {
2282 fail f_ "failed to make %s a hardlink to %s: %s",
2283 "$bpd_abs/$leaf", "$dotdot/$leaf", $!;
2285 symlink "$bpd_abs/$leaf", "$dotdot/$leaf"
2286 or die "$bpd_abs $dotdot $leaf $!";
2288 "%s: symmlinked orig from .. on other filesystem: %s\n",
2292 die "$dotdot; $!" if $!;
2296 sub import_tarball_tartrees ($$) {
2297 my ($upstreamv, $dfi) = @_;
2298 # cwd should be the playground
2300 # We unpack and record the orig tarballs first, so that we only
2301 # need disk space for one private copy of the unpacked source.
2302 # But we can't make them into commits until we have the metadata
2303 # from the debian/changelog, so we record the tree objects now and
2304 # make them into commits later.
2306 my $orig_f_base = srcfn $upstreamv, '';
2308 foreach my $fi (@$dfi) {
2309 # We actually import, and record as a commit, every tarball
2310 # (unless there is only one file, in which case there seems
2313 my $f = $fi->{Filename};
2314 printdebug "import considering $f ";
2315 (printdebug "not tar\n"), next unless $f =~ m/\.tar(\.\w+)?$/;
2316 (printdebug "signature\n"), next if $f =~ m/$orig_f_sig_re$/o;
2320 $f =~ m/^\Q$orig_f_base\E\.([^._]+)?\.tar(?:\.\w+)?$/;
2322 printdebug "Y ", (join ' ', map { $_//"(none)" }
2323 $compr_ext, $orig_f_part
2326 my $path = $fi->{Path} // $f;
2327 my $input = new IO::File $f, '<' or die "$f $!";
2331 if (defined $compr_ext) {
2333 Dpkg::Compression::compression_guess_from_filename $f;
2334 fail "Dpkg::Compression cannot handle file $f in source package"
2335 if defined $compr_ext && !defined $cname;
2337 new Dpkg::Compression::Process compression => $cname;
2338 @compr_cmd = $compr_proc->get_uncompress_cmdline();
2339 my $compr_fh = new IO::Handle;
2340 my $compr_pid = open $compr_fh, "-|" // confess "$!";
2342 open STDIN, "<&", $input or confess "$!";
2344 die "dgit (child): exec $compr_cmd[0]: $!\n";
2349 rmtree "_unpack-tar";
2350 mkdir "_unpack-tar" or confess "$!";
2351 my @tarcmd = qw(tar -x -f -
2352 --no-same-owner --no-same-permissions
2353 --no-acls --no-xattrs --no-selinux);
2354 my $tar_pid = fork // confess "$!";
2356 chdir "_unpack-tar" or confess "$!";
2357 open STDIN, "<&", $input or confess "$!";
2359 die f_ "dgit (child): exec %s: %s", $tarcmd[0], $!;
2361 $!=0; (waitpid $tar_pid, 0) == $tar_pid or confess "$!";
2362 !$? or failedcmd @tarcmd;
2365 (@compr_cmd ? ($?==SIGPIPE || failedcmd @compr_cmd)
2367 # finally, we have the results in "tarball", but maybe
2368 # with the wrong permissions
2370 runcmd qw(chmod -R +rwX _unpack-tar);
2371 changedir "_unpack-tar";
2372 remove_stray_gits($f);
2373 mktree_in_ud_here();
2375 my ($tree) = git_add_write_tree();
2376 my $tentries = cmdoutput @git, qw(ls-tree -z), $tree;
2377 if ($tentries =~ m/^\d+ tree (\w+)\t[^\000]+\000$/s) {
2379 printdebug "one subtree $1\n";
2381 printdebug "multiple subtrees\n";
2384 rmtree "_unpack-tar";
2386 my $ent = [ $f, $tree ];
2388 Orig => !!$orig_f_part,
2389 Sort => (!$orig_f_part ? 2 :
2390 $orig_f_part =~ m/-/g ? 1 :
2392 OrigPart => $orig_f_part, # 'orig', 'orig-XXX', or undef
2399 # put any without "_" first (spec is not clear whether files
2400 # are always in the usual order). Tarballs without "_" are
2401 # the main orig or the debian tarball.
2402 $a->{Sort} <=> $b->{Sort} or
2409 sub import_tarball_commits ($$) {
2410 my ($tartrees, $upstreamv) = @_;
2411 # cwd should be a playtree which has a relevant debian/changelog
2412 # fills in $tt->{Commit} for each one
2414 my $any_orig = grep { $_->{Orig} } @$tartrees;
2416 my @clogcmd = qw(dpkg-parsechangelog --format rfc822 --all);
2420 printdebug "import clog search...\n";
2421 parsechangelog_loop \@clogcmd, (__ "package changelog"), sub {
2422 my ($thisstanza, $desc) = @_;
2423 no warnings qw(exiting);
2425 $clogp //= $thisstanza;
2427 printdebug "import clog $thisstanza->{version} $desc...\n";
2429 last if !$any_orig; # we don't need $r1clogp
2431 # We look for the first (most recent) changelog entry whose
2432 # version number is lower than the upstream version of this
2433 # package. Then the last (least recent) previous changelog
2434 # entry is treated as the one which introduced this upstream
2435 # version and used for the synthetic commits for the upstream
2438 # One might think that a more sophisticated algorithm would be
2439 # necessary. But: we do not want to scan the whole changelog
2440 # file. Stopping when we see an earlier version, which
2441 # necessarily then is an earlier upstream version, is the only
2442 # realistic way to do that. Then, either the earliest
2443 # changelog entry we have seen so far is indeed the earliest
2444 # upload of this upstream version; or there are only changelog
2445 # entries relating to later upstream versions (which is not
2446 # possible unless the changelog and .dsc disagree about the
2447 # version). Then it remains to choose between the physically
2448 # last entry in the file, and the one with the lowest version
2449 # number. If these are not the same, we guess that the
2450 # versions were created in a non-monotonic order rather than
2451 # that the changelog entries have been misordered.
2453 printdebug "import clog $thisstanza->{version} vs $upstreamv...\n";
2455 last if version_compare($thisstanza->{version}, $upstreamv) < 0;
2456 $r1clogp = $thisstanza;
2458 printdebug "import clog $r1clogp->{version} becomes r1\n";
2461 $clogp or fail __ "package changelog has no entries!";
2463 my $authline = clogp_authline $clogp;
2464 my $changes = getfield $clogp, 'Changes';
2465 $changes =~ s/^\n//; # Changes: \n
2466 my $cversion = getfield $clogp, 'Version';
2470 $r1clogp //= $clogp; # maybe there's only one entry;
2471 $r1authline = clogp_authline $r1clogp;
2472 # Strictly, r1authline might now be wrong if it's going to be
2473 # unused because !$any_orig. Whatever.
2475 printdebug "import tartrees authline $authline\n";
2476 printdebug "import tartrees r1authline $r1authline\n";
2478 foreach my $tt (@$tartrees) {
2479 printdebug "import tartree $tt->{F} $tt->{Tree}\n";
2481 # untranslated so that different people's imports are identical
2482 my $mbody = sprintf "Import %s", $tt->{F};
2483 $tt->{Commit} = hash_commit_text($tt->{Orig} ? <<END_O : <<END_T);
2486 committer $r1authline
2490 [dgit import orig $tt->{F}]
2498 [dgit import tarball $package $cversion $tt->{F}]
2503 return ($authline, $r1authline, $clogp, $changes);
2506 sub generate_commits_from_dsc () {
2507 # See big comment in fetch_from_archive, below.
2508 # See also README.dsc-import.
2510 changedir $playground;
2512 my $bpd_abs = bpd_abs();
2513 my $upstreamv = upstreamversion $dsc->{version};
2514 my @dfi = dsc_files_info();
2516 dotdot_bpd_transfer_origs $bpd_abs, $upstreamv,
2517 sub { grep { $_->{Filename} eq $_[0] } @dfi };
2519 foreach my $fi (@dfi) {
2520 my $f = $fi->{Filename};
2521 die "$f ?" if $f =~ m#/|^\.|\.dsc$|\.tmp$#;
2522 my $upper_f = "$bpd_abs/$f";
2524 printdebug "considering reusing $f: ";
2526 if (link_ltarget "$upper_f,fetch", $f) {
2527 printdebug "linked (using ...,fetch).\n";
2528 } elsif ((printdebug "($!) "),
2530 fail f_ "accessing %s: %s", "$buildproductsdir/$f,fetch", $!;
2531 } elsif (link_ltarget $upper_f, $f) {
2532 printdebug "linked.\n";
2533 } elsif ((printdebug "($!) "),
2535 fail f_ "accessing %s: %s", "$buildproductsdir/$f", $!;
2537 printdebug "absent.\n";
2541 complete_file_from_dsc('.', $fi, \$refetched)
2544 printdebug "considering saving $f: ";
2546 if (rename_link_xf 1, $f, $upper_f) {
2547 printdebug "linked.\n";
2548 } elsif ((printdebug "($@) "),
2550 fail f_ "saving %s: %s", "$buildproductsdir/$f", $@;
2551 } elsif (!$refetched) {
2552 printdebug "no need.\n";
2553 } elsif (rename_link_xf 1, $f, "$upper_f,fetch") {
2554 printdebug "linked (using ...,fetch).\n";
2555 } elsif ((printdebug "($@) "),
2557 fail f_ "saving %s: %s", "$buildproductsdir/$f,fetch", $@;
2559 printdebug "cannot.\n";
2564 @tartrees = import_tarball_tartrees($upstreamv, \@dfi)
2565 unless @dfi == 1; # only one file in .dsc
2567 my $dscfn = "$package.dsc";
2569 my $treeimporthow = 'package';
2571 open D, ">", $dscfn or die "$dscfn: $!";
2572 print D $dscdata or die "$dscfn: $!";
2573 close D or die "$dscfn: $!";
2574 my @cmd = qw(dpkg-source);
2575 push @cmd, '--no-check' if $dsc_checked;
2576 if (madformat $dsc->{format}) {
2577 push @cmd, '--skip-patches';
2578 $treeimporthow = 'unpatched';
2580 push @cmd, qw(-x --), $dscfn;
2583 my ($tree,$dir) = mktree_in_ud_from_only_subdir(__ "source package");
2584 if (madformat $dsc->{format}) {
2585 check_for_vendor_patches();
2589 if (madformat $dsc->{format}) {
2590 my @pcmd = qw(dpkg-source --before-build .);
2591 runcmd shell_cmd 'exec >/dev/null', @pcmd;
2593 $dappliedtree = git_add_write_tree();
2596 my ($authline, $r1authline, $clogp, $changes) =
2597 import_tarball_commits(\@tartrees, $upstreamv);
2599 my $cversion = getfield $clogp, 'Version';
2601 printdebug "import main commit\n";
2603 open C, ">../commit.tmp" or confess "$!";
2604 print C <<END or confess "$!";
2607 print C <<END or confess "$!" foreach @tartrees;
2610 print C <<END or confess "$!";
2616 [dgit import $treeimporthow $package $cversion]
2619 close C or confess "$!";
2620 my $rawimport_hash = hash_commit qw(../commit.tmp);
2622 if (madformat $dsc->{format}) {
2623 printdebug "import apply patches...\n";
2625 # regularise the state of the working tree so that
2626 # the checkout of $rawimport_hash works nicely.
2627 my $dappliedcommit = hash_commit_text(<<END);
2634 runcmd @git, qw(checkout -q -b dapplied), $dappliedcommit;
2636 runcmd @git, qw(checkout -q -b unpa), $rawimport_hash;
2638 # We need the answers to be reproducible
2639 my @authline = clogp_authline($clogp);
2640 local $ENV{GIT_COMMITTER_NAME} = $authline[0];
2641 local $ENV{GIT_COMMITTER_EMAIL} = $authline[1];
2642 local $ENV{GIT_COMMITTER_DATE} = $authline[2];
2643 local $ENV{GIT_AUTHOR_NAME} = $authline[0];
2644 local $ENV{GIT_AUTHOR_EMAIL} = $authline[1];
2645 local $ENV{GIT_AUTHOR_DATE} = $authline[2];
2647 my $path = $ENV{PATH} or die;
2649 # we use ../../gbp-pq-output, which (given that we are in
2650 # $playground/PLAYTREE, and $playground is .git/dgit/unpack,
2653 foreach my $use_absurd (qw(0 1)) {
2654 runcmd @git, qw(checkout -q unpa);
2655 runcmd @git, qw(update-ref -d refs/heads/patch-queue/unpa);
2656 local $ENV{PATH} = $path;
2659 progress "warning: $@";
2660 $path = "$absurdity:$path";
2661 progress f_ "%s: trying slow absurd-git-apply...", $us;
2662 rename "../../gbp-pq-output","../../gbp-pq-output.0"
2667 die "forbid absurd git-apply\n" if $use_absurd
2668 && forceing [qw(import-gitapply-no-absurd)];
2669 die "only absurd git-apply!\n" if !$use_absurd
2670 && forceing [qw(import-gitapply-absurd)];
2672 local $ENV{DGIT_ABSURD_DEBUG} = $debuglevel if $use_absurd;
2673 local $ENV{PATH} = $path if $use_absurd;
2675 my @showcmd = (gbp_pq, qw(import));
2676 my @realcmd = shell_cmd
2677 'exec >/dev/null 2>>../../gbp-pq-output', @showcmd;
2678 debugcmd "+",@realcmd;
2679 if (system @realcmd) {
2680 die f_ "%s failed: %s\n",
2681 +(shellquote @showcmd),
2682 failedcmd_waitstatus();
2685 my $gapplied = git_rev_parse('HEAD');
2686 my $gappliedtree = cmdoutput @git, qw(rev-parse HEAD:);
2687 $gappliedtree eq $dappliedtree or
2688 fail f_ <<END, $gapplied, $gappliedtree, $dappliedtree;
2689 gbp-pq import and dpkg-source disagree!
2690 gbp-pq import gave commit %s
2691 gbp-pq import gave tree %s
2692 dpkg-source --before-build gave tree %s
2694 $rawimport_hash = $gapplied;
2699 { local $@; eval { runcmd qw(cat ../../gbp-pq-output); }; }
2704 progress f_ "synthesised git commit from .dsc %s", $cversion;
2706 my $rawimport_mergeinput = {
2707 Commit => $rawimport_hash,
2708 Info => __ "Import of source package",
2710 my @output = ($rawimport_mergeinput);
2712 if ($lastpush_mergeinput) {
2713 my $oldclogp = mergeinfo_getclogp($lastpush_mergeinput);
2714 my $oversion = getfield $oldclogp, 'Version';
2716 version_compare($oversion, $cversion);
2718 @output = ($rawimport_mergeinput, $lastpush_mergeinput,
2719 { ReverseParents => 1,
2720 # untranslated so that different people's pseudomerges
2721 # are not needlessly different (although they will
2722 # still differ if the series of pulls is different)
2723 Message => (sprintf <<END, $package, $cversion, $csuite) });
2724 Record %s (%s) in archive suite %s
2726 } elsif ($vcmp > 0) {
2727 print STDERR f_ <<END, $cversion, $oversion,
2729 Version actually in archive: %s (older)
2730 Last version pushed with dgit: %s (newer or same)
2733 __ $later_warning_msg or confess "$!";
2734 @output = $lastpush_mergeinput;
2736 # Same version. Use what's in the server git branch,
2737 # discarding our own import. (This could happen if the
2738 # server automatically imports all packages into git.)
2739 @output = $lastpush_mergeinput;
2747 sub complete_file_from_dsc ($$;$) {
2748 our ($dstdir, $fi, $refetched) = @_;
2749 # Ensures that we have, in $dstdir, the file $fi, with the correct
2750 # contents. (Downloading it from alongside $dscurl if necessary.)
2751 # If $refetched is defined, can overwrite "$dstdir/$fi->{Filename}"
2752 # and will set $$refetched=1 if it did so (or tried to).
2754 my $f = $fi->{Filename};
2755 my $tf = "$dstdir/$f";
2759 my $checkhash = sub {
2760 open F, "<", "$tf" or die "$tf: $!";
2761 $fi->{Digester}->reset();
2762 $fi->{Digester}->addfile(*F);
2763 F->error and confess "$!";
2764 $got = $fi->{Digester}->hexdigest();
2765 return $got eq $fi->{Hash};
2768 if (stat_exists $tf) {
2769 if ($checkhash->()) {
2770 progress f_ "using existing %s", $f;
2774 fail f_ "file %s has hash %s but .dsc demands hash %s".
2775 " (perhaps you should delete this file?)",
2776 $f, $got, $fi->{Hash};
2778 progress f_ "need to fetch correct version of %s", $f;
2779 unlink $tf or die "$tf $!";
2782 printdebug "$tf does not exist, need to fetch\n";
2786 $furl =~ s{/[^/]+$}{};
2788 die "$f ?" unless $f =~ m/^\Q${package}\E_/;
2789 die "$f ?" if $f =~ m#/#;
2790 runcmd_ordryrun_local @curl,qw(-f -o),$tf,'--',"$furl";
2791 return 0 if !act_local();
2794 fail f_ "file %s has hash %s but .dsc demands hash %s".
2795 " (got wrong file from archive!)",
2796 $f, $got, $fi->{Hash};
2801 sub ensure_we_have_orig () {
2802 my @dfi = dsc_files_info();
2803 foreach my $fi (@dfi) {
2804 my $f = $fi->{Filename};
2805 next unless is_orig_file_in_dsc($f, \@dfi);
2806 complete_file_from_dsc($buildproductsdir, $fi)
2811 #---------- git fetch ----------
2813 sub lrfetchrefs () { return "refs/dgit-fetch/".access_basedistro(); }
2814 sub lrfetchref () { return lrfetchrefs.'/'.server_branch($csuite); }
2816 # We fetch some parts of lrfetchrefs/*. Ideally we delete these
2817 # locally fetched refs because they have unhelpful names and clutter
2818 # up gitk etc. So we track whether we have "used up" head ref (ie,
2819 # whether we have made another local ref which refers to this object).
2821 # (If we deleted them unconditionally, then we might end up
2822 # re-fetching the same git objects each time dgit fetch was run.)
2824 # So, each use of lrfetchrefs needs to be accompanied by arrangements
2825 # in git_fetch_us to fetch the refs in question, and possibly a call
2826 # to lrfetchref_used.
2828 our (%lrfetchrefs_f, %lrfetchrefs_d);
2829 # $lrfetchrefs_X{lrfetchrefs."/heads/whatever"} = $objid
2831 sub lrfetchref_used ($) {
2832 my ($fullrefname) = @_;
2833 my $objid = $lrfetchrefs_f{$fullrefname};
2834 $lrfetchrefs_d{$fullrefname} = $objid if defined $objid;
2837 sub git_lrfetch_sane {
2838 my ($url, $supplementary, @specs) = @_;
2839 # Make a 'refs/'.lrfetchrefs.'/*' be just like on server,
2840 # at least as regards @specs. Also leave the results in
2841 # %lrfetchrefs_f, and arrange for lrfetchref_used to be
2842 # able to clean these up.
2844 # With $supplementary==1, @specs must not contain wildcards
2845 # and we add to our previous fetches (non-atomically).
2847 # This is rather miserable:
2848 # When git fetch --prune is passed a fetchspec ending with a *,
2849 # it does a plausible thing. If there is no * then:
2850 # - it matches subpaths too, even if the supplied refspec
2851 # starts refs, and behaves completely madly if the source
2852 # has refs/refs/something. (See, for example, Debian #NNNN.)
2853 # - if there is no matching remote ref, it bombs out the whole
2855 # We want to fetch a fixed ref, and we don't know in advance
2856 # if it exists, so this is not suitable.
2858 # Our workaround is to use git ls-remote. git ls-remote has its
2859 # own qairks. Notably, it has the absurd multi-tail-matching
2860 # behaviour: git ls-remote R refs/foo can report refs/foo AND
2861 # refs/refs/foo etc.
2863 # Also, we want an idempotent snapshot, but we have to make two
2864 # calls to the remote: one to git ls-remote and to git fetch. The
2865 # solution is use git ls-remote to obtain a target state, and
2866 # git fetch to try to generate it. If we don't manage to generate
2867 # the target state, we try again.
2869 printdebug "git_lrfetch_sane suppl=$supplementary specs @specs\n";
2871 my $specre = join '|', map {
2874 my $wildcard = $x =~ s/\\\*$/.*/;
2875 die if $wildcard && $supplementary;
2878 printdebug "git_lrfetch_sane specre=$specre\n";
2879 my $wanted_rref = sub {
2881 return m/^(?:$specre)$/;
2884 my $fetch_iteration = 0;
2887 printdebug "git_lrfetch_sane iteration $fetch_iteration\n";
2888 if (++$fetch_iteration > 10) {
2889 fail __ "too many iterations trying to get sane fetch!";
2892 my @look = map { "refs/$_" } @specs;
2893 my @lcmd = (@git, qw(ls-remote -q --refs), $url, @look);
2897 open GITLS, "-|", @lcmd or confess "$!";
2899 printdebug "=> ", $_;
2900 m/^(\w+)\s+(\S+)\n/ or die "ls-remote $_ ?";
2901 my ($objid,$rrefname) = ($1,$2);
2902 if (!$wanted_rref->($rrefname)) {
2903 print STDERR f_ <<END, "@look", $rrefname;
2904 warning: git ls-remote %s reported %s; this is silly, ignoring it.
2908 $wantr{$rrefname} = $objid;
2911 close GITLS or failedcmd @lcmd;
2913 # OK, now %want is exactly what we want for refs in @specs
2915 !m/\*$/ && !exists $wantr{"refs/$_"} ? () :
2916 "+refs/$_:".lrfetchrefs."/$_";
2919 printdebug "git_lrfetch_sane fspecs @fspecs\n";
2921 my @fcmd = (@git, qw(fetch -p -n -q), $url, @fspecs);
2922 runcmd_ordryrun_local @fcmd if @fspecs;
2924 if (!$supplementary) {
2925 %lrfetchrefs_f = ();
2929 git_for_each_ref(lrfetchrefs, sub {
2930 my ($objid,$objtype,$lrefname,$reftail) = @_;
2931 $lrfetchrefs_f{$lrefname} = $objid;
2932 $objgot{$objid} = 1;
2935 if ($supplementary) {
2939 foreach my $lrefname (sort keys %lrfetchrefs_f) {
2940 my $rrefname = 'refs'.substr($lrefname, length lrfetchrefs);
2941 if (!exists $wantr{$rrefname}) {
2942 if ($wanted_rref->($rrefname)) {
2944 git-fetch @fspecs created $lrefname which git ls-remote @look didn't list.
2947 print STDERR f_ <<END, "@fspecs", $lrefname
2948 warning: git fetch %s created %s; this is silly, deleting it.
2951 runcmd_ordryrun_local @git, qw(update-ref -d), $lrefname;
2952 delete $lrfetchrefs_f{$lrefname};
2956 foreach my $rrefname (sort keys %wantr) {
2957 my $lrefname = lrfetchrefs.substr($rrefname, 4);
2958 my $got = $lrfetchrefs_f{$lrefname} // '<none>';
2959 my $want = $wantr{$rrefname};
2960 next if $got eq $want;
2961 if (!defined $objgot{$want}) {
2962 fail __ <<END unless act_local();
2963 --dry-run specified but we actually wanted the results of git fetch,
2964 so this is not going to work. Try running dgit fetch first,
2965 or using --damp-run instead of --dry-run.
2967 print STDERR f_ <<END, $lrefname, $want;
2968 warning: git ls-remote suggests we want %s
2969 warning: and it should refer to %s
2970 warning: but git fetch didn't fetch that object to any relevant ref.
2971 warning: This may be due to a race with someone updating the server.
2972 warning: Will try again...
2974 next FETCH_ITERATION;
2977 git-fetch @fspecs made $lrefname=$got but want git ls-remote @look says $want
2979 runcmd_ordryrun_local @git, qw(update-ref -m),
2980 "dgit fetch git fetch fixup", $lrefname, $want;
2981 $lrfetchrefs_f{$lrefname} = $want;
2986 if (defined $csuite) {
2987 printdebug "git_lrfetch_sane: tidying any old suite lrfetchrefs\n";
2988 git_for_each_ref("refs/dgit-fetch/$csuite", sub {
2989 my ($objid,$objtype,$lrefname,$reftail) = @_;
2990 next if $lrfetchrefs_f{$lrefname}; # $csuite eq $distro ?
2991 runcmd_ordryrun_local @git, qw(update-ref -d), $lrefname;
2995 printdebug "git_lrfetch_sane: git fetch --no-insane emulation complete\n",
2996 Dumper(\%lrfetchrefs_f);
2999 sub git_fetch_us () {
3000 # Want to fetch only what we are going to use, unless
3001 # deliberately-not-ff, in which case we must fetch everything.
3003 my @specs = deliberately_not_fast_forward ? qw(tags/*) :
3004 map { "tags/$_" } debiantags('*',access_nomdistro);
3005 push @specs, server_branch($csuite);
3006 push @specs, $rewritemap;
3007 push @specs, qw(heads/*) if deliberately_not_fast_forward;
3009 my $url = access_giturl();
3010 git_lrfetch_sane $url, 0, @specs;
3013 my @tagpats = debiantags('*',access_nomdistro);
3015 git_for_each_ref([map { "refs/tags/$_" } @tagpats], sub {
3016 my ($objid,$objtype,$fullrefname,$reftail) = @_;
3017 printdebug "currently $fullrefname=$objid\n";
3018 $here{$fullrefname} = $objid;
3020 git_for_each_ref([map { lrfetchrefs."/tags/".$_ } @tagpats], sub {
3021 my ($objid,$objtype,$fullrefname,$reftail) = @_;
3022 my $lref = "refs".substr($fullrefname, length(lrfetchrefs));
3023 printdebug "offered $lref=$objid\n";
3024 if (!defined $here{$lref}) {
3025 my @upd = (@git, qw(update-ref), $lref, $objid, '');
3026 runcmd_ordryrun_local @upd;
3027 lrfetchref_used $fullrefname;
3028 } elsif ($here{$lref} eq $objid) {
3029 lrfetchref_used $fullrefname;
3031 print STDERR f_ "Not updating %s from %s to %s.\n",
3032 $lref, $here{$lref}, $objid;
3037 #---------- dsc and archive handling ----------
3039 sub mergeinfo_getclogp ($) {
3040 # Ensures thit $mi->{Clogp} exists and returns it
3042 $mi->{Clogp} = commit_getclogp($mi->{Commit});
3045 sub mergeinfo_version ($) {
3046 return getfield( (mergeinfo_getclogp $_[0]), 'Version' );
3049 sub fetch_from_archive_record_1 ($) {
3051 runcmd git_update_ref_cmd "dgit fetch $csuite", 'DGIT_ARCHIVE', $hash;
3052 cmdoutput @git, qw(log -n2), $hash;
3053 # ... gives git a chance to complain if our commit is malformed
3056 sub fetch_from_archive_record_2 ($) {
3058 my @upd_cmd = (git_update_ref_cmd 'dgit fetch', lrref(), $hash);
3062 dryrun_report @upd_cmd;
3066 sub parse_dsc_field_def_dsc_distro () {
3067 $dsc_distro //= cfg qw(dgit.default.old-dsc-distro
3068 dgit.default.distro);
3071 sub parse_dsc_field ($$) {
3072 my ($dsc, $what) = @_;
3074 foreach my $field (@ourdscfield) {
3075 $f = $dsc->{$field};
3080 progress f_ "%s: NO git hash", $what;
3081 parse_dsc_field_def_dsc_distro();
3082 } elsif (($dsc_hash, $dsc_distro, $dsc_hint_tag, $dsc_hint_url)
3083 = $f =~ m/^(\w+)\s+($distro_re)\s+($versiontag_re)\s+(\S+)(?:\s|$)/) {
3084 progress f_ "%s: specified git info (%s)", $what, $dsc_distro;
3085 $dsc_hint_tag = [ $dsc_hint_tag ];
3086 } elsif ($f =~ m/^\w+\s*$/) {
3088 parse_dsc_field_def_dsc_distro();
3089 $dsc_hint_tag = [ debiantags +(getfield $dsc, 'Version'),
3091 progress f_ "%s: specified git hash", $what;
3093 fail f_ "%s: invalid Dgit info", $what;
3097 sub resolve_dsc_field_commit ($$) {
3098 my ($already_distro, $already_mapref) = @_;
3100 return unless defined $dsc_hash;
3103 defined $already_mapref &&
3104 ($already_distro eq $dsc_distro || !$chase_dsc_distro)
3105 ? $already_mapref : undef;
3109 my ($what, @fetch) = @_;
3111 local $idistro = $dsc_distro;
3112 my $lrf = lrfetchrefs;
3114 if (!$chase_dsc_distro) {
3115 progress f_ "not chasing .dsc distro %s: not fetching %s",
3120 progress f_ ".dsc names distro %s: fetching %s", $dsc_distro, $what;
3122 my $url = access_giturl();
3123 if (!defined $url) {
3124 defined $dsc_hint_url or fail f_ <<END, $dsc_distro;
3125 .dsc Dgit metadata is in context of distro %s
3126 for which we have no configured url and .dsc provides no hint
3129 $dsc_hint_url =~ m#^([-+0-9a-zA-Z]+):# ? $1 :
3130 $dsc_hint_url =~ m#^/# ? 'file' : 'bad-syntax';
3131 parse_cfg_bool "dsc-url-proto-ok", 'false',
3132 cfg("dgit.dsc-url-proto-ok.$proto",
3133 "dgit.default.dsc-url-proto-ok")
3134 or fail f_ <<END, $dsc_distro, $proto;
3135 .dsc Dgit metadata is in context of distro %s
3136 for which we have no configured url;
3137 .dsc provides hinted url with protocol %s which is unsafe.
3138 (can be overridden by config - consult documentation)
3140 $url = $dsc_hint_url;
3143 git_lrfetch_sane $url, 1, @fetch;
3148 my $rewrite_enable = do {
3149 local $idistro = $dsc_distro;
3150 access_cfg('rewrite-map-enable', 'RETURN-UNDEF');
3153 if (parse_cfg_bool 'rewrite-map-enable', 'true', $rewrite_enable) {
3154 if (!defined $mapref) {
3155 my $lrf = $do_fetch->((__ "rewrite map"), $rewritemap) or return;
3156 $mapref = $lrf.'/'.$rewritemap;
3158 my $rewritemapdata = git_cat_file $mapref.':map';
3159 if (defined $rewritemapdata
3160 && $rewritemapdata =~ m/^$dsc_hash(?:[ \t](\w+))/m) {
3162 "server's git history rewrite map contains a relevant entry!";
3165 if (defined $dsc_hash) {
3166 progress __ "using rewritten git hash in place of .dsc value";
3168 progress __ "server data says .dsc hash is to be disregarded";
3173 if (!defined git_cat_file $dsc_hash) {
3174 my @tags = map { "tags/".$_ } @$dsc_hint_tag;
3175 my $lrf = $do_fetch->((__ "additional commits"), @tags) &&
3176 defined git_cat_file $dsc_hash
3177 or fail f_ <<END, $dsc_hash;
3178 .dsc Dgit metadata requires commit %s
3179 but we could not obtain that object anywhere.
3181 foreach my $t (@tags) {
3182 my $fullrefname = $lrf.'/'.$t;
3183 # print STDERR "CHK $t $fullrefname ".Dumper(\%lrfetchrefs_f);
3184 next unless $lrfetchrefs_f{$fullrefname};
3185 next unless is_fast_fwd "$fullrefname~0", $dsc_hash;
3186 lrfetchref_used $fullrefname;
3191 sub fetch_from_archive () {
3193 ensure_setup_existing_tree();
3195 # Ensures that lrref() is what is actually in the archive, one way
3196 # or another, according to us - ie this client's
3197 # appropritaely-updated archive view. Also returns the commit id.
3198 # If there is nothing in the archive, leaves lrref alone and
3199 # returns undef. git_fetch_us must have already been called.
3203 parse_dsc_field($dsc, __ 'last upload to archive');
3204 resolve_dsc_field_commit access_basedistro,
3205 lrfetchrefs."/".$rewritemap
3207 progress __ "no version available from the archive";
3210 # If the archive's .dsc has a Dgit field, there are three
3211 # relevant git commitids we need to choose between and/or merge
3213 # 1. $dsc_hash: the Dgit field from the archive
3214 # 2. $lastpush_hash: the suite branch on the dgit git server
3215 # 3. $lastfetch_hash: our local tracking brach for the suite
3217 # These may all be distinct and need not be in any fast forward
3220 # If the dsc was pushed to this suite, then the server suite
3221 # branch will have been updated; but it might have been pushed to
3222 # a different suite and copied by the archive. Conversely a more
3223 # recent version may have been pushed with dgit but not appeared
3224 # in the archive (yet).
3226 # $lastfetch_hash may be awkward because archive imports
3227 # (particularly, imports of Dgit-less .dscs) are performed only as
3228 # needed on individual clients, so different clients may perform a
3229 # different subset of them - and these imports are only made
3230 # public during push. So $lastfetch_hash may represent a set of
3231 # imports different to a subsequent upload by a different dgit
3234 # Our approach is as follows:
3236 # As between $dsc_hash and $lastpush_hash: if $lastpush_hash is a
3237 # descendant of $dsc_hash, then it was pushed by a dgit user who
3238 # had based their work on $dsc_hash, so we should prefer it.
3239 # Otherwise, $dsc_hash was installed into this suite in the
3240 # archive other than by a dgit push, and (necessarily) after the
3241 # last dgit push into that suite (since a dgit push would have
3242 # been descended from the dgit server git branch); thus, in that
3243 # case, we prefer the archive's version (and produce a
3244 # pseudo-merge to overwrite the dgit server git branch).
3246 # (If there is no Dgit field in the archive's .dsc then
3247 # generate_commit_from_dsc uses the version numbers to decide
3248 # whether the suite branch or the archive is newer. If the suite
3249 # branch is newer it ignores the archive's .dsc; otherwise it
3250 # generates an import of the .dsc, and produces a pseudo-merge to
3251 # overwrite the suite branch with the archive contents.)
3253 # The outcome of that part of the algorithm is the `public view',
3254 # and is same for all dgit clients: it does not depend on any
3255 # unpublished history in the local tracking branch.
3257 # As between the public view and the local tracking branch: The
3258 # local tracking branch is only updated by dgit fetch, and
3259 # whenever dgit fetch runs it includes the public view in the
3260 # local tracking branch. Therefore if the public view is not
3261 # descended from the local tracking branch, the local tracking
3262 # branch must contain history which was imported from the archive
3263 # but never pushed; and, its tip is now out of date. So, we make
3264 # a pseudo-merge to overwrite the old imports and stitch the old
3267 # Finally: we do not necessarily reify the public view (as
3268 # described above). This is so that we do not end up stacking two
3269 # pseudo-merges. So what we actually do is figure out the inputs
3270 # to any public view pseudo-merge and put them in @mergeinputs.
3273 # $mergeinputs[]{Commit}
3274 # $mergeinputs[]{Info}
3275 # $mergeinputs[0] is the one whose tree we use
3276 # @mergeinputs is in the order we use in the actual commit)
3279 # $mergeinputs[]{Message} is a commit message to use
3280 # $mergeinputs[]{ReverseParents} if def specifies that parent
3281 # list should be in opposite order
3282 # Such an entry has no Commit or Info. It applies only when found
3283 # in the last entry. (This ugliness is to support making
3284 # identical imports to previous dgit versions.)
3286 my $lastpush_hash = git_get_ref(lrfetchref());
3287 printdebug "previous reference hash=$lastpush_hash\n";
3288 $lastpush_mergeinput = $lastpush_hash && {
3289 Commit => $lastpush_hash,
3290 Info => (__ "dgit suite branch on dgit git server"),
3293 my $lastfetch_hash = git_get_ref(lrref());
3294 printdebug "fetch_from_archive: lastfetch=$lastfetch_hash\n";
3295 my $lastfetch_mergeinput = $lastfetch_hash && {
3296 Commit => $lastfetch_hash,
3297 Info => (__ "dgit client's archive history view"),
3300 my $dsc_mergeinput = $dsc_hash && {
3301 Commit => $dsc_hash,
3302 Info => (__ "Dgit field in .dsc from archive"),
3306 my $del_lrfetchrefs = sub {
3309 printdebug "del_lrfetchrefs...\n";
3310 foreach my $fullrefname (sort keys %lrfetchrefs_d) {
3311 my $objid = $lrfetchrefs_d{$fullrefname};
3312 printdebug "del_lrfetchrefs: $objid $fullrefname\n";
3314 $gur ||= new IO::Handle;
3315 open $gur, "|-", qw(git update-ref --stdin) or confess "$!";
3317 printf $gur "delete %s %s\n", $fullrefname, $objid;
3320 close $gur or failedcmd "git update-ref delete lrfetchrefs";
3324 if (defined $dsc_hash) {
3325 ensure_we_have_orig();
3326 if (!$lastpush_hash || $dsc_hash eq $lastpush_hash) {
3327 @mergeinputs = $dsc_mergeinput
3328 } elsif (is_fast_fwd($dsc_hash,$lastpush_hash)) {
3329 print STDERR f_ <<END, $dsc_hash, $lastpush_hash,
3331 Git commit in archive is behind the last version allegedly pushed/uploaded.
3332 Commit referred to by archive: %s
3333 Last version pushed with dgit: %s
3336 __ $later_warning_msg or confess "$!";
3337 @mergeinputs = ($lastpush_mergeinput);
3339 # Archive has .dsc which is not a descendant of the last dgit
3340 # push. This can happen if the archive moves .dscs about.
3341 # Just follow its lead.
3342 if (is_fast_fwd($lastpush_hash,$dsc_hash)) {
3343 progress __ "archive .dsc names newer git commit";
3344 @mergeinputs = ($dsc_mergeinput);
3346 progress __ "archive .dsc names other git commit, fixing up";
3347 @mergeinputs = ($dsc_mergeinput, $lastpush_mergeinput);
3351 @mergeinputs = generate_commits_from_dsc();
3352 # We have just done an import. Now, our import algorithm might
3353 # have been improved. But even so we do not want to generate
3354 # a new different import of the same package. So if the
3355 # version numbers are the same, just use our existing version.
3356 # If the version numbers are different, the archive has changed
3357 # (perhaps, rewound).
3358 if ($lastfetch_mergeinput &&
3359 !version_compare( (mergeinfo_version $lastfetch_mergeinput),
3360 (mergeinfo_version $mergeinputs[0]) )) {
3361 @mergeinputs = ($lastfetch_mergeinput);
3363 } elsif ($lastpush_hash) {
3364 # only in git, not in the archive yet
3365 @mergeinputs = ($lastpush_mergeinput);
3366 print STDERR f_ <<END,
3368 Package not found in the archive, but has allegedly been pushed using dgit.
3371 __ $later_warning_msg or confess "$!";
3373 printdebug "nothing found!\n";
3374 if (defined $skew_warning_vsn) {
3375 print STDERR f_ <<END, $skew_warning_vsn or confess "$!";
3377 Warning: relevant archive skew detected.
3378 Archive allegedly contains %s
3379 But we were not able to obtain any version from the archive or git.
3383 unshift @end, $del_lrfetchrefs;
3387 if ($lastfetch_hash &&
3389 my $h = $_->{Commit};
3390 $h and is_fast_fwd($lastfetch_hash, $h);
3391 # If true, one of the existing parents of this commit
3392 # is a descendant of the $lastfetch_hash, so we'll
3393 # be ff from that automatically.
3397 push @mergeinputs, $lastfetch_mergeinput;
3400 printdebug "fetch mergeinfos:\n";
3401 foreach my $mi (@mergeinputs) {
3403 printdebug " commit $mi->{Commit} $mi->{Info}\n";
3405 printdebug sprintf " ReverseParents=%d Message=%s",
3406 $mi->{ReverseParents}, $mi->{Message};
3410 my $compat_info= pop @mergeinputs
3411 if $mergeinputs[$#mergeinputs]{Message};
3413 @mergeinputs = grep { defined $_->{Commit} } @mergeinputs;
3416 if (@mergeinputs > 1) {
3418 my $tree_commit = $mergeinputs[0]{Commit};
3420 my $tree = get_tree_of_commit $tree_commit;;
3422 # We use the changelog author of the package in question the
3423 # author of this pseudo-merge. This is (roughly) correct if
3424 # this commit is simply representing aa non-dgit upload.
3425 # (Roughly because it does not record sponsorship - but we
3426 # don't have sponsorship info because that's in the .changes,
3427 # which isn't in the archivw.)
3429 # But, it might be that we are representing archive history
3430 # updates (including in-archive copies). These are not really
3431 # the responsibility of the person who created the .dsc, but
3432 # there is no-one whose name we should better use. (The
3433 # author of the .dsc-named commit is clearly worse.)
3435 my $useclogp = mergeinfo_getclogp $mergeinputs[0];
3436 my $author = clogp_authline $useclogp;
3437 my $cversion = getfield $useclogp, 'Version';
3439 my $mcf = dgit_privdir()."/mergecommit";
3440 open MC, ">", $mcf or die "$mcf $!";
3441 print MC <<END or confess "$!";
3445 my @parents = grep { $_->{Commit} } @mergeinputs;
3446 @parents = reverse @parents if $compat_info->{ReverseParents};
3447 print MC <<END or confess "$!" foreach @parents;
3451 print MC <<END or confess "$!";
3457 if (defined $compat_info->{Message}) {
3458 print MC $compat_info->{Message} or confess "$!";
3460 print MC f_ <<END, $package, $cversion, $csuite or confess "$!";
3461 Record %s (%s) in archive suite %s
3465 my $message_add_info = sub {
3467 my $mversion = mergeinfo_version $mi;
3468 printf MC " %-20s %s\n", $mversion, $mi->{Info}
3472 $message_add_info->($mergeinputs[0]);
3473 print MC __ <<END or confess "$!";
3474 should be treated as descended from
3476 $message_add_info->($_) foreach @mergeinputs[1..$#mergeinputs];
3479 close MC or confess "$!";
3480 $hash = hash_commit $mcf;
3482 $hash = $mergeinputs[0]{Commit};
3484 printdebug "fetch hash=$hash\n";
3487 my ($lasth, $what) = @_;
3488 return unless $lasth;
3489 confess "$lasth $hash $what ?" unless is_fast_fwd($lasth, $hash);
3492 $chkff->($lastpush_hash, __ 'dgit repo server tip (last push)')
3494 $chkff->($lastfetch_hash, __ 'local tracking tip (last fetch)');
3496 fetch_from_archive_record_1($hash);
3498 if (defined $skew_warning_vsn) {
3499 printdebug "SKEW CHECK WANT $skew_warning_vsn\n";
3500 my $gotclogp = commit_getclogp($hash);
3501 my $got_vsn = getfield $gotclogp, 'Version';
3502 printdebug "SKEW CHECK GOT $got_vsn\n";
3503 if (version_compare($got_vsn, $skew_warning_vsn) < 0) {
3504 print STDERR f_ <<END, $skew_warning_vsn, $got_vsn or confess "$!";
3506 Warning: archive skew detected. Using the available version:
3507 Archive allegedly contains %s
3508 We were able to obtain only %s
3514 if ($lastfetch_hash ne $hash) {
3515 fetch_from_archive_record_2($hash);
3518 lrfetchref_used lrfetchref();
3520 check_gitattrs($hash, __ "fetched source tree");
3522 unshift @end, $del_lrfetchrefs;
3526 sub set_local_git_config ($$) {
3528 runcmd @git, qw(config), $k, $v;
3531 sub setup_mergechangelogs (;$) {
3533 return unless $always || access_cfg_bool(1, 'setup-mergechangelogs');
3535 my $driver = 'dpkg-mergechangelogs';
3536 my $cb = "merge.$driver";
3537 confess unless defined $maindir;
3538 my $attrs = "$maindir_gitcommon/info/attributes";
3539 ensuredir "$maindir_gitcommon/info";
3541 open NATTRS, ">", "$attrs.new" or die "$attrs.new $!";
3542 if (!open ATTRS, "<", $attrs) {
3543 $!==ENOENT or die "$attrs: $!";
3547 next if m{^debian/changelog\s};
3548 print NATTRS $_, "\n" or confess "$!";
3550 ATTRS->error and confess "$!";
3553 print NATTRS "debian/changelog merge=$driver\n" or confess "$!";
3556 set_local_git_config "$cb.name", __ 'debian/changelog merge driver';
3557 set_local_git_config "$cb.driver", 'dpkg-mergechangelogs -m %O %A %B %A';
3559 rename "$attrs.new", "$attrs" or die "$attrs: $!";
3562 sub setup_useremail (;$) {
3564 return unless $always || access_cfg_bool(1, 'setup-useremail');
3567 my ($k, $envvar) = @_;
3568 my $v = access_cfg("user-$k", 'RETURN-UNDEF') // $ENV{$envvar};
3569 return unless defined $v;
3570 set_local_git_config "user.$k", $v;
3573 $setup->('email', 'DEBEMAIL');
3574 $setup->('name', 'DEBFULLNAME');
3577 sub ensure_setup_existing_tree () {
3578 my $k = "remote.$remotename.skipdefaultupdate";
3579 my $c = git_get_config $k;
3580 return if defined $c;
3581 set_local_git_config $k, 'true';
3584 sub open_main_gitattrs () {
3585 confess 'internal error no maindir' unless defined $maindir;
3586 my $gai = new IO::File "$maindir_gitcommon/info/attributes"
3588 or die "open $maindir_gitcommon/info/attributes: $!";
3592 our $gitattrs_ourmacro_re = qr{^\[attr\]dgit-defuse-attrs\s};
3594 sub is_gitattrs_setup () {
3597 # 1: gitattributes set up and should be left alone
3599 # 0: there is a dgit-defuse-attrs but it needs fixing
3600 # undef: there is none
3601 my $gai = open_main_gitattrs();
3602 return 0 unless $gai;
3604 next unless m{$gitattrs_ourmacro_re};
3605 return 1 if m{\s-working-tree-encoding\s};
3606 printdebug "is_gitattrs_setup: found old macro\n";
3609 $gai->error and confess "$!";
3610 printdebug "is_gitattrs_setup: found nothing\n";
3614 sub setup_gitattrs (;$) {
3616 return unless $always || access_cfg_bool(1, 'setup-gitattributes');
3618 my $already = is_gitattrs_setup();
3621 [attr]dgit-defuse-attrs already found, and proper, in .git/info/attributes
3622 not doing further gitattributes setup
3626 my $new = "[attr]dgit-defuse-attrs $negate_harmful_gitattrs";
3627 my $af = "$maindir_gitcommon/info/attributes";
3628 ensuredir "$maindir_gitcommon/info";
3630 open GAO, "> $af.new" or confess "$!";
3631 print GAO <<END, __ <<ENDT or confess "$!" unless defined $already;
3635 # ^ see GITATTRIBUTES in dgit(7) and dgit setup-new-tree in dgit(1)
3637 my $gai = open_main_gitattrs();
3640 if (m{$gitattrs_ourmacro_re}) {
3641 die unless defined $already;
3645 print GAO $_, "\n" or confess "$!";
3647 $gai->error and confess "$!";
3649 close GAO or confess "$!";
3650 rename "$af.new", "$af" or fail f_ "install %s: %s", $af, $!;
3653 sub setup_new_tree () {
3654 setup_mergechangelogs();
3659 sub check_gitattrs ($$) {
3660 my ($treeish, $what) = @_;
3662 return if is_gitattrs_setup;
3665 my @cmd = (@git, qw(ls-tree -lrz --), "${treeish}:");
3667 my $gafl = new IO::File;
3668 open $gafl, "-|", @cmd or confess "$!";
3671 s/^\d+\s+\w+\s+\w+\s+(\d+)\t// or die;
3673 next unless m{(?:^|/)\.gitattributes$};
3675 # oh dear, found one
3676 print STDERR f_ <<END, $what;
3677 dgit: warning: %s contains .gitattributes
3678 dgit: .gitattributes not (fully) defused. Recommended: dgit setup-new-tree.
3683 # tree contains no .gitattributes files
3684 $?=0; $!=0; close $gafl or failedcmd @cmd;
3688 sub multisuite_suite_child ($$$) {
3689 my ($tsuite, $mergeinputs, $fn) = @_;
3690 # in child, sets things up, calls $fn->(), and returns undef
3691 # in parent, returns canonical suite name for $tsuite
3692 my $canonsuitefh = IO::File::new_tmpfile;
3693 my $pid = fork // confess "$!";
3697 $us .= " [$isuite]";
3698 $debugprefix .= " ";
3699 progress f_ "fetching %s...", $tsuite;
3700 canonicalise_suite();
3701 print $canonsuitefh $csuite, "\n" or confess "$!";
3702 close $canonsuitefh or confess "$!";
3706 waitpid $pid,0 == $pid or confess "$!";
3707 fail f_ "failed to obtain %s: %s", $tsuite, waitstatusmsg()
3709 seek $canonsuitefh,0,0 or confess "$!";
3710 local $csuite = <$canonsuitefh>;
3711 confess "$!" unless defined $csuite && chomp $csuite;
3713 printdebug "multisuite $tsuite missing\n";
3716 printdebug "multisuite $tsuite ok (canon=$csuite)\n";
3717 push @$mergeinputs, {
3724 sub fork_for_multisuite ($) {
3725 my ($before_fetch_merge) = @_;
3726 # if nothing unusual, just returns ''
3729 # returns 0 to caller in child, to do first of the specified suites
3730 # in child, $csuite is not yet set
3732 # returns 1 to caller in parent, to finish up anything needed after
3733 # in parent, $csuite is set to canonicalised portmanteau
3735 my $org_isuite = $isuite;
3736 my @suites = split /\,/, $isuite;
3737 return '' unless @suites > 1;
3738 printdebug "fork_for_multisuite: @suites\n";
3742 my $cbasesuite = multisuite_suite_child($suites[0], \@mergeinputs,
3744 return 0 unless defined $cbasesuite;
3746 fail f_ "package %s missing in (base suite) %s", $package, $cbasesuite
3747 unless @mergeinputs;
3749 my @csuites = ($cbasesuite);
3751 $before_fetch_merge->();
3753 foreach my $tsuite (@suites[1..$#suites]) {
3754 $tsuite =~ s/^-/$cbasesuite-/;
3755 my $csubsuite = multisuite_suite_child($tsuite, \@mergeinputs,
3762 $csubsuite =~ s/^\Q$cbasesuite\E-/-/;
3763 push @csuites, $csubsuite;
3766 foreach my $mi (@mergeinputs) {
3767 my $ref = git_get_ref $mi->{Ref};
3768 die "$mi->{Ref} ?" unless length $ref;
3769 $mi->{Commit} = $ref;
3772 $csuite = join ",", @csuites;
3774 my $previous = git_get_ref lrref;
3776 unshift @mergeinputs, {
3777 Commit => $previous,
3778 Info => (__ "local combined tracking branch"),
3780 "archive seems to have rewound: local tracking branch is ahead!"),
3784 foreach my $ix (0..$#mergeinputs) {
3785 $mergeinputs[$ix]{Index} = $ix;
3788 @mergeinputs = sort {
3789 -version_compare(mergeinfo_version $a,
3790 mergeinfo_version $b) # highest version first
3792 $a->{Index} <=> $b->{Index}; # earliest in spec first
3798 foreach my $mi (@mergeinputs) {
3799 printdebug "multisuite merge check $mi->{Info}\n";
3800 foreach my $previous (@needed) {
3801 next unless is_fast_fwd $mi->{Commit}, $previous->{Commit};
3802 printdebug "multisuite merge un-needed $previous->{Info}\n";
3806 printdebug "multisuite merge this-needed\n";
3807 $mi->{Character} = '+';
3810 $needed[0]{Character} = '*';
3812 my $output = $needed[0]{Commit};
3815 printdebug "multisuite merge nontrivial\n";
3816 my $tree = cmdoutput qw(git rev-parse), $needed[0]{Commit}.':';
3818 my $commit = "tree $tree\n";
3819 my $msg = f_ "Combine archive branches %s [dgit]\n\n".
3820 "Input branches:\n",
3823 foreach my $mi (sort { $a->{Index} <=> $b->{Index} } @mergeinputs) {
3824 printdebug "multisuite merge include $mi->{Info}\n";
3825 $mi->{Character} //= ' ';
3826 $commit .= "parent $mi->{Commit}\n";
3827 $msg .= sprintf " %s %-25s %s\n",
3829 (mergeinfo_version $mi),
3832 my $authline = clogp_authline mergeinfo_getclogp $needed[0];
3833 $msg .= __ "\nKey\n".
3834 " * marks the highest version branch, which choose to use\n".
3835 " + marks each branch which was not already an ancestor\n\n";
3837 "[dgit multi-suite $csuite]\n";
3839 "author $authline\n".
3840 "committer $authline\n\n";
3841 $output = hash_commit_text $commit.$msg;
3842 printdebug "multisuite merge generated $output\n";
3845 fetch_from_archive_record_1($output);
3846 fetch_from_archive_record_2($output);
3848 progress f_ "calculated combined tracking suite %s", $csuite;
3853 sub clone_set_head () {
3854 open H, "> .git/HEAD" or confess "$!";
3855 print H "ref: ".lref()."\n" or confess "$!";
3856 close H or confess "$!";
3858 sub clone_finish ($) {
3860 runcmd @git, qw(reset --hard), lrref();
3861 runcmd qw(bash -ec), <<'END';
3863 git ls-tree -r --name-only -z HEAD | \
3864 xargs -0r touch -h -r . --
3866 printdone f_ "ready for work in %s", $dstdir;
3869 sub vcs_git_url_of_ctrl ($) {
3871 my $vcsgiturl = $ctrl->{'Vcs-Git'};
3872 if (length $vcsgiturl) {
3873 $vcsgiturl =~ s/\s+-b\s+\S+//g;
3874 $vcsgiturl =~ s/\s+\[[^][]*\]//g;
3880 # in multisuite, returns twice!
3881 # once in parent after first suite fetched,
3882 # and then again in child after everything is finished
3884 badusage __ "dry run makes no sense with clone" unless act_local();
3886 my $multi_fetched = fork_for_multisuite(sub {
3887 printdebug "multi clone before fetch merge\n";
3891 if ($multi_fetched) {
3892 printdebug "multi clone after fetch merge\n";
3894 clone_finish($dstdir);
3897 printdebug "clone main body\n";
3899 mkdir $dstdir or fail f_ "create \`%s': %s", $dstdir, $!;
3903 canonicalise_suite();
3904 my $hasgit = check_for_git();
3906 runcmd @git, qw(init -q);
3911 progress __ "fetching existing git history";
3914 progress __ "starting new git history";
3916 fetch_from_archive() or no_such_package;
3917 my $vcsgiturl = vcs_git_url_of_ctrl $dsc;
3918 if (length $vcsgiturl) {
3919 runcmd @git, qw(remote add vcs-git), $vcsgiturl;
3921 clone_finish($dstdir);
3925 canonicalise_suite();
3926 if (check_for_git()) {
3929 fetch_from_archive() or no_such_package();
3931 my $vcsgiturl = $dsc && $dsc->{'Vcs-Git'};
3932 if (length $vcsgiturl and
3933 (grep { $csuite eq $_ }
3935 cfg 'dgit.vcs-git.suites')) {
3936 my $current = cfg 'remote.vcs-git.url', 'RETURN-UNDEF';
3937 if (defined $current && $current ne $vcsgiturl) {
3938 print STDERR f_ <<END, $csuite;
3939 FYI: Vcs-Git in %s has different url to your vcs-git remote.
3940 Your vcs-git remote url may be out of date. Use dgit update-vcs-git ?
3944 printdone f_ "fetched into %s", lrref();
3948 my $multi_fetched = fork_for_multisuite(sub { });
3949 fetch_one() unless $multi_fetched; # parent
3950 finish 0 if $multi_fetched eq '0'; # child
3955 runcmd_ordryrun_local @git, qw(merge -m),
3956 (f_ "Merge from %s [dgit]", $csuite),
3958 printdone f_ "fetched to %s and merged into HEAD", lrref();
3961 sub check_not_dirty () {
3962 my @forbid = qw(local-options local-patch-header);
3963 @forbid = map { "debian/source/$_" } @forbid;
3964 foreach my $f (@forbid) {
3965 if (stat_exists $f) {
3966 fail f_ "git tree contains %s", $f;
3970 my @cmd = (@git, qw(status -uall --ignored --porcelain));
3971 push @cmd, qw(debian/source/format debian/source/options);
3974 my $bad = cmdoutput @cmd;
3977 "you have uncommitted changes to critical files, cannot continue:\n").
3981 return if $includedirty;
3983 git_check_unmodified();
3986 sub commit_admin ($) {
3989 runcmd_ordryrun_local @git, qw(commit -m), $m;
3992 sub quiltify_nofix_bail ($$) {
3993 my ($headinfo, $xinfo) = @_;
3994 if ($quilt_mode eq 'nofix') {
3996 "quilt fixup required but quilt mode is \`nofix'\n".
3997 "HEAD commit%s differs from tree implied by debian/patches%s",
4002 sub commit_quilty_patch () {
4003 my $output = cmdoutput @git, qw(status --ignored --porcelain);
4005 foreach my $l (split /\n/, $output) {
4006 next unless $l =~ m/\S/;
4007 if ($l =~ m{^(?:[?!][?!]| [MADRC]) (.pc|debian/patches)}) {
4011 delete $adds{'.pc'}; # if there wasn't one before, don't add it
4013 progress __ "nothing quilty to commit, ok.";
4016 quiltify_nofix_bail "", __ " (wanted to commit patch update)";
4017 my @adds = map { s/[][*?\\]/\\$&/g; $_; } sort keys %adds;
4018 runcmd_ordryrun_local @git, qw(add -f), @adds;
4019 commit_admin +(__ <<ENDT).<<END
4020 Commit Debian 3.0 (quilt) metadata
4023 [dgit ($our_version) quilt-fixup]
4027 sub get_source_format () {
4029 if (open F, "debian/source/options") {
4033 s/\s+$//; # ignore missing final newline
4035 my ($k, $v) = ($`, $'); #');
4036 $v =~ s/^"(.*)"$/$1/;
4042 F->error and confess "$!";
4045 confess "$!" unless $!==&ENOENT;
4048 if (!open F, "debian/source/format") {
4049 confess "$!" unless $!==&ENOENT;
4053 F->error and confess "$!";
4056 return ($_, \%options);
4059 sub madformat_wantfixup ($) {
4061 return 0 unless $format eq '3.0 (quilt)';
4062 our $quilt_mode_warned;
4063 if ($quilt_mode eq 'nocheck') {
4064 progress f_ "Not doing any fixup of \`%s'".
4065 " due to ----no-quilt-fixup or --quilt=nocheck", $format
4066 unless $quilt_mode_warned++;
4069 progress f_ "Format \`%s', need to check/update patch stack", $format
4070 unless $quilt_mode_warned++;
4074 sub maybe_split_brain_save ($$$) {
4075 my ($headref, $dgitview, $msg) = @_;
4076 # => message fragment "$saved" describing disposition of $dgitview
4077 # (used inside parens, in the English texts)
4078 my $save = $internal_object_save{'dgit-view'};
4079 return f_ "commit id %s", $dgitview unless defined $save;
4080 my @cmd = (shell_cmd 'cd "$1"; shift', $maindir,
4082 "dgit --dgit-view-save $msg HEAD=$headref",
4085 return f_ "and left in %s", $save;
4088 # An "infopair" is a tuple [ $thing, $what ]
4089 # (often $thing is a commit hash; $what is a description)
4091 sub infopair_cond_equal ($$) {
4093 $x->[0] eq $y->[0] or fail <<END;
4094 $x->[1] ($x->[0]) not equal to $y->[1] ($y->[0])
4098 sub infopair_lrf_tag_lookup ($$) {
4099 my ($tagnames, $what) = @_;
4100 # $tagname may be an array ref
4101 my @tagnames = ref $tagnames ? @$tagnames : ($tagnames);
4102 printdebug "infopair_lrfetchref_tag_lookup $what @tagnames\n";
4103 foreach my $tagname (@tagnames) {
4104 my $lrefname = lrfetchrefs."/tags/$tagname";
4105 my $tagobj = $lrfetchrefs_f{$lrefname};
4106 next unless defined $tagobj;
4107 printdebug "infopair_lrfetchref_tag_lookup $tagobj $tagname $what\n";
4108 return [ git_rev_parse($tagobj), $what ];
4110 fail @tagnames==1 ? (f_ <<END, $what, "@tagnames")
4111 Wanted tag %s (%s) on dgit server, but not found
4113 : (f_ <<END, $what, "@tagnames");
4114 Wanted tag %s (one of: %s) on dgit server, but not found
4118 sub infopair_cond_ff ($$) {
4119 my ($anc,$desc) = @_;
4120 is_fast_fwd($anc->[0], $desc->[0]) or
4121 fail f_ <<END, $anc->[1], $anc->[0], $desc->[1], $desc->[0];
4122 %s (%s) .. %s (%s) is not fast forward
4126 sub pseudomerge_version_check ($$) {
4127 my ($clogp, $archive_hash) = @_;
4129 my $arch_clogp = commit_getclogp $archive_hash;
4130 my $i_arch_v = [ (getfield $arch_clogp, 'Version'),
4131 __ 'version currently in archive' ];
4132 if (defined $overwrite_version) {
4133 if (length $overwrite_version) {
4134 infopair_cond_equal([ $overwrite_version,
4135 '--overwrite= version' ],
4138 my $v = $i_arch_v->[0];
4140 "Checking package changelog for archive version %s ...", $v;
4143 my @xa = ("-f$v", "-t$v");
4144 my $vclogp = parsechangelog @xa;
4147 [ (getfield $vclogp, $fn),
4148 (f_ "%s field from dpkg-parsechangelog %s",
4151 my $cv = $gf->('Version');
4152 infopair_cond_equal($i_arch_v, $cv);
4153 $cd = $gf->('Distribution');
4157 $@ =~ s/^dgit: //gm;
4159 f_ "Perhaps debian/changelog does not mention %s ?", $v;
4161 fail f_ <<END, $cd->[1], $cd->[0], $v
4163 Your tree seems to based on earlier (not uploaded) %s.
4165 if $cd->[0] =~ m/UNRELEASED/;
4169 printdebug "pseudomerge_version_check i_arch_v @$i_arch_v\n";
4173 sub pseudomerge_hash_commit ($$$$ $$) {
4174 my ($clogp, $dgitview, $archive_hash, $i_arch_v,
4175 $msg_cmd, $msg_msg) = @_;
4176 progress f_ "Declaring that HEAD includes all changes in %s...",
4179 my $tree = cmdoutput qw(git rev-parse), "${dgitview}:";
4180 my $authline = clogp_authline $clogp;
4184 !defined $overwrite_version ? ""
4185 : !length $overwrite_version ? " --overwrite"
4186 : " --overwrite=".$overwrite_version;
4188 # Contributing parent is the first parent - that makes
4189 # git rev-list --first-parent DTRT.
4190 my $pmf = dgit_privdir()."/pseudomerge";
4191 open MC, ">", $pmf or die "$pmf $!";
4192 print MC <<END or confess "$!";
4195 parent $archive_hash
4203 close MC or confess "$!";
4205 return hash_commit($pmf);
4208 sub splitbrain_pseudomerge ($$$$) {
4209 my ($clogp, $maintview, $dgitview, $archive_hash) = @_;
4210 # => $merged_dgitview
4211 printdebug "splitbrain_pseudomerge...\n";
4213 # We: debian/PREVIOUS HEAD($maintview)
4214 # expect: o ----------------- o
4217 # a/d/PREVIOUS $dgitview
4220 # we do: `------------------ o
4224 return $dgitview unless defined $archive_hash;
4225 return $dgitview if deliberately_not_fast_forward();
4227 printdebug "splitbrain_pseudomerge...\n";
4229 my $i_arch_v = pseudomerge_version_check($clogp, $archive_hash);
4231 if (!defined $overwrite_version) {
4232 progress __ "Checking that HEAD includes all changes in archive...";
4235 return $dgitview if is_fast_fwd $archive_hash, $dgitview;
4237 if (defined $overwrite_version) {
4239 my $t_dep14 = debiantag_maintview $i_arch_v->[0], access_nomdistro;
4240 my $i_dep14 = infopair_lrf_tag_lookup($t_dep14,
4241 __ "maintainer view tag");
4242 my $t_dgit = debiantag_new $i_arch_v->[0], access_nomdistro;
4243 my $i_dgit = infopair_lrf_tag_lookup($t_dgit, __ "dgit view tag");
4244 my $i_archive = [ $archive_hash, __ "current archive contents" ];
4246 printdebug "splitbrain_pseudomerge i_archive @$i_archive\n";
4248 infopair_cond_equal($i_dgit, $i_archive);
4249 infopair_cond_ff($i_dep14, $i_dgit);
4250 infopair_cond_ff($i_dep14, [ $maintview, 'HEAD' ]);
4253 $@ =~ s/^\n//; chomp $@;
4254 print STDERR <<END.(__ <<ENDT);
4257 | Not fast forward; maybe --overwrite is needed ? Please see dgit(1).
4262 my $arch_v = $i_arch_v->[0];
4263 my $r = pseudomerge_hash_commit
4264 $clogp, $dgitview, $archive_hash, $i_arch_v,
4265 "dgit --quilt=$quilt_mode",
4266 (defined $overwrite_version
4267 ? f_ "Declare fast forward from %s\n", $arch_v
4268 : f_ "Make fast forward from %s\n", $arch_v);
4270 maybe_split_brain_save $maintview, $r, "pseudomerge";
4272 progress f_ "Made pseudo-merge of %s into dgit view.", $arch_v;
4276 sub plain_overwrite_pseudomerge ($$$) {
4277 my ($clogp, $head, $archive_hash) = @_;
4279 printdebug "plain_overwrite_pseudomerge...";
4281 my $i_arch_v = pseudomerge_version_check($clogp, $archive_hash);
4283 return $head if is_fast_fwd $archive_hash, $head;
4285 my $m = f_ "Declare fast forward from %s", $i_arch_v->[0];
4287 my $r = pseudomerge_hash_commit
4288 $clogp, $head, $archive_hash, $i_arch_v,
4291 runcmd git_update_ref_cmd $m, 'HEAD', $r, $head;
4293 progress f_ "Make pseudo-merge of %s into your HEAD.", $i_arch_v->[0];
4297 sub push_parse_changelog ($) {
4300 my $clogp = Dpkg::Control::Hash->new();
4301 $clogp->load($clogpfn) or die;
4303 my $clogpackage = getfield $clogp, 'Source';
4304 $package //= $clogpackage;
4305 fail f_ "-p specified %s but changelog specified %s",
4306 $package, $clogpackage
4307 unless $package eq $clogpackage;
4308 my $cversion = getfield $clogp, 'Version';
4310 if (!$we_are_initiator) {
4311 # rpush initiator can't do this because it doesn't have $isuite yet
4312 my $tag = debiantag_new($cversion, access_nomdistro);
4313 runcmd @git, qw(check-ref-format), $tag;
4316 my $dscfn = dscfn($cversion);
4318 return ($clogp, $cversion, $dscfn);
4321 sub push_parse_dsc ($$$) {
4322 my ($dscfn,$dscfnwhat, $cversion) = @_;
4323 $dsc = parsecontrol($dscfn,$dscfnwhat);
4324 my $dversion = getfield $dsc, 'Version';
4325 my $dscpackage = getfield $dsc, 'Source';
4326 ($dscpackage eq $package && $dversion eq $cversion) or
4327 fail f_ "%s is for %s %s but debian/changelog is for %s %s",
4328 $dscfn, $dscpackage, $dversion,
4329 $package, $cversion;
4332 sub push_tagwants ($$$$) {
4333 my ($cversion, $dgithead, $maintviewhead, $tfbase) = @_;
4336 TagFn => \&debiantag_new,
4341 if (defined $maintviewhead) {
4343 TagFn => \&debiantag_maintview,
4344 Objid => $maintviewhead,
4345 TfSuffix => '-maintview',
4348 } elsif ($dodep14tag ne 'no') {
4350 TagFn => \&debiantag_maintview,
4352 TfSuffix => '-dgit',
4356 foreach my $tw (@tagwants) {
4357 $tw->{Tag} = $tw->{TagFn}($cversion, access_nomdistro);
4358 $tw->{Tfn} = sub { $tfbase.$tw->{TfSuffix}.$_[0]; };
4360 printdebug 'push_tagwants: ', Dumper(\@_, \@tagwants);
4364 sub push_mktags ($$ $$ $) {
4366 $changesfile,$changesfilewhat,
4369 die unless $tagwants->[0]{View} eq 'dgit';
4371 my $declaredistro = access_nomdistro();
4372 my $reader_giturl = do { local $access_forpush=0; access_giturl(); };
4373 $dsc->{$ourdscfield[0]} = join " ",
4374 $tagwants->[0]{Objid}, $declaredistro, $tagwants->[0]{Tag},
4376 $dsc->save("$dscfn.tmp") or confess "$!";
4378 my $changes = parsecontrol($changesfile,$changesfilewhat);
4379 foreach my $field (qw(Source Distribution Version)) {
4380 $changes->{$field} eq $clogp->{$field} or
4381 fail f_ "changes field %s \`%s' does not match changelog \`%s'",
4382 $field, $changes->{$field}, $clogp->{$field};
4385 my $cversion = getfield $clogp, 'Version';
4386 my $clogsuite = getfield $clogp, 'Distribution';
4387 my $format = getfield $dsc, 'Format';
4389 # We make the git tag by hand because (a) that makes it easier
4390 # to control the "tagger" (b) we can do remote signing
4391 my $authline = clogp_authline $clogp;
4395 my $tfn = $tw->{Tfn};
4396 my $head = $tw->{Objid};
4397 my $tag = $tw->{Tag};
4399 open TO, '>', $tfn->('.tmp') or confess "$!";
4400 print TO <<END or confess "$!";
4408 my @dtxinfo = @deliberatelies;
4409 unshift @dtxinfo, "--quilt=$quilt_mode" if madformat($format);
4410 unshift @dtxinfo, do_split_brain() ? "split" : "no-split"
4411 # rpush protocol 5 and earlier don't tell us
4412 unless $we_are_initiator && $protovsn < 6;
4413 my $dtxinfo = join(" ", "",@dtxinfo);
4414 my $tag_metadata = <<END;
4415 [dgit distro=$declaredistro$dtxinfo]
4417 foreach my $ref (sort keys %previously) {
4418 $tag_metadata .= <<END or confess "$!";
4419 [dgit previously:$ref=$previously{$ref}]
4423 if ($tw->{View} eq 'dgit') {
4424 print TO sprintf <<ENDT, $package, $cversion, $clogsuite, $csuite
4425 %s release %s for %s (%s) [dgit]
4428 } elsif ($tw->{View} eq 'maint') {
4429 print TO sprintf <<END, $package, $cversion, $clogsuite, $csuite;
4430 %s release %s for %s (%s)
4434 (maintainer view tag generated by dgit --quilt=%s)
4439 confess Dumper($tw)."?";
4441 print TO "\n", $tag_metadata;
4443 close TO or confess "$!";
4445 my $tagobjfn = $tfn->('.tmp');
4447 if (!defined $keyid) {
4448 $keyid = access_cfg('keyid','RETURN-UNDEF');
4450 if (!defined $keyid) {
4451 $keyid = getfield $clogp, 'Maintainer';
4453 unlink $tfn->('.tmp.asc') or $!==&ENOENT or confess "$!";
4454 my @sign_cmd = (@gpg, qw(--detach-sign --armor));
4455 push @sign_cmd, qw(-u),$keyid if defined $keyid;
4456 push @sign_cmd, $tfn->('.tmp');
4457 runcmd_ordryrun @sign_cmd;
4459 $tagobjfn = $tfn->('.signed.tmp');
4460 runcmd shell_cmd "exec >$tagobjfn", qw(cat --),
4461 $tfn->('.tmp'), $tfn->('.tmp.asc');
4467 my @r = map { $mktag->($_); } @$tagwants;
4471 sub sign_changes ($) {
4472 my ($changesfile) = @_;
4474 my @debsign_cmd = @debsign;
4475 push @debsign_cmd, "-k$keyid" if defined $keyid;
4476 push @debsign_cmd, "-p$gpg[0]" if $gpg[0] ne 'gpg';
4477 push @debsign_cmd, $changesfile;
4478 runcmd_ordryrun @debsign_cmd;
4483 printdebug "actually entering push\n";
4485 supplementary_message(__ <<'END');
4486 Push failed, while checking state of the archive.
4487 You can retry the push, after fixing the problem, if you like.
4489 if (check_for_git()) {
4492 my $archive_hash = fetch_from_archive();
4493 if (!$archive_hash) {
4495 fail __ "package appears to be new in this suite;".
4496 " if this is intentional, use --new";
4499 supplementary_message(__ <<'END');
4500 Push failed, while preparing your push.
4501 You can retry the push, after fixing the problem, if you like.
4506 access_giturl(); # check that success is vaguely likely
4507 rpush_handle_protovsn_bothends() if $we_are_initiator;
4509 my $clogpfn = dgit_privdir()."/changelog.822.tmp";
4510 runcmd shell_cmd "exec >$clogpfn", qw(dpkg-parsechangelog);
4512 responder_send_file('parsed-changelog', $clogpfn);
4514 my ($clogp, $cversion, $dscfn) =
4515 push_parse_changelog("$clogpfn");
4517 my $dscpath = "$buildproductsdir/$dscfn";
4518 stat_exists $dscpath or
4519 fail f_ "looked for .dsc %s, but %s; maybe you forgot to build",
4522 responder_send_file('dsc', $dscpath);
4524 push_parse_dsc($dscpath, $dscfn, $cversion);
4526 my $format = getfield $dsc, 'Format';
4528 my $symref = git_get_symref();
4529 my $actualhead = git_rev_parse('HEAD');
4531 if (branch_is_gdr_unstitched_ff($symref, $actualhead, $archive_hash)) {
4532 if (quiltmode_splitting()) {
4533 my ($ffq_prev, $gdrlast) = branch_gdr_info($symref, $actualhead);
4534 fail f_ <<END, $ffq_prev, $quilt_mode;
4535 Branch is managed by git-debrebase (%s
4536 exists), but quilt mode (%s) implies a split view.
4537 Pass the right --quilt option or adjust your git config.
4538 Or, maybe, run git-debrebase forget-was-ever-debrebase.
4541 runcmd_ordryrun_local @git_debrebase, 'stitch';
4542 $actualhead = git_rev_parse('HEAD');
4545 my $dgithead = $actualhead;
4546 my $maintviewhead = undef;
4548 my $upstreamversion = upstreamversion $clogp->{Version};
4550 if (madformat_wantfixup($format)) {
4551 # user might have not used dgit build, so maybe do this now:
4552 if (do_split_brain()) {
4553 changedir $playground;
4555 ($dgithead, $cachekey) =
4556 quilt_check_splitbrain_cache($actualhead, $upstreamversion);
4557 $dgithead or fail f_
4558 "--quilt=%s but no cached dgit view:
4559 perhaps HEAD changed since dgit build[-source] ?",
4562 if (!do_split_brain()) {
4563 # In split brain mode, do not attempt to incorporate dirty
4564 # stuff from the user's working tree. That would be mad.
4565 commit_quilty_patch();
4568 if (do_split_brain()) {
4569 $made_split_brain = 1;
4570 $dgithead = splitbrain_pseudomerge($clogp,
4571 $actualhead, $dgithead,
4573 $maintviewhead = $actualhead;
4575 prep_ud(); # so _only_subdir() works, below
4578 if (defined $overwrite_version && !defined $maintviewhead
4580 $dgithead = plain_overwrite_pseudomerge($clogp,
4588 if ($archive_hash) {
4589 if (is_fast_fwd($archive_hash, $dgithead)) {
4591 } elsif (deliberately_not_fast_forward) {
4594 fail __ "dgit push: HEAD is not a descendant".
4595 " of the archive's version.\n".
4596 "To overwrite the archive's contents,".
4597 " pass --overwrite[=VERSION].\n".
4598 "To rewind history, if permitted by the archive,".
4599 " use --deliberately-not-fast-forward.";
4603 confess unless !!$made_split_brain == do_split_brain();
4605 my $tagname = debiantag_new $cversion, access_nomdistro();
4606 if (!(forceing[qw(reusing-version)]) && git_get_ref "refs/tags/$tagname") {
4607 supplementary_message '';
4608 print STDERR f_ <<END, $cversion;
4610 Version %s has already been tagged (pushed?)
4611 If this was a failed (or incomplete or rejected) upload by you, just
4612 add a new changelog stanza for a new version number and try again.
4614 fail f_ <<END, $tagname;
4615 Tag %s already exists.
4619 changedir $playground;
4620 progress f_ "checking that %s corresponds to HEAD", $dscfn;
4621 runcmd qw(dpkg-source -x --),
4622 $dscpath =~ m#^/# ? $dscpath : "$maindir/$dscpath";
4623 my ($tree,$dir) = mktree_in_ud_from_only_subdir("source package");
4624 check_for_vendor_patches() if madformat($dsc->{format});
4626 my @diffcmd = (@git, qw(diff --quiet), $tree, $dgithead);
4627 debugcmd "+",@diffcmd;
4629 my $r = system @diffcmd;
4632 my $referent = $made_split_brain ? $dgithead : 'HEAD';
4633 my $diffs = cmdoutput @git, qw(diff --stat), $tree, $dgithead;
4636 my $raw = cmdoutput @git,
4637 qw(diff --no-renames -z -r --raw), $tree, $dgithead;
4639 foreach (split /\0/, $raw) {
4640 if (defined $changed) {
4641 push @mode_changes, "$changed: $_\n" if $changed;
4644 } elsif (m/^:0+ 0+ /) {
4646 } elsif (m/^:(?:10*)?(\d+) (?:10*)?(\d+) /) {
4647 $changed = "Mode change from $1 to $2"
4652 if (@mode_changes) {
4653 fail +(f_ <<ENDT, $dscfn).<<END
4654 HEAD specifies a different tree to %s:
4658 .(join '', @mode_changes)
4659 .(f_ <<ENDT, $tree, $referent);
4660 There is a problem with your source tree (see dgit(7) for some hints).
4661 To see a full diff, run git diff %s %s
4665 fail +(f_ <<ENDT, $dscfn).<<END.(f_ <<ENDT, $tree, $referent);
4666 HEAD specifies a different tree to %s:
4670 Perhaps you forgot to build. Or perhaps there is a problem with your
4671 source tree (see dgit(7) for some hints). To see a full diff, run
4678 if (!$changesfile) {
4679 my $pat = changespat $cversion;
4680 my @cs = glob "$buildproductsdir/$pat";
4681 fail f_ "failed to find unique changes file".
4682 " (looked for %s in %s);".
4683 " perhaps you need to use dgit -C",
4684 $pat, $buildproductsdir
4686 ($changesfile) = @cs;
4688 $changesfile = "$buildproductsdir/$changesfile";
4691 # Check that changes and .dsc agree enough
4692 $changesfile =~ m{[^/]*$};
4693 my $changes = parsecontrol($changesfile,$&);
4694 files_compare_inputs($dsc, $changes)
4695 unless forceing [qw(dsc-changes-mismatch)];
4697 # Check whether this is a source only upload
4698 my $hasdebs = $changes->{Files} =~ m{\.deb$}m;
4699 my $sourceonlypolicy = access_cfg 'source-only-uploads';
4700 if ($sourceonlypolicy eq 'ok') {
4701 } elsif ($sourceonlypolicy eq 'always') {
4702 forceable_fail [qw(uploading-binaries)],
4703 __ "uploading binaries, although distro policy is source only"
4705 } elsif ($sourceonlypolicy eq 'never') {
4706 forceable_fail [qw(uploading-source-only)],
4707 __ "source-only upload, although distro policy requires .debs"
4709 } elsif ($sourceonlypolicy eq 'not-wholly-new') {
4710 forceable_fail [qw(uploading-source-only)],
4711 f_ "source-only upload, even though package is entirely NEW\n".
4712 "(this is contrary to policy in %s)",
4716 && !(archive_query('package_not_wholly_new', $package) // 1);
4718 badcfg f_ "unknown source-only-uploads policy \`%s'",
4722 # Perhaps adjust .dsc to contain right set of origs
4723 changes_update_origs_from_dsc($dsc, $changes, $upstreamversion,
4725 unless forceing [qw(changes-origs-exactly)];
4727 # Checks complete, we're going to try and go ahead:
4729 responder_send_file('changes',$changesfile);
4730 responder_send_command("param head $dgithead");
4731 responder_send_command("param csuite $csuite");
4732 responder_send_command("param isuite $isuite");
4733 responder_send_command("param tagformat new"); # needed in $protovsn==4
4734 responder_send_command("param splitbrain $do_split_brain");
4735 if (defined $maintviewhead) {
4736 responder_send_command("param maint-view $maintviewhead");
4739 # Perhaps send buildinfo(s) for signing
4740 my $changes_files = getfield $changes, 'Files';
4741 my @buildinfos = ($changes_files =~ m/ .* (\S+\.buildinfo)$/mg);
4742 foreach my $bi (@buildinfos) {
4743 responder_send_command("param buildinfo-filename $bi");
4744 responder_send_file('buildinfo', "$buildproductsdir/$bi");
4747 if (deliberately_not_fast_forward) {
4748 git_for_each_ref(lrfetchrefs, sub {
4749 my ($objid,$objtype,$lrfetchrefname,$reftail) = @_;
4750 my $rrefname= substr($lrfetchrefname, length(lrfetchrefs) + 1);
4751 responder_send_command("previously $rrefname=$objid");
4752 $previously{$rrefname} = $objid;
4756 my @tagwants = push_tagwants($cversion, $dgithead, $maintviewhead,
4757 dgit_privdir()."/tag");
4760 supplementary_message(__ <<'END');
4761 Push failed, while signing the tag.
4762 You can retry the push, after fixing the problem, if you like.
4764 # If we manage to sign but fail to record it anywhere, it's fine.
4765 if ($we_are_responder) {
4766 @tagobjfns = map { $_->{Tfn}('.signed-tmp') } @tagwants;
4767 responder_receive_files('signed-tag', @tagobjfns);
4769 @tagobjfns = push_mktags($clogp,$dscpath,
4770 $changesfile,$changesfile,
4773 supplementary_message(__ <<'END');
4774 Push failed, *after* signing the tag.
4775 If you want to try again, you should use a new version number.
4778 pairwise { $a->{TagObjFn} = $b } @tagwants, @tagobjfns;
4780 foreach my $tw (@tagwants) {
4781 my $tag = $tw->{Tag};
4782 my $tagobjfn = $tw->{TagObjFn};
4784 cmdoutput @git, qw(hash-object -w -t tag), $tagobjfn;
4785 runcmd_ordryrun @git, qw(verify-tag), $tag_obj_hash;
4786 runcmd_ordryrun_local
4787 @git, qw(update-ref), "refs/tags/$tag", $tag_obj_hash;
4790 supplementary_message(__ <<'END');
4791 Push failed, while updating the remote git repository - see messages above.
4792 If you want to try again, you should use a new version number.
4794 if (!check_for_git()) {
4795 create_remote_git_repo();
4798 my @pushrefs = $forceflag.$dgithead.":".rrref();
4799 foreach my $tw (@tagwants) {
4800 push @pushrefs, $forceflag."refs/tags/$tw->{Tag}";
4803 runcmd_ordryrun @git,
4804 qw(-c push.followTags=false push), access_giturl(), @pushrefs;
4805 runcmd_ordryrun git_update_ref_cmd 'dgit push', lrref(), $dgithead;
4807 supplementary_message(__ <<'END');
4808 Push failed, while obtaining signatures on the .changes and .dsc.
4809 If it was just that the signature failed, you may try again by using
4810 debsign by hand to sign the changes file (see the command dgit tried,
4811 above), and then dput that changes file to complete the upload.
4812 If you need to change the package, you must use a new version number.
4814 if ($we_are_responder) {
4815 my $dryrunsuffix = act_local() ? "" : ".tmp";
4816 my @rfiles = ($dscpath, $changesfile);
4817 push @rfiles, map { "$buildproductsdir/$_" } @buildinfos;
4818 responder_receive_files('signed-dsc-changes',
4819 map { "$_$dryrunsuffix" } @rfiles);
4822 rename "$dscpath.tmp",$dscpath or die "$dscfn $!";
4824 progress f_ "[new .dsc left in %s.tmp]", $dscpath;
4826 sign_changes $changesfile;
4829 supplementary_message(f_ <<END, $changesfile);
4830 Push failed, while uploading package(s) to the archive server.
4831 You can retry the upload of exactly these same files with dput of:
4833 If that .changes file is broken, you will need to use a new version
4834 number for your next attempt at the upload.
4836 my $host = access_cfg('upload-host','RETURN-UNDEF');
4837 my @hostarg = defined($host) ? ($host,) : ();
4838 runcmd_ordryrun @dput, @hostarg, $changesfile;
4839 printdone f_ "pushed and uploaded %s", $cversion;
4841 supplementary_message('');
4842 responder_send_command("complete");
4846 not_necessarily_a_tree();
4851 badusage __ "-p is not allowed with clone; specify as argument instead"
4852 if defined $package;
4855 } elsif (@ARGV==2 && $ARGV[1] =~ m#^\w#) {
4856 ($package,$isuite) = @ARGV;
4857 } elsif (@ARGV==2 && $ARGV[1] =~ m#^[./]#) {
4858 ($package,$dstdir) = @ARGV;
4859 } elsif (@ARGV==3) {
4860 ($package,$isuite,$dstdir) = @ARGV;
4862 badusage __ "incorrect arguments to dgit clone";
4866 $dstdir ||= "$package";
4867 if (stat_exists $dstdir) {
4868 fail f_ "%s already exists", $dstdir;
4872 if ($rmonerror && !$dryrun_level) {
4873 $cwd_remove= getcwd();
4875 return unless defined $cwd_remove;
4876 if (!chdir "$cwd_remove") {
4877 return if $!==&ENOENT;
4878 confess "chdir $cwd_remove: $!";
4880 printdebug "clone rmonerror removing $dstdir\n";
4882 rmtree($dstdir) or fail f_ "remove %s: %s\n", $dstdir, $!;
4883 } elsif (grep { $! == $_ }
4884 (ENOENT, ENOTDIR, EACCES, EPERM, ELOOP)) {
4886 print STDERR f_ "check whether to remove %s: %s\n",
4893 $cwd_remove = undef;
4896 sub branchsuite () {
4897 my $branch = git_get_symref();
4898 if (defined $branch && $branch =~ m#$lbranch_re#o) {
4905 sub package_from_d_control () {
4906 if (!defined $package) {
4907 my $sourcep = parsecontrol('debian/control','debian/control');
4908 $package = getfield $sourcep, 'Source';
4912 sub fetchpullargs () {
4913 package_from_d_control();
4915 $isuite = branchsuite();
4917 my $clogp = parsechangelog();
4918 my $clogsuite = getfield $clogp, 'Distribution';
4919 $isuite= $clogsuite if $clogsuite ne 'UNRELEASED';
4921 } elsif (@ARGV==1) {
4924 badusage __ "incorrect arguments to dgit fetch or dgit pull";
4938 determine_whether_split_brain get_source_format();
4939 if (do_split_brain()) {
4940 my ($format, $fopts) = get_source_format();
4941 madformat($format) and fail f_ <<END, $quilt_mode
4942 dgit pull not yet supported in split view mode (including with view-splitting quilt modes)
4950 package_from_d_control();
4951 @ARGV==1 or badusage __ "dgit checkout needs a suite argument";
4955 foreach my $canon (qw(0 1)) {
4960 canonicalise_suite();
4962 if (length git_get_ref lref()) {
4963 # local branch already exists, yay
4966 if (!length git_get_ref lrref()) {
4974 runcmd (@git, qw(update-ref), lref(), lrref(), '');
4977 local $ENV{GIT_REFLOG_ACTION} = git_reflog_action_msg
4978 "dgit checkout $isuite";
4979 runcmd (@git, qw(checkout), lbranch());
4982 sub cmd_update_vcs_git () {
4984 if (@ARGV==0 || $ARGV[0] =~ m/^-/) {
4985 ($specsuite,) = split /\;/, cfg 'dgit.vcs-git.suites';
4987 ($specsuite) = (@ARGV);
4992 if ($ARGV[0] eq '-') {
4994 } elsif ($ARGV[0] eq '-') {
4999 package_from_d_control();
5001 if ($specsuite eq '.') {
5002 $ctrl = parsecontrol 'debian/control', 'debian/control';
5004 $isuite = $specsuite;
5008 my $url = vcs_git_url_of_ctrl $ctrl;
5009 fail 'no Vcs-Git header in control file' unless length $url;
5012 my $orgurl = cfg 'remote.vcs-git.url', 'RETURN-UNDEF';
5013 if (!defined $orgurl) {
5014 print STDERR f_ "setting up vcs-git: %s\n", $url;
5015 @cmd = (@git, qw(remote add vcs-git), $url);
5016 } elsif ($orgurl eq $url) {
5017 print STDERR f_ "vcs git unchanged: %s\n", $url;
5019 print STDERR f_ "changing vcs-git url to: %s\n", $url;
5020 @cmd = (@git, qw(remote set-url vcs-git), $url);
5022 runcmd_ordryrun_local @cmd if @cmd;
5024 print f_ "fetching (%s)\n", "@ARGV";
5025 runcmd_ordryrun_local @git, qw(fetch vcs-git), @ARGV;
5031 build_or_push_prep_early();
5033 build_or_push_prep_modes();
5037 } elsif (@ARGV==1) {
5038 ($specsuite) = (@ARGV);
5040 badusage f_ "incorrect arguments to dgit %s", $subcommand;
5043 local ($package) = $existing_package; # this is a hack
5044 canonicalise_suite();
5046 canonicalise_suite();
5048 if (defined $specsuite &&
5049 $specsuite ne $isuite &&
5050 $specsuite ne $csuite) {
5051 fail f_ "dgit %s: changelog specifies %s (%s)".
5052 " but command line specifies %s",
5053 $subcommand, $isuite, $csuite, $specsuite;
5062 #---------- remote commands' implementation ----------
5064 sub pre_remote_push_build_host {
5065 my ($nrargs) = shift @ARGV;
5066 my (@rargs) = @ARGV[0..$nrargs-1];
5067 @ARGV = @ARGV[$nrargs..$#ARGV];
5069 my ($dir,$vsnwant) = @rargs;
5070 # vsnwant is a comma-separated list; we report which we have
5071 # chosen in our ready response (so other end can tell if they
5074 $we_are_responder = 1;
5075 $us .= " (build host)";
5077 open PI, "<&STDIN" or confess "$!";
5078 open STDIN, "/dev/null" or confess "$!";
5079 open PO, ">&STDOUT" or confess "$!";
5081 open STDOUT, ">&STDERR" or confess "$!";
5085 ($protovsn) = grep {
5086 $vsnwant =~ m{^(?:.*,)?$_(?:,.*)?$}
5087 } @rpushprotovsn_support;
5089 fail f_ "build host has dgit rpush protocol versions %s".
5090 " but invocation host has %s",
5091 (join ",", @rpushprotovsn_support), $vsnwant
5092 unless defined $protovsn;
5096 sub cmd_remote_push_build_host {
5097 responder_send_command("dgit-remote-push-ready $protovsn");
5101 sub pre_remote_push_responder { pre_remote_push_build_host(); }
5102 sub cmd_remote_push_responder { cmd_remote_push_build_host(); }
5103 # ... for compatibility with proto vsn.1 dgit (just so that user gets
5104 # a good error message)
5106 sub rpush_handle_protovsn_bothends () {
5113 my $report = i_child_report();
5114 if (defined $report) {
5115 printdebug "($report)\n";
5116 } elsif ($i_child_pid) {
5117 printdebug "(killing build host child $i_child_pid)\n";
5118 kill 15, $i_child_pid;
5120 if (defined $i_tmp && !defined $initiator_tempdir) {
5122 eval { rmtree $i_tmp; };
5127 return unless forkcheck_mainprocess();
5132 my ($base,$selector,@args) = @_;
5133 $selector =~ s/\-/_/g;
5134 { no strict qw(refs); &{"${base}_${selector}"}(@args); }
5138 not_necessarily_a_tree();
5143 if ($host =~ m/^((?:[^][]|\[[^][]*\])*)\:/) {
5151 push @rargs, join ",", @rpushprotovsn_support;
5154 push @rdgit, @ropts;
5155 push @rdgit, qw(remote-push-build-host), (scalar @rargs), @rargs;
5157 my @cmd = (@ssh, $host, shellquote @rdgit);
5160 $we_are_initiator=1;
5162 if (defined $initiator_tempdir) {
5163 rmtree $initiator_tempdir;
5164 mkdir $initiator_tempdir, 0700
5165 or fail f_ "create %s: %s", $initiator_tempdir, $!;
5166 $i_tmp = $initiator_tempdir;
5170 $i_child_pid = open2(\*RO, \*RI, @cmd);
5172 ($protovsn) = initiator_expect { m/^dgit-remote-push-ready (\S+)/ };
5173 die "$protovsn ?" unless grep { $_ eq $protovsn } @rpushprotovsn_support;
5176 my ($icmd,$iargs) = initiator_expect {
5177 m/^(\S+)(?: (.*))?$/;
5180 i_method "i_resp", $icmd, $iargs;
5184 sub i_resp_progress ($) {
5186 my $msg = protocol_read_bytes \*RO, $rhs;
5190 sub i_resp_supplementary_message ($) {
5192 $supplementary_message = protocol_read_bytes \*RO, $rhs;
5195 sub i_resp_complete {
5196 my $pid = $i_child_pid;
5197 $i_child_pid = undef; # prevents killing some other process with same pid
5198 printdebug "waiting for build host child $pid...\n";
5199 my $got = waitpid $pid, 0;
5200 confess "$!" unless $got == $pid;
5201 fail f_ "build host child failed: %s", waitstatusmsg() if $?;
5204 printdebug __ "all done\n";
5208 sub i_resp_file ($) {
5210 my $localname = i_method "i_localname", $keyword;
5211 my $localpath = "$i_tmp/$localname";
5212 stat_exists $localpath and
5213 badproto \*RO, f_ "file %s (%s) twice", $keyword, $localpath;
5214 protocol_receive_file \*RO, $localpath;
5215 i_method "i_file", $keyword;
5220 sub i_resp_param ($) {
5221 $_[0] =~ m/^(\S+) (.*)$/ or badproto \*RO, __ "bad param spec";
5225 sub i_resp_previously ($) {
5226 $_[0] =~ m#^(refs/tags/\S+)=(\w+)$#
5227 or badproto \*RO, __ "bad previously spec";
5228 my $r = system qw(git check-ref-format), $1;
5229 confess "bad previously ref spec ($r)" if $r;
5230 $previously{$1} = $2;
5234 our ($i_clogp, $i_version, $i_dscfn, $i_changesfn, @i_buildinfos);
5236 sub i_resp_want ($) {
5238 die "$keyword ?" if $i_wanted{$keyword}++;
5240 defined $i_param{'csuite'} or badproto \*RO, "premature desire, no csuite";
5241 $isuite = $i_param{'isuite'} // $i_param{'csuite'};
5242 die unless $isuite =~ m/^$suite_re$/;
5244 if (!defined $dsc) {
5246 rpush_handle_protovsn_bothends();
5247 push_parse_dsc $i_dscfn, 'remote dsc', $i_version;
5248 if ($protovsn >= 6) {
5249 determine_whether_split_brain getfield $dsc, 'Format';
5250 $do_split_brain eq ($i_param{'splitbrain'} // '<unsent>')
5252 "split brain mismatch, $do_split_brain != $i_param{'split_brain'}";
5253 printdebug "rpush split brain $do_split_brain\n";
5257 my @localpaths = i_method "i_want", $keyword;
5258 printdebug "[[ $keyword @localpaths\n";
5259 foreach my $localpath (@localpaths) {
5260 protocol_send_file \*RI, $localpath;
5262 print RI "files-end\n" or confess "$!";
5265 sub i_localname_parsed_changelog {
5266 return "remote-changelog.822";
5268 sub i_file_parsed_changelog {
5269 ($i_clogp, $i_version, $i_dscfn) =
5270 push_parse_changelog "$i_tmp/remote-changelog.822";
5271 die if $i_dscfn =~ m#/|^\W#;
5274 sub i_localname_dsc {
5275 defined $i_dscfn or badproto \*RO, "dsc (before parsed-changelog)";
5280 sub i_localname_buildinfo ($) {
5281 my $bi = $i_param{'buildinfo-filename'};
5282 defined $bi or badproto \*RO, "buildinfo before filename";
5283 defined $i_changesfn or badproto \*RO, "buildinfo before changes";
5284 $bi =~ m{^\Q$package\E_[!-.0-~]*\.buildinfo$}s
5285 or badproto \*RO, "improper buildinfo filename";
5288 sub i_file_buildinfo {
5289 my $bi = $i_param{'buildinfo-filename'};
5290 my $bd = parsecontrol "$i_tmp/$bi", $bi;
5291 my $ch = parsecontrol "$i_tmp/$i_changesfn", 'changes';
5292 if (!forceing [qw(buildinfo-changes-mismatch)]) {
5293 files_compare_inputs($bd, $ch);
5294 (getfield $bd, $_) eq (getfield $ch, $_) or
5295 fail f_ "buildinfo mismatch in field %s", $_
5296 foreach qw(Source Version);
5297 !defined $bd->{$_} or
5298 fail f_ "buildinfo contains forbidden field %s", $_
5299 foreach qw(Changes Changed-by Distribution);
5301 push @i_buildinfos, $bi;
5302 delete $i_param{'buildinfo-filename'};
5305 sub i_localname_changes {
5306 defined $i_dscfn or badproto \*RO, "dsc (before parsed-changelog)";
5307 $i_changesfn = $i_dscfn;
5308 $i_changesfn =~ s/\.dsc$/_dgit.changes/ or die;
5309 return $i_changesfn;
5311 sub i_file_changes { }
5313 sub i_want_signed_tag {
5314 printdebug Dumper(\%i_param, $i_dscfn);
5315 defined $i_param{'head'} && defined $i_dscfn && defined $i_clogp
5316 && defined $i_param{'csuite'}
5317 or badproto \*RO, "premature desire for signed-tag";
5318 my $head = $i_param{'head'};
5319 die if $head =~ m/[^0-9a-f]/ || $head !~ m/^../;
5321 my $maintview = $i_param{'maint-view'};
5322 die if defined $maintview && $maintview =~ m/[^0-9a-f]/;
5324 if ($protovsn == 4) {
5325 my $p = $i_param{'tagformat'} // '<undef>';
5327 or badproto \*RO, "tag format mismatch: $p vs. new";
5330 die unless $i_param{'csuite'} =~ m/^$suite_re$/;
5332 defined $dsc or badproto \*RO, "dsc (before parsed-changelog)";
5334 my @tagwants = push_tagwants $i_version, $head, $maintview, "tag";
5337 push_mktags $i_clogp, $i_dscfn,
5338 $i_changesfn, (__ 'remote changes file'),
5342 sub i_want_signed_dsc_changes {
5343 rename "$i_dscfn.tmp","$i_dscfn" or die "$i_dscfn $!";
5344 sign_changes $i_changesfn;
5345 return ($i_dscfn, $i_changesfn, @i_buildinfos);
5348 #---------- building etc. ----------
5354 #----- `3.0 (quilt)' handling -----
5356 our $fakeeditorenv = 'DGIT_FAKE_EDITOR_QUILT';
5358 sub quiltify_dpkg_commit ($$$;$) {
5359 my ($patchname,$author,$msg, $xinfo) = @_;
5362 mkpath '.git/dgit'; # we are in playtree
5363 my $descfn = ".git/dgit/quilt-description.tmp";
5364 open O, '>', $descfn or confess "$descfn: $!";
5365 $msg =~ s/\n+/\n\n/;
5366 print O <<END or confess "$!";
5368 ${xinfo}Subject: $msg
5372 close O or confess "$!";
5375 local $ENV{'EDITOR'} = cmdoutput qw(realpath --), $0;
5376 local $ENV{'VISUAL'} = $ENV{'EDITOR'};
5377 local $ENV{$fakeeditorenv} = cmdoutput qw(realpath --), $descfn;
5378 runcmd @dpkgsource, qw(--commit --include-removal .), $patchname;
5382 sub quiltify_trees_differ ($$;$$$) {
5383 my ($x,$y,$finegrained,$ignorenamesr,$unrepres) = @_;
5384 # returns true iff the two tree objects differ other than in debian/
5385 # with $finegrained,
5386 # returns bitmask 01 - differ in upstream files except .gitignore
5387 # 02 - differ in .gitignore
5388 # if $ignorenamesr is defined, $ingorenamesr->{$fn}
5389 # is set for each modified .gitignore filename $fn
5390 # if $unrepres is defined, array ref to which is appeneded
5391 # a list of unrepresentable changes (removals of upstream files
5394 my @cmd = (@git, qw(diff-tree -z --no-renames));
5395 push @cmd, qw(--name-only) unless $unrepres;
5396 push @cmd, qw(-r) if $finegrained || $unrepres;
5398 my $diffs= cmdoutput @cmd;
5401 foreach my $f (split /\0/, $diffs) {
5402 if ($unrepres && !@lmodes) {
5403 @lmodes = $f =~ m/^\:(\w+) (\w+) \w+ \w+ / or die "$_ ?";
5406 my ($oldmode,$newmode) = @lmodes;
5409 next if $f =~ m#^debian(?:/.*)?$#s;
5413 die __ "not a plain file or symlink\n"
5414 unless $newmode =~ m/^(?:10|12)\d{4}$/ ||
5415 $oldmode =~ m/^(?:10|12)\d{4}$/;
5416 if ($oldmode =~ m/[^0]/ &&
5417 $newmode =~ m/[^0]/) {
5418 # both old and new files exist
5419 die __ "mode or type changed\n" if $oldmode ne $newmode;
5420 die __ "modified symlink\n" unless $newmode =~ m/^10/;
5421 } elsif ($oldmode =~ m/[^0]/) {
5423 die __ "deletion of symlink\n"
5424 unless $oldmode =~ m/^10/;
5427 die __ "creation with non-default mode\n"
5428 unless $newmode =~ m/^100644$/ or
5429 $newmode =~ m/^120000$/;
5433 local $/="\n"; chomp $@;
5434 push @$unrepres, [ $f, "$@ ($oldmode->$newmode)" ];
5438 my $isignore = $f =~ m#^(?:.*/)?.gitignore$#s;
5439 $r |= $isignore ? 02 : 01;
5440 $ignorenamesr->{$f}=1 if $ignorenamesr && $isignore;
5442 printdebug "quiltify_trees_differ $x $y => $r\n";
5446 sub quiltify_tree_sentinelfiles ($) {
5447 # lists the `sentinel' files present in the tree
5449 my $r = cmdoutput @git, qw(ls-tree --name-only), $x,
5450 qw(-- debian/rules debian/control);
5455 sub quiltify_splitting ($$$$$$$) {
5456 my ($clogp, $unapplied, $headref, $oldtiptree, $diffbits,
5457 $editedignores, $cachekey) = @_;
5458 my $gitignore_special = 1;
5459 if ($quilt_mode !~ m/gbp|dpm|baredebian/) {
5460 # treat .gitignore just like any other upstream file
5461 $diffbits = { %$diffbits };
5462 $_ = !!$_ foreach values %$diffbits;
5463 $gitignore_special = 0;
5465 # We would like any commits we generate to be reproducible
5466 my @authline = clogp_authline($clogp);
5467 local $ENV{GIT_COMMITTER_NAME} = $authline[0];
5468 local $ENV{GIT_COMMITTER_EMAIL} = $authline[1];
5469 local $ENV{GIT_COMMITTER_DATE} = $authline[2];
5470 local $ENV{GIT_AUTHOR_NAME} = $authline[0];
5471 local $ENV{GIT_AUTHOR_EMAIL} = $authline[1];
5472 local $ENV{GIT_AUTHOR_DATE} = $authline[2];
5474 confess unless do_split_brain();
5476 my $fulldiffhint = sub {
5478 my $cmd = "git diff $x $y -- :/ ':!debian'";
5479 $cmd .= " ':!/.gitignore' ':!*/.gitignore'" if $gitignore_special;
5480 return f_ "\nFor full diff showing the problem(s), type:\n %s\n",
5484 if ($quilt_mode =~ m/gbp|unapplied|baredebian/ &&
5485 ($diffbits->{O2H} & 01)) {
5487 "--quilt=%s specified, implying patches-unapplied git tree\n".
5488 " but git tree differs from orig in upstream files.",
5490 $msg .= $fulldiffhint->($unapplied, 'HEAD');
5491 if (!stat_exists "debian/patches" and $quilt_mode !~ m/baredebian/) {
5493 "\n ... debian/patches is missing; perhaps this is a patch queue branch?";
5497 if ($quilt_mode =~ m/dpm/ &&
5498 ($diffbits->{H2A} & 01)) {
5499 fail +(f_ <<END, $quilt_mode). $fulldiffhint->($oldtiptree,'HEAD');
5500 --quilt=%s specified, implying patches-applied git tree
5501 but git tree differs from result of applying debian/patches to upstream
5504 if ($quilt_mode =~ m/baredebian/) {
5505 # We need to construct a merge which has upstream files from
5506 # upstream and debian/ files from HEAD.
5508 read_tree_upstream $quilt_upstream_commitish, 1, $headref;
5509 my $version = getfield $clogp, 'Version';
5510 my $upsversion = upstreamversion $version;
5511 my $merge = make_commit
5512 [ $headref, $quilt_upstream_commitish ],
5513 [ +(f_ <<ENDT, $upsversion), $quilt_upstream_commitish_message, <<ENDU ];
5514 Combine debian/ with upstream source for %s
5516 [dgit ($our_version) baredebian-merge $version $quilt_upstream_commitish_used]
5518 runcmd @git, qw(reset -q --hard), $merge;
5520 if ($quilt_mode =~ m/gbp|unapplied|baredebian/ &&
5521 ($diffbits->{O2A} & 01)) { # some patches
5522 progress __ "dgit view: creating patches-applied version using gbp pq";
5523 runcmd shell_cmd 'exec >/dev/null', gbp_pq, qw(import);
5524 # gbp pq import creates a fresh branch; push back to dgit-view
5525 runcmd @git, qw(update-ref refs/heads/dgit-view HEAD);
5526 runcmd @git, qw(checkout -q dgit-view);
5528 if ($quilt_mode =~ m/gbp|dpm/ &&
5529 ($diffbits->{O2A} & 02)) {
5530 fail f_ <<END, $quilt_mode;
5531 --quilt=%s specified, implying that HEAD is for use with a
5532 tool which does not create patches for changes to upstream
5533 .gitignores: but, such patches exist in debian/patches.
5536 if (($diffbits->{O2H} & 02) && # user has modified .gitignore
5537 !($diffbits->{O2A} & 02)) { # patches do not change .gitignore
5539 "dgit view: creating patch to represent .gitignore changes";
5540 ensuredir "debian/patches";
5541 my $gipatch = "debian/patches/auto-gitignore";
5542 open GIPATCH, ">>", "$gipatch" or confess "$gipatch: $!";
5543 stat GIPATCH or confess "$gipatch: $!";
5544 fail f_ "%s already exists; but want to create it".
5545 " to record .gitignore changes",
5548 print GIPATCH +(__ <<END).<<ENDU or die "$gipatch: $!";
5549 Subject: Update .gitignore from Debian packaging branch
5551 The Debian packaging git branch contains these updates to the upstream
5552 .gitignore file(s). This patch is autogenerated, to provide these
5553 updates to users of the official Debian archive view of the package.
5556 [dgit ($our_version) update-gitignore]
5559 close GIPATCH or die "$gipatch: $!";
5560 runcmd shell_cmd "exec >>$gipatch", @git, qw(diff),
5561 $unapplied, $headref, "--", sort keys %$editedignores;
5562 open SERIES, "+>>", "debian/patches/series" or confess "$!";
5563 defined seek SERIES, -1, 2 or $!==EINVAL or confess "$!";
5565 defined read SERIES, $newline, 1 or confess "$!";
5566 print SERIES "\n" or confess "$!" unless $newline eq "\n";
5567 print SERIES "auto-gitignore\n" or confess "$!";
5568 close SERIES or die $!;
5569 runcmd @git, qw(add -f -- debian/patches/series), $gipatch;
5570 commit_admin +(__ <<END).<<ENDU
5571 Commit patch to update .gitignore
5574 [dgit ($our_version) update-gitignore-quilt-fixup]
5579 sub quiltify ($$$$) {
5580 my ($clogp,$target,$oldtiptree,$failsuggestion) = @_;
5582 # Quilt patchification algorithm
5584 # We search backwards through the history of the main tree's HEAD
5585 # (T) looking for a start commit S whose tree object is identical
5586 # to to the patch tip tree (ie the tree corresponding to the
5587 # current dpkg-committed patch series). For these purposes
5588 # `identical' disregards anything in debian/ - this wrinkle is
5589 # necessary because dpkg-source treates debian/ specially.
5591 # We can only traverse edges where at most one of the ancestors'
5592 # trees differs (in changes outside in debian/). And we cannot
5593 # handle edges which change .pc/ or debian/patches. To avoid
5594 # going down a rathole we avoid traversing edges which introduce
5595 # debian/rules or debian/control. And we set a limit on the
5596 # number of edges we are willing to look at.
5598 # If we succeed, we walk forwards again. For each traversed edge
5599 # PC (with P parent, C child) (starting with P=S and ending with
5600 # C=T) to we do this:
5602 # - dpkg-source --commit with a patch name and message derived from C
5603 # After traversing PT, we git commit the changes which
5604 # should be contained within debian/patches.
5606 # The search for the path S..T is breadth-first. We maintain a
5607 # todo list containing search nodes. A search node identifies a
5608 # commit, and looks something like this:
5610 # Commit => $git_commit_id,
5611 # Child => $c, # or undef if P=T
5612 # Whynot => $reason_edge_PC_unsuitable, # in @nots only
5613 # Nontrivial => true iff $p..$c has relevant changes
5620 my %considered; # saves being exponential on some weird graphs
5622 my $t_sentinels = quiltify_tree_sentinelfiles $target;
5625 my ($search,$whynot) = @_;
5626 printdebug " search NOT $search->{Commit} $whynot\n";
5627 $search->{Whynot} = $whynot;
5628 push @nots, $search;
5629 no warnings qw(exiting);
5638 my $c = shift @todo;
5639 next if $considered{$c->{Commit}}++;
5641 $not->($c, __ "maximum search space exceeded") if --$max_work <= 0;
5643 printdebug "quiltify investigate $c->{Commit}\n";
5646 if (!quiltify_trees_differ $c->{Commit}, $oldtiptree) {
5647 printdebug " search finished hooray!\n";
5652 quiltify_nofix_bail " $c->{Commit}", " (tree object $oldtiptree)";
5653 if ($quilt_mode eq 'smash') {
5654 printdebug " search quitting smash\n";
5658 my $c_sentinels = quiltify_tree_sentinelfiles $c->{Commit};
5659 $not->($c, f_ "has %s not %s", $c_sentinels, $t_sentinels)
5660 if $c_sentinels ne $t_sentinels;
5662 my $commitdata = cmdoutput @git, qw(cat-file commit), $c->{Commit};
5663 $commitdata =~ m/\n\n/;
5665 my @parents = ($commitdata =~ m/^parent (\w+)$/gm);
5666 @parents = map { { Commit => $_, Child => $c } } @parents;
5668 $not->($c, __ "root commit") if !@parents;
5670 foreach my $p (@parents) {
5671 $p->{Nontrivial}= quiltify_trees_differ $p->{Commit},$c->{Commit};
5673 my $ndiffers = grep { $_->{Nontrivial} } @parents;
5674 $not->($c, f_ "merge (%s nontrivial parents)", $ndiffers)
5677 foreach my $p (@parents) {
5678 printdebug "considering C=$c->{Commit} P=$p->{Commit}\n";
5680 my @cmd= (@git, qw(diff-tree -r --name-only),
5681 $p->{Commit},$c->{Commit},
5682 qw(-- debian/patches .pc debian/source/format));
5683 my $patchstackchange = cmdoutput @cmd;
5684 if (length $patchstackchange) {
5685 $patchstackchange =~ s/\n/,/g;
5686 $not->($p, f_ "changed %s", $patchstackchange);
5689 printdebug " search queue P=$p->{Commit} ",
5690 ($p->{Nontrivial} ? "NT" : "triv"),"\n";
5696 printdebug "quiltify want to smash\n";
5699 my $x = $_[0]{Commit};
5700 $x =~ s/(.*?[0-9a-z]{8})[0-9a-z]*$/$1/;
5703 if ($quilt_mode eq 'linear') {
5705 "\n%s: error: quilt fixup cannot be linear. Stopped at:\n",
5707 my $all_gdr = !!@nots;
5708 foreach my $notp (@nots) {
5709 my $c = $notp->{Child};
5710 my $cprange = $abbrev->($notp);
5711 $cprange .= "..".$abbrev->($c) if $c;
5712 print STDERR f_ "%s: %s: %s\n",
5713 $us, $cprange, $notp->{Whynot};
5714 $all_gdr &&= $notp->{Child} &&
5715 (git_cat_file $notp->{Child}{Commit}, 'commit')
5716 =~ m{^\[git-debrebase(?! split[: ]).*\]$}m;
5720 [ grep { $_->[0] ne 'quilt-mode' } @$failsuggestion ]
5722 print STDERR "$us: $_->[1]\n" foreach @$failsuggestion;
5724 "quilt history linearisation failed. Search \`quilt fixup' in dgit(7).\n";
5725 } elsif ($quilt_mode eq 'smash') {
5726 } elsif ($quilt_mode eq 'auto') {
5727 progress __ "quilt fixup cannot be linear, smashing...";
5729 confess "$quilt_mode ?";
5732 my $time = $ENV{'GIT_COMMITTER_DATE'} || time;
5733 $time =~ s/\s.*//; # trim timezone from GIT_COMMITTER_DATE
5735 my $msg = cmdoutput @git, qw(log), "-n$ncommits";
5737 quiltify_dpkg_commit "auto-$version-$target-$time",
5738 (getfield $clogp, 'Maintainer'),
5739 (f_ "Automatically generated patch (%s)\n".
5740 "Last (up to) %s git changes, FYI:\n\n",
5741 $clogp->{Version}, $ncommits).
5746 progress __ "quiltify linearisation planning successful, executing...";
5748 for (my $p = $sref_S;
5749 my $c = $p->{Child};
5751 printdebug "quiltify traverse $p->{Commit}..$c->{Commit}\n";
5752 next unless $p->{Nontrivial};
5754 my $cc = $c->{Commit};
5756 my $commitdata = cmdoutput @git, qw(cat-file commit), $cc;
5757 $commitdata =~ m/\n\n/ or die "$c ?";
5760 $commitdata =~ m/^author (.*) \d+ [-+0-9]+$/m or die "$cc ?";
5763 my $commitdate = cmdoutput
5764 @git, qw(log -n1 --pretty=format:%aD), $cc;
5766 $msg =~ s/^(.*)\n*/$1\n/ or die "$cc $msg ?";
5768 my $strip_nls = sub { $msg =~ s/\n+$//; $msg .= "\n"; };
5775 my $gbp_check_suitable = sub {
5780 die __ "contains unexpected slashes\n" if m{//} || m{/$};
5781 die __ "contains leading punctuation\n" if m{^\W} || m{/\W};
5782 die __ "contains bad character(s)\n" if m{[^-a-z0-9_.+=~/]}i;
5783 die __ "is series file\n" if m{$series_filename_re}o;
5784 die __ "too long\n" if length > 200;
5786 return $_ unless $@;
5788 "quiltifying commit %s: ignoring/dropping Gbp-Pq %s: %s",
5793 if ($msg =~ s/^ (?: gbp(?:-pq)? : \s* name \s+ |
5795 (\S+) \s* \n //ixm) {
5796 $patchname = $gbp_check_suitable->($1, 'Name');
5798 if ($msg =~ s/^ (?: gbp(?:-pq)? : \s* topic \s+ |
5800 (\S+) \s* \n //ixm) {
5801 $patchdir = $gbp_check_suitable->($1, 'Topic');
5806 if (!defined $patchname) {
5807 $patchname = $title;
5808 $patchname =~ s/[.:]$//;
5811 my $converter = new Text::Iconv qw(UTF-8 ASCII//TRANSLIT);
5812 my $translitname = $converter->convert($patchname);
5813 die unless defined $translitname;
5814 $patchname = $translitname;
5817 +(f_ "dgit: patch title transliteration error: %s", $@)
5819 $patchname =~ y/ A-Z/-a-z/;
5820 $patchname =~ y/-a-z0-9_.+=~//cd;
5821 $patchname =~ s/^\W/x-$&/;
5822 $patchname = substr($patchname,0,40);
5823 $patchname .= ".patch";
5825 if (!defined $patchdir) {
5828 if (length $patchdir) {
5829 $patchname = "$patchdir/$patchname";
5831 if ($patchname =~ m{^(.*)/}) {
5832 mkpath "debian/patches/$1";
5837 stat "debian/patches/$patchname$index";
5839 $!==ENOENT or confess "$patchname$index $!";
5841 runcmd @git, qw(checkout -q), $cc;
5843 # We use the tip's changelog so that dpkg-source doesn't
5844 # produce complaining messages from dpkg-parsechangelog. None
5845 # of the information dpkg-source gets from the changelog is
5846 # actually relevant - it gets put into the original message
5847 # which dpkg-source provides our stunt editor, and then
5849 runcmd @git, qw(checkout -q), $target, qw(debian/changelog);
5851 quiltify_dpkg_commit "$patchname$index", $author, $msg,
5852 "Date: $commitdate\n".
5853 "X-Dgit-Generated: $clogp->{Version} $cc\n";
5855 runcmd @git, qw(checkout -q), $cc, qw(debian/changelog);
5859 sub build_maybe_quilt_fixup () {
5860 my ($format,$fopts) = get_source_format;
5861 return unless madformat_wantfixup $format;
5864 check_for_vendor_patches();
5866 my $clogp = parsechangelog();
5867 my $headref = git_rev_parse('HEAD');
5868 my $symref = git_get_symref();
5869 my $upstreamversion = upstreamversion $version;
5872 changedir $playground;
5874 my $splitbrain_cachekey;
5876 if (do_split_brain()) {
5878 ($cachehit, $splitbrain_cachekey) =
5879 quilt_check_splitbrain_cache($headref, $upstreamversion);
5886 unpack_playtree_need_cd_work($headref);
5887 if (do_split_brain()) {
5888 runcmd @git, qw(checkout -q -b dgit-view);
5889 # so long as work is not deleted, its current branch will
5890 # remain dgit-view, rather than master, so subsequent calls to
5891 # unpack_playtree_need_cd_work
5892 # will DTRT, resetting dgit-view.
5893 confess if $made_split_brain;
5894 $made_split_brain = 1;
5898 if ($fopts->{'single-debian-patch'}) {
5900 "quilt mode %s does not make sense (or is not supported) with single-debian-patch",
5902 if quiltmode_splitting();
5903 quilt_fixup_singlepatch($clogp, $headref, $upstreamversion);
5905 quilt_fixup_multipatch($clogp, $headref, $upstreamversion,
5906 $splitbrain_cachekey);
5909 if (do_split_brain()) {
5910 my $dgitview = git_rev_parse 'HEAD';
5913 reflog_cache_insert "refs/$splitbraincache",
5914 $splitbrain_cachekey, $dgitview;
5916 changedir "$playground/work";
5918 my $saved = maybe_split_brain_save $headref, $dgitview, __ "converted";
5919 progress f_ "dgit view: created (%s)", $saved;
5923 runcmd_ordryrun_local
5924 @git, qw(pull --ff-only -q), "$playground/work", qw(master);
5927 sub build_check_quilt_splitbrain () {
5928 build_maybe_quilt_fixup();
5931 sub unpack_playtree_need_cd_work ($) {
5934 # prep_ud() must have been called already.
5935 if (!chdir "work") {
5936 # Check in the filesystem because sometimes we run prep_ud
5937 # in between multiple calls to unpack_playtree_need_cd_work.
5938 confess "$!" unless $!==ENOENT;
5939 mkdir "work" or confess "$!";
5941 mktree_in_ud_here();
5943 runcmd @git, qw(reset -q --hard), $headref;
5946 sub unpack_playtree_linkorigs ($$) {
5947 my ($upstreamversion, $fn) = @_;
5948 # calls $fn->($leafname);
5950 my $bpd_abs = bpd_abs();
5952 dotdot_bpd_transfer_origs $bpd_abs, $upstreamversion, sub { 1 };
5954 opendir QFD, $bpd_abs or fail "buildproductsdir: $bpd_abs: $!";
5955 while ($!=0, defined(my $leaf = readdir QFD)) {
5956 my $f = bpd_abs()."/".$leaf;
5958 local ($debuglevel) = $debuglevel-1;
5959 printdebug "QF linkorigs bpd $leaf, $f ?\n";
5961 next unless is_orig_file_of_vsn $leaf, $upstreamversion;
5962 printdebug "QF linkorigs $leaf, $f Y\n";
5963 link_ltarget $f, $leaf or die "$leaf $!";
5966 die "$buildproductsdir: $!" if $!;
5970 sub quilt_fixup_delete_pc () {
5971 runcmd @git, qw(rm -rqf .pc);
5972 commit_admin +(__ <<END).<<ENDU
5973 Commit removal of .pc (quilt series tracking data)
5976 [dgit ($our_version) upgrade quilt-remove-pc]
5980 sub quilt_fixup_singlepatch ($$$) {
5981 my ($clogp, $headref, $upstreamversion) = @_;
5983 progress __ "starting quiltify (single-debian-patch)";
5985 # dpkg-source --commit generates new patches even if
5986 # single-debian-patch is in debian/source/options. In order to
5987 # get it to generate debian/patches/debian-changes, it is
5988 # necessary to build the source package.
5990 unpack_playtree_linkorigs($upstreamversion, sub { });
5991 unpack_playtree_need_cd_work($headref);
5993 rmtree("debian/patches");
5995 runcmd @dpkgsource, qw(-b .);
5997 runcmd @dpkgsource, qw(-x), (srcfn $version, ".dsc");
5998 rename srcfn("$upstreamversion", "/debian/patches"),
5999 "work/debian/patches"
6001 or confess "install d/patches: $!";
6004 commit_quilty_patch();
6007 sub quilt_need_fake_dsc ($) {
6008 # cwd should be playground
6009 my ($upstreamversion) = @_;
6011 return if stat_exists "fake.dsc";
6012 # ^ OK to test this as a sentinel because if we created it
6013 # we must either have done the rest too, or crashed.
6015 my $fakeversion="$upstreamversion-~~DGITFAKE";
6017 my $fakedsc=new IO::File 'fake.dsc', '>' or confess "$!";
6018 print $fakedsc <<END or confess "$!";
6021 Version: $fakeversion
6025 my $dscaddfile=sub {
6028 my $md = new Digest::MD5;
6030 my $fh = new IO::File $leaf, '<' or die "$leaf $!";
6031 stat $fh or confess "$!";
6035 print $fakedsc " ".$md->hexdigest." $size $leaf\n" or confess "$!";
6038 unpack_playtree_linkorigs($upstreamversion, $dscaddfile);
6040 my @files=qw(debian/source/format debian/rules
6041 debian/control debian/changelog);
6042 foreach my $maybe (qw(debian/patches debian/source/options
6043 debian/tests/control)) {
6044 next unless stat_exists "$maindir/$maybe";
6045 push @files, $maybe;
6048 my $debtar= srcfn $fakeversion,'.debian.tar.gz';
6049 runcmd qw(env GZIP=-1n tar -zcf), "./$debtar", qw(-C), $maindir, @files;
6051 $dscaddfile->($debtar);
6052 close $fakedsc or confess "$!";
6055 sub quilt_fakedsc2unapplied ($$) {
6056 my ($headref, $upstreamversion) = @_;
6057 # must be run in the playground
6058 # quilt_need_fake_dsc must have been called
6060 quilt_need_fake_dsc($upstreamversion);
6062 'exec dpkg-source --no-check --skip-patches -x fake.dsc >/dev/null';
6064 my $fakexdir= $package.'-'.(stripepoch $upstreamversion);
6065 rename $fakexdir, "fake" or die "$fakexdir $!";
6069 remove_stray_gits(__ "source package");
6070 mktree_in_ud_here();
6074 rmtree 'debian'; # git checkout commitish paths does not delete!
6075 runcmd @git, qw(checkout -f), $headref, qw(-- debian);
6076 my $unapplied=git_add_write_tree();
6077 printdebug "fake orig tree object $unapplied\n";
6081 sub quilt_check_splitbrain_cache ($$) {
6082 my ($headref, $upstreamversion) = @_;
6083 # Called only if we are in (potentially) split brain mode.
6084 # Called in playground.
6085 # Computes the cache key and looks in the cache.
6086 # Returns ($dgit_view_commitid, $cachekey) or (undef, $cachekey)
6088 quilt_need_fake_dsc($upstreamversion);
6090 my $splitbrain_cachekey;
6093 "dgit: split brain (separate dgit view) may be needed (--quilt=%s).",
6095 # we look in the reflog of dgit-intern/quilt-cache
6096 # we look for an entry whose message is the key for the cache lookup
6097 my @cachekey = (qw(dgit), $our_version);
6098 push @cachekey, $upstreamversion;
6099 push @cachekey, $quilt_mode;
6100 push @cachekey, $headref;
6101 push @cachekey, $quilt_upstream_commitish // '-';
6103 push @cachekey, hashfile('fake.dsc');
6105 my $srcshash = Digest::SHA->new(256);
6106 my %sfs = ( %INC, '$0(dgit)' => $0 );
6107 foreach my $sfk (sort keys %sfs) {
6108 next unless $sfk =~ m/^\$0\b/ || $sfk =~ m{^Debian/Dgit\b};
6109 $srcshash->add($sfk," ");
6110 $srcshash->add(hashfile($sfs{$sfk}));
6111 $srcshash->add("\n");
6113 push @cachekey, $srcshash->hexdigest();
6114 $splitbrain_cachekey = "@cachekey";
6116 printdebug "splitbrain cachekey $splitbrain_cachekey\n";
6118 my $cachehit = reflog_cache_lookup
6119 "refs/$splitbraincache", $splitbrain_cachekey;
6122 unpack_playtree_need_cd_work($headref);
6123 my $saved = maybe_split_brain_save $headref, $cachehit, "cache-hit";
6124 if ($cachehit ne $headref) {
6125 progress f_ "dgit view: found cached (%s)", $saved;
6126 runcmd @git, qw(checkout -q -b dgit-view), $cachehit;
6127 $made_split_brain = 1;
6128 return ($cachehit, $splitbrain_cachekey);
6130 progress __ "dgit view: found cached, no changes required";
6131 return ($headref, $splitbrain_cachekey);
6134 printdebug "splitbrain cache miss\n";
6135 return (undef, $splitbrain_cachekey);
6138 sub baredebian_origtarballs_scan ($$$) {
6139 my ($fakedfi, $upstreamversion, $dir) = @_;
6140 if (!opendir OD, $dir) {
6141 return if $! == ENOENT;
6142 fail "opendir $dir (origs): $!";
6145 while ($!=0, defined(my $leaf = readdir OD)) {
6147 local ($debuglevel) = $debuglevel-1;
6148 printdebug "BDOS $dir $leaf ?\n";
6150 next unless is_orig_file_of_vsn $leaf, $upstreamversion;
6151 next if grep { $_->{Filename} eq $leaf } @$fakedfi;
6154 Path => "$dir/$leaf",
6158 die "$dir; $!" if $!;
6162 sub quilt_fixup_multipatch ($$$) {
6163 my ($clogp, $headref, $upstreamversion, $splitbrain_cachekey) = @_;
6165 progress f_ "examining quilt state (multiple patches, %s mode)",
6169 # - honour any existing .pc in case it has any strangeness
6170 # - determine the git commit corresponding to the tip of
6171 # the patch stack (if there is one)
6172 # - if there is such a git commit, convert each subsequent
6173 # git commit into a quilt patch with dpkg-source --commit
6174 # - otherwise convert all the differences in the tree into
6175 # a single git commit
6179 # Our git tree doesn't necessarily contain .pc. (Some versions of
6180 # dgit would include the .pc in the git tree.) If there isn't
6181 # one, we need to generate one by unpacking the patches that we
6184 # We first look for a .pc in the git tree. If there is one, we
6185 # will use it. (This is not the normal case.)
6187 # Otherwise need to regenerate .pc so that dpkg-source --commit
6188 # can work. We do this as follows:
6189 # 1. Collect all relevant .orig from parent directory
6190 # 2. Generate a debian.tar.gz out of
6191 # debian/{patches,rules,source/format,source/options}
6192 # 3. Generate a fake .dsc containing just these fields:
6193 # Format Source Version Files
6194 # 4. Extract the fake .dsc
6195 # Now the fake .dsc has a .pc directory.
6196 # (In fact we do this in every case, because in future we will
6197 # want to search for a good base commit for generating patches.)
6199 # Then we can actually do the dpkg-source --commit
6200 # 1. Make a new working tree with the same object
6201 # store as our main tree and check out the main
6203 # 2. Copy .pc from the fake's extraction, if necessary
6204 # 3. Run dpkg-source --commit
6205 # 4. If the result has changes to debian/, then
6206 # - git add them them
6207 # - git add .pc if we had a .pc in-tree
6209 # 5. If we had a .pc in-tree, delete it, and git commit
6210 # 6. Back in the main tree, fast forward to the new HEAD
6212 # Another situation we may have to cope with is gbp-style
6213 # patches-unapplied trees.
6215 # We would want to detect these, so we know to escape into
6216 # quilt_fixup_gbp. However, this is in general not possible.
6217 # Consider a package with a one patch which the dgit user reverts
6218 # (with git revert or the moral equivalent).
6220 # That is indistinguishable in contents from a patches-unapplied
6221 # tree. And looking at the history to distinguish them is not
6222 # useful because the user might have made a confusing-looking git
6223 # history structure (which ought to produce an error if dgit can't
6224 # cope, not a silent reintroduction of an unwanted patch).
6226 # So gbp users will have to pass an option. But we can usually
6227 # detect their failure to do so: if the tree is not a clean
6228 # patches-applied tree, quilt linearisation fails, but the tree
6229 # _is_ a clean patches-unapplied tree, we can suggest that maybe
6230 # they want --quilt=unapplied.
6232 # To help detect this, when we are extracting the fake dsc, we
6233 # first extract it with --skip-patches, and then apply the patches
6234 # afterwards with dpkg-source --before-build. That lets us save a
6235 # tree object corresponding to .origs.
6237 if ($quilt_mode eq 'linear'
6238 && branch_is_gdr($headref)) {
6239 # This is much faster. It also makes patches that gdr
6240 # likes better for future updates without laundering.
6242 # However, it can fail in some casses where we would
6243 # succeed: if there are existing patches, which correspond
6244 # to a prefix of the branch, but are not in gbp/gdr
6245 # format, gdr will fail (exiting status 7), but we might
6246 # be able to figure out where to start linearising. That
6247 # will be slower so hopefully there's not much to do.
6249 unpack_playtree_need_cd_work $headref;
6251 my @cmd = (@git_debrebase,
6252 qw(--noop-ok -funclean-mixed -funclean-ordering
6253 make-patches --quiet-would-amend));
6254 # We tolerate soe snags that gdr wouldn't, by default.
6260 and not ($? == 7*256 or
6261 $? == -1 && $!==ENOENT);
6265 $headref = git_rev_parse('HEAD');
6270 my $unapplied=quilt_fakedsc2unapplied($headref, $upstreamversion);
6274 my @bbcmd = (qw(sh -ec), 'exec dpkg-source --before-build . >/dev/null');
6276 if (system @bbcmd) {
6277 failedcmd @bbcmd if $? < 0;
6279 failed to apply your git tree's patch stack (from debian/patches/) to
6280 the corresponding upstream tarball(s). Your source tree and .orig
6281 are probably too inconsistent. dgit can only fix up certain kinds of
6282 anomaly (depending on the quilt mode). Please see --quilt= in dgit(1).
6288 unpack_playtree_need_cd_work($headref);
6291 if (stat_exists ".pc") {
6293 progress __ "Tree already contains .pc - will use it then delete it.";
6296 rename '../fake/.pc','.pc' or confess "$!";
6299 changedir '../fake';
6301 my $oldtiptree=git_add_write_tree();
6302 printdebug "fake o+d/p tree object $unapplied\n";
6303 changedir '../work';
6306 # We calculate some guesswork now about what kind of tree this might
6307 # be. This is mostly for error reporting.
6309 my $tentries = cmdoutput @git, qw(ls-tree --name-only -z), $headref;
6310 my $onlydebian = $tentries eq "debian\0";
6312 my $uheadref = $headref;
6313 my $uhead_whatshort = 'HEAD';
6315 if ($quilt_mode =~ m/baredebian\+tarball/) {
6316 # We need to make a tarball import. Yuk.
6317 # We want to do this here so that we have a $uheadref value
6320 baredebian_origtarballs_scan \@fakedfi, $upstreamversion, bpd_abs();
6321 baredebian_origtarballs_scan \@fakedfi, $upstreamversion,
6322 "$maindir/.." unless $buildproductsdir eq '..';
6325 my @tartrees = import_tarball_tartrees $upstreamversion, \@fakedfi;
6327 fail __ "baredebian quilt fixup: could not find any origs"
6331 my ($authline, $r1authline, $clogp,) =
6332 import_tarball_commits \@tartrees, $upstreamversion;
6334 if (@tartrees == 1) {
6335 $uheadref = $tartrees[0]{Commit};
6336 # TRANSLATORS: this translation must fit in the ASCII art
6337 # quilt differences display. The untranslated display
6338 # says %9.9s, so with that display it must be at most 9
6340 $uhead_whatshort = __ 'tarball';
6342 # on .dsc import we do not make a separate commit, but
6343 # here we need to do so
6344 rm_subdir_cached '.';
6346 foreach my $ti (@tartrees) {
6347 my $c = $ti->{Commit};
6348 if ($ti->{OrigPart} eq 'orig') {
6349 runcmd qw(git read-tree), $c;
6350 } elsif ($ti->{OrigPart} =~ m/orig-/) {
6351 read_tree_subdir $', $c;
6353 confess "$ti->OrigPart} ?"
6355 $parents .= "parent $c\n";
6357 my $tree = git_write_tree();
6358 my $mbody = f_ 'Combine orig tarballs for %s %s',
6359 $package, $upstreamversion;
6360 $uheadref = hash_commit_text <<END;
6362 ${parents}author $r1authline
6363 committer $r1authline
6367 [dgit import tarballs combine $package $upstreamversion]
6369 # TRANSLATORS: this translation must fit in the ASCII art
6370 # quilt differences display. The untranslated display
6371 # says %9.9s, so with that display it must be at most 9
6372 # characters. This fragmentt is referring to multiple
6373 # orig tarballs in a source package.
6374 $uhead_whatshort = __ 'tarballs';
6376 runcmd @git, qw(reset -q);
6378 $quilt_upstream_commitish = $uheadref;
6379 $quilt_upstream_commitish_used = '*orig*';
6380 $quilt_upstream_commitish_message = '';
6382 if ($quilt_mode =~ m/baredebian$/) {
6383 $uheadref = $quilt_upstream_commitish;
6384 # TRANSLATORS: this translation must fit in the ASCII art
6385 # quilt differences display. The untranslated display
6386 # says %9.9s, so with that display it must be at most 9
6388 $uhead_whatshort = __ 'upstream';
6395 # O = orig, without patches applied
6396 # A = "applied", ie orig with H's debian/patches applied
6397 O2H => quiltify_trees_differ($unapplied,$uheadref, 1,
6398 \%editedignores, \@unrepres),
6399 H2A => quiltify_trees_differ($uheadref, $oldtiptree,1),
6400 O2A => quiltify_trees_differ($unapplied,$oldtiptree,1),
6404 foreach my $bits (qw(01 02)) {
6405 foreach my $v (qw(O2H O2A H2A)) {
6406 push @dl, ($diffbits->{$v} & $bits) ? '##' : '==';
6409 printdebug "differences \@dl @dl.\n";
6412 "%s: base trees orig=%.20s o+d/p=%.20s",
6413 $us, $unapplied, $oldtiptree;
6414 # TRANSLATORS: Try to keep this ascii-art layout right. The 0s in
6415 # %9.00009s will be ignored and are there to make the format the
6416 # same length (9 characters) as the output it generates. If you
6417 # change the value 9, your translations of "upstream" and
6418 # 'tarball' must fit into the new length, and you should change
6419 # the number of 0s. Do not reduce it below 4 as HEAD has to fit
6422 "%s: quilt differences: src: %s orig %s gitignores: %s orig %s\n".
6423 "%s: quilt differences: %9.00009s %s o+d/p %9.00009s %s o+d/p",
6424 $us, $dl[0], $dl[1], $dl[3], $dl[4],
6425 $us, $uhead_whatshort, $dl[2], $uhead_whatshort, $dl[5];
6427 if (@unrepres && $quilt_mode !~ m/baredebian/) {
6428 # With baredebian, even if the upstream commitish has this
6429 # problem, we don't want to print this message, as nothing
6430 # is going to try to make a patch out of it anyway.
6431 print STDERR f_ "dgit: cannot represent change: %s: %s\n",
6434 forceable_fail [qw(unrepresentable)], __ <<END;
6435 HEAD has changes to .orig[s] which are not representable by `3.0 (quilt)'
6441 push @failsuggestion, [ 'onlydebian', __
6442 "This has only a debian/ directory; you probably want --quilt=bare debian." ]
6443 unless $quilt_mode =~ m/baredebian/;
6444 } elsif (!($diffbits->{O2H} & $diffbits->{O2A})) {
6445 push @failsuggestion, [ 'unapplied', __
6446 "This might be a patches-unapplied branch." ];
6447 } elsif (!($diffbits->{H2A} & $diffbits->{O2A})) {
6448 push @failsuggestion, [ 'applied', __
6449 "This might be a patches-applied branch." ];
6451 push @failsuggestion, [ 'quilt-mode', __
6452 "Maybe you need one of --[quilt=]gbp --[quilt=]dpm --quilt=unapplied ?" ];
6454 push @failsuggestion, [ 'gitattrs', __
6455 "Warning: Tree has .gitattributes. See GITATTRIBUTES in dgit(7)." ]
6456 if stat_exists '.gitattributes';
6458 push @failsuggestion, [ 'origs', __
6459 "Maybe orig tarball(s) are not identical to git representation?" ]
6460 unless $onlydebian && $quilt_mode !~ m/baredebian/;
6461 # ^ in that case, we didn't really look properly
6463 if (quiltmode_splitting()) {
6464 quiltify_splitting($clogp, $unapplied, $headref, $oldtiptree,
6465 $diffbits, \%editedignores,
6466 $splitbrain_cachekey);
6470 progress f_ "starting quiltify (multiple patches, %s mode)", $quilt_mode;
6471 quiltify($clogp,$headref,$oldtiptree,\@failsuggestion);
6472 runcmd @git, qw(checkout -q), (qw(master dgit-view)[do_split_brain()]);
6474 if (!open P, '>>', ".pc/applied-patches") {
6475 $!==&ENOENT or confess "$!";
6480 commit_quilty_patch();
6482 if ($mustdeletepc) {
6483 quilt_fixup_delete_pc();
6487 sub quilt_fixup_editor () {
6488 my $descfn = $ENV{$fakeeditorenv};
6489 my $editing = $ARGV[$#ARGV];
6490 open I1, '<', $descfn or confess "$descfn: $!";
6491 open I2, '<', $editing or confess "$editing: $!";
6492 unlink $editing or confess "$editing: $!";
6493 open O, '>', $editing or confess "$editing: $!";
6494 while (<I1>) { print O or confess "$!"; } I1->error and confess "$!";
6497 $copying ||= m/^\-\-\- /;
6498 next unless $copying;
6499 print O or confess "$!";
6501 I2->error and confess "$!";
6506 sub maybe_apply_patches_dirtily () {
6507 return unless $quilt_mode =~ m/gbp|unapplied|baredebian/;
6508 print STDERR __ <<END or confess "$!";
6510 dgit: Building, or cleaning with rules target, in patches-unapplied tree.
6511 dgit: Have to apply the patches - making the tree dirty.
6512 dgit: (Consider specifying --clean=git and (or) using dgit sbuild.)
6515 $patches_applied_dirtily = 01;
6516 $patches_applied_dirtily |= 02 unless stat_exists '.pc';
6517 runcmd qw(dpkg-source --before-build .);
6520 sub maybe_unapply_patches_again () {
6521 progress __ "dgit: Unapplying patches again to tidy up the tree."
6522 if $patches_applied_dirtily;
6523 runcmd qw(dpkg-source --after-build .)
6524 if $patches_applied_dirtily & 01;
6526 if $patches_applied_dirtily & 02;
6527 $patches_applied_dirtily = 0;
6530 #----- other building -----
6532 sub clean_tree_check_git ($$$) {
6533 my ($honour_ignores, $message, $ignmessage) = @_;
6534 my @cmd = (@git, qw(clean -dn));
6535 push @cmd, qw(-x) unless $honour_ignores;
6536 my $leftovers = cmdoutput @cmd;
6537 if (length $leftovers) {
6538 print STDERR $leftovers, "\n" or confess "$!";
6539 $message .= $ignmessage if $honour_ignores;
6544 sub clean_tree_check_git_wd ($) {
6546 return if $cleanmode =~ m{no-check};
6547 return if $patches_applied_dirtily; # yuk
6548 clean_tree_check_git +($cleanmode !~ m{all-check}),
6549 $message, "\n".__ <<END;
6550 If this is just missing .gitignore entries, use a different clean
6551 mode, eg --clean=dpkg-source,no-check (-wdn/-wddn) to ignore them
6552 or --clean=git (-wg/-wgf) to use \`git clean' instead.
6556 sub clean_tree_check () {
6557 # This function needs to not care about modified but tracked files.
6558 # That was done by check_not_dirty, and by now we may have run
6559 # the rules clean target which might modify tracked files (!)
6560 if ($cleanmode =~ m{^check}) {
6561 clean_tree_check_git +($cleanmode =~ m{ignores}), __
6562 "tree contains uncommitted files and --clean=check specified", '';
6563 } elsif ($cleanmode =~ m{^dpkg-source}) {
6564 clean_tree_check_git_wd __
6565 "tree contains uncommitted files (NB dgit didn't run rules clean)";
6566 } elsif ($cleanmode =~ m{^git}) {
6567 clean_tree_check_git 1, __
6568 "tree contains uncommited, untracked, unignored files\n".
6569 "You can use --clean=git[-ff],always (-wga/-wgfa) to delete them.", '';
6570 } elsif ($cleanmode eq 'none') {
6572 confess "$cleanmode ?";
6577 # We always clean the tree ourselves, rather than leave it to the
6578 # builder (dpkg-source, or soemthing which calls dpkg-source).
6579 if ($quilt_mode =~ m/baredebian/ and $cleanmode =~ m/git/) {
6580 fail f_ <<END, $quilt_mode, $cleanmode;
6581 quilt mode %s (generally needs untracked upstream files)
6582 contradicts clean mode %s (which would delete them)
6584 # This is not 100% true: dgit build-source and push-source
6585 # (for example) could operate just fine with no upstream
6586 # source in the working tree. But it doesn't seem likely that
6587 # the user wants dgit to proactively delete such things.
6588 # -wn, for example, would produce identical output without
6589 # deleting anything from the working tree.
6591 if ($cleanmode =~ m{^dpkg-source}) {
6592 my @cmd = @dpkgbuildpackage;
6593 push @cmd, qw(-d) if $cleanmode =~ m{^dpkg-source-d};
6594 push @cmd, qw(-T clean);
6595 maybe_apply_patches_dirtily();
6596 runcmd_ordryrun_local @cmd;
6597 clean_tree_check_git_wd __
6598 "tree contains uncommitted files (after running rules clean)";
6599 } elsif ($cleanmode =~ m{^git(?!-)}) {
6600 runcmd_ordryrun_local @git, qw(clean -xdf);
6601 } elsif ($cleanmode =~ m{^git-ff}) {
6602 runcmd_ordryrun_local @git, qw(clean -xdff);
6603 } elsif ($cleanmode =~ m{^check}) {
6605 } elsif ($cleanmode eq 'none') {
6607 confess "$cleanmode ?";
6612 badusage __ "clean takes no additional arguments" if @ARGV;
6615 maybe_unapply_patches_again();
6618 # return values from massage_dbp_args are one or both of these flags
6619 sub WANTSRC_SOURCE () { 01; } # caller should build source (separately)
6620 sub WANTSRC_BUILDER () { 02; } # caller should run dpkg-buildpackage
6622 sub build_or_push_prep_early () {
6623 our $build_or_push_prep_early_done //= 0;
6624 return if $build_or_push_prep_early_done++;
6625 my $clogp = parsechangelog();
6626 $isuite = getfield $clogp, 'Distribution';
6627 my $gotpackage = getfield $clogp, 'Source';
6628 $version = getfield $clogp, 'Version';
6629 $package //= $gotpackage;
6630 if ($package ne $gotpackage) {
6631 fail f_ "-p specified package %s, but changelog says %s",
6632 $package, $gotpackage;
6634 $dscfn = dscfn($version);
6637 sub build_or_push_prep_modes () {
6638 my ($format) = get_source_format();
6639 determine_whether_split_brain($format);
6641 fail __ "dgit: --include-dirty is not supported with split view".
6642 " (including with view-splitting quilt modes)"
6643 if do_split_brain() && $includedirty;
6645 if (madformat_wantfixup $format and $quilt_mode =~ m/baredebian$/) {
6646 ($quilt_upstream_commitish, $quilt_upstream_commitish_used,
6647 $quilt_upstream_commitish_message)
6648 = resolve_upstream_version
6649 $quilt_upstream_commitish, upstreamversion $version;
6650 progress f_ "dgit: --quilt=%s, %s", $quilt_mode,
6651 $quilt_upstream_commitish_message;
6652 } elsif (defined $quilt_upstream_commitish) {
6654 "dgit: --upstream-commitish only makes sense with --quilt=baredebian"
6658 sub build_prep_early () {
6659 build_or_push_prep_early();
6661 build_or_push_prep_modes();
6665 sub build_prep ($) {
6669 if (!building_source_in_playtree() || ($wantsrc & WANTSRC_BUILDER)
6670 # Clean the tree because we're going to use the contents of
6671 # $maindir. (We trying to include dirty changes in the source
6672 # package, or we are running the builder in $maindir.)
6673 || $cleanmode =~ m{always}) {
6674 # Or because the user asked us to.
6677 # We don't actually need to do anything in $maindir, but we
6678 # should do some kind of cleanliness check because (i) the
6679 # user may have forgotten a `git add', and (ii) if the user
6680 # said -wc we should still do the check.
6683 build_check_quilt_splitbrain();
6685 my $pat = changespat $version;
6686 foreach my $f (glob "$buildproductsdir/$pat") {
6689 fail f_ "remove old changes file %s: %s", $f, $!;
6691 progress f_ "would remove %s", $f;
6697 sub maybe_warn_opt_confusion ($$$) {
6698 my ($subcommand, $willrun, $optsref) = @_;
6699 foreach (@$optsref) {
6700 if (m/^(?: --dry-run $
6702 | --clean= | -w[gcnd]
6703 | --(?:include|ignore)-dirty$
6704 | --quilt= | --gbp$ | --dpm$ | --baredebian
6706 | --build-products-dir=
6708 print STDERR f_ <<END, $&, $subcommand or die $!;
6709 warning: dgit option %s must be passed before %s on dgit command line
6715 print STDERR f_ <<END, $&, $subcommand, $willrun or die $!;
6716 warning: option %s should probably be passed to dgit before %s sub-command on the dgit command line, so that it is seen by dgit and not simply passed to %s
6722 sub changesopts_initial () {
6723 my @opts =@changesopts[1..$#changesopts];
6726 sub changesopts_version () {
6727 if (!defined $changes_since_version) {
6730 @vsns = archive_query('archive_query');
6731 my @quirk = access_quirk();
6732 if ($quirk[0] eq 'backports') {
6733 local $isuite = $quirk[2];
6735 canonicalise_suite();
6736 push @vsns, archive_query('archive_query');
6742 "archive query failed (queried because --since-version not specified)";
6745 @vsns = map { $_->[0] } @vsns;
6746 @vsns = sort { -version_compare($a, $b) } @vsns;
6747 $changes_since_version = $vsns[0];
6748 progress f_ "changelog will contain changes since %s", $vsns[0];
6750 $changes_since_version = '_';
6751 progress __ "package seems new, not specifying -v<version>";
6754 if ($changes_since_version ne '_') {
6755 return ("-v$changes_since_version");
6761 sub changesopts () {
6762 return (changesopts_initial(), changesopts_version());
6765 sub massage_dbp_args ($;$) {
6766 my ($cmd,$xargs) = @_;
6767 # Since we split the source build out so we can do strange things
6768 # to it, massage the arguments to dpkg-buildpackage so that the
6769 # main build doessn't build source (or add an argument to stop it
6770 # building source by default).
6771 debugcmd '#massaging#', @$cmd if $debuglevel>1;
6772 # -nc has the side effect of specifying -b if nothing else specified
6773 # and some combinations of -S, -b, et al, are errors, rather than
6774 # later simply overriding earlie. So we need to:
6775 # - search the command line for these options
6776 # - pick the last one
6777 # - perhaps add our own as a default
6778 # - perhaps adjust it to the corresponding non-source-building version
6780 foreach my $l ($cmd, $xargs) {
6782 @$l = grep { !(m/^-[SgGFABb]$|^--build=/s and $dmode=$_) } @$l;
6785 #print STDERR "MASS1 ",Dumper($cmd, $xargs, $dmode);
6786 my $r = WANTSRC_BUILDER;
6787 printdebug "massage split $dmode.\n";
6788 if ($dmode =~ s/^--build=//) {
6790 my @d = split /,/, $dmode;
6791 $r |= WANTSRC_SOURCE if grep { s/^full$/binary/ } @d;
6792 $r |= WANTSRC_SOURCE if grep { s/^source$// } @d;
6793 $r |= WANTSRC_BUILDER if grep { m/./ } @d;
6794 fail __ "Wanted to build nothing!" unless $r;
6795 $dmode = '--build='. join ',', grep m/./, @d;
6798 $dmode =~ m/[S]/ ? WANTSRC_SOURCE :
6799 $dmode =~ y/gGF/ABb/ ? WANTSRC_SOURCE | WANTSRC_BUILDER :
6800 $dmode =~ m/[ABb]/ ? WANTSRC_BUILDER :
6803 printdebug "massage done $r $dmode.\n";
6805 #print STDERR "MASS2 ",Dumper($cmd, $xargs, $r);
6811 my $wasdir = must_getcwd();
6812 changedir $buildproductsdir;
6817 # this sub must run with CWD=$buildproductsdir (eg in in_bpd)
6818 sub postbuild_mergechanges ($) {
6819 my ($msg_if_onlyone) = @_;
6820 # If there is only one .changes file, fail with $msg_if_onlyone,
6821 # or if that is undef, be a no-op.
6822 # Returns the changes file to report to the user.
6823 my $pat = changespat $version;
6824 my @changesfiles = grep { !m/_multi\.changes/ } glob $pat;
6825 @changesfiles = sort {
6826 ($b =~ m/_source\.changes$/ <=> $a =~ m/_source\.changes$/)
6830 if (@changesfiles==1) {
6831 fail +(f_ <<END, "@changesfiles").$msg_if_onlyone
6832 only one changes file from build (%s)
6834 if defined $msg_if_onlyone;
6835 $result = $changesfiles[0];
6836 } elsif (@changesfiles==2) {
6837 my $binchanges = parsecontrol($changesfiles[1], "binary changes file");
6838 foreach my $l (split /\n/, getfield $binchanges, 'Files') {
6839 fail f_ "%s found in binaries changes file %s", $l, $binchanges
6842 runcmd_ordryrun_local @mergechanges, @changesfiles;
6843 my $multichanges = changespat $version,'multi';
6845 stat_exists $multichanges or fail f_
6846 "%s unexpectedly not created by build", $multichanges;
6847 foreach my $cf (glob $pat) {
6848 next if $cf eq $multichanges;
6849 rename "$cf", "$cf.inmulti" or fail f_
6850 "install new changes %s\{,.inmulti}: %s", $cf, $!;
6853 $result = $multichanges;
6855 fail f_ "wrong number of different changes files (%s)",
6858 printdone f_ "build successful, results in %s\n", $result
6862 sub midbuild_checkchanges () {
6863 my $pat = changespat $version;
6864 return if $rmchanges;
6865 my @unwanted = map { s#.*/##; $_; } glob "$bpd_glob/$pat";
6867 $_ ne changespat $version,'source' and
6868 $_ ne changespat $version,'multi'
6870 fail +(f_ <<END, $pat, "@unwanted")
6871 changes files other than source matching %s already present; building would result in ambiguity about the intended results.
6872 Suggest you delete %s.
6877 sub midbuild_checkchanges_vanilla ($) {
6879 midbuild_checkchanges() if $wantsrc == (WANTSRC_SOURCE|WANTSRC_BUILDER);
6882 sub postbuild_mergechanges_vanilla ($) {
6884 if ($wantsrc == (WANTSRC_SOURCE|WANTSRC_BUILDER)) {
6886 postbuild_mergechanges(undef);
6889 printdone __ "build successful\n";
6895 maybe_warn_opt_confusion 'build', 'dpkg-buildpackage', \@ARGV;
6896 $buildproductsdir eq '..' or print STDERR +(f_ <<END, $us, $us);
6897 %s: warning: build-products-dir set, but not supported by dpkg-buildpackage
6898 %s: warning: build-products-dir will be ignored; files will go to ..
6900 $buildproductsdir = '..';
6901 my @dbp = (@dpkgbuildpackage, qw(-us -uc), changesopts_initial(), @ARGV);
6902 my $wantsrc = massage_dbp_args \@dbp;
6903 build_prep($wantsrc);
6904 if ($wantsrc & WANTSRC_SOURCE) {
6906 midbuild_checkchanges_vanilla $wantsrc;
6908 if ($wantsrc & WANTSRC_BUILDER) {
6909 push @dbp, changesopts_version();
6910 maybe_apply_patches_dirtily();
6911 runcmd_ordryrun_local @dbp;
6913 maybe_unapply_patches_again();
6914 postbuild_mergechanges_vanilla $wantsrc;
6918 $quilt_mode //= 'gbp';
6923 maybe_warn_opt_confusion 'gbp-build', 'gbp buildpackage', \@ARGV;
6925 # gbp can make .origs out of thin air. In my tests it does this
6926 # even for a 1.0 format package, with no origs present. So I
6927 # guess it keys off just the version number. We don't know
6928 # exactly what .origs ought to exist, but let's assume that we
6929 # should run gbp if: the version has an upstream part and the main
6931 my $upstreamversion = upstreamversion $version;
6932 my $origfnpat = srcfn $upstreamversion, '.orig.tar.*';
6933 my $gbp_make_orig = $version =~ m/-/ && !(() = glob "$bpd_glob/$origfnpat");
6935 if ($gbp_make_orig) {
6937 $cleanmode = 'none'; # don't do it again
6940 my @dbp = @dpkgbuildpackage;
6942 my $wantsrc = massage_dbp_args \@dbp, \@ARGV;
6944 if (!length $gbp_build[0]) {
6945 if (length executable_on_path('git-buildpackage')) {
6946 $gbp_build[0] = qw(git-buildpackage);
6948 $gbp_build[0] = 'gbp buildpackage';
6951 my @cmd = opts_opt_multi_cmd [], @gbp_build;
6953 push @cmd, (qw(-us -uc --git-no-sign-tags),
6954 "--git-builder=".(shellquote @dbp));
6956 if ($gbp_make_orig) {
6957 my $priv = dgit_privdir();
6958 my $ok = "$priv/origs-gen-ok";
6959 unlink $ok or $!==&ENOENT or confess "$!";
6960 my @origs_cmd = @cmd;
6961 push @origs_cmd, qw(--git-cleaner=true);
6962 push @origs_cmd, "--git-prebuild=".
6963 "touch ".(shellquote $ok)." ".(shellquote "$priv/no-such-dir/ok");
6964 push @origs_cmd, @ARGV;
6966 debugcmd @origs_cmd;
6968 do { local $!; stat_exists $ok; }
6969 or failedcmd @origs_cmd;
6971 dryrun_report @origs_cmd;
6975 build_prep($wantsrc);
6976 if ($wantsrc & WANTSRC_SOURCE) {
6978 midbuild_checkchanges_vanilla $wantsrc;
6980 push @cmd, '--git-cleaner=true';
6982 maybe_unapply_patches_again();
6983 if ($wantsrc & WANTSRC_BUILDER) {
6984 push @cmd, changesopts();
6985 runcmd_ordryrun_local @cmd, @ARGV;
6987 postbuild_mergechanges_vanilla $wantsrc;
6989 sub cmd_git_build { cmd_gbp_build(); } # compatibility with <= 1.0
6991 sub building_source_in_playtree {
6992 # If $includedirty, we have to build the source package from the
6993 # working tree, not a playtree, so that uncommitted changes are
6994 # included (copying or hardlinking them into the playtree could
6997 # Note that if we are building a source package in split brain
6998 # mode we do not support including uncommitted changes, because
6999 # that makes quilt fixup too hard. I.e. ($made_split_brain && (dgit is
7000 # building a source package)) => !$includedirty
7001 return !$includedirty;
7005 $sourcechanges = changespat $version,'source';
7007 unlink "$buildproductsdir/$sourcechanges" or $!==ENOENT
7008 or fail f_ "remove %s: %s", $sourcechanges, $!;
7010 # confess unless !!$made_split_brain == do_split_brain();
7012 my @cmd = (@dpkgsource, qw(-b --));
7014 if (building_source_in_playtree()) {
7016 my $headref = git_rev_parse('HEAD');
7017 # If we are in split brain, there is already a playtree with
7018 # the thing we should package into a .dsc (thanks to quilt
7019 # fixup). If not, make a playtree
7020 prep_ud() unless $made_split_brain;
7021 changedir $playground;
7022 unless ($made_split_brain) {
7023 my $upstreamversion = upstreamversion $version;
7024 unpack_playtree_linkorigs($upstreamversion, sub { });
7025 unpack_playtree_need_cd_work($headref);
7029 $leafdir = basename $maindir;
7031 if ($buildproductsdir ne '..') {
7032 # Well, we are going to run dpkg-source -b which consumes
7033 # origs from .. and generates output there. To make this
7034 # work when the bpd is not .. , we would have to (i) link
7035 # origs from bpd to .. , (ii) check for files that
7036 # dpkg-source -b would/might overwrite, and afterwards
7037 # (iii) move all the outputs back to the bpd (iv) except
7038 # for the origs which should be deleted from .. if they
7039 # weren't there beforehand. And if there is an error and
7040 # we don't run to completion we would necessarily leave a
7041 # mess. This is too much. The real way to fix this
7042 # is for dpkg-source to have bpd support.
7043 confess unless $includedirty;
7045 "--include-dirty not supported with --build-products-dir, sorry";
7050 runcmd_ordryrun_local @cmd, $leafdir;
7053 runcmd_ordryrun_local qw(sh -ec),
7054 'exec >../$1; shift; exec "$@"','x', $sourcechanges,
7055 @dpkggenchanges, qw(-S), changesopts();
7058 printdebug "moving $dscfn, $sourcechanges, etc. to ".bpd_abs()."\n";
7059 $dsc = parsecontrol($dscfn, "source package");
7063 printdebug " renaming ($why) $l\n";
7064 rename_link_xf 0, "$l", bpd_abs()."/$l"
7065 or fail f_ "put in place new built file (%s): %s", $l, $@;
7067 foreach my $l (split /\n/, getfield $dsc, 'Files') {
7068 $l =~ m/\S+$/ or next;
7071 $mv->('dsc', $dscfn);
7072 $mv->('changes', $sourcechanges);
7077 sub cmd_build_source {
7078 badusage __ "build-source takes no additional arguments" if @ARGV;
7079 build_prep(WANTSRC_SOURCE);
7081 maybe_unapply_patches_again();
7082 printdone f_ "source built, results in %s and %s",
7083 $dscfn, $sourcechanges;
7086 sub cmd_push_source {
7089 "dgit push-source: --include-dirty/--ignore-dirty does not make".
7090 "sense with push-source!"
7092 build_check_quilt_splitbrain();
7094 my $changes = parsecontrol("$buildproductsdir/$changesfile",
7095 __ "source changes file");
7096 unless (test_source_only_changes($changes)) {
7097 fail __ "user-specified changes file is not source-only";
7100 # Building a source package is very fast, so just do it
7102 confess "er, patches are applied dirtily but shouldn't be.."
7103 if $patches_applied_dirtily;
7104 $changesfile = $sourcechanges;
7109 sub binary_builder {
7110 my ($bbuilder, $pbmc_msg, @args) = @_;
7111 build_prep(WANTSRC_SOURCE);
7113 midbuild_checkchanges();
7116 stat_exists $dscfn or fail f_
7117 "%s (in build products dir): %s", $dscfn, $!;
7118 stat_exists $sourcechanges or fail f_
7119 "%s (in build products dir): %s", $sourcechanges, $!;
7121 runcmd_ordryrun_local @$bbuilder, @args;
7123 maybe_unapply_patches_again();
7125 postbuild_mergechanges($pbmc_msg);
7131 maybe_warn_opt_confusion 'sbuild', 'sbuild', \@ARGV;
7132 binary_builder(\@sbuild, (__ <<END), qw(-d), $isuite, @ARGV, $dscfn);
7133 perhaps you need to pass -A ? (sbuild's default is to build only
7134 arch-specific binaries; dgit 1.4 used to override that.)
7139 my ($pbuilder) = @_;
7141 maybe_warn_opt_confusion 'pbuilder', 'pbuilder', \@ARGV;
7142 # @ARGV is allowed to contain only things that should be passed to
7143 # pbuilder under debbuildopts; just massage those
7144 my $wantsrc = massage_dbp_args \@ARGV;
7146 "you asked for a builder but your debbuildopts didn't ask for".
7147 " any binaries -- is this really what you meant?"
7148 unless $wantsrc & WANTSRC_BUILDER;
7150 "we must build a .dsc to pass to the builder but your debbuiltopts".
7151 " forbids the building of a source package; cannot continue"
7152 unless $wantsrc & WANTSRC_SOURCE;
7153 # We do not want to include the verb "build" in @pbuilder because
7154 # the user can customise @pbuilder and they shouldn't be required
7155 # to include "build" in their customised value. However, if the
7156 # user passes any additional args to pbuilder using the dgit
7157 # option --pbuilder:foo, such args need to come after the "build"
7158 # verb. opts_opt_multi_cmd does all of that.
7159 binary_builder([opts_opt_multi_cmd ["build"], @$pbuilder], undef,
7160 qw(--debbuildopts), "@ARGV", qw(--distribution), $isuite,
7165 pbuilder(\@pbuilder);
7168 sub cmd_cowbuilder {
7169 pbuilder(\@cowbuilder);
7172 sub cmd_quilt_fixup {
7173 badusage "incorrect arguments to dgit quilt-fixup" if @ARGV;
7176 build_maybe_quilt_fixup();
7179 sub cmd_print_unapplied_treeish {
7180 badusage __ "incorrect arguments to dgit print-unapplied-treeish"
7182 my $headref = git_rev_parse('HEAD');
7183 my $clogp = commit_getclogp $headref;
7184 $package = getfield $clogp, 'Source';
7185 $version = getfield $clogp, 'Version';
7186 $isuite = getfield $clogp, 'Distribution';
7187 $csuite = $isuite; # we want this to be offline!
7191 changedir $playground;
7192 my $uv = upstreamversion $version;
7193 my $u = quilt_fakedsc2unapplied($headref, $uv);
7194 print $u, "\n" or confess "$!";
7197 sub import_dsc_result {
7198 my ($dstref, $newhash, $what_log, $what_msg) = @_;
7199 my @cmd = (git_update_ref_cmd $what_log, $dstref, $newhash);
7201 check_gitattrs($newhash, __ "source tree");
7203 progress f_ "dgit: import-dsc: %s", $what_msg;
7206 sub cmd_import_dsc {
7210 last unless $ARGV[0] =~ m/^-/;
7213 if (m/^--require-valid-signature$/) {
7216 badusage f_ "unknown dgit import-dsc sub-option \`%s'", $_;
7220 badusage __ "usage: dgit import-dsc .../PATH/TO/.DSC BRANCH"
7222 my ($dscfn, $dstbranch) = @ARGV;
7224 badusage __ "dry run makes no sense with import-dsc"
7227 my $force = $dstbranch =~ s/^\+// ? +1 :
7228 $dstbranch =~ s/^\.\.// ? -1 :
7230 my $info = $force ? " $&" : '';
7231 $info = "$dscfn$info";
7233 my $specbranch = $dstbranch;
7234 $dstbranch = "refs/heads/$dstbranch" unless $dstbranch =~ m#^refs/#;
7235 $dstbranch = cmdoutput @git, qw(check-ref-format --normalize), $dstbranch;
7237 my @symcmd = (@git, qw(symbolic-ref -q HEAD));
7238 my $chead = cmdoutput_errok @symcmd;
7239 defined $chead or $?==256 or failedcmd @symcmd;
7241 fail f_ "%s is checked out - will not update it", $dstbranch
7242 if defined $chead and $chead eq $dstbranch;
7244 my $oldhash = git_get_ref $dstbranch;
7246 open D, "<", $dscfn or fail f_ "open import .dsc (%s): %s", $dscfn, $!;
7247 $dscdata = do { local $/ = undef; <D>; };
7248 D->error and fail f_ "read %s: %s", $dscfn, $!;
7251 # we don't normally need this so import it here
7252 use Dpkg::Source::Package;
7253 my $dp = new Dpkg::Source::Package filename => $dscfn,
7254 require_valid_signature => $needsig;
7256 local $SIG{__WARN__} = sub {
7258 return unless $needsig;
7259 fail __ "import-dsc signature check failed";
7261 if (!$dp->is_signed()) {
7262 warn f_ "%s: warning: importing unsigned .dsc\n", $us;
7264 my $r = $dp->check_signature();
7265 confess "->check_signature => $r" if $needsig && $r;
7271 $package = getfield $dsc, 'Source';
7273 parse_dsc_field($dsc, __ "Dgit metadata in .dsc")
7274 unless forceing [qw(import-dsc-with-dgit-field)];
7275 parse_dsc_field_def_dsc_distro();
7277 $isuite = 'DGIT-IMPORT-DSC';
7278 $idistro //= $dsc_distro;
7282 if (defined $dsc_hash) {
7284 "dgit: import-dsc of .dsc with Dgit field, using git hash";
7285 resolve_dsc_field_commit undef, undef;
7287 if (defined $dsc_hash) {
7288 my @cmd = (qw(sh -ec),
7289 "echo $dsc_hash | git cat-file --batch-check");
7290 my $objgot = cmdoutput @cmd;
7291 if ($objgot =~ m#^\w+ missing\b#) {
7292 fail f_ <<END, $dsc_hash
7293 .dsc contains Dgit field referring to object %s
7294 Your git tree does not have that object. Try `git fetch' from a
7295 plausible server (browse.dgit.d.o? salsa?), and try the import-dsc again.
7298 if ($oldhash && !is_fast_fwd $oldhash, $dsc_hash) {
7300 progress __ "Not fast forward, forced update.";
7302 fail f_ "Not fast forward to %s", $dsc_hash;
7305 import_dsc_result $dstbranch, $dsc_hash,
7306 "dgit import-dsc (Dgit): $info",
7307 f_ "updated git ref %s", $dstbranch;
7311 fail f_ <<END, $dstbranch, $specbranch, $specbranch
7312 Branch %s already exists
7313 Specify ..%s for a pseudo-merge, binding in existing history
7314 Specify +%s to overwrite, discarding existing history
7316 if $oldhash && !$force;
7318 my @dfi = dsc_files_info();
7319 foreach my $fi (@dfi) {
7320 my $f = $fi->{Filename};
7321 # We transfer all the pieces of the dsc to the bpd, not just
7322 # origs. This is by analogy with dgit fetch, which wants to
7323 # keep them somewhere to avoid downloading them again.
7324 # We make symlinks, though. If the user wants copies, then
7325 # they can copy the parts of the dsc to the bpd using dcmd,
7327 my $here = "$buildproductsdir/$f";
7332 fail f_ "lstat %s works but stat gives %s !", $here, $!;
7334 fail f_ "stat %s: %s", $here, $! unless $! == ENOENT;
7335 printdebug "not in bpd, $f ...\n";
7336 # $f does not exist in bpd, we need to transfer it
7338 $there =~ s{[^/]+$}{$f} or confess "$there ?";
7339 # $there is file we want, relative to user's cwd, or abs
7340 printdebug "not in bpd, $f, test $there ...\n";
7341 stat $there or fail f_
7342 "import %s requires %s, but: %s", $dscfn, $there, $!;
7343 if ($there =~ m#^(?:\./+)?\.\./+#) {
7344 # $there is relative to user's cwd
7345 my $there_from_parent = $';
7346 if ($buildproductsdir !~ m{^/}) {
7347 # abs2rel, despite its name, can take two relative paths
7348 $there = File::Spec->abs2rel($there,$buildproductsdir);
7349 # now $there is relative to bpd, great
7350 printdebug "not in bpd, $f, abs2rel, $there ...\n";
7352 $there = (dirname $maindir)."/$there_from_parent";
7353 # now $there is absoute
7354 printdebug "not in bpd, $f, rel2rel, $there ...\n";
7356 } elsif ($there =~ m#^/#) {
7357 # $there is absolute already
7358 printdebug "not in bpd, $f, abs, $there ...\n";
7361 "cannot import %s which seems to be inside working tree!",
7364 symlink $there, $here or fail f_
7365 "symlink %s to %s: %s", $there, $here, $!;
7366 progress f_ "made symlink %s -> %s", $here, $there;
7367 # print STDERR Dumper($fi);
7369 my @mergeinputs = generate_commits_from_dsc();
7370 die unless @mergeinputs == 1;
7372 my $newhash = $mergeinputs[0]{Commit};
7377 "Import, forced update - synthetic orphan git history.";
7378 } elsif ($force < 0) {
7379 progress __ "Import, merging.";
7380 my $tree = cmdoutput @git, qw(rev-parse), "$newhash:";
7381 my $version = getfield $dsc, 'Version';
7382 my $clogp = commit_getclogp $newhash;
7383 my $authline = clogp_authline $clogp;
7384 $newhash = hash_commit_text <<ENDU
7392 .(f_ <<END, $package, $version, $dstbranch);
7393 Merge %s (%s) import into %s
7396 die; # caught earlier
7400 import_dsc_result $dstbranch, $newhash,
7401 "dgit import-dsc: $info",
7402 f_ "results are in git ref %s", $dstbranch;
7405 sub pre_archive_api_query () {
7406 not_necessarily_a_tree();
7408 sub cmd_archive_api_query {
7409 badusage __ "need only 1 subpath argument" unless @ARGV==1;
7410 my ($subpath) = @ARGV;
7411 local $isuite = 'DGIT-API-QUERY-CMD';
7412 my $json = api_query_raw $subpath;
7413 print $json or die "$!";
7416 sub repos_server_url () {
7417 $package = '_dgit-repos-server';
7418 local $access_forpush = 1;
7419 local $isuite = 'DGIT-REPOS-SERVER';
7420 my $url = access_giturl();
7423 sub pre_clone_dgit_repos_server () {
7424 not_necessarily_a_tree();
7426 sub cmd_clone_dgit_repos_server {
7427 badusage __ "need destination argument" unless @ARGV==1;
7428 my ($destdir) = @ARGV;
7429 my $url = repos_server_url();
7430 my @cmd = (@git, qw(clone), $url, $destdir);
7432 exec @cmd or fail f_ "exec git clone: %s\n", $!;
7435 sub pre_print_dgit_repos_server_source_url () {
7436 not_necessarily_a_tree();
7438 sub cmd_print_dgit_repos_server_source_url {
7440 "no arguments allowed to dgit print-dgit-repos-server-source-url"
7442 my $url = repos_server_url();
7443 print $url, "\n" or confess "$!";
7446 sub pre_print_dpkg_source_ignores {
7447 not_necessarily_a_tree();
7449 sub cmd_print_dpkg_source_ignores {
7451 "no arguments allowed to dgit print-dpkg-source-ignores"
7453 print "@dpkg_source_ignores\n" or confess "$!";
7456 sub cmd_setup_mergechangelogs {
7457 badusage __ "no arguments allowed to dgit setup-mergechangelogs"
7459 local $isuite = 'DGIT-SETUP-TREE';
7460 setup_mergechangelogs(1);
7463 sub cmd_setup_useremail {
7464 badusage __ "no arguments allowed to dgit setup-useremail" if @ARGV;
7465 local $isuite = 'DGIT-SETUP-TREE';
7469 sub cmd_setup_gitattributes {
7470 badusage __ "no arguments allowed to dgit setup-useremail" if @ARGV;
7471 local $isuite = 'DGIT-SETUP-TREE';
7475 sub cmd_setup_new_tree {
7476 badusage __ "no arguments allowed to dgit setup-tree" if @ARGV;
7477 local $isuite = 'DGIT-SETUP-TREE';
7481 #---------- argument parsing and main program ----------
7484 print "dgit version $our_version\n" or confess "$!";
7488 our (%valopts_long, %valopts_short);
7489 our (%funcopts_long);
7491 our (@modeopt_cfgs);
7493 sub defvalopt ($$$$) {
7494 my ($long,$short,$val_re,$how) = @_;
7495 my $oi = { Long => $long, Short => $short, Re => $val_re, How => $how };
7496 $valopts_long{$long} = $oi;
7497 $valopts_short{$short} = $oi;
7498 # $how subref should:
7499 # do whatever assignemnt or thing it likes with $_[0]
7500 # if the option should not be passed on to remote, @rvalopts=()
7501 # or $how can be a scalar ref, meaning simply assign the value
7504 defvalopt '--since-version', '-v', '[^_]+|_', \$changes_since_version;
7505 defvalopt '--distro', '-d', '.+', \$idistro;
7506 defvalopt '', '-k', '.+', \$keyid;
7507 defvalopt '--existing-package','', '.*', \$existing_package;
7508 defvalopt '--build-products-dir','','.*', \$buildproductsdir;
7509 defvalopt '--clean', '', $cleanmode_re, \$cleanmode;
7510 defvalopt '--package', '-p', $package_re, \$package;
7511 defvalopt '--quilt', '', $quilt_modes_re, \$quilt_mode;
7513 defvalopt '', '-C', '.+', sub {
7514 ($changesfile) = (@_);
7515 if ($changesfile =~ s#^(.*)/##) {
7516 $buildproductsdir = $1;
7520 defvalopt '--initiator-tempdir','','.*', sub {
7521 ($initiator_tempdir) = (@_);
7522 $initiator_tempdir =~ m#^/# or
7523 badusage __ "--initiator-tempdir must be used specify an".
7524 " absolute, not relative, directory."
7527 sub defoptmodes ($@) {
7528 my ($varref, $cfgkey, $default, %optmap) = @_;
7530 while (my ($opt,$val) = each %optmap) {
7531 $funcopts_long{$opt} = sub { $$varref = $val; };
7532 $permit{$val} = $val;
7534 push @modeopt_cfgs, {
7537 Default => $default,
7542 defoptmodes \$dodep14tag, qw( dep14tag want
7545 --always-dep14tag always );
7550 if (defined $ENV{'DGIT_SSH'}) {
7551 @ssh = string_to_ssh $ENV{'DGIT_SSH'};
7552 } elsif (defined $ENV{'GIT_SSH'}) {
7553 @ssh = ($ENV{'GIT_SSH'});
7561 if (!defined $val) {
7562 badusage f_ "%s needs a value", $what unless @ARGV;
7564 push @rvalopts, $val;
7566 badusage f_ "bad value \`%s' for %s", $val, $what unless
7567 $val =~ m/^$oi->{Re}$(?!\n)/s;
7568 my $how = $oi->{How};
7569 if (ref($how) eq 'SCALAR') {
7574 push @ropts, @rvalopts;
7578 last unless $ARGV[0] =~ m/^-/;
7582 if (m/^--dry-run$/) {
7585 } elsif (m/^--damp-run$/) {
7588 } elsif (m/^--no-sign$/) {
7591 } elsif (m/^--help$/) {
7593 } elsif (m/^--version$/) {
7595 } elsif (m/^--new$/) {
7598 } elsif (m/^--([-0-9a-z]+)=(.+)/s &&
7599 ($om = $opts_opt_map{$1}) &&
7603 } elsif (m/^--([-0-9a-z]+):(.*)/s &&
7604 !$opts_opt_cmdonly{$1} &&
7605 ($om = $opts_opt_map{$1})) {
7608 } elsif (m/^--([-0-9a-z]+)\!:(.*)/s &&
7609 !$opts_opt_cmdonly{$1} &&
7610 ($om = $opts_opt_map{$1})) {
7612 my $cmd = shift @$om;
7613 @$om = ($cmd, grep { $_ ne $2 } @$om);
7614 } elsif (m/^--($quilt_options_re)$/s) {
7615 push @ropts, "--quilt=$1";
7617 } elsif (m/^--(?:ignore|include)-dirty$/s) {
7620 } elsif (m/^--no-quilt-fixup$/s) {
7622 $quilt_mode = 'nocheck';
7623 } elsif (m/^--no-rm-on-error$/s) {
7626 } elsif (m/^--no-chase-dsc-distro$/s) {
7628 $chase_dsc_distro = 0;
7629 } elsif (m/^--overwrite$/s) {
7631 $overwrite_version = '';
7632 } elsif (m/^--split-(?:view|brain)$/s) {
7634 $splitview_mode = 'always';
7635 } elsif (m/^--split-(?:view|brain)=($splitview_modes_re)$/s) {
7637 $splitview_mode = $1;
7638 } elsif (m/^--overwrite=(.+)$/s) {
7640 $overwrite_version = $1;
7641 } elsif (m/^--delayed=(\d+)$/s) {
7644 } elsif (m/^--upstream-commitish=(.+)$/s) {
7646 $quilt_upstream_commitish = $1;
7647 } elsif (m/^--save-(dgit-view)=(.+)$/s ||
7648 m/^--(dgit-view)-save=(.+)$/s
7650 my ($k,$v) = ($1,$2);
7652 $v =~ s#^(?!refs/)#refs/heads/#;
7653 $internal_object_save{$k} = $v;
7654 } elsif (m/^--(no-)?rm-old-changes$/s) {
7657 } elsif (m/^--deliberately-($deliberately_re)$/s) {
7659 push @deliberatelies, $&;
7660 } elsif (m/^--force-(.*)/ && defined $forceopts{$1}) {
7664 } elsif (m/^--force-/) {
7666 f_ "%s: warning: ignoring unknown force option %s\n",
7669 } elsif (m/^--for-push$/s) {
7671 $access_forpush = 1;
7672 } elsif (m/^--config-lookup-explode=(.+)$/s) {
7673 # undocumented, for testing
7675 $gitcfgs{cmdline}{$1} = 'CONFIG-LOOKUP-EXPLODE';
7676 # ^ it's supposed to be an array ref
7677 } elsif (m/^(--[-0-9a-z]+)(=|$)/ && ($oi = $valopts_long{$1})) {
7678 $val = $2 ? $' : undef; #';
7679 $valopt->($oi->{Long});
7680 } elsif ($funcopts_long{$_}) {
7682 $funcopts_long{$_}();
7684 badusage f_ "unknown long option \`%s'", $_;
7691 } elsif (s/^-L/-/) {
7694 } elsif (s/^-h/-/) {
7696 } elsif (s/^-D/-/) {
7700 } elsif (s/^-N/-/) {
7705 push @changesopts, $_;
7707 } elsif (s/^-wn$//s) {
7709 $cleanmode = 'none';
7710 } elsif (s/^-wg(f?)(a?)$//s) {
7713 $cleanmode .= '-ff' if $1;
7714 $cleanmode .= ',always' if $2;
7715 } elsif (s/^-wd(d?)([na]?)$//s) {
7717 $cleanmode = 'dpkg-source';
7718 $cleanmode .= '-d' if $1;
7719 $cleanmode .= ',no-check' if $2 eq 'n';
7720 $cleanmode .= ',all-check' if $2 eq 'a';
7721 } elsif (s/^-wc$//s) {
7723 $cleanmode = 'check';
7724 } elsif (s/^-wci$//s) {
7726 $cleanmode = 'check,ignores';
7727 } elsif (s/^-c([^=]*)\=(.*)$//s) {
7728 push @git, '-c', $&;
7729 $gitcfgs{cmdline}{$1} = [ $2 ];
7730 } elsif (s/^-c([^=]+)$//s) {
7731 push @git, '-c', $&;
7732 $gitcfgs{cmdline}{$1} = [ 'true' ];
7733 } elsif (m/^-[a-zA-Z]/ && ($oi = $valopts_short{$&})) {
7735 $val = undef unless length $val;
7736 $valopt->($oi->{Short});
7739 badusage f_ "unknown short option \`%s'", $_;
7746 sub check_env_sanity () {
7747 my $blocked = new POSIX::SigSet;
7748 sigprocmask SIG_UNBLOCK, $blocked, $blocked or confess "$!";
7751 foreach my $name (qw(PIPE CHLD)) {
7752 my $signame = "SIG$name";
7753 my $signum = eval "POSIX::$signame" // die;
7754 die f_ "%s is set to something other than SIG_DFL\n",
7756 if defined $SIG{$name} and $SIG{$name} ne 'DEFAULT';
7757 $blocked->ismember($signum) and
7758 die f_ "%s is blocked\n", $signame;
7764 On entry to dgit, %s
7765 This is a bug produced by something in your execution environment.
7771 sub parseopts_late_defaults () {
7772 $isuite //= cfg("dgit-distro.$idistro.default-suite", 'RETURN-UNDEF')
7773 if defined $idistro;
7774 $isuite //= cfg('dgit.default.default-suite');
7776 foreach my $k (keys %opts_opt_map) {
7777 my $om = $opts_opt_map{$k};
7779 my $v = access_cfg("cmd-$k", 'RETURN-UNDEF');
7781 badcfg f_ "cannot set command for %s", $k
7782 unless length $om->[0];
7786 foreach my $c (access_cfg_cfgs("opts-$k")) {
7788 map { $_ ? @$_ : () }
7789 map { $gitcfgs{$_}{$c} }
7790 reverse @gitcfgsources;
7791 printdebug "CL $c ", (join " ", map { shellquote } @vl),
7792 "\n" if $debuglevel >= 4;
7794 badcfg f_ "cannot configure options for %s", $k
7795 if $opts_opt_cmdonly{$k};
7796 my $insertpos = $opts_cfg_insertpos{$k};
7797 @$om = ( @$om[0..$insertpos-1],
7799 @$om[$insertpos..$#$om] );
7803 if (!defined $rmchanges) {
7804 local $access_forpush;
7805 $rmchanges = access_cfg_bool(0, 'rm-old-changes');
7808 if (!defined $quilt_mode) {
7809 local $access_forpush;
7810 $quilt_mode = cfg('dgit.force.quilt-mode', 'RETURN-UNDEF')
7811 // access_cfg('quilt-mode', 'RETURN-UNDEF')
7813 $quilt_mode =~ m/^($quilt_modes_re)$/
7814 or badcfg f_ "unknown quilt-mode \`%s'", $quilt_mode;
7817 $quilt_mode =~ s/^(baredebian)\+git$/$1/;
7819 foreach my $moc (@modeopt_cfgs) {
7820 local $access_forpush;
7821 my $vr = $moc->{Var};
7822 next if defined $$vr;
7823 $$vr = access_cfg($moc->{Key}, 'RETURN-UNDEF') // $moc->{Default};
7824 my $v = $moc->{Vals}{$$vr};
7825 badcfg f_ "unknown %s setting \`%s'", $moc->{Key}, $$vr
7831 local $access_forpush;
7832 default_from_access_cfg(\$cleanmode, 'clean-mode', 'dpkg-source',
7836 $buildproductsdir //= access_cfg('build-products-dir', 'RETURN-UNDEF');
7837 $buildproductsdir //= '..';
7838 $bpd_glob = $buildproductsdir;
7839 $bpd_glob =~ s#[][\\{}*?~]#\\$&#g;
7842 setlocale(LC_MESSAGES, "");
7845 if ($ENV{$fakeeditorenv}) {
7847 quilt_fixup_editor();
7853 print STDERR __ "DRY RUN ONLY\n" if $dryrun_level > 1;
7854 print STDERR __ "DAMP RUN - WILL MAKE LOCAL (UNSIGNED) CHANGES\n"
7855 if $dryrun_level == 1;
7857 print STDERR __ $helpmsg or confess "$!";
7860 $cmd = $subcommand = shift @ARGV;
7863 my $pre_fn = ${*::}{"pre_$cmd"};
7864 $pre_fn->() if $pre_fn;
7866 if ($invoked_in_git_tree) {
7867 changedir_git_toplevel();
7872 my $fn = ${*::}{"cmd_$cmd"};
7873 $fn or badusage f_ "unknown operation %s", $cmd;