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
105 import-gitapply-absurd
106 import-gitapply-no-absurd
107 import-dsc-with-dgit-field);
109 our %format_ok = map { $_=>1 } ("1.0","3.0 (native)","3.0 (quilt)");
111 our $cleanmode_re = qr{(?: dpkg-source (?: -d )? (?: ,no-check | ,all-check )?
112 | (?: git | git-ff ) (?: ,always )?
113 | check (?: ,ignores )?
117 our $git_authline_re = '^([^<>]+) \<(\S+)\> (\d+ [-+]\d+)$';
118 our $splitbraincache = 'dgit-intern/quilt-cache';
119 our $rewritemap = 'dgit-rewrite/map';
121 our @dpkg_source_ignores = qw(-i(?:^|/)\.git(?:/|$) -I.git);
123 our (@dget) = qw(dget);
124 our (@curl) = (qw(curl --proto-redir), '-all,http,https', qw(-L));
125 our (@dput) = qw(dput);
126 our (@debsign) = qw(debsign);
127 our (@gpg) = qw(gpg);
128 our (@sbuild) = (qw(sbuild --no-source));
130 our (@dgit) = qw(dgit);
131 our (@git_debrebase) = qw(git-debrebase);
132 our (@aptget) = qw(apt-get);
133 our (@aptcache) = qw(apt-cache);
134 our (@dpkgbuildpackage) = (qw(dpkg-buildpackage), @dpkg_source_ignores);
135 our (@dpkgsource) = (qw(dpkg-source), @dpkg_source_ignores);
136 our (@dpkggenchanges) = qw(dpkg-genchanges);
137 our (@mergechanges) = qw(mergechanges -f);
138 our (@gbp_build) = ('');
139 our (@gbp_pq) = ('gbp pq');
140 our (@changesopts) = ('');
141 our (@pbuilder) = ("sudo -E pbuilder","--no-source-only-changes");
142 our (@cowbuilder) = ("sudo -E cowbuilder","--no-source-only-changes");
144 our %opts_opt_map = ('dget' => \@dget, # accept for compatibility
147 'debsign' => \@debsign,
149 'sbuild' => \@sbuild,
153 'git-debrebase' => \@git_debrebase,
154 'apt-get' => \@aptget,
155 'apt-cache' => \@aptcache,
156 'dpkg-source' => \@dpkgsource,
157 'dpkg-buildpackage' => \@dpkgbuildpackage,
158 'dpkg-genchanges' => \@dpkggenchanges,
159 'gbp-build' => \@gbp_build,
160 'gbp-pq' => \@gbp_pq,
161 'ch' => \@changesopts,
162 'mergechanges' => \@mergechanges,
163 'pbuilder' => \@pbuilder,
164 'cowbuilder' => \@cowbuilder);
166 our %opts_opt_cmdonly = ('gpg' => 1, 'git' => 1);
167 our %opts_cfg_insertpos = map {
169 scalar @{ $opts_opt_map{$_} }
170 } keys %opts_opt_map;
172 sub parseopts_late_defaults();
173 sub quiltify_trees_differ ($$;$$$);
174 sub setup_gitattrs(;$);
175 sub check_gitattrs($$);
182 our $supplementary_message = '';
183 our $made_split_brain = 0;
186 # Interactions between quilt mode and split brain
187 # (currently, split brain only implemented iff
188 # madformat_wantfixup && quiltmode_splitting)
190 # source format sane `3.0 (quilt)'
191 # madformat_wantfixup()
193 # quilt mode normal quiltmode
194 # (eg linear) _splitbrain
196 # ------------ ------------------------------------------------
198 # no split no q cache no q cache forbidden,
199 # brain PM on master q fixup on master prevented
200 # !do_split_brain() PM on master
202 # split brain no q cache q fixup cached, to dgit view
203 # PM in dgit view PM in dgit view
205 # PM = pseudomerge to make ff, due to overwrite (or split view)
206 # "no q cache" = do not record in cache on build, do not check cache
207 # `3.0 (quilt)' with --quilt=nocheck is treated as sane format
211 return unless forkcheck_mainprocess();
212 print STDERR "! $_\n" foreach $supplementary_message =~ m/^.+$/mg;
215 our $remotename = 'dgit';
216 our @ourdscfield = qw(Dgit Vcs-Dgit-Master);
220 if (!defined $absurdity) {
222 $absurdity =~ s{/[^/]+$}{/absurd} or die;
225 sub madformat ($) { $_[0] eq '3.0 (quilt)' }
227 sub lbranch () { return "$branchprefix/$csuite"; }
228 my $lbranch_re = '^refs/heads/'.$branchprefix.'/([^/.]+)$';
229 sub lref () { return "refs/heads/".lbranch(); }
230 sub lrref () { return "refs/remotes/$remotename/".server_branch($csuite); }
231 sub rrref () { return server_ref($csuite); }
234 my ($vsn, $sfx) = @_;
235 return &source_file_leafname($package, $vsn, $sfx);
237 sub is_orig_file_of_vsn ($$) {
238 my ($f, $upstreamvsn) = @_;
239 return is_orig_file_of_p_v($f, $package, $upstreamvsn);
244 return srcfn($vsn,".dsc");
247 sub changespat ($;$) {
248 my ($vsn, $arch) = @_;
249 return "${package}_".(stripepoch $vsn)."_".($arch//'*').".changes";
258 return unless forkcheck_mainprocess();
259 foreach my $f (@end) {
261 print STDERR "$us: cleanup: $@" if length $@;
266 print STDERR f_ "%s: invalid configuration: %s\n", $us, "@_";
270 sub forceable_fail ($$) {
271 my ($forceoptsl, $msg) = @_;
272 fail $msg unless grep { $forceopts{$_} } @$forceoptsl;
273 print STDERR +(__ "warning: overriding problem due to --force:\n"). $msg;
277 my ($forceoptsl) = @_;
278 my @got = grep { $forceopts{$_} } @$forceoptsl;
279 return 0 unless @got;
281 "warning: skipping checks or functionality due to --force-%s\n",
285 sub no_such_package () {
286 print STDERR f_ "%s: source package %s does not exist in suite %s\n",
287 $us, $package, $isuite;
291 sub deliberately ($) {
293 return !!grep { $_ eq "--deliberately-$enquiry" } @deliberatelies;
296 sub deliberately_not_fast_forward () {
297 foreach (qw(not-fast-forward fresh-repo)) {
298 return 1 if deliberately($_) || deliberately("TEST-dgit-only-$_");
302 sub quiltmode_splitting () {
303 $quilt_mode =~ m/gbp|dpm|unapplied|baredebian/;
305 sub format_quiltmode_splitting ($) {
307 return madformat_wantfixup($format) && quiltmode_splitting();
310 sub do_split_brain () { !!($do_split_brain // confess) }
312 sub opts_opt_multi_cmd {
315 push @cmd, split /\s+/, shift @_;
322 return opts_opt_multi_cmd [], @gbp_pq;
325 sub dgit_privdir () {
326 our $dgit_privdir_made //= ensure_a_playground 'dgit';
330 my $r = $buildproductsdir;
331 $r = "$maindir/$r" unless $r =~ m{^/};
335 sub get_tree_of_commit ($) {
336 my ($commitish) = @_;
337 my $cdata = cmdoutput @git, qw(cat-file commit), $commitish;
338 $cdata =~ m/\n\n/; $cdata = $`;
339 $cdata =~ m/^tree (\w+)$/m or confess "cdata $cdata ?";
343 sub branch_gdr_info ($$) {
344 my ($symref, $head) = @_;
345 my ($status, $msg, $current, $ffq_prev, $gdrlast) =
346 gdr_ffq_prev_branchinfo($symref);
347 return () unless $status eq 'branch';
348 $ffq_prev = git_get_ref $ffq_prev;
349 $gdrlast = git_get_ref $gdrlast;
350 $gdrlast &&= is_fast_fwd $gdrlast, $head;
351 return ($ffq_prev, $gdrlast);
354 sub branch_is_gdr_unstitched_ff ($$$) {
355 my ($symref, $head, $ancestor) = @_;
356 my ($ffq_prev, $gdrlast) = branch_gdr_info($symref, $head);
357 return 0 unless $ffq_prev;
358 return 0 unless !defined $ancestor or is_fast_fwd $ancestor, $ffq_prev;
362 sub branch_is_gdr ($) {
364 # This is quite like git-debrebase's keycommits.
365 # We have our own implementation because:
366 # - our algorighm can do fewer tests so is faster
367 # - it saves testing to see if gdr is installed
369 # NB we use this jsut for deciding whether to run gdr make-patches
370 # Before reusing this algorithm for somthing else, its
371 # suitability should be reconsidered.
374 local $Debian::Dgit::debugcmd_when_debuglevel = 3;
375 printdebug "branch_is_gdr $head...\n";
376 my $get_patches = sub {
377 my $t = git_cat_file "$_[0]:debian/patches", [qw(missing tree)];
380 my $tip_patches = $get_patches->($head);
383 my $cdata = git_cat_file $walk, 'commit';
384 my ($hdrs,$msg) = $cdata =~ m{\n\n} ? ($`,$') : ($cdata,'');
385 if ($msg =~ m{^\[git-debrebase\ (
386 anchor | changelog | make-patches |
387 merged-breakwater | pseudomerge
389 # no need to analyse this - it's sufficient
390 # (gdr classifications: Anchor, MergedBreakwaters)
391 # (made by gdr: Pseudomerge, Changelog)
392 printdebug "branch_is_gdr $walk gdr $1 YES\n";
395 my @parents = ($hdrs =~ m/^parent (\w+)$/gm);
397 my $walk_tree = get_tree_of_commit $walk;
398 foreach my $p (@parents) {
399 my $p_tree = get_tree_of_commit $p;
400 if ($p_tree eq $walk_tree) { # pseudomerge contriburor
401 # (gdr classification: Pseudomerge; not made by gdr)
402 printdebug "branch_is_gdr $walk unmarked pseudomerge\n"
408 # some other non-gdr merge
409 # (gdr classification: VanillaMerge, DgitImportUnpatched, ?)
410 printdebug "branch_is_gdr $walk ?-2-merge NO\n";
414 # (gdr classification: ?)
415 printdebug "branch_is_gdr $walk ?-octopus NO\n";
419 printdebug "branch_is_gdr $walk origin\n";
422 if ($get_patches->($walk) ne $tip_patches) {
423 # Our parent added, removed, or edited patches, and wasn't
424 # a gdr make-patches commit. gdr make-patches probably
425 # won't do that well, then.
426 # (gdr classification of parent: AddPatches or ?)
427 printdebug "branch_is_gdr $walk ?-patches NO\n";
430 if ($tip_patches eq '' and
431 !defined git_cat_file "$walk~:debian" and
432 !quiltify_trees_differ "$walk~", $walk
434 # (gdr classification of parent: BreakwaterStart
435 printdebug "branch_is_gdr $walk unmarked BreakwaterStart YES\n";
438 # (gdr classification: Upstream Packaging Mixed Changelog)
439 printdebug "branch_is_gdr $walk plain\n"
445 #---------- remote protocol support, common ----------
447 # remote push initiator/responder protocol:
448 # $ dgit remote-push-build-host <n-rargs> <rargs>... <push-args>...
449 # where <rargs> is <push-host-dir> <supported-proto-vsn>,... ...
450 # < dgit-remote-push-ready <actual-proto-vsn>
457 # > supplementary-message NBYTES
462 # > file parsed-changelog
463 # [indicates that output of dpkg-parsechangelog follows]
464 # > data-block NBYTES
465 # > [NBYTES bytes of data (no newline)]
466 # [maybe some more blocks]
475 # > param head DGIT-VIEW-HEAD
476 # > param csuite SUITE
477 # > param tagformat new # $protovsn == 4
478 # > param splitbrain 0|1 # $protovsn >= 6
479 # > param maint-view MAINT-VIEW-HEAD
481 # > param buildinfo-filename P_V_X.buildinfo # zero or more times
482 # > file buildinfo # for buildinfos to sign
484 # > previously REFNAME=OBJNAME # if --deliberately-not-fast-forward
485 # # goes into tag, for replay prevention
488 # [indicates that signed tag is wanted]
489 # < data-block NBYTES
490 # < [NBYTES bytes of data (no newline)]
491 # [maybe some more blocks]
495 # > want signed-dsc-changes
496 # < data-block NBYTES [transfer of signed dsc]
498 # < data-block NBYTES [transfer of signed changes]
500 # < data-block NBYTES [transfer of each signed buildinfo
501 # [etc] same number and order as "file buildinfo"]
509 sub i_child_report () {
510 # Sees if our child has died, and reap it if so. Returns a string
511 # describing how it died if it failed, or undef otherwise.
512 return undef unless $i_child_pid;
513 my $got = waitpid $i_child_pid, WNOHANG;
514 return undef if $got <= 0;
515 die unless $got == $i_child_pid;
516 $i_child_pid = undef;
517 return undef unless $?;
518 return f_ "build host child %s", waitstatusmsg();
523 fail f_ "connection lost: %s", $! if $fh->error;
524 fail f_ "protocol violation; %s not expected", $m;
527 sub badproto_badread ($$) {
529 fail f_ "connection lost: %s", $! if $!;
530 my $report = i_child_report();
531 fail $report if defined $report;
532 badproto $fh, f_ "eof (reading %s)", $wh;
535 sub protocol_expect (&$) {
536 my ($match, $fh) = @_;
539 defined && chomp or badproto_badread $fh, __ "protocol message";
547 badproto $fh, f_ "\`%s'", $_;
550 sub protocol_send_file ($$) {
551 my ($fh, $ourfn) = @_;
552 open PF, "<", $ourfn or die "$ourfn: $!";
555 my $got = read PF, $d, 65536;
556 die "$ourfn: $!" unless defined $got;
558 print $fh "data-block ".length($d)."\n" or confess "$!";
559 print $fh $d or confess "$!";
561 PF->error and die "$ourfn $!";
562 print $fh "data-end\n" or confess "$!";
566 sub protocol_read_bytes ($$) {
567 my ($fh, $nbytes) = @_;
568 $nbytes =~ m/^[1-9]\d{0,5}$|^0$/ or badproto \*RO, __ "bad byte count";
570 my $got = read $fh, $d, $nbytes;
571 $got==$nbytes or badproto_badread $fh, __ "data block";
575 sub protocol_receive_file ($$) {
576 my ($fh, $ourfn) = @_;
577 printdebug "() $ourfn\n";
578 open PF, ">", $ourfn or die "$ourfn: $!";
580 my ($y,$l) = protocol_expect {
581 m/^data-block (.*)$/ ? (1,$1) :
582 m/^data-end$/ ? (0,) :
586 my $d = protocol_read_bytes $fh, $l;
587 print PF $d or confess "$!";
589 close PF or confess "$!";
592 #---------- remote protocol support, responder ----------
594 sub responder_send_command ($) {
596 return unless $we_are_responder;
597 # called even without $we_are_responder
598 printdebug ">> $command\n";
599 print PO $command, "\n" or confess "$!";
602 sub responder_send_file ($$) {
603 my ($keyword, $ourfn) = @_;
604 return unless $we_are_responder;
605 printdebug "]] $keyword $ourfn\n";
606 responder_send_command "file $keyword";
607 protocol_send_file \*PO, $ourfn;
610 sub responder_receive_files ($@) {
611 my ($keyword, @ourfns) = @_;
612 die unless $we_are_responder;
613 printdebug "[[ $keyword @ourfns\n";
614 responder_send_command "want $keyword";
615 foreach my $fn (@ourfns) {
616 protocol_receive_file \*PI, $fn;
619 protocol_expect { m/^files-end$/ } \*PI;
622 #---------- remote protocol support, initiator ----------
624 sub initiator_expect (&) {
626 protocol_expect { &$match } \*RO;
629 #---------- end remote code ----------
632 if ($we_are_responder) {
634 responder_send_command "progress ".length($m) or confess "$!";
635 print PO $m or confess "$!";
643 our ($dscdata,$dscurl,$dsc,$dsc_checked,$skew_warning_vsn);
645 sub act_local () { return $dryrun_level <= 1; }
646 sub act_scary () { return !$dryrun_level; }
649 if (!$dryrun_level) {
650 progress f_ "%s ok: %s", $us, "@_";
652 progress f_ "would be ok: %s (but dry run only)", "@_";
657 printcmd(\*STDERR,$debugprefix."#",@_);
660 sub runcmd_ordryrun {
668 sub runcmd_ordryrun_local {
676 our $helpmsg = i_ <<END;
678 dgit [dgit-opts] clone [dgit-opts] package [suite] [./dir|/dir]
679 dgit [dgit-opts] fetch|pull [dgit-opts] [suite]
680 dgit [dgit-opts] build [dpkg-buildpackage-opts]
681 dgit [dgit-opts] sbuild [sbuild-opts]
682 dgit [dgit-opts] pbuilder|cowbuilder [debbuildopts]
683 dgit [dgit-opts] push [dgit-opts] [suite]
684 dgit [dgit-opts] push-source [dgit-opts] [suite]
685 dgit [dgit-opts] rpush build-host:build-dir ...
686 important dgit options:
687 -k<keyid> sign tag and package with <keyid> instead of default
688 --dry-run -n do not change anything, but go through the motions
689 --damp-run -L like --dry-run but make local changes, without signing
690 --new -N allow introducing a new package
691 --debug -D increase debug level
692 -c<name>=<value> set git config option (used directly by dgit too)
695 our $later_warning_msg = i_ <<END;
696 Perhaps the upload is stuck in incoming. Using the version from git.
700 print STDERR f_ "%s: %s\n%s", $us, "@_", __ $helpmsg or confess "$!";
705 @ARGV or badusage __ "too few arguments";
706 return scalar shift @ARGV;
710 not_necessarily_a_tree();
713 print __ $helpmsg or confess "$!";
717 our $td = $ENV{DGIT_TEST_DUMMY_DIR} || "DGIT_TEST_DUMMY_DIR-unset";
719 our %defcfg = ('dgit.default.distro' => 'debian',
720 'dgit.default.default-suite' => 'unstable',
721 'dgit.default.old-dsc-distro' => 'debian',
722 'dgit-suite.*-security.distro' => 'debian-security',
723 'dgit.default.username' => '',
724 'dgit.default.archive-query-default-component' => 'main',
725 'dgit.default.ssh' => 'ssh',
726 'dgit.default.archive-query' => 'madison:',
727 'dgit.default.sshpsql-dbname' => 'service=projectb',
728 'dgit.default.aptget-components' => 'main',
729 'dgit.default.source-only-uploads' => 'ok',
730 'dgit.dsc-url-proto-ok.http' => 'true',
731 'dgit.dsc-url-proto-ok.https' => 'true',
732 'dgit.dsc-url-proto-ok.git' => 'true',
733 'dgit.vcs-git.suites', => 'sid', # ;-separated
734 'dgit.default.dsc-url-proto-ok' => 'false',
735 # old means "repo server accepts pushes with old dgit tags"
736 # new means "repo server accepts pushes with new dgit tags"
737 # maint means "repo server accepts split brain pushes"
738 # hist means "repo server may have old pushes without new tag"
739 # ("hist" is implied by "old")
740 'dgit-distro.debian.archive-query' => 'ftpmasterapi:',
741 'dgit-distro.debian.git-check' => 'url',
742 'dgit-distro.debian.git-check-suffix' => '/info/refs',
743 'dgit-distro.debian.new-private-pushers' => 't',
744 'dgit-distro.debian.source-only-uploads' => 'not-wholly-new',
745 'dgit-distro.debian/push.git-url' => '',
746 'dgit-distro.debian/push.git-host' => 'push.dgit.debian.org',
747 'dgit-distro.debian/push.git-user-force' => 'dgit',
748 'dgit-distro.debian/push.git-proto' => 'git+ssh://',
749 'dgit-distro.debian/push.git-path' => '/dgit/debian/repos',
750 'dgit-distro.debian/push.git-create' => 'true',
751 'dgit-distro.debian/push.git-check' => 'ssh-cmd',
752 'dgit-distro.debian.archive-query-url', 'https://api.ftp-master.debian.org/',
753 # 'dgit-distro.debian.archive-query-tls-key',
754 # '/etc/ssl/certs/%HOST%.pem:/etc/dgit/%HOST%.pem',
755 # ^ this does not work because curl is broken nowadays
756 # Fixing #790093 properly will involve providing providing the key
757 # in some pacagke and maybe updating these paths.
759 # 'dgit-distro.debian.archive-query-tls-curl-args',
760 # '--ca-path=/etc/ssl/ca-debian',
761 # ^ this is a workaround but works (only) on DSA-administered machines
762 'dgit-distro.debian.git-url' => 'https://git.dgit.debian.org',
763 'dgit-distro.debian.git-url-suffix' => '',
764 'dgit-distro.debian.upload-host' => 'ftp-master', # for dput
765 'dgit-distro.debian.mirror' => 'http://ftp.debian.org/debian/',
766 'dgit-distro.debian-security.archive-query' => 'aptget:',
767 'dgit-distro.debian-security.mirror' => 'http://security.debian.org/debian-security/',
768 'dgit-distro.debian-security.aptget-suite-map' => 's#-security$#/updates#',
769 'dgit-distro.debian-security.aptget-suite-rmap' => 's#$#-security#',
770 'dgit-distro.debian-security.nominal-distro' => 'debian',
771 'dgit-distro.debian.backports-quirk' => '(squeeze)-backports*',
772 'dgit-distro.debian-backports.mirror' => 'http://backports.debian.org/debian-backports/',
773 'dgit-distro.ubuntu.git-check' => 'false',
774 'dgit-distro.ubuntu.mirror' => 'http://archive.ubuntu.com/ubuntu',
775 'dgit-distro.ubuntucloud.git-check' => 'false',
776 'dgit-distro.ubuntucloud.nominal-distro' => 'ubuntu',
777 'dgit-distro.ubuntucloud.archive-query' => 'aptget:',
778 'dgit-distro.ubuntucloud.mirror' => 'http://ubuntu-cloud.archive.canonical.com/ubuntu',
779 'dgit-distro.ubuntucloud.aptget-suite-map' => 's#^([^-]+):([^:]+)$#${1}-updates/$2#; s#^(.+)-(.+):(.+)#$1-$2/$3#;',
780 'dgit-distro.ubuntucloud.aptget-suite-rmap' => 's#/(.+)$#-$1#',
781 'dgit-distro.test-dummy.ssh' => "$td/ssh",
782 'dgit-distro.test-dummy.username' => "alice",
783 'dgit-distro.test-dummy.git-check' => "ssh-cmd",
784 'dgit-distro.test-dummy.git-create' => "ssh-cmd",
785 'dgit-distro.test-dummy.git-url' => "$td/git",
786 'dgit-distro.test-dummy.git-host' => "git",
787 'dgit-distro.test-dummy.git-path' => "$td/git",
788 'dgit-distro.test-dummy.archive-query' => "dummycatapi:",
789 'dgit-distro.test-dummy.archive-query-url' => "file://$td/aq/",
790 'dgit-distro.test-dummy.mirror' => "file://$td/mirror/",
791 'dgit-distro.test-dummy.upload-host' => 'test-dummy',
795 our @gitcfgsources = qw(cmdline local global system);
796 our $invoked_in_git_tree = 1;
798 sub git_slurp_config () {
799 # This algoritm is a bit subtle, but this is needed so that for
800 # options which we want to be single-valued, we allow the
801 # different config sources to override properly. See #835858.
802 foreach my $src (@gitcfgsources) {
803 next if $src eq 'cmdline';
804 # we do this ourselves since git doesn't handle it
806 $gitcfgs{$src} = git_slurp_config_src $src;
810 sub git_get_config ($) {
812 foreach my $src (@gitcfgsources) {
813 my $l = $gitcfgs{$src}{$c};
814 confess "internal error ($l $c)" if $l && !ref $l;
815 printdebug"C $c ".(defined $l ?
816 join " ", map { messagequote "'$_'" } @$l :
821 f_ "multiple values for %s (in %s git config)", $c, $src
823 $l->[0] =~ m/\n/ and badcfg f_
824 "value for config option %s (in %s git config) contains newline(s)!",
833 return undef if $c =~ /RETURN-UNDEF/;
834 printdebug "C? $c\n" if $debuglevel >= 5;
835 my $v = git_get_config($c);
836 return $v if defined $v;
837 my $dv = $defcfg{$c};
839 printdebug "CD $c $dv\n" if $debuglevel >= 4;
844 "need value for one of: %s\n".
845 "%s: distro or suite appears not to be (properly) supported",
849 sub not_necessarily_a_tree () {
850 # needs to be called from pre_*
851 @gitcfgsources = grep { $_ ne 'local' } @gitcfgsources;
852 $invoked_in_git_tree = 0;
855 sub access_basedistro__noalias () {
856 if (defined $idistro) {
859 my $def = cfg("dgit-suite.$isuite.distro", 'RETURN-UNDEF');
860 return $def if defined $def;
861 foreach my $src (@gitcfgsources, 'internal') {
862 my $kl = $src eq 'internal' ? \%defcfg : $gitcfgs{$src};
864 foreach my $k (keys %$kl) {
865 next unless $k =~ m#^dgit-suite\.(.*)\.distro$#;
867 next unless match_glob $dpat, $isuite;
871 foreach my $csvf (</usr/share/distro-info/*.csv>) {
873 $csvf =~ m{/(\w+)\.csv$} ? $1 : do {
874 printdebug "skipping $csvf\n";
877 my $csv = Text::CSV->new({ binary => 1, auto_diag => 2 }) or die;
878 my $fh = new IO::File $csvf, "<:encoding(utf8)"
879 or die "open $csvf: $!";
880 while (my $cols = $csv->getline($fh)) {
881 next unless $cols->[2] eq $isuite;
884 die "$csvf $!" if $fh->error;
887 return cfg("dgit.default.distro");
891 sub access_basedistro () {
892 my $noalias = access_basedistro__noalias();
893 my $canon = cfg("dgit-distro.$noalias.alias-canon",'RETURN-UNDEF');
894 return $canon // $noalias;
897 sub access_nomdistro () {
898 my $base = access_basedistro();
899 my $r = cfg("dgit-distro.$base.nominal-distro",'RETURN-UNDEF') // $base;
900 $r =~ m/^$distro_re$/ or badcfg
901 f_ "bad syntax for (nominal) distro \`%s' (does not match %s)",
902 $r, "/^$distro_re$/";
906 sub access_quirk () {
907 # returns (quirk name, distro to use instead or undef, quirk-specific info)
908 my $basedistro = access_basedistro();
909 my $backports_quirk = cfg("dgit-distro.$basedistro.backports-quirk",
911 if (defined $backports_quirk) {
912 my $re = $backports_quirk;
913 $re =~ s/[^-0-9a-z_\%*()]/\\$&/ig;
915 $re =~ s/\%/([-0-9a-z_]+)/
916 or $re =~ m/[()]/ or badcfg __ "backports-quirk needs \% or ( )";
917 if ($isuite =~ m/^$re$/) {
918 return ('backports',"$basedistro-backports",$1);
921 return ('none',undef);
926 sub parse_cfg_bool ($$$) {
927 my ($what,$def,$v) = @_;
930 $v =~ m/^[ty1]/ ? 1 :
931 $v =~ m/^[fn0]/ ? 0 :
932 badcfg f_ "%s needs t (true, y, 1) or f (false, n, 0) not \`%s'",
936 sub access_forpush_config () {
937 my $d = access_basedistro();
941 parse_cfg_bool('new-private-pushers', 0,
942 cfg("dgit-distro.$d.new-private-pushers",
945 my $v = cfg("dgit-distro.$d.readonly", 'RETURN-UNDEF');
948 $v =~ m/^[ty1]/ ? 0 : # force readonly, forpush = 0
949 $v =~ m/^[fn0]/ ? 1 : # force nonreadonly, forpush = 1
950 $v =~ m/^[a]/ ? '' : # auto, forpush = ''
952 "readonly needs t (true, y, 1) or f (false, n, 0) or a (auto)";
955 sub access_forpush () {
956 $access_forpush //= access_forpush_config();
957 return $access_forpush;
960 sub default_from_access_cfg ($$$;$) {
961 my ($var, $keybase, $defval, $permit_re) = @_;
962 return if defined $$var;
964 $$var = access_cfg("$keybase-newer", 'RETURN-UNDEF');
965 $$var = undef if $$var && $$var !~ m/^$permit_re$/;
967 $$var //= access_cfg($keybase, 'RETURN-UNDEF');
970 badcfg f_ "unknown %s \`%s'", $keybase, $$var
971 if defined $permit_re and $$var !~ m/$permit_re/;
975 confess +(__ 'internal error').' '.Dumper($access_forpush)," ?" if
976 defined $access_forpush and !$access_forpush;
977 badcfg __ "pushing but distro is configured readonly"
978 if access_forpush_config() eq '0';
980 $supplementary_message = __ <<'END' unless $we_are_responder;
981 Push failed, before we got started.
982 You can retry the push, after fixing the problem, if you like.
984 parseopts_late_defaults();
988 parseopts_late_defaults();
991 sub determine_whether_split_brain ($) {
994 local $access_forpush;
995 default_from_access_cfg(\$splitview_mode, 'split-view', 'auto',
996 $splitview_modes_re);
997 $do_split_brain = 1 if $splitview_mode eq 'always';
1000 printdebug "format $format, quilt mode $quilt_mode\n";
1002 if (format_quiltmode_splitting $format) {
1003 $splitview_mode ne 'never' or
1004 fail f_ "dgit: quilt mode \`%s' (for format \`%s')".
1005 " implies split view, but split-view set to \`%s'",
1006 $quilt_mode, $format, $splitview_mode;
1007 $do_split_brain = 1;
1009 $do_split_brain //= 0;
1012 sub supplementary_message ($) {
1014 if (!$we_are_responder) {
1015 $supplementary_message = $msg;
1018 responder_send_command "supplementary-message ".length($msg)
1020 print PO $msg or confess "$!";
1024 sub access_distros () {
1025 # Returns list of distros to try, in order
1028 # 0. `instead of' distro name(s) we have been pointed to
1029 # 1. the access_quirk distro, if any
1030 # 2a. the user's specified distro, or failing that } basedistro
1031 # 2b. the distro calculated from the suite }
1032 my @l = access_basedistro();
1034 my (undef,$quirkdistro) = access_quirk();
1035 unshift @l, $quirkdistro;
1036 unshift @l, $instead_distro;
1037 @l = grep { defined } @l;
1039 push @l, access_nomdistro();
1041 if (access_forpush()) {
1042 @l = map { ("$_/push", $_) } @l;
1047 sub access_cfg_cfgs (@) {
1050 # The nesting of these loops determines the search order. We put
1051 # the key loop on the outside so that we search all the distros
1052 # for each key, before going on to the next key. That means that
1053 # if access_cfg is called with a more specific, and then a less
1054 # specific, key, an earlier distro can override the less specific
1055 # without necessarily overriding any more specific keys. (If the
1056 # distro wants to override the more specific keys it can simply do
1057 # so; whereas if we did the loop the other way around, it would be
1058 # impossible to for an earlier distro to override a less specific
1059 # key but not the more specific ones without restating the unknown
1060 # values of the more specific keys.
1063 # We have to deal with RETURN-UNDEF specially, so that we don't
1064 # terminate the search prematurely.
1066 if (m/RETURN-UNDEF/) { push @rundef, $_; last; }
1069 foreach my $d (access_distros()) {
1070 push @cfgs, map { "dgit-distro.$d.$_" } @realkeys;
1072 push @cfgs, map { "dgit.default.$_" } @realkeys;
1073 push @cfgs, @rundef;
1077 sub access_cfg (@) {
1079 my (@cfgs) = access_cfg_cfgs(@keys);
1080 my $value = cfg(@cfgs);
1084 sub access_cfg_bool ($$) {
1085 my ($def, @keys) = @_;
1086 parse_cfg_bool($keys[0], $def, access_cfg(@keys, 'RETURN-UNDEF'));
1089 sub string_to_ssh ($) {
1091 if ($spec =~ m/\s/) {
1092 return qw(sh -ec), 'exec '.$spec.' "$@"', 'x';
1098 sub access_cfg_ssh () {
1099 my $gitssh = access_cfg('ssh', 'RETURN-UNDEF');
1100 if (!defined $gitssh) {
1103 return string_to_ssh $gitssh;
1107 sub access_runeinfo ($) {
1109 return ": dgit ".access_basedistro()." $info ;";
1112 sub access_someuserhost ($) {
1114 my $user = access_cfg("$some-user-force", 'RETURN-UNDEF');
1115 defined($user) && length($user) or
1116 $user = access_cfg("$some-user",'username');
1117 my $host = access_cfg("$some-host");
1118 return length($user) ? "$user\@$host" : $host;
1121 sub access_gituserhost () {
1122 return access_someuserhost('git');
1125 sub access_giturl (;$) {
1126 my ($optional) = @_;
1127 my $url = access_cfg('git-url','RETURN-UNDEF');
1130 my $proto = access_cfg('git-proto', 'RETURN-UNDEF');
1131 return undef unless defined $proto;
1134 access_gituserhost().
1135 access_cfg('git-path');
1137 $suffix = access_cfg('git-url-suffix','RETURN-UNDEF');
1140 return "$url/$package$suffix";
1143 sub commit_getclogp ($) {
1144 # Returns the parsed changelog hashref for a particular commit
1146 our %commit_getclogp_memo;
1147 my $memo = $commit_getclogp_memo{$objid};
1148 return $memo if $memo;
1150 my $mclog = dgit_privdir()."clog";
1151 runcmd shell_cmd "exec >$mclog", @git, qw(cat-file blob),
1152 "$objid:debian/changelog";
1153 $commit_getclogp_memo{$objid} = parsechangelog("-l$mclog");
1156 sub parse_dscdata () {
1157 my $dscfh = new IO::File \$dscdata, '<' or confess "$!";
1158 printdebug Dumper($dscdata) if $debuglevel>1;
1159 $dsc = parsecontrolfh($dscfh,$dscurl,1);
1160 printdebug Dumper($dsc) if $debuglevel>1;
1165 sub archive_query ($;@) {
1166 my ($method) = shift @_;
1167 fail __ "this operation does not support multiple comma-separated suites"
1169 my $query = access_cfg('archive-query','RETURN-UNDEF');
1170 $query =~ s/^(\w+):// or badcfg "invalid archive-query method \`$query'";
1173 { no strict qw(refs); &{"${method}_${proto}"}($proto,$data,@_); }
1176 sub archive_query_prepend_mirror {
1177 my $m = access_cfg('mirror');
1178 return map { [ $_->[0], $m.$_->[1], @$_[2..$#$_] ] } @_;
1181 sub pool_dsc_subpath ($$) {
1182 my ($vsn,$component) = @_; # $package is implict arg
1183 my $prefix = substr($package, 0, $package =~ m/^l/ ? 4 : 1);
1184 return "/pool/$component/$prefix/$package/".dscfn($vsn);
1187 sub cfg_apply_map ($$$) {
1188 my ($varref, $what, $mapspec) = @_;
1189 return unless $mapspec;
1191 printdebug "config $what EVAL{ $mapspec; }\n";
1193 eval "package Dgit::Config; $mapspec;";
1198 sub url_fetch ($;@) {
1199 my ($url, %xopts) = @_;
1200 # Ok404 => 1 means give undef for 404
1201 # AccessBase => 'archive-query' (eg)
1202 # CurlOpts => { key => value }
1204 my $curl = WWW::Curl::Easy->new;
1207 my $x = $curl->setopt($k, $v);
1208 confess "$k $v ".$curl->strerror($x)." ?" if $x;
1211 my $response_body = '';
1212 $setopt->(CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS|CURLPROTO_HTTP);
1213 $setopt->(CURLOPT_URL, $url);
1214 $setopt->(CURLOPT_NOSIGNAL, 1);
1215 $setopt->(CURLOPT_WRITEDATA, \$response_body);
1217 my $xcurlopts = $xopts{CurlOpts} // { };
1219 while (my ($k,$v) = each %$xcurlopts) { $setopt->($k,$v); }
1221 if ($xopts{AccessBase} && $url =~ m#^https://([-.0-9a-z]+)/#) {
1222 foreach my $k ("$xopts{AccessBase}-tls-key",
1223 "$xopts{AccessBase}-tls-curl-ca-args") {
1224 fail "config option $k is obsolete and no longer supported"
1225 if defined access_cfg($k, 'RETURN-UNDEF');
1229 printdebug "query: fetching $url...\n";
1231 local $SIG{PIPE} = 'IGNORE';
1233 my $x = $curl->perform();
1234 fail f_ "fetch of %s failed (%s): %s",
1235 $url, $curl->strerror($x), $curl->errbuf
1238 my $code = $curl->getinfo(CURLINFO_HTTP_CODE);
1239 if ($code eq '404' && $xopts{Ok404}) { return undef; }
1241 fail f_ "fetch of %s gave HTTP code %s", $url, $code
1242 unless $url =~ m#^file://# or $code =~ m/^2/;
1244 confess unless defined $response_body;
1245 return $response_body;
1248 #---------- `ftpmasterapi' archive query method (nascent) ----------
1250 sub api_query_raw ($;$) {
1251 my ($subpath, $ok404) = @_;
1252 my $url = access_cfg('archive-query-url');
1254 return url_fetch $url,
1256 AccessBase => 'archive-query';
1259 sub api_query ($$;$) {
1260 my ($data, $subpath, $ok404) = @_;
1262 badcfg __ "ftpmasterapi archive query method takes no data part"
1264 my $json = api_query_raw $subpath, $ok404;
1265 return undef unless defined $json;
1266 return decode_json($json);
1269 sub canonicalise_suite_ftpmasterapi {
1270 my ($proto,$data) = @_;
1271 my $suites = api_query($data, 'suites');
1273 foreach my $entry (@$suites) {
1275 my $v = $entry->{$_};
1276 defined $v && $v eq $isuite;
1277 } qw(codename name);
1278 push @matched, $entry;
1280 fail f_ "unknown suite %s, maybe -d would help", $isuite
1284 @matched==1 or die f_ "multiple matches for suite %s\n", $isuite;
1285 $cn = "$matched[0]{codename}";
1286 defined $cn or die f_ "suite %s info has no codename\n", $isuite;
1287 $cn =~ m/^$suite_re$/
1288 or die f_ "suite %s maps to bad codename\n", $isuite;
1290 die +(__ "bad ftpmaster api response: ")."$@\n".Dumper(\@matched)
1295 sub archive_query_ftpmasterapi {
1296 my ($proto,$data) = @_;
1297 my $info = api_query($data, "dsc_in_suite/$isuite/$package");
1299 my $digester = Digest::SHA->new(256);
1300 foreach my $entry (@$info) {
1302 my $vsn = "$entry->{version}";
1303 my ($ok,$msg) = version_check $vsn;
1304 die f_ "bad version: %s\n", $msg unless $ok;
1305 my $component = "$entry->{component}";
1306 $component =~ m/^$component_re$/ or die __ "bad component";
1307 my $filename = "$entry->{filename}";
1308 $filename && $filename !~ m#[^-+:._~0-9a-zA-Z/]|^[/.]|/[/.]#
1309 or die __ "bad filename";
1310 my $sha256sum = "$entry->{sha256sum}";
1311 $sha256sum =~ m/^[0-9a-f]+$/ or die __ "bad sha256sum";
1312 push @rows, [ $vsn, "/pool/$component/$filename",
1313 $digester, $sha256sum ];
1315 die +(__ "bad ftpmaster api response: ")."$@\n".Dumper($entry)
1318 @rows = sort { -version_compare($a->[0],$b->[0]) } @rows;
1319 return archive_query_prepend_mirror @rows;
1322 sub file_in_archive_ftpmasterapi {
1323 my ($proto,$data,$filename) = @_;
1324 my $pat = $filename;
1327 $pat =~ s#[^-+_.0-9a-z/]# sprintf '%%%02x', ord $& #ge;
1328 my $info = api_query($data, "file_in_archive/$pat", 1);
1331 sub package_not_wholly_new_ftpmasterapi {
1332 my ($proto,$data,$pkg) = @_;
1333 my $info = api_query($data,"madison?package=${pkg}&f=json");
1337 #---------- `aptget' archive query method ----------
1340 our $aptget_releasefile;
1341 our $aptget_configpath;
1343 sub aptget_aptget () { return @aptget, qw(-c), $aptget_configpath; }
1344 sub aptget_aptcache () { return @aptcache, qw(-c), $aptget_configpath; }
1346 sub aptget_cache_clean {
1347 runcmd_ordryrun_local qw(sh -ec),
1348 'cd "$1"; find -atime +30 -type f -print0 | xargs -0r rm --',
1352 sub aptget_lock_acquire () {
1353 my $lockfile = "$aptget_base/lock";
1354 open APTGET_LOCK, '>', $lockfile or confess "open $lockfile: $!";
1355 flock APTGET_LOCK, LOCK_EX or confess "lock $lockfile: $!";
1358 sub aptget_prep ($) {
1360 return if defined $aptget_base;
1362 badcfg __ "aptget archive query method takes no data part"
1365 my $cache = $ENV{XDG_CACHE_DIR} // "$ENV{HOME}/.cache";
1368 ensuredir "$cache/dgit";
1370 access_cfg('aptget-cachekey','RETURN-UNDEF')
1371 // access_nomdistro();
1373 $aptget_base = "$cache/dgit/aptget";
1374 ensuredir $aptget_base;
1376 my $quoted_base = $aptget_base;
1377 confess "$quoted_base contains bad chars, cannot continue"
1378 if $quoted_base =~ m/["\\]/; # apt.conf(5) says no escaping :-/
1380 ensuredir $aptget_base;
1382 aptget_lock_acquire();
1384 aptget_cache_clean();
1386 $aptget_configpath = "$aptget_base/apt.conf#$cachekey";
1387 my $sourceslist = "source.list#$cachekey";
1389 my $aptsuites = $isuite;
1390 cfg_apply_map(\$aptsuites, 'suite map',
1391 access_cfg('aptget-suite-map', 'RETURN-UNDEF'));
1393 open SRCS, ">", "$aptget_base/$sourceslist" or confess "$!";
1394 printf SRCS "deb-src %s %s %s\n",
1395 access_cfg('mirror'),
1397 access_cfg('aptget-components')
1400 ensuredir "$aptget_base/cache";
1401 ensuredir "$aptget_base/lists";
1403 open CONF, ">", $aptget_configpath or confess "$!";
1405 Debug::NoLocking "true";
1406 APT::Get::List-Cleanup "false";
1407 #clear APT::Update::Post-Invoke-Success;
1408 Dir::Etc::SourceList "$quoted_base/$sourceslist";
1409 Dir::State::Lists "$quoted_base/lists";
1410 Dir::Etc::preferences "$quoted_base/preferences";
1411 Dir::Cache::srcpkgcache "$quoted_base/cache/srcs#$cachekey";
1412 Dir::Cache::pkgcache "$quoted_base/cache/pkgs#$cachekey";
1415 foreach my $key (qw(
1418 Dir::Cache::Archives
1419 Dir::Etc::SourceParts
1420 Dir::Etc::preferencesparts
1422 ensuredir "$aptget_base/$key";
1423 print CONF "$key \"$quoted_base/$key\";\n" or confess "$!";
1426 my $oldatime = (time // confess "$!") - 1;
1427 foreach my $oldlist (<$aptget_base/lists/*Release>) {
1428 next unless stat_exists $oldlist;
1429 my ($mtime) = (stat _)[9];
1430 utime $oldatime, $mtime, $oldlist or die "$oldlist $!";
1433 runcmd_ordryrun_local aptget_aptget(), qw(update);
1436 foreach my $oldlist (<$aptget_base/lists/*Release>) {
1437 next unless stat_exists $oldlist;
1438 my ($atime) = (stat _)[8];
1439 next if $atime == $oldatime;
1440 push @releasefiles, $oldlist;
1442 my @inreleasefiles = grep { m#/InRelease$# } @releasefiles;
1443 @releasefiles = @inreleasefiles if @inreleasefiles;
1444 if (!@releasefiles) {
1445 fail f_ <<END, $isuite, $cache;
1446 apt seemed to not to update dgit's cached Release files for %s.
1448 is on a filesystem mounted `noatime'; if so, please use `relatime'.)
1451 confess "apt updated too many Release files (@releasefiles), erk"
1452 unless @releasefiles == 1;
1454 ($aptget_releasefile) = @releasefiles;
1457 sub canonicalise_suite_aptget {
1458 my ($proto,$data) = @_;
1461 my $release = parsecontrol $aptget_releasefile, "Release file", 1;
1463 foreach my $name (qw(Codename Suite)) {
1464 my $val = $release->{$name};
1466 printdebug "release file $name: $val\n";
1467 cfg_apply_map(\$val, 'suite rmap',
1468 access_cfg('aptget-suite-rmap', 'RETURN-UNDEF'));
1469 $val =~ m/^$suite_re$/o or fail f_
1470 "Release file (%s) specifies intolerable %s",
1471 $aptget_releasefile, $name;
1478 sub archive_query_aptget {
1479 my ($proto,$data) = @_;
1482 ensuredir "$aptget_base/source";
1483 foreach my $old (<$aptget_base/source/*.dsc>) {
1484 unlink $old or die "$old: $!";
1487 my $showsrc = cmdoutput aptget_aptcache(), qw(showsrc), $package;
1488 return () unless $showsrc =~ m/^package:\s*\Q$package\E\s*$/mi;
1489 # avoids apt-get source failing with ambiguous error code
1491 runcmd_ordryrun_local
1492 shell_cmd 'cd "$1"/source; shift', $aptget_base,
1493 aptget_aptget(), qw(--download-only --only-source source), $package;
1495 my @dscs = <$aptget_base/source/*.dsc>;
1496 fail __ "apt-get source did not produce a .dsc" unless @dscs;
1497 fail f_ "apt-get source produced several .dscs (%s)", "@dscs"
1500 my $pre_dsc = parsecontrol $dscs[0], $dscs[0], 1;
1503 my $uri = "file://". uri_escape $dscs[0];
1504 $uri =~ s{\%2f}{/}gi;
1505 return [ (getfield $pre_dsc, 'Version'), $uri ];
1508 sub file_in_archive_aptget () { return undef; }
1509 sub package_not_wholly_new_aptget () { return undef; }
1511 #---------- `dummyapicat' archive query method ----------
1512 # (untranslated, because this is for testing purposes etc.)
1514 sub archive_query_dummycatapi { archive_query_ftpmasterapi @_; }
1515 sub canonicalise_suite_dummycatapi { canonicalise_suite_ftpmasterapi @_; }
1517 sub dummycatapi_run_in_mirror ($@) {
1518 # runs $fn with FIA open onto rune
1519 my ($rune, $argl, $fn) = @_;
1521 my $mirror = access_cfg('mirror');
1522 $mirror =~ s#^file://#/# or die "$mirror ?";
1523 my @cmd = (qw(sh -ec), 'cd "$1"; shift'."\n".$rune,
1524 qw(x), $mirror, @$argl);
1525 debugcmd "-|", @cmd;
1526 open FIA, "-|", @cmd or confess "$!";
1528 close FIA or ($!==0 && $?==141) or die failedcmd @cmd;
1532 sub file_in_archive_dummycatapi ($$$) {
1533 my ($proto,$data,$filename) = @_;
1535 dummycatapi_run_in_mirror '
1536 find -name "$1" -print0 |
1538 ', [$filename], sub {
1541 printdebug "| $_\n";
1542 m/^(\w+) (\S+)$/ or die "$_ ?";
1543 push @out, { sha256sum => $1, filename => $2 };
1549 sub package_not_wholly_new_dummycatapi {
1550 my ($proto,$data,$pkg) = @_;
1551 dummycatapi_run_in_mirror "
1552 find -name ${pkg}_*.dsc
1559 #---------- `madison' archive query method ----------
1561 sub archive_query_madison {
1562 return archive_query_prepend_mirror
1563 map { [ @$_[0..1] ] } madison_get_parse(@_);
1566 sub madison_get_parse {
1567 my ($proto,$data) = @_;
1568 die unless $proto eq 'madison';
1569 if (!length $data) {
1570 $data= access_cfg('madison-distro','RETURN-UNDEF');
1571 $data //= access_basedistro();
1573 $rmad{$proto,$data,$package} ||= cmdoutput
1574 qw(rmadison -asource),"-s$isuite","-u$data",$package;
1575 my $rmad = $rmad{$proto,$data,$package};
1578 foreach my $l (split /\n/, $rmad) {
1579 $l =~ m{^ \s*( [^ \t|]+ )\s* \|
1580 \s*( [^ \t|]+ )\s* \|
1581 \s*( [^ \t|/]+ )(?:/([^ \t|/]+))? \s* \|
1582 \s*( [^ \t|]+ )\s* }x or die "$rmad ?";
1583 $1 eq $package or die "$rmad $package ?";
1590 $component = access_cfg('archive-query-default-component');
1592 $5 eq 'source' or die "$rmad ?";
1593 push @out, [$vsn,pool_dsc_subpath($vsn,$component),$newsuite];
1595 return sort { -version_compare($a->[0],$b->[0]); } @out;
1598 sub canonicalise_suite_madison {
1599 # madison canonicalises for us
1600 my @r = madison_get_parse(@_);
1602 "unable to canonicalise suite using package %s".
1603 " which does not appear to exist in suite %s;".
1604 " --existing-package may help",
1609 sub file_in_archive_madison { return undef; }
1610 sub package_not_wholly_new_madison { return undef; }
1612 #---------- `sshpsql' archive query method ----------
1613 # (untranslated, because this is obsolete)
1616 my ($data,$runeinfo,$sql) = @_;
1617 if (!length $data) {
1618 $data= access_someuserhost('sshpsql').':'.
1619 access_cfg('sshpsql-dbname');
1621 $data =~ m/:/ or badcfg "invalid sshpsql method string \`$data'";
1622 my ($userhost,$dbname) = ($`,$'); #';
1624 my @cmd = (access_cfg_ssh, $userhost,
1625 access_runeinfo("ssh-psql $runeinfo").
1626 " export LC_MESSAGES=C; export LC_CTYPE=C;".
1627 " ".shellquote qw(psql -A), $dbname, qw(-c), $sql);
1629 open P, "-|", @cmd or confess "$!";
1632 printdebug(">|$_|\n");
1635 $!=0; $?=0; close P or failedcmd @cmd;
1637 my $nrows = pop @rows;
1638 $nrows =~ s/^\((\d+) rows?\)$/$1/ or die "$nrows ?";
1639 @rows == $nrows+1 or die "$nrows ".(scalar @rows)." ?";
1640 @rows = map { [ split /\|/, $_ ] } @rows;
1641 my $ncols = scalar @{ shift @rows };
1642 die if grep { scalar @$_ != $ncols } @rows;
1646 sub sql_injection_check {
1647 foreach (@_) { die "$_ $& ?" if m{[^-+=:_.,/0-9a-zA-Z]}; }
1650 sub archive_query_sshpsql ($$) {
1651 my ($proto,$data) = @_;
1652 sql_injection_check $isuite, $package;
1653 my @rows = sshpsql($data, "archive-query $isuite $package", <<END);
1654 SELECT source.version, component.name, files.filename, files.sha256sum
1656 JOIN src_associations ON source.id = src_associations.source
1657 JOIN suite ON suite.id = src_associations.suite
1658 JOIN dsc_files ON dsc_files.source = source.id
1659 JOIN files_archive_map ON files_archive_map.file_id = dsc_files.file
1660 JOIN component ON component.id = files_archive_map.component_id
1661 JOIN files ON files.id = dsc_files.file
1662 WHERE ( suite.suite_name='$isuite' OR suite.codename='$isuite' )
1663 AND source.source='$package'
1664 AND files.filename LIKE '%.dsc';
1666 @rows = sort { -version_compare($a->[0],$b->[0]) } @rows;
1667 my $digester = Digest::SHA->new(256);
1669 my ($vsn,$component,$filename,$sha256sum) = @$_;
1670 [ $vsn, "/pool/$component/$filename",$digester,$sha256sum ];
1672 return archive_query_prepend_mirror @rows;
1675 sub canonicalise_suite_sshpsql ($$) {
1676 my ($proto,$data) = @_;
1677 sql_injection_check $isuite;
1678 my @rows = sshpsql($data, "canonicalise-suite $isuite", <<END);
1679 SELECT suite.codename
1680 FROM suite where suite_name='$isuite' or codename='$isuite';
1682 @rows = map { $_->[0] } @rows;
1683 fail "unknown suite $isuite" unless @rows;
1684 die "ambiguous $isuite: @rows ?" if @rows>1;
1688 sub file_in_archive_sshpsql ($$$) { return undef; }
1689 sub package_not_wholly_new_sshpsql ($$$) { return undef; }
1691 #---------- `dummycat' archive query method ----------
1692 # (untranslated, because this is for testing purposes etc.)
1694 sub canonicalise_suite_dummycat ($$) {
1695 my ($proto,$data) = @_;
1696 my $dpath = "$data/suite.$isuite";
1697 if (!open C, "<", $dpath) {
1698 $!==ENOENT or die "$dpath: $!";
1699 printdebug "dummycat canonicalise_suite $isuite $dpath ENOENT\n";
1703 chomp or die "$dpath: $!";
1705 printdebug "dummycat canonicalise_suite $isuite $dpath = $_\n";
1709 sub archive_query_dummycat ($$) {
1710 my ($proto,$data) = @_;
1711 canonicalise_suite();
1712 my $dpath = "$data/package.$csuite.$package";
1713 if (!open C, "<", $dpath) {
1714 $!==ENOENT or die "$dpath: $!";
1715 printdebug "dummycat query $csuite $package $dpath ENOENT\n";
1723 printdebug "dummycat query $csuite $package $dpath | $_\n";
1724 my @row = split /\s+/, $_;
1725 @row==2 or die "$dpath: $_ ?";
1728 C->error and die "$dpath: $!";
1730 return archive_query_prepend_mirror
1731 sort { -version_compare($a->[0],$b->[0]); } @rows;
1734 sub file_in_archive_dummycat () { return undef; }
1735 sub package_not_wholly_new_dummycat () { return undef; }
1737 #---------- archive query entrypoints and rest of program ----------
1739 sub canonicalise_suite () {
1740 return if defined $csuite;
1741 fail f_ "cannot operate on %s suite", $isuite if $isuite eq 'UNRELEASED';
1742 $csuite = archive_query('canonicalise_suite');
1743 if ($isuite ne $csuite) {
1744 progress f_ "canonical suite name for %s is %s", $isuite, $csuite;
1746 progress f_ "canonical suite name is %s", $csuite;
1750 sub get_archive_dsc () {
1751 canonicalise_suite();
1752 my @vsns = archive_query('archive_query');
1753 foreach my $vinfo (@vsns) {
1754 my ($vsn,$vsn_dscurl,$digester,$digest) = @$vinfo;
1755 $dscurl = $vsn_dscurl;
1756 $dscdata = url_fetch($dscurl);
1758 $skew_warning_vsn = $vsn if !defined $skew_warning_vsn;
1763 $digester->add($dscdata);
1764 my $got = $digester->hexdigest();
1766 fail f_ "%s has hash %s but archive told us to expect %s",
1767 $dscurl, $got, $digest;
1770 my $fmt = getfield $dsc, 'Format';
1771 $format_ok{$fmt} or forceable_fail [qw(unsupported-source-format)],
1772 f_ "unsupported source format %s, sorry", $fmt;
1774 $dsc_checked = !!$digester;
1775 printdebug "get_archive_dsc: Version ".(getfield $dsc, 'Version')."\n";
1779 printdebug "get_archive_dsc: nothing in archive, returning undef\n";
1782 sub check_for_git ();
1783 sub check_for_git () {
1785 my $how = access_cfg('git-check');
1786 if ($how eq 'ssh-cmd') {
1788 (access_cfg_ssh, access_gituserhost(),
1789 access_runeinfo("git-check $package").
1790 " set -e; cd ".access_cfg('git-path').";".
1791 " if test -d $package.git; then echo 1; else echo 0; fi");
1792 my $r= cmdoutput @cmd;
1793 if (defined $r and $r =~ m/^divert (\w+)$/) {
1795 my ($usedistro,) = access_distros();
1796 # NB that if we are pushing, $usedistro will be $distro/push
1797 $instead_distro= cfg("dgit-distro.$usedistro.diverts.$divert");
1798 $instead_distro =~ s{^/}{ access_basedistro()."/" }e;
1799 progress f_ "diverting to %s (using config for %s)",
1800 $divert, $instead_distro;
1801 return check_for_git();
1803 failedcmd @cmd unless defined $r and $r =~ m/^[01]$/;
1805 } elsif ($how eq 'url') {
1806 my $prefix = access_cfg('git-check-url','git-url');
1807 my $suffix = access_cfg('git-check-suffix','git-suffix',
1808 'RETURN-UNDEF') // '.git';
1809 my $url = "$prefix/$package$suffix";
1810 my $result = url_fetch $url,
1811 CurlOpts => { CURLOPT_NOBODY() => 1 },
1813 AccessBase => 'git-check';
1814 $result = defined $result;
1815 printdebug "dgit-repos check_for_git => $result.\n";
1817 } elsif ($how eq 'true') {
1819 } elsif ($how eq 'false') {
1822 badcfg f_ "unknown git-check \`%s'", $how;
1826 sub create_remote_git_repo () {
1827 my $how = access_cfg('git-create');
1828 if ($how eq 'ssh-cmd') {
1830 (access_cfg_ssh, access_gituserhost(),
1831 access_runeinfo("git-create $package").
1832 "set -e; cd ".access_cfg('git-path').";".
1833 " cp -a _template $package.git");
1834 } elsif ($how eq 'true') {
1837 badcfg f_ "unknown git-create \`%s'", $how;
1841 our ($dsc_hash,$lastpush_mergeinput);
1842 our ($dsc_distro, $dsc_hint_tag, $dsc_hint_url);
1846 dgit_privdir(); # ensures that $dgit_privdir_made is based on $maindir
1847 $playground = fresh_playground 'dgit/unpack';
1850 sub mktree_in_ud_here () {
1854 sub git_write_tree () {
1855 my $tree = cmdoutput @git, qw(write-tree);
1856 $tree =~ m/^\w+$/ or die "$tree ?";
1860 sub git_add_write_tree () {
1861 runcmd @git, qw(add -Af .);
1862 return git_write_tree();
1865 sub remove_stray_gits ($) {
1867 my @gitscmd = qw(find -name .git -prune -print0);
1868 debugcmd "|",@gitscmd;
1869 open GITS, "-|", @gitscmd or confess "$!";
1874 print STDERR f_ "%s: warning: removing from %s: %s\n",
1875 $us, $what, (messagequote $_);
1879 $!=0; $?=0; close GITS or failedcmd @gitscmd;
1882 sub mktree_in_ud_from_only_subdir ($;$) {
1883 my ($what,$raw) = @_;
1884 # changes into the subdir
1887 confess "expected one subdir but found @dirs ?" unless @dirs==1;
1888 $dirs[0] =~ m#^([^/]+)/\.$# or die;
1892 remove_stray_gits($what);
1893 mktree_in_ud_here();
1895 my ($format, $fopts) = get_source_format();
1896 if (madformat($format)) {
1901 my $tree=git_add_write_tree();
1902 return ($tree,$dir);
1905 our @files_csum_info_fields =
1906 (['Checksums-Sha256','Digest::SHA', 'new(256)', 'sha256sum'],
1907 ['Checksums-Sha1', 'Digest::SHA', 'new(1)', 'sha1sum'],
1908 ['Files', 'Digest::MD5', 'new()', 'md5sum']);
1910 sub dsc_files_info () {
1911 foreach my $csumi (@files_csum_info_fields) {
1912 my ($fname, $module, $method) = @$csumi;
1913 my $field = $dsc->{$fname};
1914 next unless defined $field;
1915 eval "use $module; 1;" or die $@;
1917 foreach (split /\n/, $field) {
1919 m/^(\w+) (\d+) (\S+)$/ or
1920 fail f_ "could not parse .dsc %s line \`%s'", $fname, $_;
1921 my $digester = eval "$module"."->$method;" or die $@;
1926 Digester => $digester,
1931 fail f_ "missing any supported Checksums-* or Files field in %s",
1932 $dsc->get_option('name');
1936 map { $_->{Filename} } dsc_files_info();
1939 sub files_compare_inputs (@) {
1944 my $showinputs = sub {
1945 return join "; ", map { $_->get_option('name') } @$inputs;
1948 foreach my $in (@$inputs) {
1950 my $in_name = $in->get_option('name');
1952 printdebug "files_compare_inputs $in_name\n";
1954 foreach my $csumi (@files_csum_info_fields) {
1955 my ($fname) = @$csumi;
1956 printdebug "files_compare_inputs $in_name $fname\n";
1958 my $field = $in->{$fname};
1959 next unless defined $field;
1962 foreach (split /\n/, $field) {
1965 my ($info, $f) = m/^(\w+ \d+) (?:\S+ \S+ )?(\S+)$/ or
1966 fail "could not parse $in_name $fname line \`$_'";
1968 printdebug "files_compare_inputs $in_name $fname $f\n";
1972 my $re = \ $record{$f}{$fname};
1974 $fchecked{$f}{$in_name} = 1;
1977 "hash or size of %s varies in %s fields (between: %s)",
1978 $f, $fname, $showinputs->();
1983 @files = sort @files;
1984 $expected_files //= \@files;
1985 "@$expected_files" eq "@files" or
1986 fail f_ "file list in %s varies between hash fields!",
1990 fail f_ "%s has no files list field(s)", $in_name;
1992 printdebug "files_compare_inputs ".Dumper(\%fchecked, \%record)
1995 grep { keys %$_ == @$inputs-1 } values %fchecked
1996 or fail f_ "no file appears in all file lists (looked in: %s)",
2000 sub is_orig_file_in_dsc ($$) {
2001 my ($f, $dsc_files_info) = @_;
2002 return 0 if @$dsc_files_info <= 1;
2003 # One file means no origs, and the filename doesn't have a "what
2004 # part of dsc" component. (Consider versions ending `.orig'.)
2005 return 0 unless $f =~ m/\.$orig_f_tail_re$/o;
2009 # This function determines whether a .changes file is source-only from
2010 # the point of view of dak. Thus, it permits *_source.buildinfo
2013 # It does not, however, permit any other buildinfo files. After a
2014 # source-only upload, the buildds will try to upload files like
2015 # foo_1.2.3_amd64.buildinfo. If the package maintainer included files
2016 # named like this in their (otherwise) source-only upload, the uploads
2017 # of the buildd can be rejected by dak. Fixing the resultant
2018 # situation can require manual intervention. So we block such
2019 # .buildinfo files when the user tells us to perform a source-only
2020 # upload (such as when using the push-source subcommand with the -C
2021 # option, which calls this function).
2023 # Note, though, that when dgit is told to prepare a source-only
2024 # upload, such as when subcommands like build-source and push-source
2025 # without -C are used, dgit has a more restrictive notion of
2026 # source-only .changes than dak: such uploads will never include
2027 # *_source.buildinfo files. This is because there is no use for such
2028 # files when using a tool like dgit to produce the source package, as
2029 # dgit ensures the source is identical to git HEAD.
2030 sub test_source_only_changes ($) {
2032 foreach my $l (split /\n/, getfield $changes, 'Files') {
2033 $l =~ m/\S+$/ or next;
2034 # \.tar\.[a-z0-9]+ covers orig.tar and the tarballs in native packages
2035 unless ($& =~ m/(?:\.dsc|\.diff\.gz|$tarball_f_ext_re|_source\.buildinfo)$/) {
2036 print f_ "purportedly source-only changes polluted by %s\n", $&;
2043 sub changes_update_origs_from_dsc ($$$$) {
2044 my ($dsc, $changes, $upstreamvsn, $changesfile) = @_;
2046 printdebug "checking origs needed ($upstreamvsn)...\n";
2047 $_ = getfield $changes, 'Files';
2048 m/^\w+ \d+ (\S+ \S+) \S+$/m or
2049 fail __ "cannot find section/priority from .changes Files field";
2050 my $placementinfo = $1;
2052 printdebug "checking origs needed placement '$placementinfo'...\n";
2053 foreach my $l (split /\n/, getfield $dsc, 'Files') {
2054 $l =~ m/\S+$/ or next;
2056 printdebug "origs $file | $l\n";
2057 next unless is_orig_file_of_vsn $file, $upstreamvsn;
2058 printdebug "origs $file is_orig\n";
2059 my $have = archive_query('file_in_archive', $file);
2060 if (!defined $have) {
2061 print STDERR __ <<END;
2062 archive does not support .orig check; hope you used --ch:--sa/-sd if needed
2068 printdebug "origs $file \$#\$have=$#$have\n";
2069 foreach my $h (@$have) {
2072 foreach my $csumi (@files_csum_info_fields) {
2073 my ($fname, $module, $method, $archivefield) = @$csumi;
2074 next unless defined $h->{$archivefield};
2075 $_ = $dsc->{$fname};
2076 next unless defined;
2077 m/^(\w+) .* \Q$file\E$/m or
2078 fail f_ ".dsc %s missing entry for %s", $fname, $file;
2079 if ($h->{$archivefield} eq $1) {
2083 "%s: %s (archive) != %s (local .dsc)",
2084 $archivefield, $h->{$archivefield}, $1;
2087 confess "$file ".Dumper($h)." ?!" if $same && @differ;
2091 f_ "archive %s: %s", $h->{filename}, join "; ", @differ
2094 printdebug "origs $file f.same=$found_same".
2095 " #f._differ=$#found_differ\n";
2096 if (@found_differ && !$found_same) {
2098 (f_ "archive contains %s with different checksum", $file),
2101 # Now we edit the changes file to add or remove it
2102 foreach my $csumi (@files_csum_info_fields) {
2103 my ($fname, $module, $method, $archivefield) = @$csumi;
2104 next unless defined $changes->{$fname};
2106 # in archive, delete from .changes if it's there
2107 $changed{$file} = "removed" if
2108 $changes->{$fname} =~ s/\n.* \Q$file\E$(?:)$//m;
2109 } elsif ($changes->{$fname} =~ m/^.* \Q$file\E$(?:)$/m) {
2110 # not in archive, but it's here in the .changes
2112 my $dsc_data = getfield $dsc, $fname;
2113 $dsc_data =~ m/^(.* \Q$file\E$)$/m or die "$dsc_data $file ?";
2115 $extra =~ s/ \d+ /$&$placementinfo /
2116 or confess "$fname $extra >$dsc_data< ?"
2117 if $fname eq 'Files';
2118 $changes->{$fname} .= "\n". $extra;
2119 $changed{$file} = "added";
2124 foreach my $file (keys %changed) {
2126 "edited .changes for archive .orig contents: %s %s",
2127 $changed{$file}, $file;
2129 my $chtmp = "$changesfile.tmp";
2130 $changes->save($chtmp);
2132 rename $chtmp,$changesfile or die "$changesfile $!";
2134 progress f_ "[new .changes left in %s]", $changesfile;
2137 progress f_ "%s already has appropriate .orig(s) (if any)",
2142 sub clogp_authline ($) {
2144 my $author = getfield $clogp, 'Maintainer';
2145 if ($author =~ m/^[^"\@]+\,/) {
2146 # single entry Maintainer field with unquoted comma
2147 $author = ($& =~ y/,//rd).$'; # strip the comma
2149 # git wants a single author; any remaining commas in $author
2150 # are by now preceded by @ (or "). It seems safer to punt on
2151 # "..." for now rather than attempting to dequote or something.
2152 $author =~ s#,.*##ms unless $author =~ m/"/;
2153 my $date = cmdoutput qw(date), '+%s %z', qw(-d), getfield($clogp,'Date');
2154 my $authline = "$author $date";
2155 $authline =~ m/$git_authline_re/o or
2156 fail f_ "unexpected commit author line format \`%s'".
2157 " (was generated from changelog Maintainer field)",
2159 return ($1,$2,$3) if wantarray;
2163 sub vendor_patches_distro ($$) {
2164 my ($checkdistro, $what) = @_;
2165 return unless defined $checkdistro;
2167 my $series = "debian/patches/\L$checkdistro\E.series";
2168 printdebug "checking for vendor-specific $series ($what)\n";
2170 if (!open SERIES, "<", $series) {
2171 confess "$series $!" unless $!==ENOENT;
2178 print STDERR __ <<END;
2180 Unfortunately, this source package uses a feature of dpkg-source where
2181 the same source package unpacks to different source code on different
2182 distros. dgit cannot safely operate on such packages on affected
2183 distros, because the meaning of source packages is not stable.
2185 Please ask the distro/maintainer to remove the distro-specific series
2186 files and use a different technique (if necessary, uploading actually
2187 different packages, if different distros are supposed to have
2191 fail f_ "Found active distro-specific series file for".
2192 " %s (%s): %s, cannot continue",
2193 $checkdistro, $what, $series;
2195 die "$series $!" if SERIES->error;
2199 sub check_for_vendor_patches () {
2200 # This dpkg-source feature doesn't seem to be documented anywhere!
2201 # But it can be found in the changelog (reformatted):
2203 # commit 4fa01b70df1dc4458daee306cfa1f987b69da58c
2204 # Author: Raphael Hertzog <hertzog@debian.org>
2205 # Date: Sun Oct 3 09:36:48 2010 +0200
2207 # dpkg-source: correctly create .pc/.quilt_series with alternate
2210 # If you have debian/patches/ubuntu.series and you were
2211 # unpacking the source package on ubuntu, quilt was still
2212 # directed to debian/patches/series instead of
2213 # debian/patches/ubuntu.series.
2215 # debian/changelog | 3 +++
2216 # scripts/Dpkg/Source/Package/V3/quilt.pm | 4 +++-
2217 # 2 files changed, 6 insertions(+), 1 deletion(-)
2220 vendor_patches_distro($ENV{DEB_VENDOR}, "DEB_VENDOR");
2221 vendor_patches_distro(Dpkg::Vendor::get_current_vendor(),
2222 __ "Dpkg::Vendor \`current vendor'");
2223 vendor_patches_distro(access_basedistro(),
2224 __ "(base) distro being accessed");
2225 vendor_patches_distro(access_nomdistro(),
2226 __ "(nominal) distro being accessed");
2229 sub check_bpd_exists () {
2230 stat $buildproductsdir
2231 or fail f_ "build-products-dir %s is not accessible: %s\n",
2232 $buildproductsdir, $!;
2235 sub dotdot_bpd_transfer_origs ($$$) {
2236 my ($bpd_abs, $upstreamversion, $wanted) = @_;
2237 # checks is_orig_file_of_vsn and if
2238 # calls $wanted->{$leaf} and expects boolish
2240 return if $buildproductsdir eq '..';
2243 my $dotdot = $maindir;
2244 $dotdot =~ s{/[^/]+$}{};
2245 opendir DD, $dotdot or fail "opendir .. ($dotdot): $!";
2246 while ($!=0, defined(my $leaf = readdir DD)) {
2248 local ($debuglevel) = $debuglevel-1;
2249 printdebug "DD_BPD $leaf ?\n";
2251 next unless is_orig_file_of_vsn $leaf, $upstreamversion;
2252 next unless $wanted->($leaf);
2253 next if lstat "$bpd_abs/$leaf";
2256 "%s: found orig(s) in .. missing from build-products-dir, transferring:\n",
2259 $! == &ENOENT or fail f_
2260 "check orig file %s in bpd %s: %s", $leaf, $bpd_abs, $!;
2261 lstat "$dotdot/$leaf" or fail f_
2262 "check orig file %s in ..: %s", $leaf, $!;
2264 stat "$dotdot/$leaf" or fail f_
2265 "check target of orig symlink %s in ..: %s", $leaf, $!;
2266 my $ltarget = readlink "$dotdot/$leaf" or
2267 die "readlink $dotdot/$leaf: $!";
2268 if ($ltarget !~ m{^/}) {
2269 $ltarget = "$dotdot/$ltarget";
2271 symlink $ltarget, "$bpd_abs/$leaf"
2272 or die "$ltarget $bpd_abs $leaf: $!";
2274 "%s: cloned orig symlink from ..: %s\n",
2276 } elsif (link "$dotdot/$leaf", "$bpd_abs/$leaf") {
2278 "%s: hardlinked orig from ..: %s\n",
2280 } elsif ($! != EXDEV) {
2281 fail f_ "failed to make %s a hardlink to %s: %s",
2282 "$bpd_abs/$leaf", "$dotdot/$leaf", $!;
2284 symlink "$bpd_abs/$leaf", "$dotdot/$leaf"
2285 or die "$bpd_abs $dotdot $leaf $!";
2287 "%s: symmlinked orig from .. on other filesystem: %s\n",
2291 die "$dotdot; $!" if $!;
2295 sub import_tarball_tartrees ($$) {
2296 my ($upstreamv, $dfi) = @_;
2297 # cwd should be the playground
2299 # We unpack and record the orig tarballs first, so that we only
2300 # need disk space for one private copy of the unpacked source.
2301 # But we can't make them into commits until we have the metadata
2302 # from the debian/changelog, so we record the tree objects now and
2303 # make them into commits later.
2305 my $orig_f_base = srcfn $upstreamv, '';
2307 foreach my $fi (@$dfi) {
2308 # We actually import, and record as a commit, every tarball
2309 # (unless there is only one file, in which case there seems
2312 my $f = $fi->{Filename};
2313 printdebug "import considering $f ";
2314 (printdebug "not tar\n"), next unless $f =~ m/\.tar(\.\w+)?$/;
2315 (printdebug "signature\n"), next if $f =~ m/$orig_f_sig_re$/o;
2319 $f =~ m/^\Q$orig_f_base\E\.([^._]+)?\.tar(?:\.\w+)?$/;
2321 printdebug "Y ", (join ' ', map { $_//"(none)" }
2322 $compr_ext, $orig_f_part
2325 my $path = $fi->{Path} // $f;
2326 my $input = new IO::File $f, '<' or die "$f $!";
2330 if (defined $compr_ext) {
2332 Dpkg::Compression::compression_guess_from_filename $f;
2333 fail "Dpkg::Compression cannot handle file $f in source package"
2334 if defined $compr_ext && !defined $cname;
2336 new Dpkg::Compression::Process compression => $cname;
2337 @compr_cmd = $compr_proc->get_uncompress_cmdline();
2338 my $compr_fh = new IO::Handle;
2339 my $compr_pid = open $compr_fh, "-|" // confess "$!";
2341 open STDIN, "<&", $input or confess "$!";
2343 die "dgit (child): exec $compr_cmd[0]: $!\n";
2348 rmtree "_unpack-tar";
2349 mkdir "_unpack-tar" or confess "$!";
2350 my @tarcmd = qw(tar -x -f -
2351 --no-same-owner --no-same-permissions
2352 --no-acls --no-xattrs --no-selinux);
2353 my $tar_pid = fork // confess "$!";
2355 chdir "_unpack-tar" or confess "$!";
2356 open STDIN, "<&", $input or confess "$!";
2358 die f_ "dgit (child): exec %s: %s", $tarcmd[0], $!;
2360 $!=0; (waitpid $tar_pid, 0) == $tar_pid or confess "$!";
2361 !$? or failedcmd @tarcmd;
2364 (@compr_cmd ? ($?==SIGPIPE || failedcmd @compr_cmd)
2366 # finally, we have the results in "tarball", but maybe
2367 # with the wrong permissions
2369 runcmd qw(chmod -R +rwX _unpack-tar);
2370 changedir "_unpack-tar";
2371 remove_stray_gits($f);
2372 mktree_in_ud_here();
2374 my ($tree) = git_add_write_tree();
2375 my $tentries = cmdoutput @git, qw(ls-tree -z), $tree;
2376 if ($tentries =~ m/^\d+ tree (\w+)\t[^\000]+\000$/s) {
2378 printdebug "one subtree $1\n";
2380 printdebug "multiple subtrees\n";
2383 rmtree "_unpack-tar";
2385 my $ent = [ $f, $tree ];
2387 Orig => !!$orig_f_part,
2388 Sort => (!$orig_f_part ? 2 :
2389 $orig_f_part =~ m/-/g ? 1 :
2391 OrigPart => $orig_f_part, # 'orig', 'orig-XXX', or undef
2398 # put any without "_" first (spec is not clear whether files
2399 # are always in the usual order). Tarballs without "_" are
2400 # the main orig or the debian tarball.
2401 $a->{Sort} <=> $b->{Sort} or
2408 sub import_tarball_commits ($$) {
2409 my ($tartrees, $upstreamv) = @_;
2410 # cwd should be a playtree which has a relevant debian/changelog
2411 # fills in $tt->{Commit} for each one
2413 my $any_orig = grep { $_->{Orig} } @$tartrees;
2415 my @clogcmd = qw(dpkg-parsechangelog --format rfc822 --all);
2419 printdebug "import clog search...\n";
2420 parsechangelog_loop \@clogcmd, (__ "package changelog"), sub {
2421 my ($thisstanza, $desc) = @_;
2422 no warnings qw(exiting);
2424 $clogp //= $thisstanza;
2426 printdebug "import clog $thisstanza->{version} $desc...\n";
2428 last if !$any_orig; # we don't need $r1clogp
2430 # We look for the first (most recent) changelog entry whose
2431 # version number is lower than the upstream version of this
2432 # package. Then the last (least recent) previous changelog
2433 # entry is treated as the one which introduced this upstream
2434 # version and used for the synthetic commits for the upstream
2437 # One might think that a more sophisticated algorithm would be
2438 # necessary. But: we do not want to scan the whole changelog
2439 # file. Stopping when we see an earlier version, which
2440 # necessarily then is an earlier upstream version, is the only
2441 # realistic way to do that. Then, either the earliest
2442 # changelog entry we have seen so far is indeed the earliest
2443 # upload of this upstream version; or there are only changelog
2444 # entries relating to later upstream versions (which is not
2445 # possible unless the changelog and .dsc disagree about the
2446 # version). Then it remains to choose between the physically
2447 # last entry in the file, and the one with the lowest version
2448 # number. If these are not the same, we guess that the
2449 # versions were created in a non-monotonic order rather than
2450 # that the changelog entries have been misordered.
2452 printdebug "import clog $thisstanza->{version} vs $upstreamv...\n";
2454 last if version_compare($thisstanza->{version}, $upstreamv) < 0;
2455 $r1clogp = $thisstanza;
2457 printdebug "import clog $r1clogp->{version} becomes r1\n";
2460 $clogp or fail __ "package changelog has no entries!";
2462 my $authline = clogp_authline $clogp;
2463 my $changes = getfield $clogp, 'Changes';
2464 $changes =~ s/^\n//; # Changes: \n
2465 my $cversion = getfield $clogp, 'Version';
2469 $r1clogp //= $clogp; # maybe there's only one entry;
2470 $r1authline = clogp_authline $r1clogp;
2471 # Strictly, r1authline might now be wrong if it's going to be
2472 # unused because !$any_orig. Whatever.
2474 printdebug "import tartrees authline $authline\n";
2475 printdebug "import tartrees r1authline $r1authline\n";
2477 foreach my $tt (@$tartrees) {
2478 printdebug "import tartree $tt->{F} $tt->{Tree}\n";
2480 # untranslated so that different people's imports are identical
2481 my $mbody = sprintf "Import %s", $tt->{F};
2482 $tt->{Commit} = hash_commit_text($tt->{Orig} ? <<END_O : <<END_T);
2485 committer $r1authline
2489 [dgit import orig $tt->{F}]
2497 [dgit import tarball $package $cversion $tt->{F}]
2502 return ($authline, $r1authline, $clogp, $changes);
2505 sub generate_commits_from_dsc () {
2506 # See big comment in fetch_from_archive, below.
2507 # See also README.dsc-import.
2509 changedir $playground;
2511 my $bpd_abs = bpd_abs();
2512 my $upstreamv = upstreamversion $dsc->{version};
2513 my @dfi = dsc_files_info();
2515 dotdot_bpd_transfer_origs $bpd_abs, $upstreamv,
2516 sub { grep { $_->{Filename} eq $_[0] } @dfi };
2518 foreach my $fi (@dfi) {
2519 my $f = $fi->{Filename};
2520 die "$f ?" if $f =~ m#/|^\.|\.dsc$|\.tmp$#;
2521 my $upper_f = "$bpd_abs/$f";
2523 printdebug "considering reusing $f: ";
2525 if (link_ltarget "$upper_f,fetch", $f) {
2526 printdebug "linked (using ...,fetch).\n";
2527 } elsif ((printdebug "($!) "),
2529 fail f_ "accessing %s: %s", "$buildproductsdir/$f,fetch", $!;
2530 } elsif (link_ltarget $upper_f, $f) {
2531 printdebug "linked.\n";
2532 } elsif ((printdebug "($!) "),
2534 fail f_ "accessing %s: %s", "$buildproductsdir/$f", $!;
2536 printdebug "absent.\n";
2540 complete_file_from_dsc('.', $fi, \$refetched)
2543 printdebug "considering saving $f: ";
2545 if (rename_link_xf 1, $f, $upper_f) {
2546 printdebug "linked.\n";
2547 } elsif ((printdebug "($@) "),
2549 fail f_ "saving %s: %s", "$buildproductsdir/$f", $@;
2550 } elsif (!$refetched) {
2551 printdebug "no need.\n";
2552 } elsif (rename_link_xf 1, $f, "$upper_f,fetch") {
2553 printdebug "linked (using ...,fetch).\n";
2554 } elsif ((printdebug "($@) "),
2556 fail f_ "saving %s: %s", "$buildproductsdir/$f,fetch", $@;
2558 printdebug "cannot.\n";
2563 @tartrees = import_tarball_tartrees($upstreamv, \@dfi)
2564 unless @dfi == 1; # only one file in .dsc
2566 my $dscfn = "$package.dsc";
2568 my $treeimporthow = 'package';
2570 open D, ">", $dscfn or die "$dscfn: $!";
2571 print D $dscdata or die "$dscfn: $!";
2572 close D or die "$dscfn: $!";
2573 my @cmd = qw(dpkg-source);
2574 push @cmd, '--no-check' if $dsc_checked;
2575 if (madformat $dsc->{format}) {
2576 push @cmd, '--skip-patches';
2577 $treeimporthow = 'unpatched';
2579 push @cmd, qw(-x --), $dscfn;
2582 my ($tree,$dir) = mktree_in_ud_from_only_subdir(__ "source package");
2583 if (madformat $dsc->{format}) {
2584 check_for_vendor_patches();
2588 if (madformat $dsc->{format}) {
2589 my @pcmd = qw(dpkg-source --before-build .);
2590 runcmd shell_cmd 'exec >/dev/null', @pcmd;
2592 $dappliedtree = git_add_write_tree();
2595 my ($authline, $r1authline, $clogp, $changes) =
2596 import_tarball_commits(\@tartrees, $upstreamv);
2598 my $cversion = getfield $clogp, 'Version';
2600 printdebug "import main commit\n";
2602 open C, ">../commit.tmp" or confess "$!";
2603 print C <<END or confess "$!";
2606 print C <<END or confess "$!" foreach @tartrees;
2609 print C <<END or confess "$!";
2615 [dgit import $treeimporthow $package $cversion]
2618 close C or confess "$!";
2619 my $rawimport_hash = hash_commit qw(../commit.tmp);
2621 if (madformat $dsc->{format}) {
2622 printdebug "import apply patches...\n";
2624 # regularise the state of the working tree so that
2625 # the checkout of $rawimport_hash works nicely.
2626 my $dappliedcommit = hash_commit_text(<<END);
2633 runcmd @git, qw(checkout -q -b dapplied), $dappliedcommit;
2635 runcmd @git, qw(checkout -q -b unpa), $rawimport_hash;
2637 # We need the answers to be reproducible
2638 my @authline = clogp_authline($clogp);
2639 local $ENV{GIT_COMMITTER_NAME} = $authline[0];
2640 local $ENV{GIT_COMMITTER_EMAIL} = $authline[1];
2641 local $ENV{GIT_COMMITTER_DATE} = $authline[2];
2642 local $ENV{GIT_AUTHOR_NAME} = $authline[0];
2643 local $ENV{GIT_AUTHOR_EMAIL} = $authline[1];
2644 local $ENV{GIT_AUTHOR_DATE} = $authline[2];
2646 my $path = $ENV{PATH} or die;
2648 # we use ../../gbp-pq-output, which (given that we are in
2649 # $playground/PLAYTREE, and $playground is .git/dgit/unpack,
2652 foreach my $use_absurd (qw(0 1)) {
2653 runcmd @git, qw(checkout -q unpa);
2654 runcmd @git, qw(update-ref -d refs/heads/patch-queue/unpa);
2655 local $ENV{PATH} = $path;
2658 progress "warning: $@";
2659 $path = "$absurdity:$path";
2660 progress f_ "%s: trying slow absurd-git-apply...", $us;
2661 rename "../../gbp-pq-output","../../gbp-pq-output.0"
2666 die "forbid absurd git-apply\n" if $use_absurd
2667 && forceing [qw(import-gitapply-no-absurd)];
2668 die "only absurd git-apply!\n" if !$use_absurd
2669 && forceing [qw(import-gitapply-absurd)];
2671 local $ENV{DGIT_ABSURD_DEBUG} = $debuglevel if $use_absurd;
2672 local $ENV{PATH} = $path if $use_absurd;
2674 my @showcmd = (gbp_pq, qw(import));
2675 my @realcmd = shell_cmd
2676 'exec >/dev/null 2>>../../gbp-pq-output', @showcmd;
2677 debugcmd "+",@realcmd;
2678 if (system @realcmd) {
2679 die f_ "%s failed: %s\n",
2680 +(shellquote @showcmd),
2681 failedcmd_waitstatus();
2684 my $gapplied = git_rev_parse('HEAD');
2685 my $gappliedtree = cmdoutput @git, qw(rev-parse HEAD:);
2686 $gappliedtree eq $dappliedtree or
2687 fail f_ <<END, $gapplied, $gappliedtree, $dappliedtree;
2688 gbp-pq import and dpkg-source disagree!
2689 gbp-pq import gave commit %s
2690 gbp-pq import gave tree %s
2691 dpkg-source --before-build gave tree %s
2693 $rawimport_hash = $gapplied;
2698 { local $@; eval { runcmd qw(cat ../../gbp-pq-output); }; }
2703 progress f_ "synthesised git commit from .dsc %s", $cversion;
2705 my $rawimport_mergeinput = {
2706 Commit => $rawimport_hash,
2707 Info => __ "Import of source package",
2709 my @output = ($rawimport_mergeinput);
2711 if ($lastpush_mergeinput) {
2712 my $oldclogp = mergeinfo_getclogp($lastpush_mergeinput);
2713 my $oversion = getfield $oldclogp, 'Version';
2715 version_compare($oversion, $cversion);
2717 @output = ($rawimport_mergeinput, $lastpush_mergeinput,
2718 { ReverseParents => 1,
2719 # untranslated so that different people's pseudomerges
2720 # are not needlessly different (although they will
2721 # still differ if the series of pulls is different)
2722 Message => (sprintf <<END, $package, $cversion, $csuite) });
2723 Record %s (%s) in archive suite %s
2725 } elsif ($vcmp > 0) {
2726 print STDERR f_ <<END, $cversion, $oversion,
2728 Version actually in archive: %s (older)
2729 Last version pushed with dgit: %s (newer or same)
2732 __ $later_warning_msg or confess "$!";
2733 @output = $lastpush_mergeinput;
2735 # Same version. Use what's in the server git branch,
2736 # discarding our own import. (This could happen if the
2737 # server automatically imports all packages into git.)
2738 @output = $lastpush_mergeinput;
2746 sub complete_file_from_dsc ($$;$) {
2747 our ($dstdir, $fi, $refetched) = @_;
2748 # Ensures that we have, in $dstdir, the file $fi, with the correct
2749 # contents. (Downloading it from alongside $dscurl if necessary.)
2750 # If $refetched is defined, can overwrite "$dstdir/$fi->{Filename}"
2751 # and will set $$refetched=1 if it did so (or tried to).
2753 my $f = $fi->{Filename};
2754 my $tf = "$dstdir/$f";
2758 my $checkhash = sub {
2759 open F, "<", "$tf" or die "$tf: $!";
2760 $fi->{Digester}->reset();
2761 $fi->{Digester}->addfile(*F);
2762 F->error and confess "$!";
2763 $got = $fi->{Digester}->hexdigest();
2764 return $got eq $fi->{Hash};
2767 if (stat_exists $tf) {
2768 if ($checkhash->()) {
2769 progress f_ "using existing %s", $f;
2773 fail f_ "file %s has hash %s but .dsc demands hash %s".
2774 " (perhaps you should delete this file?)",
2775 $f, $got, $fi->{Hash};
2777 progress f_ "need to fetch correct version of %s", $f;
2778 unlink $tf or die "$tf $!";
2781 printdebug "$tf does not exist, need to fetch\n";
2785 $furl =~ s{/[^/]+$}{};
2787 die "$f ?" unless $f =~ m/^\Q${package}\E_/;
2788 die "$f ?" if $f =~ m#/#;
2789 runcmd_ordryrun_local @curl,qw(-f -o),$tf,'--',"$furl";
2790 return 0 if !act_local();
2793 fail f_ "file %s has hash %s but .dsc demands hash %s".
2794 " (got wrong file from archive!)",
2795 $f, $got, $fi->{Hash};
2800 sub ensure_we_have_orig () {
2801 my @dfi = dsc_files_info();
2802 foreach my $fi (@dfi) {
2803 my $f = $fi->{Filename};
2804 next unless is_orig_file_in_dsc($f, \@dfi);
2805 complete_file_from_dsc($buildproductsdir, $fi)
2810 #---------- git fetch ----------
2812 sub lrfetchrefs () { return "refs/dgit-fetch/".access_basedistro(); }
2813 sub lrfetchref () { return lrfetchrefs.'/'.server_branch($csuite); }
2815 # We fetch some parts of lrfetchrefs/*. Ideally we delete these
2816 # locally fetched refs because they have unhelpful names and clutter
2817 # up gitk etc. So we track whether we have "used up" head ref (ie,
2818 # whether we have made another local ref which refers to this object).
2820 # (If we deleted them unconditionally, then we might end up
2821 # re-fetching the same git objects each time dgit fetch was run.)
2823 # So, each use of lrfetchrefs needs to be accompanied by arrangements
2824 # in git_fetch_us to fetch the refs in question, and possibly a call
2825 # to lrfetchref_used.
2827 our (%lrfetchrefs_f, %lrfetchrefs_d);
2828 # $lrfetchrefs_X{lrfetchrefs."/heads/whatever"} = $objid
2830 sub lrfetchref_used ($) {
2831 my ($fullrefname) = @_;
2832 my $objid = $lrfetchrefs_f{$fullrefname};
2833 $lrfetchrefs_d{$fullrefname} = $objid if defined $objid;
2836 sub git_lrfetch_sane {
2837 my ($url, $supplementary, @specs) = @_;
2838 # Make a 'refs/'.lrfetchrefs.'/*' be just like on server,
2839 # at least as regards @specs. Also leave the results in
2840 # %lrfetchrefs_f, and arrange for lrfetchref_used to be
2841 # able to clean these up.
2843 # With $supplementary==1, @specs must not contain wildcards
2844 # and we add to our previous fetches (non-atomically).
2846 # This is rather miserable:
2847 # When git fetch --prune is passed a fetchspec ending with a *,
2848 # it does a plausible thing. If there is no * then:
2849 # - it matches subpaths too, even if the supplied refspec
2850 # starts refs, and behaves completely madly if the source
2851 # has refs/refs/something. (See, for example, Debian #NNNN.)
2852 # - if there is no matching remote ref, it bombs out the whole
2854 # We want to fetch a fixed ref, and we don't know in advance
2855 # if it exists, so this is not suitable.
2857 # Our workaround is to use git ls-remote. git ls-remote has its
2858 # own qairks. Notably, it has the absurd multi-tail-matching
2859 # behaviour: git ls-remote R refs/foo can report refs/foo AND
2860 # refs/refs/foo etc.
2862 # Also, we want an idempotent snapshot, but we have to make two
2863 # calls to the remote: one to git ls-remote and to git fetch. The
2864 # solution is use git ls-remote to obtain a target state, and
2865 # git fetch to try to generate it. If we don't manage to generate
2866 # the target state, we try again.
2868 printdebug "git_lrfetch_sane suppl=$supplementary specs @specs\n";
2870 my $specre = join '|', map {
2873 my $wildcard = $x =~ s/\\\*$/.*/;
2874 die if $wildcard && $supplementary;
2877 printdebug "git_lrfetch_sane specre=$specre\n";
2878 my $wanted_rref = sub {
2880 return m/^(?:$specre)$/;
2883 my $fetch_iteration = 0;
2886 printdebug "git_lrfetch_sane iteration $fetch_iteration\n";
2887 if (++$fetch_iteration > 10) {
2888 fail __ "too many iterations trying to get sane fetch!";
2891 my @look = map { "refs/$_" } @specs;
2892 my @lcmd = (@git, qw(ls-remote -q --refs), $url, @look);
2896 open GITLS, "-|", @lcmd or confess "$!";
2898 printdebug "=> ", $_;
2899 m/^(\w+)\s+(\S+)\n/ or die "ls-remote $_ ?";
2900 my ($objid,$rrefname) = ($1,$2);
2901 if (!$wanted_rref->($rrefname)) {
2902 print STDERR f_ <<END, "@look", $rrefname;
2903 warning: git ls-remote %s reported %s; this is silly, ignoring it.
2907 $wantr{$rrefname} = $objid;
2910 close GITLS or failedcmd @lcmd;
2912 # OK, now %want is exactly what we want for refs in @specs
2914 !m/\*$/ && !exists $wantr{"refs/$_"} ? () :
2915 "+refs/$_:".lrfetchrefs."/$_";
2918 printdebug "git_lrfetch_sane fspecs @fspecs\n";
2920 my @fcmd = (@git, qw(fetch -p -n -q), $url, @fspecs);
2921 runcmd_ordryrun_local @fcmd if @fspecs;
2923 if (!$supplementary) {
2924 %lrfetchrefs_f = ();
2928 git_for_each_ref(lrfetchrefs, sub {
2929 my ($objid,$objtype,$lrefname,$reftail) = @_;
2930 $lrfetchrefs_f{$lrefname} = $objid;
2931 $objgot{$objid} = 1;
2934 if ($supplementary) {
2938 foreach my $lrefname (sort keys %lrfetchrefs_f) {
2939 my $rrefname = 'refs'.substr($lrefname, length lrfetchrefs);
2940 if (!exists $wantr{$rrefname}) {
2941 if ($wanted_rref->($rrefname)) {
2943 git-fetch @fspecs created $lrefname which git ls-remote @look didn't list.
2946 print STDERR f_ <<END, "@fspecs", $lrefname
2947 warning: git fetch %s created %s; this is silly, deleting it.
2950 runcmd_ordryrun_local @git, qw(update-ref -d), $lrefname;
2951 delete $lrfetchrefs_f{$lrefname};
2955 foreach my $rrefname (sort keys %wantr) {
2956 my $lrefname = lrfetchrefs.substr($rrefname, 4);
2957 my $got = $lrfetchrefs_f{$lrefname} // '<none>';
2958 my $want = $wantr{$rrefname};
2959 next if $got eq $want;
2960 if (!defined $objgot{$want}) {
2961 fail __ <<END unless act_local();
2962 --dry-run specified but we actually wanted the results of git fetch,
2963 so this is not going to work. Try running dgit fetch first,
2964 or using --damp-run instead of --dry-run.
2966 print STDERR f_ <<END, $lrefname, $want;
2967 warning: git ls-remote suggests we want %s
2968 warning: and it should refer to %s
2969 warning: but git fetch didn't fetch that object to any relevant ref.
2970 warning: This may be due to a race with someone updating the server.
2971 warning: Will try again...
2973 next FETCH_ITERATION;
2976 git-fetch @fspecs made $lrefname=$got but want git ls-remote @look says $want
2978 runcmd_ordryrun_local @git, qw(update-ref -m),
2979 "dgit fetch git fetch fixup", $lrefname, $want;
2980 $lrfetchrefs_f{$lrefname} = $want;
2985 if (defined $csuite) {
2986 printdebug "git_lrfetch_sane: tidying any old suite lrfetchrefs\n";
2987 git_for_each_ref("refs/dgit-fetch/$csuite", sub {
2988 my ($objid,$objtype,$lrefname,$reftail) = @_;
2989 next if $lrfetchrefs_f{$lrefname}; # $csuite eq $distro ?
2990 runcmd_ordryrun_local @git, qw(update-ref -d), $lrefname;
2994 printdebug "git_lrfetch_sane: git fetch --no-insane emulation complete\n",
2995 Dumper(\%lrfetchrefs_f);
2998 sub git_fetch_us () {
2999 # Want to fetch only what we are going to use, unless
3000 # deliberately-not-ff, in which case we must fetch everything.
3002 my @specs = deliberately_not_fast_forward ? qw(tags/*) :
3003 map { "tags/$_" } debiantags('*',access_nomdistro);
3004 push @specs, server_branch($csuite);
3005 push @specs, $rewritemap;
3006 push @specs, qw(heads/*) if deliberately_not_fast_forward;
3008 my $url = access_giturl();
3009 git_lrfetch_sane $url, 0, @specs;
3012 my @tagpats = debiantags('*',access_nomdistro);
3014 git_for_each_ref([map { "refs/tags/$_" } @tagpats], sub {
3015 my ($objid,$objtype,$fullrefname,$reftail) = @_;
3016 printdebug "currently $fullrefname=$objid\n";
3017 $here{$fullrefname} = $objid;
3019 git_for_each_ref([map { lrfetchrefs."/tags/".$_ } @tagpats], sub {
3020 my ($objid,$objtype,$fullrefname,$reftail) = @_;
3021 my $lref = "refs".substr($fullrefname, length(lrfetchrefs));
3022 printdebug "offered $lref=$objid\n";
3023 if (!defined $here{$lref}) {
3024 my @upd = (@git, qw(update-ref), $lref, $objid, '');
3025 runcmd_ordryrun_local @upd;
3026 lrfetchref_used $fullrefname;
3027 } elsif ($here{$lref} eq $objid) {
3028 lrfetchref_used $fullrefname;
3030 print STDERR f_ "Not updating %s from %s to %s.\n",
3031 $lref, $here{$lref}, $objid;
3036 #---------- dsc and archive handling ----------
3038 sub mergeinfo_getclogp ($) {
3039 # Ensures thit $mi->{Clogp} exists and returns it
3041 $mi->{Clogp} = commit_getclogp($mi->{Commit});
3044 sub mergeinfo_version ($) {
3045 return getfield( (mergeinfo_getclogp $_[0]), 'Version' );
3048 sub fetch_from_archive_record_1 ($) {
3050 runcmd git_update_ref_cmd "dgit fetch $csuite", 'DGIT_ARCHIVE', $hash;
3051 cmdoutput @git, qw(log -n2), $hash;
3052 # ... gives git a chance to complain if our commit is malformed
3055 sub fetch_from_archive_record_2 ($) {
3057 my @upd_cmd = (git_update_ref_cmd 'dgit fetch', lrref(), $hash);
3061 dryrun_report @upd_cmd;
3065 sub parse_dsc_field_def_dsc_distro () {
3066 $dsc_distro //= cfg qw(dgit.default.old-dsc-distro
3067 dgit.default.distro);
3070 sub parse_dsc_field ($$) {
3071 my ($dsc, $what) = @_;
3073 foreach my $field (@ourdscfield) {
3074 $f = $dsc->{$field};
3079 progress f_ "%s: NO git hash", $what;
3080 parse_dsc_field_def_dsc_distro();
3081 } elsif (($dsc_hash, $dsc_distro, $dsc_hint_tag, $dsc_hint_url)
3082 = $f =~ m/^(\w+)\s+($distro_re)\s+($versiontag_re)\s+(\S+)(?:\s|$)/) {
3083 progress f_ "%s: specified git info (%s)", $what, $dsc_distro;
3084 $dsc_hint_tag = [ $dsc_hint_tag ];
3085 } elsif ($f =~ m/^\w+\s*$/) {
3087 parse_dsc_field_def_dsc_distro();
3088 $dsc_hint_tag = [ debiantags +(getfield $dsc, 'Version'),
3090 progress f_ "%s: specified git hash", $what;
3092 fail f_ "%s: invalid Dgit info", $what;
3096 sub resolve_dsc_field_commit ($$) {
3097 my ($already_distro, $already_mapref) = @_;
3099 return unless defined $dsc_hash;
3102 defined $already_mapref &&
3103 ($already_distro eq $dsc_distro || !$chase_dsc_distro)
3104 ? $already_mapref : undef;
3108 my ($what, @fetch) = @_;
3110 local $idistro = $dsc_distro;
3111 my $lrf = lrfetchrefs;
3113 if (!$chase_dsc_distro) {
3114 progress f_ "not chasing .dsc distro %s: not fetching %s",
3119 progress f_ ".dsc names distro %s: fetching %s", $dsc_distro, $what;
3121 my $url = access_giturl();
3122 if (!defined $url) {
3123 defined $dsc_hint_url or fail f_ <<END, $dsc_distro;
3124 .dsc Dgit metadata is in context of distro %s
3125 for which we have no configured url and .dsc provides no hint
3128 $dsc_hint_url =~ m#^([-+0-9a-zA-Z]+):# ? $1 :
3129 $dsc_hint_url =~ m#^/# ? 'file' : 'bad-syntax';
3130 parse_cfg_bool "dsc-url-proto-ok", 'false',
3131 cfg("dgit.dsc-url-proto-ok.$proto",
3132 "dgit.default.dsc-url-proto-ok")
3133 or fail f_ <<END, $dsc_distro, $proto;
3134 .dsc Dgit metadata is in context of distro %s
3135 for which we have no configured url;
3136 .dsc provides hinted url with protocol %s which is unsafe.
3137 (can be overridden by config - consult documentation)
3139 $url = $dsc_hint_url;
3142 git_lrfetch_sane $url, 1, @fetch;
3147 my $rewrite_enable = do {
3148 local $idistro = $dsc_distro;
3149 access_cfg('rewrite-map-enable', 'RETURN-UNDEF');
3152 if (parse_cfg_bool 'rewrite-map-enable', 'true', $rewrite_enable) {
3153 if (!defined $mapref) {
3154 my $lrf = $do_fetch->((__ "rewrite map"), $rewritemap) or return;
3155 $mapref = $lrf.'/'.$rewritemap;
3157 my $rewritemapdata = git_cat_file $mapref.':map';
3158 if (defined $rewritemapdata
3159 && $rewritemapdata =~ m/^$dsc_hash(?:[ \t](\w+))/m) {
3161 "server's git history rewrite map contains a relevant entry!";
3164 if (defined $dsc_hash) {
3165 progress __ "using rewritten git hash in place of .dsc value";
3167 progress __ "server data says .dsc hash is to be disregarded";
3172 if (!defined git_cat_file $dsc_hash) {
3173 my @tags = map { "tags/".$_ } @$dsc_hint_tag;
3174 my $lrf = $do_fetch->((__ "additional commits"), @tags) &&
3175 defined git_cat_file $dsc_hash
3176 or fail f_ <<END, $dsc_hash;
3177 .dsc Dgit metadata requires commit %s
3178 but we could not obtain that object anywhere.
3180 foreach my $t (@tags) {
3181 my $fullrefname = $lrf.'/'.$t;
3182 # print STDERR "CHK $t $fullrefname ".Dumper(\%lrfetchrefs_f);
3183 next unless $lrfetchrefs_f{$fullrefname};
3184 next unless is_fast_fwd "$fullrefname~0", $dsc_hash;
3185 lrfetchref_used $fullrefname;
3190 sub fetch_from_archive () {
3192 ensure_setup_existing_tree();
3194 # Ensures that lrref() is what is actually in the archive, one way
3195 # or another, according to us - ie this client's
3196 # appropritaely-updated archive view. Also returns the commit id.
3197 # If there is nothing in the archive, leaves lrref alone and
3198 # returns undef. git_fetch_us must have already been called.
3202 parse_dsc_field($dsc, __ 'last upload to archive');
3203 resolve_dsc_field_commit access_basedistro,
3204 lrfetchrefs."/".$rewritemap
3206 progress __ "no version available from the archive";
3209 # If the archive's .dsc has a Dgit field, there are three
3210 # relevant git commitids we need to choose between and/or merge
3212 # 1. $dsc_hash: the Dgit field from the archive
3213 # 2. $lastpush_hash: the suite branch on the dgit git server
3214 # 3. $lastfetch_hash: our local tracking brach for the suite
3216 # These may all be distinct and need not be in any fast forward
3219 # If the dsc was pushed to this suite, then the server suite
3220 # branch will have been updated; but it might have been pushed to
3221 # a different suite and copied by the archive. Conversely a more
3222 # recent version may have been pushed with dgit but not appeared
3223 # in the archive (yet).
3225 # $lastfetch_hash may be awkward because archive imports
3226 # (particularly, imports of Dgit-less .dscs) are performed only as
3227 # needed on individual clients, so different clients may perform a
3228 # different subset of them - and these imports are only made
3229 # public during push. So $lastfetch_hash may represent a set of
3230 # imports different to a subsequent upload by a different dgit
3233 # Our approach is as follows:
3235 # As between $dsc_hash and $lastpush_hash: if $lastpush_hash is a
3236 # descendant of $dsc_hash, then it was pushed by a dgit user who
3237 # had based their work on $dsc_hash, so we should prefer it.
3238 # Otherwise, $dsc_hash was installed into this suite in the
3239 # archive other than by a dgit push, and (necessarily) after the
3240 # last dgit push into that suite (since a dgit push would have
3241 # been descended from the dgit server git branch); thus, in that
3242 # case, we prefer the archive's version (and produce a
3243 # pseudo-merge to overwrite the dgit server git branch).
3245 # (If there is no Dgit field in the archive's .dsc then
3246 # generate_commit_from_dsc uses the version numbers to decide
3247 # whether the suite branch or the archive is newer. If the suite
3248 # branch is newer it ignores the archive's .dsc; otherwise it
3249 # generates an import of the .dsc, and produces a pseudo-merge to
3250 # overwrite the suite branch with the archive contents.)
3252 # The outcome of that part of the algorithm is the `public view',
3253 # and is same for all dgit clients: it does not depend on any
3254 # unpublished history in the local tracking branch.
3256 # As between the public view and the local tracking branch: The
3257 # local tracking branch is only updated by dgit fetch, and
3258 # whenever dgit fetch runs it includes the public view in the
3259 # local tracking branch. Therefore if the public view is not
3260 # descended from the local tracking branch, the local tracking
3261 # branch must contain history which was imported from the archive
3262 # but never pushed; and, its tip is now out of date. So, we make
3263 # a pseudo-merge to overwrite the old imports and stitch the old
3266 # Finally: we do not necessarily reify the public view (as
3267 # described above). This is so that we do not end up stacking two
3268 # pseudo-merges. So what we actually do is figure out the inputs
3269 # to any public view pseudo-merge and put them in @mergeinputs.
3272 # $mergeinputs[]{Commit}
3273 # $mergeinputs[]{Info}
3274 # $mergeinputs[0] is the one whose tree we use
3275 # @mergeinputs is in the order we use in the actual commit)
3278 # $mergeinputs[]{Message} is a commit message to use
3279 # $mergeinputs[]{ReverseParents} if def specifies that parent
3280 # list should be in opposite order
3281 # Such an entry has no Commit or Info. It applies only when found
3282 # in the last entry. (This ugliness is to support making
3283 # identical imports to previous dgit versions.)
3285 my $lastpush_hash = git_get_ref(lrfetchref());
3286 printdebug "previous reference hash=$lastpush_hash\n";
3287 $lastpush_mergeinput = $lastpush_hash && {
3288 Commit => $lastpush_hash,
3289 Info => (__ "dgit suite branch on dgit git server"),
3292 my $lastfetch_hash = git_get_ref(lrref());
3293 printdebug "fetch_from_archive: lastfetch=$lastfetch_hash\n";
3294 my $lastfetch_mergeinput = $lastfetch_hash && {
3295 Commit => $lastfetch_hash,
3296 Info => (__ "dgit client's archive history view"),
3299 my $dsc_mergeinput = $dsc_hash && {
3300 Commit => $dsc_hash,
3301 Info => (__ "Dgit field in .dsc from archive"),
3305 my $del_lrfetchrefs = sub {
3308 printdebug "del_lrfetchrefs...\n";
3309 foreach my $fullrefname (sort keys %lrfetchrefs_d) {
3310 my $objid = $lrfetchrefs_d{$fullrefname};
3311 printdebug "del_lrfetchrefs: $objid $fullrefname\n";
3313 $gur ||= new IO::Handle;
3314 open $gur, "|-", qw(git update-ref --stdin) or confess "$!";
3316 printf $gur "delete %s %s\n", $fullrefname, $objid;
3319 close $gur or failedcmd "git update-ref delete lrfetchrefs";
3323 if (defined $dsc_hash) {
3324 ensure_we_have_orig();
3325 if (!$lastpush_hash || $dsc_hash eq $lastpush_hash) {
3326 @mergeinputs = $dsc_mergeinput
3327 } elsif (is_fast_fwd($dsc_hash,$lastpush_hash)) {
3328 print STDERR f_ <<END, $dsc_hash, $lastpush_hash,
3330 Git commit in archive is behind the last version allegedly pushed/uploaded.
3331 Commit referred to by archive: %s
3332 Last version pushed with dgit: %s
3335 __ $later_warning_msg or confess "$!";
3336 @mergeinputs = ($lastpush_mergeinput);
3338 # Archive has .dsc which is not a descendant of the last dgit
3339 # push. This can happen if the archive moves .dscs about.
3340 # Just follow its lead.
3341 if (is_fast_fwd($lastpush_hash,$dsc_hash)) {
3342 progress __ "archive .dsc names newer git commit";
3343 @mergeinputs = ($dsc_mergeinput);
3345 progress __ "archive .dsc names other git commit, fixing up";
3346 @mergeinputs = ($dsc_mergeinput, $lastpush_mergeinput);
3350 @mergeinputs = generate_commits_from_dsc();
3351 # We have just done an import. Now, our import algorithm might
3352 # have been improved. But even so we do not want to generate
3353 # a new different import of the same package. So if the
3354 # version numbers are the same, just use our existing version.
3355 # If the version numbers are different, the archive has changed
3356 # (perhaps, rewound).
3357 if ($lastfetch_mergeinput &&
3358 !version_compare( (mergeinfo_version $lastfetch_mergeinput),
3359 (mergeinfo_version $mergeinputs[0]) )) {
3360 @mergeinputs = ($lastfetch_mergeinput);
3362 } elsif ($lastpush_hash) {
3363 # only in git, not in the archive yet
3364 @mergeinputs = ($lastpush_mergeinput);
3365 print STDERR f_ <<END,
3367 Package not found in the archive, but has allegedly been pushed using dgit.
3370 __ $later_warning_msg or confess "$!";
3372 printdebug "nothing found!\n";
3373 if (defined $skew_warning_vsn) {
3374 print STDERR f_ <<END, $skew_warning_vsn or confess "$!";
3376 Warning: relevant archive skew detected.
3377 Archive allegedly contains %s
3378 But we were not able to obtain any version from the archive or git.
3382 unshift @end, $del_lrfetchrefs;
3386 if ($lastfetch_hash &&
3388 my $h = $_->{Commit};
3389 $h and is_fast_fwd($lastfetch_hash, $h);
3390 # If true, one of the existing parents of this commit
3391 # is a descendant of the $lastfetch_hash, so we'll
3392 # be ff from that automatically.
3396 push @mergeinputs, $lastfetch_mergeinput;
3399 printdebug "fetch mergeinfos:\n";
3400 foreach my $mi (@mergeinputs) {
3402 printdebug " commit $mi->{Commit} $mi->{Info}\n";
3404 printdebug sprintf " ReverseParents=%d Message=%s",
3405 $mi->{ReverseParents}, $mi->{Message};
3409 my $compat_info= pop @mergeinputs
3410 if $mergeinputs[$#mergeinputs]{Message};
3412 @mergeinputs = grep { defined $_->{Commit} } @mergeinputs;
3415 if (@mergeinputs > 1) {
3417 my $tree_commit = $mergeinputs[0]{Commit};
3419 my $tree = get_tree_of_commit $tree_commit;;
3421 # We use the changelog author of the package in question the
3422 # author of this pseudo-merge. This is (roughly) correct if
3423 # this commit is simply representing aa non-dgit upload.
3424 # (Roughly because it does not record sponsorship - but we
3425 # don't have sponsorship info because that's in the .changes,
3426 # which isn't in the archivw.)
3428 # But, it might be that we are representing archive history
3429 # updates (including in-archive copies). These are not really
3430 # the responsibility of the person who created the .dsc, but
3431 # there is no-one whose name we should better use. (The
3432 # author of the .dsc-named commit is clearly worse.)
3434 my $useclogp = mergeinfo_getclogp $mergeinputs[0];
3435 my $author = clogp_authline $useclogp;
3436 my $cversion = getfield $useclogp, 'Version';
3438 my $mcf = dgit_privdir()."/mergecommit";
3439 open MC, ">", $mcf or die "$mcf $!";
3440 print MC <<END or confess "$!";
3444 my @parents = grep { $_->{Commit} } @mergeinputs;
3445 @parents = reverse @parents if $compat_info->{ReverseParents};
3446 print MC <<END or confess "$!" foreach @parents;
3450 print MC <<END or confess "$!";
3456 if (defined $compat_info->{Message}) {
3457 print MC $compat_info->{Message} or confess "$!";
3459 print MC f_ <<END, $package, $cversion, $csuite or confess "$!";
3460 Record %s (%s) in archive suite %s
3464 my $message_add_info = sub {
3466 my $mversion = mergeinfo_version $mi;
3467 printf MC " %-20s %s\n", $mversion, $mi->{Info}
3471 $message_add_info->($mergeinputs[0]);
3472 print MC __ <<END or confess "$!";
3473 should be treated as descended from
3475 $message_add_info->($_) foreach @mergeinputs[1..$#mergeinputs];
3478 close MC or confess "$!";
3479 $hash = hash_commit $mcf;
3481 $hash = $mergeinputs[0]{Commit};
3483 printdebug "fetch hash=$hash\n";
3486 my ($lasth, $what) = @_;
3487 return unless $lasth;
3488 confess "$lasth $hash $what ?" unless is_fast_fwd($lasth, $hash);
3491 $chkff->($lastpush_hash, __ 'dgit repo server tip (last push)')
3493 $chkff->($lastfetch_hash, __ 'local tracking tip (last fetch)');
3495 fetch_from_archive_record_1($hash);
3497 if (defined $skew_warning_vsn) {
3498 printdebug "SKEW CHECK WANT $skew_warning_vsn\n";
3499 my $gotclogp = commit_getclogp($hash);
3500 my $got_vsn = getfield $gotclogp, 'Version';
3501 printdebug "SKEW CHECK GOT $got_vsn\n";
3502 if (version_compare($got_vsn, $skew_warning_vsn) < 0) {
3503 print STDERR f_ <<END, $skew_warning_vsn, $got_vsn or confess "$!";
3505 Warning: archive skew detected. Using the available version:
3506 Archive allegedly contains %s
3507 We were able to obtain only %s
3513 if ($lastfetch_hash ne $hash) {
3514 fetch_from_archive_record_2($hash);
3517 lrfetchref_used lrfetchref();
3519 check_gitattrs($hash, __ "fetched source tree");
3521 unshift @end, $del_lrfetchrefs;
3525 sub set_local_git_config ($$) {
3527 runcmd @git, qw(config), $k, $v;
3530 sub setup_mergechangelogs (;$) {
3532 return unless $always || access_cfg_bool(1, 'setup-mergechangelogs');
3534 my $driver = 'dpkg-mergechangelogs';
3535 my $cb = "merge.$driver";
3536 confess unless defined $maindir;
3537 my $attrs = "$maindir_gitcommon/info/attributes";
3538 ensuredir "$maindir_gitcommon/info";
3540 open NATTRS, ">", "$attrs.new" or die "$attrs.new $!";
3541 if (!open ATTRS, "<", $attrs) {
3542 $!==ENOENT or die "$attrs: $!";
3546 next if m{^debian/changelog\s};
3547 print NATTRS $_, "\n" or confess "$!";
3549 ATTRS->error and confess "$!";
3552 print NATTRS "debian/changelog merge=$driver\n" or confess "$!";
3555 set_local_git_config "$cb.name", __ 'debian/changelog merge driver';
3556 set_local_git_config "$cb.driver", 'dpkg-mergechangelogs -m %O %A %B %A';
3558 rename "$attrs.new", "$attrs" or die "$attrs: $!";
3561 sub setup_useremail (;$) {
3563 return unless $always || access_cfg_bool(1, 'setup-useremail');
3566 my ($k, $envvar) = @_;
3567 my $v = access_cfg("user-$k", 'RETURN-UNDEF') // $ENV{$envvar};
3568 return unless defined $v;
3569 set_local_git_config "user.$k", $v;
3572 $setup->('email', 'DEBEMAIL');
3573 $setup->('name', 'DEBFULLNAME');
3576 sub ensure_setup_existing_tree () {
3577 my $k = "remote.$remotename.skipdefaultupdate";
3578 my $c = git_get_config $k;
3579 return if defined $c;
3580 set_local_git_config $k, 'true';
3583 sub open_main_gitattrs () {
3584 confess 'internal error no maindir' unless defined $maindir;
3585 my $gai = new IO::File "$maindir_gitcommon/info/attributes"
3587 or die "open $maindir_gitcommon/info/attributes: $!";
3591 our $gitattrs_ourmacro_re = qr{^\[attr\]dgit-defuse-attrs\s};
3593 sub is_gitattrs_setup () {
3596 # 1: gitattributes set up and should be left alone
3598 # 0: there is a dgit-defuse-attrs but it needs fixing
3599 # undef: there is none
3600 my $gai = open_main_gitattrs();
3601 return 0 unless $gai;
3603 next unless m{$gitattrs_ourmacro_re};
3604 return 1 if m{\s-working-tree-encoding\s};
3605 printdebug "is_gitattrs_setup: found old macro\n";
3608 $gai->error and confess "$!";
3609 printdebug "is_gitattrs_setup: found nothing\n";
3613 sub setup_gitattrs (;$) {
3615 return unless $always || access_cfg_bool(1, 'setup-gitattributes');
3617 my $already = is_gitattrs_setup();
3620 [attr]dgit-defuse-attrs already found, and proper, in .git/info/attributes
3621 not doing further gitattributes setup
3625 my $new = "[attr]dgit-defuse-attrs $negate_harmful_gitattrs";
3626 my $af = "$maindir_gitcommon/info/attributes";
3627 ensuredir "$maindir_gitcommon/info";
3629 open GAO, "> $af.new" or confess "$!";
3630 print GAO <<END, __ <<ENDT or confess "$!" unless defined $already;
3634 # ^ see GITATTRIBUTES in dgit(7) and dgit setup-new-tree in dgit(1)
3636 my $gai = open_main_gitattrs();
3639 if (m{$gitattrs_ourmacro_re}) {
3640 die unless defined $already;
3644 print GAO $_, "\n" or confess "$!";
3646 $gai->error and confess "$!";
3648 close GAO or confess "$!";
3649 rename "$af.new", "$af" or fail f_ "install %s: %s", $af, $!;
3652 sub setup_new_tree () {
3653 setup_mergechangelogs();
3658 sub check_gitattrs ($$) {
3659 my ($treeish, $what) = @_;
3661 return if is_gitattrs_setup;
3664 my @cmd = (@git, qw(ls-tree -lrz --), "${treeish}:");
3666 my $gafl = new IO::File;
3667 open $gafl, "-|", @cmd or confess "$!";
3670 s/^\d+\s+\w+\s+\w+\s+(\d+)\t// or die;
3672 next unless m{(?:^|/)\.gitattributes$};
3674 # oh dear, found one
3675 print STDERR f_ <<END, $what;
3676 dgit: warning: %s contains .gitattributes
3677 dgit: .gitattributes not (fully) defused. Recommended: dgit setup-new-tree.
3682 # tree contains no .gitattributes files
3683 $?=0; $!=0; close $gafl or failedcmd @cmd;
3687 sub multisuite_suite_child ($$$) {
3688 my ($tsuite, $mergeinputs, $fn) = @_;
3689 # in child, sets things up, calls $fn->(), and returns undef
3690 # in parent, returns canonical suite name for $tsuite
3691 my $canonsuitefh = IO::File::new_tmpfile;
3692 my $pid = fork // confess "$!";
3696 $us .= " [$isuite]";
3697 $debugprefix .= " ";
3698 progress f_ "fetching %s...", $tsuite;
3699 canonicalise_suite();
3700 print $canonsuitefh $csuite, "\n" or confess "$!";
3701 close $canonsuitefh or confess "$!";
3705 waitpid $pid,0 == $pid or confess "$!";
3706 fail f_ "failed to obtain %s: %s", $tsuite, waitstatusmsg()
3708 seek $canonsuitefh,0,0 or confess "$!";
3709 local $csuite = <$canonsuitefh>;
3710 confess "$!" unless defined $csuite && chomp $csuite;
3712 printdebug "multisuite $tsuite missing\n";
3715 printdebug "multisuite $tsuite ok (canon=$csuite)\n";
3716 push @$mergeinputs, {
3723 sub fork_for_multisuite ($) {
3724 my ($before_fetch_merge) = @_;
3725 # if nothing unusual, just returns ''
3728 # returns 0 to caller in child, to do first of the specified suites
3729 # in child, $csuite is not yet set
3731 # returns 1 to caller in parent, to finish up anything needed after
3732 # in parent, $csuite is set to canonicalised portmanteau
3734 my $org_isuite = $isuite;
3735 my @suites = split /\,/, $isuite;
3736 return '' unless @suites > 1;
3737 printdebug "fork_for_multisuite: @suites\n";
3741 my $cbasesuite = multisuite_suite_child($suites[0], \@mergeinputs,
3743 return 0 unless defined $cbasesuite;
3745 fail f_ "package %s missing in (base suite) %s", $package, $cbasesuite
3746 unless @mergeinputs;
3748 my @csuites = ($cbasesuite);
3750 $before_fetch_merge->();
3752 foreach my $tsuite (@suites[1..$#suites]) {
3753 $tsuite =~ s/^-/$cbasesuite-/;
3754 my $csubsuite = multisuite_suite_child($tsuite, \@mergeinputs,
3761 $csubsuite =~ s/^\Q$cbasesuite\E-/-/;
3762 push @csuites, $csubsuite;
3765 foreach my $mi (@mergeinputs) {
3766 my $ref = git_get_ref $mi->{Ref};
3767 die "$mi->{Ref} ?" unless length $ref;
3768 $mi->{Commit} = $ref;
3771 $csuite = join ",", @csuites;
3773 my $previous = git_get_ref lrref;
3775 unshift @mergeinputs, {
3776 Commit => $previous,
3777 Info => (__ "local combined tracking branch"),
3779 "archive seems to have rewound: local tracking branch is ahead!"),
3783 foreach my $ix (0..$#mergeinputs) {
3784 $mergeinputs[$ix]{Index} = $ix;
3787 @mergeinputs = sort {
3788 -version_compare(mergeinfo_version $a,
3789 mergeinfo_version $b) # highest version first
3791 $a->{Index} <=> $b->{Index}; # earliest in spec first
3797 foreach my $mi (@mergeinputs) {
3798 printdebug "multisuite merge check $mi->{Info}\n";
3799 foreach my $previous (@needed) {
3800 next unless is_fast_fwd $mi->{Commit}, $previous->{Commit};
3801 printdebug "multisuite merge un-needed $previous->{Info}\n";
3805 printdebug "multisuite merge this-needed\n";
3806 $mi->{Character} = '+';
3809 $needed[0]{Character} = '*';
3811 my $output = $needed[0]{Commit};
3814 printdebug "multisuite merge nontrivial\n";
3815 my $tree = cmdoutput qw(git rev-parse), $needed[0]{Commit}.':';
3817 my $commit = "tree $tree\n";
3818 my $msg = f_ "Combine archive branches %s [dgit]\n\n".
3819 "Input branches:\n",
3822 foreach my $mi (sort { $a->{Index} <=> $b->{Index} } @mergeinputs) {
3823 printdebug "multisuite merge include $mi->{Info}\n";
3824 $mi->{Character} //= ' ';
3825 $commit .= "parent $mi->{Commit}\n";
3826 $msg .= sprintf " %s %-25s %s\n",
3828 (mergeinfo_version $mi),
3831 my $authline = clogp_authline mergeinfo_getclogp $needed[0];
3832 $msg .= __ "\nKey\n".
3833 " * marks the highest version branch, which choose to use\n".
3834 " + marks each branch which was not already an ancestor\n\n";
3836 "[dgit multi-suite $csuite]\n";
3838 "author $authline\n".
3839 "committer $authline\n\n";
3840 $output = hash_commit_text $commit.$msg;
3841 printdebug "multisuite merge generated $output\n";
3844 fetch_from_archive_record_1($output);
3845 fetch_from_archive_record_2($output);
3847 progress f_ "calculated combined tracking suite %s", $csuite;
3852 sub clone_set_head () {
3853 open H, "> .git/HEAD" or confess "$!";
3854 print H "ref: ".lref()."\n" or confess "$!";
3855 close H or confess "$!";
3857 sub clone_finish ($) {
3859 runcmd @git, qw(reset --hard), lrref();
3860 runcmd qw(bash -ec), <<'END';
3862 git ls-tree -r --name-only -z HEAD | \
3863 xargs -0r touch -h -r . --
3865 printdone f_ "ready for work in %s", $dstdir;
3868 sub vcs_git_url_of_ctrl ($) {
3870 my $vcsgiturl = $ctrl->{'Vcs-Git'};
3871 if (length $vcsgiturl) {
3872 $vcsgiturl =~ s/\s+-b\s+\S+//g;
3873 $vcsgiturl =~ s/\s+\[[^][]*\]//g;
3879 # in multisuite, returns twice!
3880 # once in parent after first suite fetched,
3881 # and then again in child after everything is finished
3883 badusage __ "dry run makes no sense with clone" unless act_local();
3885 my $multi_fetched = fork_for_multisuite(sub {
3886 printdebug "multi clone before fetch merge\n";
3890 if ($multi_fetched) {
3891 printdebug "multi clone after fetch merge\n";
3893 clone_finish($dstdir);
3896 printdebug "clone main body\n";
3898 mkdir $dstdir or fail f_ "create \`%s': %s", $dstdir, $!;
3902 canonicalise_suite();
3903 my $hasgit = check_for_git();
3905 runcmd @git, qw(init -q);
3910 progress __ "fetching existing git history";
3913 progress __ "starting new git history";
3915 fetch_from_archive() or no_such_package;
3916 my $vcsgiturl = vcs_git_url_of_ctrl $dsc;
3917 if (length $vcsgiturl) {
3918 runcmd @git, qw(remote add vcs-git), $vcsgiturl;
3920 clone_finish($dstdir);
3924 canonicalise_suite();
3925 if (check_for_git()) {
3928 fetch_from_archive() or no_such_package();
3930 my $vcsgiturl = $dsc && $dsc->{'Vcs-Git'};
3931 if (length $vcsgiturl and
3932 (grep { $csuite eq $_ }
3934 cfg 'dgit.vcs-git.suites')) {
3935 my $current = cfg 'remote.vcs-git.url', 'RETURN-UNDEF';
3936 if (defined $current && $current ne $vcsgiturl) {
3937 print STDERR f_ <<END, $csuite;
3938 FYI: Vcs-Git in %s has different url to your vcs-git remote.
3939 Your vcs-git remote url may be out of date. Use dgit update-vcs-git ?
3943 printdone f_ "fetched into %s", lrref();
3947 my $multi_fetched = fork_for_multisuite(sub { });
3948 fetch_one() unless $multi_fetched; # parent
3949 finish 0 if $multi_fetched eq '0'; # child
3954 runcmd_ordryrun_local @git, qw(merge -m),
3955 (f_ "Merge from %s [dgit]", $csuite),
3957 printdone f_ "fetched to %s and merged into HEAD", lrref();
3960 sub check_not_dirty () {
3961 my @forbid = qw(local-options local-patch-header);
3962 @forbid = map { "debian/source/$_" } @forbid;
3963 foreach my $f (@forbid) {
3964 if (stat_exists $f) {
3965 fail f_ "git tree contains %s", $f;
3969 my @cmd = (@git, qw(status -uall --ignored --porcelain));
3970 push @cmd, qw(debian/source/format debian/source/options);
3973 my $bad = cmdoutput @cmd;
3976 "you have uncommitted changes to critical files, cannot continue:\n").
3980 return if $includedirty;
3982 git_check_unmodified();
3985 sub commit_admin ($) {
3988 runcmd_ordryrun_local @git, qw(commit -m), $m;
3991 sub quiltify_nofix_bail ($$) {
3992 my ($headinfo, $xinfo) = @_;
3993 if ($quilt_mode eq 'nofix') {
3995 "quilt fixup required but quilt mode is \`nofix'\n".
3996 "HEAD commit%s differs from tree implied by debian/patches%s",
4001 sub commit_quilty_patch () {
4002 my $output = cmdoutput @git, qw(status --ignored --porcelain);
4004 foreach my $l (split /\n/, $output) {
4005 next unless $l =~ m/\S/;
4006 if ($l =~ m{^(?:[?!][?!]| [MADRC]) (.pc|debian/patches)}) {
4010 delete $adds{'.pc'}; # if there wasn't one before, don't add it
4012 progress __ "nothing quilty to commit, ok.";
4015 quiltify_nofix_bail "", __ " (wanted to commit patch update)";
4016 my @adds = map { s/[][*?\\]/\\$&/g; $_; } sort keys %adds;
4017 runcmd_ordryrun_local @git, qw(add -f), @adds;
4018 commit_admin +(__ <<ENDT).<<END
4019 Commit Debian 3.0 (quilt) metadata
4022 [dgit ($our_version) quilt-fixup]
4026 sub get_source_format () {
4028 if (open F, "debian/source/options") {
4032 s/\s+$//; # ignore missing final newline
4034 my ($k, $v) = ($`, $'); #');
4035 $v =~ s/^"(.*)"$/$1/;
4041 F->error and confess "$!";
4044 confess "$!" unless $!==&ENOENT;
4047 if (!open F, "debian/source/format") {
4048 confess "$!" unless $!==&ENOENT;
4052 F->error and confess "$!";
4055 return ($_, \%options);
4058 sub madformat_wantfixup ($) {
4060 return 0 unless $format eq '3.0 (quilt)';
4061 our $quilt_mode_warned;
4062 if ($quilt_mode eq 'nocheck') {
4063 progress f_ "Not doing any fixup of \`%s'".
4064 " due to ----no-quilt-fixup or --quilt=nocheck", $format
4065 unless $quilt_mode_warned++;
4068 progress f_ "Format \`%s', need to check/update patch stack", $format
4069 unless $quilt_mode_warned++;
4073 sub maybe_split_brain_save ($$$) {
4074 my ($headref, $dgitview, $msg) = @_;
4075 # => message fragment "$saved" describing disposition of $dgitview
4076 # (used inside parens, in the English texts)
4077 my $save = $internal_object_save{'dgit-view'};
4078 return f_ "commit id %s", $dgitview unless defined $save;
4079 my @cmd = (shell_cmd 'cd "$1"; shift', $maindir,
4081 "dgit --dgit-view-save $msg HEAD=$headref",
4084 return f_ "and left in %s", $save;
4087 # An "infopair" is a tuple [ $thing, $what ]
4088 # (often $thing is a commit hash; $what is a description)
4090 sub infopair_cond_equal ($$) {
4092 $x->[0] eq $y->[0] or fail <<END;
4093 $x->[1] ($x->[0]) not equal to $y->[1] ($y->[0])
4097 sub infopair_lrf_tag_lookup ($$) {
4098 my ($tagnames, $what) = @_;
4099 # $tagname may be an array ref
4100 my @tagnames = ref $tagnames ? @$tagnames : ($tagnames);
4101 printdebug "infopair_lrfetchref_tag_lookup $what @tagnames\n";
4102 foreach my $tagname (@tagnames) {
4103 my $lrefname = lrfetchrefs."/tags/$tagname";
4104 my $tagobj = $lrfetchrefs_f{$lrefname};
4105 next unless defined $tagobj;
4106 printdebug "infopair_lrfetchref_tag_lookup $tagobj $tagname $what\n";
4107 return [ git_rev_parse($tagobj), $what ];
4109 fail @tagnames==1 ? (f_ <<END, $what, "@tagnames")
4110 Wanted tag %s (%s) on dgit server, but not found
4112 : (f_ <<END, $what, "@tagnames");
4113 Wanted tag %s (one of: %s) on dgit server, but not found
4117 sub infopair_cond_ff ($$) {
4118 my ($anc,$desc) = @_;
4119 is_fast_fwd($anc->[0], $desc->[0]) or
4120 fail f_ <<END, $anc->[1], $anc->[0], $desc->[1], $desc->[0];
4121 %s (%s) .. %s (%s) is not fast forward
4125 sub pseudomerge_version_check ($$) {
4126 my ($clogp, $archive_hash) = @_;
4128 my $arch_clogp = commit_getclogp $archive_hash;
4129 my $i_arch_v = [ (getfield $arch_clogp, 'Version'),
4130 __ 'version currently in archive' ];
4131 if (defined $overwrite_version) {
4132 if (length $overwrite_version) {
4133 infopair_cond_equal([ $overwrite_version,
4134 '--overwrite= version' ],
4137 my $v = $i_arch_v->[0];
4139 "Checking package changelog for archive version %s ...", $v;
4142 my @xa = ("-f$v", "-t$v");
4143 my $vclogp = parsechangelog @xa;
4146 [ (getfield $vclogp, $fn),
4147 (f_ "%s field from dpkg-parsechangelog %s",
4150 my $cv = $gf->('Version');
4151 infopair_cond_equal($i_arch_v, $cv);
4152 $cd = $gf->('Distribution');
4156 $@ =~ s/^dgit: //gm;
4158 f_ "Perhaps debian/changelog does not mention %s ?", $v;
4160 fail f_ <<END, $cd->[1], $cd->[0], $v
4162 Your tree seems to based on earlier (not uploaded) %s.
4164 if $cd->[0] =~ m/UNRELEASED/;
4168 printdebug "pseudomerge_version_check i_arch_v @$i_arch_v\n";
4172 sub pseudomerge_hash_commit ($$$$ $$) {
4173 my ($clogp, $dgitview, $archive_hash, $i_arch_v,
4174 $msg_cmd, $msg_msg) = @_;
4175 progress f_ "Declaring that HEAD includes all changes in %s...",
4178 my $tree = cmdoutput qw(git rev-parse), "${dgitview}:";
4179 my $authline = clogp_authline $clogp;
4183 !defined $overwrite_version ? ""
4184 : !length $overwrite_version ? " --overwrite"
4185 : " --overwrite=".$overwrite_version;
4187 # Contributing parent is the first parent - that makes
4188 # git rev-list --first-parent DTRT.
4189 my $pmf = dgit_privdir()."/pseudomerge";
4190 open MC, ">", $pmf or die "$pmf $!";
4191 print MC <<END or confess "$!";
4194 parent $archive_hash
4202 close MC or confess "$!";
4204 return hash_commit($pmf);
4207 sub splitbrain_pseudomerge ($$$$) {
4208 my ($clogp, $maintview, $dgitview, $archive_hash) = @_;
4209 # => $merged_dgitview
4210 printdebug "splitbrain_pseudomerge...\n";
4212 # We: debian/PREVIOUS HEAD($maintview)
4213 # expect: o ----------------- o
4216 # a/d/PREVIOUS $dgitview
4219 # we do: `------------------ o
4223 return $dgitview unless defined $archive_hash;
4224 return $dgitview if deliberately_not_fast_forward();
4226 printdebug "splitbrain_pseudomerge...\n";
4228 my $i_arch_v = pseudomerge_version_check($clogp, $archive_hash);
4230 if (!defined $overwrite_version) {
4231 progress __ "Checking that HEAD includes all changes in archive...";
4234 return $dgitview if is_fast_fwd $archive_hash, $dgitview;
4236 if (defined $overwrite_version) {
4238 my $t_dep14 = debiantag_maintview $i_arch_v->[0], access_nomdistro;
4239 my $i_dep14 = infopair_lrf_tag_lookup($t_dep14,
4240 __ "maintainer view tag");
4241 my $t_dgit = debiantag_new $i_arch_v->[0], access_nomdistro;
4242 my $i_dgit = infopair_lrf_tag_lookup($t_dgit, __ "dgit view tag");
4243 my $i_archive = [ $archive_hash, __ "current archive contents" ];
4245 printdebug "splitbrain_pseudomerge i_archive @$i_archive\n";
4247 infopair_cond_equal($i_dgit, $i_archive);
4248 infopair_cond_ff($i_dep14, $i_dgit);
4249 infopair_cond_ff($i_dep14, [ $maintview, 'HEAD' ]);
4252 $@ =~ s/^\n//; chomp $@;
4253 print STDERR <<END.(__ <<ENDT);
4256 | Not fast forward; maybe --overwrite is needed ? Please see dgit(1).
4261 my $arch_v = $i_arch_v->[0];
4262 my $r = pseudomerge_hash_commit
4263 $clogp, $dgitview, $archive_hash, $i_arch_v,
4264 "dgit --quilt=$quilt_mode",
4265 (defined $overwrite_version
4266 ? f_ "Declare fast forward from %s\n", $arch_v
4267 : f_ "Make fast forward from %s\n", $arch_v);
4269 maybe_split_brain_save $maintview, $r, "pseudomerge";
4271 progress f_ "Made pseudo-merge of %s into dgit view.", $arch_v;
4275 sub plain_overwrite_pseudomerge ($$$) {
4276 my ($clogp, $head, $archive_hash) = @_;
4278 printdebug "plain_overwrite_pseudomerge...";
4280 my $i_arch_v = pseudomerge_version_check($clogp, $archive_hash);
4282 return $head if is_fast_fwd $archive_hash, $head;
4284 my $m = f_ "Declare fast forward from %s", $i_arch_v->[0];
4286 my $r = pseudomerge_hash_commit
4287 $clogp, $head, $archive_hash, $i_arch_v,
4290 runcmd git_update_ref_cmd $m, 'HEAD', $r, $head;
4292 progress f_ "Make pseudo-merge of %s into your HEAD.", $i_arch_v->[0];
4296 sub push_parse_changelog ($) {
4299 my $clogp = Dpkg::Control::Hash->new();
4300 $clogp->load($clogpfn) or die;
4302 my $clogpackage = getfield $clogp, 'Source';
4303 $package //= $clogpackage;
4304 fail f_ "-p specified %s but changelog specified %s",
4305 $package, $clogpackage
4306 unless $package eq $clogpackage;
4307 my $cversion = getfield $clogp, 'Version';
4309 if (!$we_are_initiator) {
4310 # rpush initiator can't do this because it doesn't have $isuite yet
4311 my $tag = debiantag_new($cversion, access_nomdistro);
4312 runcmd @git, qw(check-ref-format), $tag;
4315 my $dscfn = dscfn($cversion);
4317 return ($clogp, $cversion, $dscfn);
4320 sub push_parse_dsc ($$$) {
4321 my ($dscfn,$dscfnwhat, $cversion) = @_;
4322 $dsc = parsecontrol($dscfn,$dscfnwhat);
4323 my $dversion = getfield $dsc, 'Version';
4324 my $dscpackage = getfield $dsc, 'Source';
4325 ($dscpackage eq $package && $dversion eq $cversion) or
4326 fail f_ "%s is for %s %s but debian/changelog is for %s %s",
4327 $dscfn, $dscpackage, $dversion,
4328 $package, $cversion;
4331 sub push_tagwants ($$$$) {
4332 my ($cversion, $dgithead, $maintviewhead, $tfbase) = @_;
4335 TagFn => \&debiantag_new,
4340 if (defined $maintviewhead) {
4342 TagFn => \&debiantag_maintview,
4343 Objid => $maintviewhead,
4344 TfSuffix => '-maintview',
4347 } elsif ($dodep14tag ne 'no') {
4349 TagFn => \&debiantag_maintview,
4351 TfSuffix => '-dgit',
4355 foreach my $tw (@tagwants) {
4356 $tw->{Tag} = $tw->{TagFn}($cversion, access_nomdistro);
4357 $tw->{Tfn} = sub { $tfbase.$tw->{TfSuffix}.$_[0]; };
4359 printdebug 'push_tagwants: ', Dumper(\@_, \@tagwants);
4363 sub push_mktags ($$ $$ $) {
4365 $changesfile,$changesfilewhat,
4368 die unless $tagwants->[0]{View} eq 'dgit';
4370 my $declaredistro = access_nomdistro();
4371 my $reader_giturl = do { local $access_forpush=0; access_giturl(); };
4372 $dsc->{$ourdscfield[0]} = join " ",
4373 $tagwants->[0]{Objid}, $declaredistro, $tagwants->[0]{Tag},
4375 $dsc->save("$dscfn.tmp") or confess "$!";
4377 my $changes = parsecontrol($changesfile,$changesfilewhat);
4378 foreach my $field (qw(Source Distribution Version)) {
4379 $changes->{$field} eq $clogp->{$field} or
4380 fail f_ "changes field %s \`%s' does not match changelog \`%s'",
4381 $field, $changes->{$field}, $clogp->{$field};
4384 my $cversion = getfield $clogp, 'Version';
4385 my $clogsuite = getfield $clogp, 'Distribution';
4386 my $format = getfield $dsc, 'Format';
4388 # We make the git tag by hand because (a) that makes it easier
4389 # to control the "tagger" (b) we can do remote signing
4390 my $authline = clogp_authline $clogp;
4394 my $tfn = $tw->{Tfn};
4395 my $head = $tw->{Objid};
4396 my $tag = $tw->{Tag};
4398 open TO, '>', $tfn->('.tmp') or confess "$!";
4399 print TO <<END or confess "$!";
4407 my @dtxinfo = @deliberatelies;
4408 unshift @dtxinfo, "--quilt=$quilt_mode" if madformat($format);
4409 unshift @dtxinfo, do_split_brain() ? "split" : "no-split"
4410 # rpush protocol 5 and earlier don't tell us
4411 unless $we_are_initiator && $protovsn < 6;
4412 my $dtxinfo = join(" ", "",@dtxinfo);
4413 my $tag_metadata = <<END;
4414 [dgit distro=$declaredistro$dtxinfo]
4416 foreach my $ref (sort keys %previously) {
4417 $tag_metadata .= <<END or confess "$!";
4418 [dgit previously:$ref=$previously{$ref}]
4422 if ($tw->{View} eq 'dgit') {
4423 print TO sprintf <<ENDT, $package, $cversion, $clogsuite, $csuite
4424 %s release %s for %s (%s) [dgit]
4427 } elsif ($tw->{View} eq 'maint') {
4428 print TO sprintf <<END, $package, $cversion, $clogsuite, $csuite;
4429 %s release %s for %s (%s)
4433 (maintainer view tag generated by dgit --quilt=%s)
4438 confess Dumper($tw)."?";
4440 print TO "\n", $tag_metadata;
4442 close TO or confess "$!";
4444 my $tagobjfn = $tfn->('.tmp');
4446 if (!defined $keyid) {
4447 $keyid = access_cfg('keyid','RETURN-UNDEF');
4449 if (!defined $keyid) {
4450 $keyid = getfield $clogp, 'Maintainer';
4452 unlink $tfn->('.tmp.asc') or $!==&ENOENT or confess "$!";
4453 my @sign_cmd = (@gpg, qw(--detach-sign --armor));
4454 push @sign_cmd, qw(-u),$keyid if defined $keyid;
4455 push @sign_cmd, $tfn->('.tmp');
4456 runcmd_ordryrun @sign_cmd;
4458 $tagobjfn = $tfn->('.signed.tmp');
4459 runcmd shell_cmd "exec >$tagobjfn", qw(cat --),
4460 $tfn->('.tmp'), $tfn->('.tmp.asc');
4466 my @r = map { $mktag->($_); } @$tagwants;
4470 sub sign_changes ($) {
4471 my ($changesfile) = @_;
4473 my @debsign_cmd = @debsign;
4474 push @debsign_cmd, "-k$keyid" if defined $keyid;
4475 push @debsign_cmd, "-p$gpg[0]" if $gpg[0] ne 'gpg';
4476 push @debsign_cmd, $changesfile;
4477 runcmd_ordryrun @debsign_cmd;
4482 printdebug "actually entering push\n";
4484 supplementary_message(__ <<'END');
4485 Push failed, while checking state of the archive.
4486 You can retry the push, after fixing the problem, if you like.
4488 if (check_for_git()) {
4491 my $archive_hash = fetch_from_archive();
4492 if (!$archive_hash) {
4494 fail __ "package appears to be new in this suite;".
4495 " if this is intentional, use --new";
4498 supplementary_message(__ <<'END');
4499 Push failed, while preparing your push.
4500 You can retry the push, after fixing the problem, if you like.
4505 access_giturl(); # check that success is vaguely likely
4506 rpush_handle_protovsn_bothends() if $we_are_initiator;
4508 my $clogpfn = dgit_privdir()."/changelog.822.tmp";
4509 runcmd shell_cmd "exec >$clogpfn", qw(dpkg-parsechangelog);
4511 responder_send_file('parsed-changelog', $clogpfn);
4513 my ($clogp, $cversion, $dscfn) =
4514 push_parse_changelog("$clogpfn");
4516 my $dscpath = "$buildproductsdir/$dscfn";
4517 stat_exists $dscpath or
4518 fail f_ "looked for .dsc %s, but %s; maybe you forgot to build",
4521 responder_send_file('dsc', $dscpath);
4523 push_parse_dsc($dscpath, $dscfn, $cversion);
4525 my $format = getfield $dsc, 'Format';
4527 my $symref = git_get_symref();
4528 my $actualhead = git_rev_parse('HEAD');
4530 if (branch_is_gdr_unstitched_ff($symref, $actualhead, $archive_hash)) {
4531 if (quiltmode_splitting()) {
4532 my ($ffq_prev, $gdrlast) = branch_gdr_info($symref, $actualhead);
4533 fail f_ <<END, $ffq_prev, $quilt_mode;
4534 Branch is managed by git-debrebase (%s
4535 exists), but quilt mode (%s) implies a split view.
4536 Pass the right --quilt option or adjust your git config.
4537 Or, maybe, run git-debrebase forget-was-ever-debrebase.
4540 runcmd_ordryrun_local @git_debrebase, 'stitch';
4541 $actualhead = git_rev_parse('HEAD');
4544 my $dgithead = $actualhead;
4545 my $maintviewhead = undef;
4547 my $upstreamversion = upstreamversion $clogp->{Version};
4549 if (madformat_wantfixup($format)) {
4550 # user might have not used dgit build, so maybe do this now:
4551 if (do_split_brain()) {
4552 changedir $playground;
4554 ($dgithead, $cachekey) =
4555 quilt_check_splitbrain_cache($actualhead, $upstreamversion);
4556 $dgithead or fail f_
4557 "--quilt=%s but no cached dgit view:
4558 perhaps HEAD changed since dgit build[-source] ?",
4561 if (!do_split_brain()) {
4562 # In split brain mode, do not attempt to incorporate dirty
4563 # stuff from the user's working tree. That would be mad.
4564 commit_quilty_patch();
4567 if (do_split_brain()) {
4568 $made_split_brain = 1;
4569 $dgithead = splitbrain_pseudomerge($clogp,
4570 $actualhead, $dgithead,
4572 $maintviewhead = $actualhead;
4574 prep_ud(); # so _only_subdir() works, below
4577 if (defined $overwrite_version && !defined $maintviewhead
4579 $dgithead = plain_overwrite_pseudomerge($clogp,
4587 if ($archive_hash) {
4588 if (is_fast_fwd($archive_hash, $dgithead)) {
4590 } elsif (deliberately_not_fast_forward) {
4593 fail __ "dgit push: HEAD is not a descendant".
4594 " of the archive's version.\n".
4595 "To overwrite the archive's contents,".
4596 " pass --overwrite[=VERSION].\n".
4597 "To rewind history, if permitted by the archive,".
4598 " use --deliberately-not-fast-forward.";
4602 confess unless !!$made_split_brain == do_split_brain();
4604 changedir $playground;
4605 progress f_ "checking that %s corresponds to HEAD", $dscfn;
4606 runcmd qw(dpkg-source -x --),
4607 $dscpath =~ m#^/# ? $dscpath : "$maindir/$dscpath";
4608 my ($tree,$dir) = mktree_in_ud_from_only_subdir("source package");
4609 check_for_vendor_patches() if madformat($dsc->{format});
4611 my @diffcmd = (@git, qw(diff --quiet), $tree, $dgithead);
4612 debugcmd "+",@diffcmd;
4614 my $r = system @diffcmd;
4617 my $referent = $made_split_brain ? $dgithead : 'HEAD';
4618 my $diffs = cmdoutput @git, qw(diff --stat), $tree, $dgithead;
4621 my $raw = cmdoutput @git,
4622 qw(diff --no-renames -z -r --raw), $tree, $dgithead;
4624 foreach (split /\0/, $raw) {
4625 if (defined $changed) {
4626 push @mode_changes, "$changed: $_\n" if $changed;
4629 } elsif (m/^:0+ 0+ /) {
4631 } elsif (m/^:(?:10*)?(\d+) (?:10*)?(\d+) /) {
4632 $changed = "Mode change from $1 to $2"
4637 if (@mode_changes) {
4638 fail +(f_ <<ENDT, $dscfn).<<END
4639 HEAD specifies a different tree to %s:
4643 .(join '', @mode_changes)
4644 .(f_ <<ENDT, $tree, $referent);
4645 There is a problem with your source tree (see dgit(7) for some hints).
4646 To see a full diff, run git diff %s %s
4650 fail +(f_ <<ENDT, $dscfn).<<END.(f_ <<ENDT, $tree, $referent);
4651 HEAD specifies a different tree to %s:
4655 Perhaps you forgot to build. Or perhaps there is a problem with your
4656 source tree (see dgit(7) for some hints). To see a full diff, run
4663 if (!$changesfile) {
4664 my $pat = changespat $cversion;
4665 my @cs = glob "$buildproductsdir/$pat";
4666 fail f_ "failed to find unique changes file".
4667 " (looked for %s in %s);".
4668 " perhaps you need to use dgit -C",
4669 $pat, $buildproductsdir
4671 ($changesfile) = @cs;
4673 $changesfile = "$buildproductsdir/$changesfile";
4676 # Check that changes and .dsc agree enough
4677 $changesfile =~ m{[^/]*$};
4678 my $changes = parsecontrol($changesfile,$&);
4679 files_compare_inputs($dsc, $changes)
4680 unless forceing [qw(dsc-changes-mismatch)];
4682 # Check whether this is a source only upload
4683 my $hasdebs = $changes->{Files} =~ m{\.deb$}m;
4684 my $sourceonlypolicy = access_cfg 'source-only-uploads';
4685 if ($sourceonlypolicy eq 'ok') {
4686 } elsif ($sourceonlypolicy eq 'always') {
4687 forceable_fail [qw(uploading-binaries)],
4688 __ "uploading binaries, although distro policy is source only"
4690 } elsif ($sourceonlypolicy eq 'never') {
4691 forceable_fail [qw(uploading-source-only)],
4692 __ "source-only upload, although distro policy requires .debs"
4694 } elsif ($sourceonlypolicy eq 'not-wholly-new') {
4695 forceable_fail [qw(uploading-source-only)],
4696 f_ "source-only upload, even though package is entirely NEW\n".
4697 "(this is contrary to policy in %s)",
4701 && !(archive_query('package_not_wholly_new', $package) // 1);
4703 badcfg f_ "unknown source-only-uploads policy \`%s'",
4707 # Perhaps adjust .dsc to contain right set of origs
4708 changes_update_origs_from_dsc($dsc, $changes, $upstreamversion,
4710 unless forceing [qw(changes-origs-exactly)];
4712 # Checks complete, we're going to try and go ahead:
4714 responder_send_file('changes',$changesfile);
4715 responder_send_command("param head $dgithead");
4716 responder_send_command("param csuite $csuite");
4717 responder_send_command("param isuite $isuite");
4718 responder_send_command("param tagformat new"); # needed in $protovsn==4
4719 responder_send_command("param splitbrain $do_split_brain");
4720 if (defined $maintviewhead) {
4721 responder_send_command("param maint-view $maintviewhead");
4724 # Perhaps send buildinfo(s) for signing
4725 my $changes_files = getfield $changes, 'Files';
4726 my @buildinfos = ($changes_files =~ m/ .* (\S+\.buildinfo)$/mg);
4727 foreach my $bi (@buildinfos) {
4728 responder_send_command("param buildinfo-filename $bi");
4729 responder_send_file('buildinfo', "$buildproductsdir/$bi");
4732 if (deliberately_not_fast_forward) {
4733 git_for_each_ref(lrfetchrefs, sub {
4734 my ($objid,$objtype,$lrfetchrefname,$reftail) = @_;
4735 my $rrefname= substr($lrfetchrefname, length(lrfetchrefs) + 1);
4736 responder_send_command("previously $rrefname=$objid");
4737 $previously{$rrefname} = $objid;
4741 my @tagwants = push_tagwants($cversion, $dgithead, $maintviewhead,
4742 dgit_privdir()."/tag");
4745 supplementary_message(__ <<'END');
4746 Push failed, while signing the tag.
4747 You can retry the push, after fixing the problem, if you like.
4749 # If we manage to sign but fail to record it anywhere, it's fine.
4750 if ($we_are_responder) {
4751 @tagobjfns = map { $_->{Tfn}('.signed-tmp') } @tagwants;
4752 responder_receive_files('signed-tag', @tagobjfns);
4754 @tagobjfns = push_mktags($clogp,$dscpath,
4755 $changesfile,$changesfile,
4758 supplementary_message(__ <<'END');
4759 Push failed, *after* signing the tag.
4760 If you want to try again, you should use a new version number.
4763 pairwise { $a->{TagObjFn} = $b } @tagwants, @tagobjfns;
4765 foreach my $tw (@tagwants) {
4766 my $tag = $tw->{Tag};
4767 my $tagobjfn = $tw->{TagObjFn};
4769 cmdoutput @git, qw(hash-object -w -t tag), $tagobjfn;
4770 runcmd_ordryrun @git, qw(verify-tag), $tag_obj_hash;
4771 runcmd_ordryrun_local
4772 @git, qw(update-ref), "refs/tags/$tag", $tag_obj_hash;
4775 supplementary_message(__ <<'END');
4776 Push failed, while updating the remote git repository - see messages above.
4777 If you want to try again, you should use a new version number.
4779 if (!check_for_git()) {
4780 create_remote_git_repo();
4783 my @pushrefs = $forceflag.$dgithead.":".rrref();
4784 foreach my $tw (@tagwants) {
4785 push @pushrefs, $forceflag."refs/tags/$tw->{Tag}";
4788 runcmd_ordryrun @git,
4789 qw(-c push.followTags=false push), access_giturl(), @pushrefs;
4790 runcmd_ordryrun git_update_ref_cmd 'dgit push', lrref(), $dgithead;
4792 supplementary_message(__ <<'END');
4793 Push failed, while obtaining signatures on the .changes and .dsc.
4794 If it was just that the signature failed, you may try again by using
4795 debsign by hand to sign the changes file (see the command dgit tried,
4796 above), and then dput that changes file to complete the upload.
4797 If you need to change the package, you must use a new version number.
4799 if ($we_are_responder) {
4800 my $dryrunsuffix = act_local() ? "" : ".tmp";
4801 my @rfiles = ($dscpath, $changesfile);
4802 push @rfiles, map { "$buildproductsdir/$_" } @buildinfos;
4803 responder_receive_files('signed-dsc-changes',
4804 map { "$_$dryrunsuffix" } @rfiles);
4807 rename "$dscpath.tmp",$dscpath or die "$dscfn $!";
4809 progress f_ "[new .dsc left in %s.tmp]", $dscpath;
4811 sign_changes $changesfile;
4814 supplementary_message(f_ <<END, $changesfile);
4815 Push failed, while uploading package(s) to the archive server.
4816 You can retry the upload of exactly these same files with dput of:
4818 If that .changes file is broken, you will need to use a new version
4819 number for your next attempt at the upload.
4821 my $host = access_cfg('upload-host','RETURN-UNDEF');
4822 my @hostarg = defined($host) ? ($host,) : ();
4823 runcmd_ordryrun @dput, @hostarg, $changesfile;
4824 printdone f_ "pushed and uploaded %s", $cversion;
4826 supplementary_message('');
4827 responder_send_command("complete");
4831 not_necessarily_a_tree();
4836 badusage __ "-p is not allowed with clone; specify as argument instead"
4837 if defined $package;
4840 } elsif (@ARGV==2 && $ARGV[1] =~ m#^\w#) {
4841 ($package,$isuite) = @ARGV;
4842 } elsif (@ARGV==2 && $ARGV[1] =~ m#^[./]#) {
4843 ($package,$dstdir) = @ARGV;
4844 } elsif (@ARGV==3) {
4845 ($package,$isuite,$dstdir) = @ARGV;
4847 badusage __ "incorrect arguments to dgit clone";
4851 $dstdir ||= "$package";
4852 if (stat_exists $dstdir) {
4853 fail f_ "%s already exists", $dstdir;
4857 if ($rmonerror && !$dryrun_level) {
4858 $cwd_remove= getcwd();
4860 return unless defined $cwd_remove;
4861 if (!chdir "$cwd_remove") {
4862 return if $!==&ENOENT;
4863 confess "chdir $cwd_remove: $!";
4865 printdebug "clone rmonerror removing $dstdir\n";
4867 rmtree($dstdir) or fail f_ "remove %s: %s\n", $dstdir, $!;
4868 } elsif (grep { $! == $_ }
4869 (ENOENT, ENOTDIR, EACCES, EPERM, ELOOP)) {
4871 print STDERR f_ "check whether to remove %s: %s\n",
4878 $cwd_remove = undef;
4881 sub branchsuite () {
4882 my $branch = git_get_symref();
4883 if (defined $branch && $branch =~ m#$lbranch_re#o) {
4890 sub package_from_d_control () {
4891 if (!defined $package) {
4892 my $sourcep = parsecontrol('debian/control','debian/control');
4893 $package = getfield $sourcep, 'Source';
4897 sub fetchpullargs () {
4898 package_from_d_control();
4900 $isuite = branchsuite();
4902 my $clogp = parsechangelog();
4903 my $clogsuite = getfield $clogp, 'Distribution';
4904 $isuite= $clogsuite if $clogsuite ne 'UNRELEASED';
4906 } elsif (@ARGV==1) {
4909 badusage __ "incorrect arguments to dgit fetch or dgit pull";
4923 determine_whether_split_brain get_source_format();
4924 if (do_split_brain()) {
4925 my ($format, $fopts) = get_source_format();
4926 madformat($format) and fail f_ <<END, $quilt_mode
4927 dgit pull not yet supported in split view mode (including with view-splitting quilt modes)
4935 package_from_d_control();
4936 @ARGV==1 or badusage __ "dgit checkout needs a suite argument";
4940 foreach my $canon (qw(0 1)) {
4945 canonicalise_suite();
4947 if (length git_get_ref lref()) {
4948 # local branch already exists, yay
4951 if (!length git_get_ref lrref()) {
4959 runcmd (@git, qw(update-ref), lref(), lrref(), '');
4962 local $ENV{GIT_REFLOG_ACTION} = git_reflog_action_msg
4963 "dgit checkout $isuite";
4964 runcmd (@git, qw(checkout), lbranch());
4967 sub cmd_update_vcs_git () {
4969 if (@ARGV==0 || $ARGV[0] =~ m/^-/) {
4970 ($specsuite,) = split /\;/, cfg 'dgit.vcs-git.suites';
4972 ($specsuite) = (@ARGV);
4977 if ($ARGV[0] eq '-') {
4979 } elsif ($ARGV[0] eq '-') {
4984 package_from_d_control();
4986 if ($specsuite eq '.') {
4987 $ctrl = parsecontrol 'debian/control', 'debian/control';
4989 $isuite = $specsuite;
4993 my $url = vcs_git_url_of_ctrl $ctrl;
4994 fail 'no Vcs-Git header in control file' unless length $url;
4997 my $orgurl = cfg 'remote.vcs-git.url', 'RETURN-UNDEF';
4998 if (!defined $orgurl) {
4999 print STDERR f_ "setting up vcs-git: %s\n", $url;
5000 @cmd = (@git, qw(remote add vcs-git), $url);
5001 } elsif ($orgurl eq $url) {
5002 print STDERR f_ "vcs git unchanged: %s\n", $url;
5004 print STDERR f_ "changing vcs-git url to: %s\n", $url;
5005 @cmd = (@git, qw(remote set-url vcs-git), $url);
5007 runcmd_ordryrun_local @cmd if @cmd;
5009 print f_ "fetching (%s)\n", "@ARGV";
5010 runcmd_ordryrun_local @git, qw(fetch vcs-git), @ARGV;
5016 build_or_push_prep_early();
5018 build_or_push_prep_modes();
5022 } elsif (@ARGV==1) {
5023 ($specsuite) = (@ARGV);
5025 badusage f_ "incorrect arguments to dgit %s", $subcommand;
5028 local ($package) = $existing_package; # this is a hack
5029 canonicalise_suite();
5031 canonicalise_suite();
5033 if (defined $specsuite &&
5034 $specsuite ne $isuite &&
5035 $specsuite ne $csuite) {
5036 fail f_ "dgit %s: changelog specifies %s (%s)".
5037 " but command line specifies %s",
5038 $subcommand, $isuite, $csuite, $specsuite;
5047 #---------- remote commands' implementation ----------
5049 sub pre_remote_push_build_host {
5050 my ($nrargs) = shift @ARGV;
5051 my (@rargs) = @ARGV[0..$nrargs-1];
5052 @ARGV = @ARGV[$nrargs..$#ARGV];
5054 my ($dir,$vsnwant) = @rargs;
5055 # vsnwant is a comma-separated list; we report which we have
5056 # chosen in our ready response (so other end can tell if they
5059 $we_are_responder = 1;
5060 $us .= " (build host)";
5062 open PI, "<&STDIN" or confess "$!";
5063 open STDIN, "/dev/null" or confess "$!";
5064 open PO, ">&STDOUT" or confess "$!";
5066 open STDOUT, ">&STDERR" or confess "$!";
5070 ($protovsn) = grep {
5071 $vsnwant =~ m{^(?:.*,)?$_(?:,.*)?$}
5072 } @rpushprotovsn_support;
5074 fail f_ "build host has dgit rpush protocol versions %s".
5075 " but invocation host has %s",
5076 (join ",", @rpushprotovsn_support), $vsnwant
5077 unless defined $protovsn;
5081 sub cmd_remote_push_build_host {
5082 responder_send_command("dgit-remote-push-ready $protovsn");
5086 sub pre_remote_push_responder { pre_remote_push_build_host(); }
5087 sub cmd_remote_push_responder { cmd_remote_push_build_host(); }
5088 # ... for compatibility with proto vsn.1 dgit (just so that user gets
5089 # a good error message)
5091 sub rpush_handle_protovsn_bothends () {
5098 my $report = i_child_report();
5099 if (defined $report) {
5100 printdebug "($report)\n";
5101 } elsif ($i_child_pid) {
5102 printdebug "(killing build host child $i_child_pid)\n";
5103 kill 15, $i_child_pid;
5105 if (defined $i_tmp && !defined $initiator_tempdir) {
5107 eval { rmtree $i_tmp; };
5112 return unless forkcheck_mainprocess();
5117 my ($base,$selector,@args) = @_;
5118 $selector =~ s/\-/_/g;
5119 { no strict qw(refs); &{"${base}_${selector}"}(@args); }
5123 not_necessarily_a_tree();
5128 if ($host =~ m/^((?:[^][]|\[[^][]*\])*)\:/) {
5136 push @rargs, join ",", @rpushprotovsn_support;
5139 push @rdgit, @ropts;
5140 push @rdgit, qw(remote-push-build-host), (scalar @rargs), @rargs;
5142 my @cmd = (@ssh, $host, shellquote @rdgit);
5145 $we_are_initiator=1;
5147 if (defined $initiator_tempdir) {
5148 rmtree $initiator_tempdir;
5149 mkdir $initiator_tempdir, 0700
5150 or fail f_ "create %s: %s", $initiator_tempdir, $!;
5151 $i_tmp = $initiator_tempdir;
5155 $i_child_pid = open2(\*RO, \*RI, @cmd);
5157 ($protovsn) = initiator_expect { m/^dgit-remote-push-ready (\S+)/ };
5158 die "$protovsn ?" unless grep { $_ eq $protovsn } @rpushprotovsn_support;
5161 my ($icmd,$iargs) = initiator_expect {
5162 m/^(\S+)(?: (.*))?$/;
5165 i_method "i_resp", $icmd, $iargs;
5169 sub i_resp_progress ($) {
5171 my $msg = protocol_read_bytes \*RO, $rhs;
5175 sub i_resp_supplementary_message ($) {
5177 $supplementary_message = protocol_read_bytes \*RO, $rhs;
5180 sub i_resp_complete {
5181 my $pid = $i_child_pid;
5182 $i_child_pid = undef; # prevents killing some other process with same pid
5183 printdebug "waiting for build host child $pid...\n";
5184 my $got = waitpid $pid, 0;
5185 confess "$!" unless $got == $pid;
5186 fail f_ "build host child failed: %s", waitstatusmsg() if $?;
5189 printdebug __ "all done\n";
5193 sub i_resp_file ($) {
5195 my $localname = i_method "i_localname", $keyword;
5196 my $localpath = "$i_tmp/$localname";
5197 stat_exists $localpath and
5198 badproto \*RO, f_ "file %s (%s) twice", $keyword, $localpath;
5199 protocol_receive_file \*RO, $localpath;
5200 i_method "i_file", $keyword;
5205 sub i_resp_param ($) {
5206 $_[0] =~ m/^(\S+) (.*)$/ or badproto \*RO, __ "bad param spec";
5210 sub i_resp_previously ($) {
5211 $_[0] =~ m#^(refs/tags/\S+)=(\w+)$#
5212 or badproto \*RO, __ "bad previously spec";
5213 my $r = system qw(git check-ref-format), $1;
5214 confess "bad previously ref spec ($r)" if $r;
5215 $previously{$1} = $2;
5219 our ($i_clogp, $i_version, $i_dscfn, $i_changesfn, @i_buildinfos);
5221 sub i_resp_want ($) {
5223 die "$keyword ?" if $i_wanted{$keyword}++;
5225 defined $i_param{'csuite'} or badproto \*RO, "premature desire, no csuite";
5226 $isuite = $i_param{'isuite'} // $i_param{'csuite'};
5227 die unless $isuite =~ m/^$suite_re$/;
5229 if (!defined $dsc) {
5231 rpush_handle_protovsn_bothends();
5232 push_parse_dsc $i_dscfn, 'remote dsc', $i_version;
5233 if ($protovsn >= 6) {
5234 determine_whether_split_brain getfield $dsc, 'Format';
5235 $do_split_brain eq ($i_param{'splitbrain'} // '<unsent>')
5237 "split brain mismatch, $do_split_brain != $i_param{'split_brain'}";
5238 printdebug "rpush split brain $do_split_brain\n";
5242 my @localpaths = i_method "i_want", $keyword;
5243 printdebug "[[ $keyword @localpaths\n";
5244 foreach my $localpath (@localpaths) {
5245 protocol_send_file \*RI, $localpath;
5247 print RI "files-end\n" or confess "$!";
5250 sub i_localname_parsed_changelog {
5251 return "remote-changelog.822";
5253 sub i_file_parsed_changelog {
5254 ($i_clogp, $i_version, $i_dscfn) =
5255 push_parse_changelog "$i_tmp/remote-changelog.822";
5256 die if $i_dscfn =~ m#/|^\W#;
5259 sub i_localname_dsc {
5260 defined $i_dscfn or badproto \*RO, "dsc (before parsed-changelog)";
5265 sub i_localname_buildinfo ($) {
5266 my $bi = $i_param{'buildinfo-filename'};
5267 defined $bi or badproto \*RO, "buildinfo before filename";
5268 defined $i_changesfn or badproto \*RO, "buildinfo before changes";
5269 $bi =~ m{^\Q$package\E_[!-.0-~]*\.buildinfo$}s
5270 or badproto \*RO, "improper buildinfo filename";
5273 sub i_file_buildinfo {
5274 my $bi = $i_param{'buildinfo-filename'};
5275 my $bd = parsecontrol "$i_tmp/$bi", $bi;
5276 my $ch = parsecontrol "$i_tmp/$i_changesfn", 'changes';
5277 if (!forceing [qw(buildinfo-changes-mismatch)]) {
5278 files_compare_inputs($bd, $ch);
5279 (getfield $bd, $_) eq (getfield $ch, $_) or
5280 fail f_ "buildinfo mismatch in field %s", $_
5281 foreach qw(Source Version);
5282 !defined $bd->{$_} or
5283 fail f_ "buildinfo contains forbidden field %s", $_
5284 foreach qw(Changes Changed-by Distribution);
5286 push @i_buildinfos, $bi;
5287 delete $i_param{'buildinfo-filename'};
5290 sub i_localname_changes {
5291 defined $i_dscfn or badproto \*RO, "dsc (before parsed-changelog)";
5292 $i_changesfn = $i_dscfn;
5293 $i_changesfn =~ s/\.dsc$/_dgit.changes/ or die;
5294 return $i_changesfn;
5296 sub i_file_changes { }
5298 sub i_want_signed_tag {
5299 printdebug Dumper(\%i_param, $i_dscfn);
5300 defined $i_param{'head'} && defined $i_dscfn && defined $i_clogp
5301 && defined $i_param{'csuite'}
5302 or badproto \*RO, "premature desire for signed-tag";
5303 my $head = $i_param{'head'};
5304 die if $head =~ m/[^0-9a-f]/ || $head !~ m/^../;
5306 my $maintview = $i_param{'maint-view'};
5307 die if defined $maintview && $maintview =~ m/[^0-9a-f]/;
5309 if ($protovsn == 4) {
5310 my $p = $i_param{'tagformat'} // '<undef>';
5312 or badproto \*RO, "tag format mismatch: $p vs. new";
5315 die unless $i_param{'csuite'} =~ m/^$suite_re$/;
5317 defined $dsc or badproto \*RO, "dsc (before parsed-changelog)";
5319 my @tagwants = push_tagwants $i_version, $head, $maintview, "tag";
5322 push_mktags $i_clogp, $i_dscfn,
5323 $i_changesfn, (__ 'remote changes file'),
5327 sub i_want_signed_dsc_changes {
5328 rename "$i_dscfn.tmp","$i_dscfn" or die "$i_dscfn $!";
5329 sign_changes $i_changesfn;
5330 return ($i_dscfn, $i_changesfn, @i_buildinfos);
5333 #---------- building etc. ----------
5339 #----- `3.0 (quilt)' handling -----
5341 our $fakeeditorenv = 'DGIT_FAKE_EDITOR_QUILT';
5343 sub quiltify_dpkg_commit ($$$;$) {
5344 my ($patchname,$author,$msg, $xinfo) = @_;
5347 mkpath '.git/dgit'; # we are in playtree
5348 my $descfn = ".git/dgit/quilt-description.tmp";
5349 open O, '>', $descfn or confess "$descfn: $!";
5350 $msg =~ s/\n+/\n\n/;
5351 print O <<END or confess "$!";
5353 ${xinfo}Subject: $msg
5357 close O or confess "$!";
5360 local $ENV{'EDITOR'} = cmdoutput qw(realpath --), $0;
5361 local $ENV{'VISUAL'} = $ENV{'EDITOR'};
5362 local $ENV{$fakeeditorenv} = cmdoutput qw(realpath --), $descfn;
5363 runcmd @dpkgsource, qw(--commit --include-removal .), $patchname;
5367 sub quiltify_trees_differ ($$;$$$) {
5368 my ($x,$y,$finegrained,$ignorenamesr,$unrepres) = @_;
5369 # returns true iff the two tree objects differ other than in debian/
5370 # with $finegrained,
5371 # returns bitmask 01 - differ in upstream files except .gitignore
5372 # 02 - differ in .gitignore
5373 # if $ignorenamesr is defined, $ingorenamesr->{$fn}
5374 # is set for each modified .gitignore filename $fn
5375 # if $unrepres is defined, array ref to which is appeneded
5376 # a list of unrepresentable changes (removals of upstream files
5379 my @cmd = (@git, qw(diff-tree -z --no-renames));
5380 push @cmd, qw(--name-only) unless $unrepres;
5381 push @cmd, qw(-r) if $finegrained || $unrepres;
5383 my $diffs= cmdoutput @cmd;
5386 foreach my $f (split /\0/, $diffs) {
5387 if ($unrepres && !@lmodes) {
5388 @lmodes = $f =~ m/^\:(\w+) (\w+) \w+ \w+ / or die "$_ ?";
5391 my ($oldmode,$newmode) = @lmodes;
5394 next if $f =~ m#^debian(?:/.*)?$#s;
5398 die __ "not a plain file or symlink\n"
5399 unless $newmode =~ m/^(?:10|12)\d{4}$/ ||
5400 $oldmode =~ m/^(?:10|12)\d{4}$/;
5401 if ($oldmode =~ m/[^0]/ &&
5402 $newmode =~ m/[^0]/) {
5403 # both old and new files exist
5404 die __ "mode or type changed\n" if $oldmode ne $newmode;
5405 die __ "modified symlink\n" unless $newmode =~ m/^10/;
5406 } elsif ($oldmode =~ m/[^0]/) {
5408 die __ "deletion of symlink\n"
5409 unless $oldmode =~ m/^10/;
5412 die __ "creation with non-default mode\n"
5413 unless $newmode =~ m/^100644$/ or
5414 $newmode =~ m/^120000$/;
5418 local $/="\n"; chomp $@;
5419 push @$unrepres, [ $f, "$@ ($oldmode->$newmode)" ];
5423 my $isignore = $f =~ m#^(?:.*/)?.gitignore$#s;
5424 $r |= $isignore ? 02 : 01;
5425 $ignorenamesr->{$f}=1 if $ignorenamesr && $isignore;
5427 printdebug "quiltify_trees_differ $x $y => $r\n";
5431 sub quiltify_tree_sentinelfiles ($) {
5432 # lists the `sentinel' files present in the tree
5434 my $r = cmdoutput @git, qw(ls-tree --name-only), $x,
5435 qw(-- debian/rules debian/control);
5440 sub quiltify_splitting ($$$$$$$) {
5441 my ($clogp, $unapplied, $headref, $oldtiptree, $diffbits,
5442 $editedignores, $cachekey) = @_;
5443 my $gitignore_special = 1;
5444 if ($quilt_mode !~ m/gbp|dpm|baredebian/) {
5445 # treat .gitignore just like any other upstream file
5446 $diffbits = { %$diffbits };
5447 $_ = !!$_ foreach values %$diffbits;
5448 $gitignore_special = 0;
5450 # We would like any commits we generate to be reproducible
5451 my @authline = clogp_authline($clogp);
5452 local $ENV{GIT_COMMITTER_NAME} = $authline[0];
5453 local $ENV{GIT_COMMITTER_EMAIL} = $authline[1];
5454 local $ENV{GIT_COMMITTER_DATE} = $authline[2];
5455 local $ENV{GIT_AUTHOR_NAME} = $authline[0];
5456 local $ENV{GIT_AUTHOR_EMAIL} = $authline[1];
5457 local $ENV{GIT_AUTHOR_DATE} = $authline[2];
5459 confess unless do_split_brain();
5461 my $fulldiffhint = sub {
5463 my $cmd = "git diff $x $y -- :/ ':!debian'";
5464 $cmd .= " ':!/.gitignore' ':!*/.gitignore'" if $gitignore_special;
5465 return f_ "\nFor full diff showing the problem(s), type:\n %s\n",
5469 if ($quilt_mode =~ m/gbp|unapplied|baredebian/ &&
5470 ($diffbits->{O2H} & 01)) {
5472 "--quilt=%s specified, implying patches-unapplied git tree\n".
5473 " but git tree differs from orig in upstream files.",
5475 $msg .= $fulldiffhint->($unapplied, 'HEAD');
5476 if (!stat_exists "debian/patches" and $quilt_mode !~ m/baredebian/) {
5478 "\n ... debian/patches is missing; perhaps this is a patch queue branch?";
5482 if ($quilt_mode =~ m/dpm/ &&
5483 ($diffbits->{H2A} & 01)) {
5484 fail +(f_ <<END, $quilt_mode). $fulldiffhint->($oldtiptree,'HEAD');
5485 --quilt=%s specified, implying patches-applied git tree
5486 but git tree differs from result of applying debian/patches to upstream
5489 if ($quilt_mode =~ m/baredebian/) {
5490 # We need to construct a merge which has upstream files from
5491 # upstream and debian/ files from HEAD.
5493 read_tree_upstream $quilt_upstream_commitish, 1, $headref;
5494 my $version = getfield $clogp, 'Version';
5495 my $upsversion = upstreamversion $version;
5496 my $merge = make_commit
5497 [ $headref, $quilt_upstream_commitish ],
5498 [ +(f_ <<ENDT, $upsversion), $quilt_upstream_commitish_message, <<ENDU ];
5499 Combine debian/ with upstream source for %s
5501 [dgit ($our_version) baredebian-merge $version $quilt_upstream_commitish_used]
5503 runcmd @git, qw(reset -q --hard), $merge;
5505 if ($quilt_mode =~ m/gbp|unapplied|baredebian/ &&
5506 ($diffbits->{O2A} & 01)) { # some patches
5507 progress __ "dgit view: creating patches-applied version using gbp pq";
5508 runcmd shell_cmd 'exec >/dev/null', gbp_pq, qw(import);
5509 # gbp pq import creates a fresh branch; push back to dgit-view
5510 runcmd @git, qw(update-ref refs/heads/dgit-view HEAD);
5511 runcmd @git, qw(checkout -q dgit-view);
5513 if ($quilt_mode =~ m/gbp|dpm/ &&
5514 ($diffbits->{O2A} & 02)) {
5515 fail f_ <<END, $quilt_mode;
5516 --quilt=%s specified, implying that HEAD is for use with a
5517 tool which does not create patches for changes to upstream
5518 .gitignores: but, such patches exist in debian/patches.
5521 if (($diffbits->{O2H} & 02) && # user has modified .gitignore
5522 !($diffbits->{O2A} & 02)) { # patches do not change .gitignore
5524 "dgit view: creating patch to represent .gitignore changes";
5525 ensuredir "debian/patches";
5526 my $gipatch = "debian/patches/auto-gitignore";
5527 open GIPATCH, ">>", "$gipatch" or confess "$gipatch: $!";
5528 stat GIPATCH or confess "$gipatch: $!";
5529 fail f_ "%s already exists; but want to create it".
5530 " to record .gitignore changes",
5533 print GIPATCH +(__ <<END).<<ENDU or die "$gipatch: $!";
5534 Subject: Update .gitignore from Debian packaging branch
5536 The Debian packaging git branch contains these updates to the upstream
5537 .gitignore file(s). This patch is autogenerated, to provide these
5538 updates to users of the official Debian archive view of the package.
5541 [dgit ($our_version) update-gitignore]
5544 close GIPATCH or die "$gipatch: $!";
5545 runcmd shell_cmd "exec >>$gipatch", @git, qw(diff),
5546 $unapplied, $headref, "--", sort keys %$editedignores;
5547 open SERIES, "+>>", "debian/patches/series" or confess "$!";
5548 defined seek SERIES, -1, 2 or $!==EINVAL or confess "$!";
5550 defined read SERIES, $newline, 1 or confess "$!";
5551 print SERIES "\n" or confess "$!" unless $newline eq "\n";
5552 print SERIES "auto-gitignore\n" or confess "$!";
5553 close SERIES or die $!;
5554 runcmd @git, qw(add -f -- debian/patches/series), $gipatch;
5555 commit_admin +(__ <<END).<<ENDU
5556 Commit patch to update .gitignore
5559 [dgit ($our_version) update-gitignore-quilt-fixup]
5564 sub quiltify ($$$$) {
5565 my ($clogp,$target,$oldtiptree,$failsuggestion) = @_;
5567 # Quilt patchification algorithm
5569 # We search backwards through the history of the main tree's HEAD
5570 # (T) looking for a start commit S whose tree object is identical
5571 # to to the patch tip tree (ie the tree corresponding to the
5572 # current dpkg-committed patch series). For these purposes
5573 # `identical' disregards anything in debian/ - this wrinkle is
5574 # necessary because dpkg-source treates debian/ specially.
5576 # We can only traverse edges where at most one of the ancestors'
5577 # trees differs (in changes outside in debian/). And we cannot
5578 # handle edges which change .pc/ or debian/patches. To avoid
5579 # going down a rathole we avoid traversing edges which introduce
5580 # debian/rules or debian/control. And we set a limit on the
5581 # number of edges we are willing to look at.
5583 # If we succeed, we walk forwards again. For each traversed edge
5584 # PC (with P parent, C child) (starting with P=S and ending with
5585 # C=T) to we do this:
5587 # - dpkg-source --commit with a patch name and message derived from C
5588 # After traversing PT, we git commit the changes which
5589 # should be contained within debian/patches.
5591 # The search for the path S..T is breadth-first. We maintain a
5592 # todo list containing search nodes. A search node identifies a
5593 # commit, and looks something like this:
5595 # Commit => $git_commit_id,
5596 # Child => $c, # or undef if P=T
5597 # Whynot => $reason_edge_PC_unsuitable, # in @nots only
5598 # Nontrivial => true iff $p..$c has relevant changes
5605 my %considered; # saves being exponential on some weird graphs
5607 my $t_sentinels = quiltify_tree_sentinelfiles $target;
5610 my ($search,$whynot) = @_;
5611 printdebug " search NOT $search->{Commit} $whynot\n";
5612 $search->{Whynot} = $whynot;
5613 push @nots, $search;
5614 no warnings qw(exiting);
5623 my $c = shift @todo;
5624 next if $considered{$c->{Commit}}++;
5626 $not->($c, __ "maximum search space exceeded") if --$max_work <= 0;
5628 printdebug "quiltify investigate $c->{Commit}\n";
5631 if (!quiltify_trees_differ $c->{Commit}, $oldtiptree) {
5632 printdebug " search finished hooray!\n";
5637 quiltify_nofix_bail " $c->{Commit}", " (tree object $oldtiptree)";
5638 if ($quilt_mode eq 'smash') {
5639 printdebug " search quitting smash\n";
5643 my $c_sentinels = quiltify_tree_sentinelfiles $c->{Commit};
5644 $not->($c, f_ "has %s not %s", $c_sentinels, $t_sentinels)
5645 if $c_sentinels ne $t_sentinels;
5647 my $commitdata = cmdoutput @git, qw(cat-file commit), $c->{Commit};
5648 $commitdata =~ m/\n\n/;
5650 my @parents = ($commitdata =~ m/^parent (\w+)$/gm);
5651 @parents = map { { Commit => $_, Child => $c } } @parents;
5653 $not->($c, __ "root commit") if !@parents;
5655 foreach my $p (@parents) {
5656 $p->{Nontrivial}= quiltify_trees_differ $p->{Commit},$c->{Commit};
5658 my $ndiffers = grep { $_->{Nontrivial} } @parents;
5659 $not->($c, f_ "merge (%s nontrivial parents)", $ndiffers)
5662 foreach my $p (@parents) {
5663 printdebug "considering C=$c->{Commit} P=$p->{Commit}\n";
5665 my @cmd= (@git, qw(diff-tree -r --name-only),
5666 $p->{Commit},$c->{Commit},
5667 qw(-- debian/patches .pc debian/source/format));
5668 my $patchstackchange = cmdoutput @cmd;
5669 if (length $patchstackchange) {
5670 $patchstackchange =~ s/\n/,/g;
5671 $not->($p, f_ "changed %s", $patchstackchange);
5674 printdebug " search queue P=$p->{Commit} ",
5675 ($p->{Nontrivial} ? "NT" : "triv"),"\n";
5681 printdebug "quiltify want to smash\n";
5684 my $x = $_[0]{Commit};
5685 $x =~ s/(.*?[0-9a-z]{8})[0-9a-z]*$/$1/;
5688 if ($quilt_mode eq 'linear') {
5690 "\n%s: error: quilt fixup cannot be linear. Stopped at:\n",
5692 my $all_gdr = !!@nots;
5693 foreach my $notp (@nots) {
5694 my $c = $notp->{Child};
5695 my $cprange = $abbrev->($notp);
5696 $cprange .= "..".$abbrev->($c) if $c;
5697 print STDERR f_ "%s: %s: %s\n",
5698 $us, $cprange, $notp->{Whynot};
5699 $all_gdr &&= $notp->{Child} &&
5700 (git_cat_file $notp->{Child}{Commit}, 'commit')
5701 =~ m{^\[git-debrebase(?! split[: ]).*\]$}m;
5705 [ grep { $_->[0] ne 'quilt-mode' } @$failsuggestion ]
5707 print STDERR "$us: $_->[1]\n" foreach @$failsuggestion;
5709 "quilt history linearisation failed. Search \`quilt fixup' in dgit(7).\n";
5710 } elsif ($quilt_mode eq 'smash') {
5711 } elsif ($quilt_mode eq 'auto') {
5712 progress __ "quilt fixup cannot be linear, smashing...";
5714 confess "$quilt_mode ?";
5717 my $time = $ENV{'GIT_COMMITTER_DATE'} || time;
5718 $time =~ s/\s.*//; # trim timezone from GIT_COMMITTER_DATE
5720 my $msg = cmdoutput @git, qw(log), "-n$ncommits";
5722 quiltify_dpkg_commit "auto-$version-$target-$time",
5723 (getfield $clogp, 'Maintainer'),
5724 (f_ "Automatically generated patch (%s)\n".
5725 "Last (up to) %s git changes, FYI:\n\n",
5726 $clogp->{Version}, $ncommits).
5731 progress __ "quiltify linearisation planning successful, executing...";
5733 for (my $p = $sref_S;
5734 my $c = $p->{Child};
5736 printdebug "quiltify traverse $p->{Commit}..$c->{Commit}\n";
5737 next unless $p->{Nontrivial};
5739 my $cc = $c->{Commit};
5741 my $commitdata = cmdoutput @git, qw(cat-file commit), $cc;
5742 $commitdata =~ m/\n\n/ or die "$c ?";
5745 $commitdata =~ m/^author (.*) \d+ [-+0-9]+$/m or die "$cc ?";
5748 my $commitdate = cmdoutput
5749 @git, qw(log -n1 --pretty=format:%aD), $cc;
5751 $msg =~ s/^(.*)\n*/$1\n/ or die "$cc $msg ?";
5753 my $strip_nls = sub { $msg =~ s/\n+$//; $msg .= "\n"; };
5760 my $gbp_check_suitable = sub {
5765 die __ "contains unexpected slashes\n" if m{//} || m{/$};
5766 die __ "contains leading punctuation\n" if m{^\W} || m{/\W};
5767 die __ "contains bad character(s)\n" if m{[^-a-z0-9_.+=~/]}i;
5768 die __ "is series file\n" if m{$series_filename_re}o;
5769 die __ "too long\n" if length > 200;
5771 return $_ unless $@;
5773 "quiltifying commit %s: ignoring/dropping Gbp-Pq %s: %s",
5778 if ($msg =~ s/^ (?: gbp(?:-pq)? : \s* name \s+ |
5780 (\S+) \s* \n //ixm) {
5781 $patchname = $gbp_check_suitable->($1, 'Name');
5783 if ($msg =~ s/^ (?: gbp(?:-pq)? : \s* topic \s+ |
5785 (\S+) \s* \n //ixm) {
5786 $patchdir = $gbp_check_suitable->($1, 'Topic');
5791 if (!defined $patchname) {
5792 $patchname = $title;
5793 $patchname =~ s/[.:]$//;
5796 my $converter = new Text::Iconv qw(UTF-8 ASCII//TRANSLIT);
5797 my $translitname = $converter->convert($patchname);
5798 die unless defined $translitname;
5799 $patchname = $translitname;
5802 +(f_ "dgit: patch title transliteration error: %s", $@)
5804 $patchname =~ y/ A-Z/-a-z/;
5805 $patchname =~ y/-a-z0-9_.+=~//cd;
5806 $patchname =~ s/^\W/x-$&/;
5807 $patchname = substr($patchname,0,40);
5808 $patchname .= ".patch";
5810 if (!defined $patchdir) {
5813 if (length $patchdir) {
5814 $patchname = "$patchdir/$patchname";
5816 if ($patchname =~ m{^(.*)/}) {
5817 mkpath "debian/patches/$1";
5822 stat "debian/patches/$patchname$index";
5824 $!==ENOENT or confess "$patchname$index $!";
5826 runcmd @git, qw(checkout -q), $cc;
5828 # We use the tip's changelog so that dpkg-source doesn't
5829 # produce complaining messages from dpkg-parsechangelog. None
5830 # of the information dpkg-source gets from the changelog is
5831 # actually relevant - it gets put into the original message
5832 # which dpkg-source provides our stunt editor, and then
5834 runcmd @git, qw(checkout -q), $target, qw(debian/changelog);
5836 quiltify_dpkg_commit "$patchname$index", $author, $msg,
5837 "Date: $commitdate\n".
5838 "X-Dgit-Generated: $clogp->{Version} $cc\n";
5840 runcmd @git, qw(checkout -q), $cc, qw(debian/changelog);
5844 sub build_maybe_quilt_fixup () {
5845 my ($format,$fopts) = get_source_format;
5846 return unless madformat_wantfixup $format;
5849 check_for_vendor_patches();
5851 my $clogp = parsechangelog();
5852 my $headref = git_rev_parse('HEAD');
5853 my $symref = git_get_symref();
5854 my $upstreamversion = upstreamversion $version;
5857 changedir $playground;
5859 my $splitbrain_cachekey;
5861 if (do_split_brain()) {
5863 ($cachehit, $splitbrain_cachekey) =
5864 quilt_check_splitbrain_cache($headref, $upstreamversion);
5871 unpack_playtree_need_cd_work($headref);
5872 if (do_split_brain()) {
5873 runcmd @git, qw(checkout -q -b dgit-view);
5874 # so long as work is not deleted, its current branch will
5875 # remain dgit-view, rather than master, so subsequent calls to
5876 # unpack_playtree_need_cd_work
5877 # will DTRT, resetting dgit-view.
5878 confess if $made_split_brain;
5879 $made_split_brain = 1;
5883 if ($fopts->{'single-debian-patch'}) {
5885 "quilt mode %s does not make sense (or is not supported) with single-debian-patch",
5887 if quiltmode_splitting();
5888 quilt_fixup_singlepatch($clogp, $headref, $upstreamversion);
5890 quilt_fixup_multipatch($clogp, $headref, $upstreamversion,
5891 $splitbrain_cachekey);
5894 if (do_split_brain()) {
5895 my $dgitview = git_rev_parse 'HEAD';
5898 reflog_cache_insert "refs/$splitbraincache",
5899 $splitbrain_cachekey, $dgitview;
5901 changedir "$playground/work";
5903 my $saved = maybe_split_brain_save $headref, $dgitview, __ "converted";
5904 progress f_ "dgit view: created (%s)", $saved;
5908 runcmd_ordryrun_local
5909 @git, qw(pull --ff-only -q), "$playground/work", qw(master);
5912 sub build_check_quilt_splitbrain () {
5913 build_maybe_quilt_fixup();
5916 sub unpack_playtree_need_cd_work ($) {
5919 # prep_ud() must have been called already.
5920 if (!chdir "work") {
5921 # Check in the filesystem because sometimes we run prep_ud
5922 # in between multiple calls to unpack_playtree_need_cd_work.
5923 confess "$!" unless $!==ENOENT;
5924 mkdir "work" or confess "$!";
5926 mktree_in_ud_here();
5928 runcmd @git, qw(reset -q --hard), $headref;
5931 sub unpack_playtree_linkorigs ($$) {
5932 my ($upstreamversion, $fn) = @_;
5933 # calls $fn->($leafname);
5935 my $bpd_abs = bpd_abs();
5937 dotdot_bpd_transfer_origs $bpd_abs, $upstreamversion, sub { 1 };
5939 opendir QFD, $bpd_abs or fail "buildproductsdir: $bpd_abs: $!";
5940 while ($!=0, defined(my $leaf = readdir QFD)) {
5941 my $f = bpd_abs()."/".$leaf;
5943 local ($debuglevel) = $debuglevel-1;
5944 printdebug "QF linkorigs bpd $leaf, $f ?\n";
5946 next unless is_orig_file_of_vsn $leaf, $upstreamversion;
5947 printdebug "QF linkorigs $leaf, $f Y\n";
5948 link_ltarget $f, $leaf or die "$leaf $!";
5951 die "$buildproductsdir: $!" if $!;
5955 sub quilt_fixup_delete_pc () {
5956 runcmd @git, qw(rm -rqf .pc);
5957 commit_admin +(__ <<END).<<ENDU
5958 Commit removal of .pc (quilt series tracking data)
5961 [dgit ($our_version) upgrade quilt-remove-pc]
5965 sub quilt_fixup_singlepatch ($$$) {
5966 my ($clogp, $headref, $upstreamversion) = @_;
5968 progress __ "starting quiltify (single-debian-patch)";
5970 # dpkg-source --commit generates new patches even if
5971 # single-debian-patch is in debian/source/options. In order to
5972 # get it to generate debian/patches/debian-changes, it is
5973 # necessary to build the source package.
5975 unpack_playtree_linkorigs($upstreamversion, sub { });
5976 unpack_playtree_need_cd_work($headref);
5978 rmtree("debian/patches");
5980 runcmd @dpkgsource, qw(-b .);
5982 runcmd @dpkgsource, qw(-x), (srcfn $version, ".dsc");
5983 rename srcfn("$upstreamversion", "/debian/patches"),
5984 "work/debian/patches"
5986 or confess "install d/patches: $!";
5989 commit_quilty_patch();
5992 sub quilt_need_fake_dsc ($) {
5993 # cwd should be playground
5994 my ($upstreamversion) = @_;
5996 return if stat_exists "fake.dsc";
5997 # ^ OK to test this as a sentinel because if we created it
5998 # we must either have done the rest too, or crashed.
6000 my $fakeversion="$upstreamversion-~~DGITFAKE";
6002 my $fakedsc=new IO::File 'fake.dsc', '>' or confess "$!";
6003 print $fakedsc <<END or confess "$!";
6006 Version: $fakeversion
6010 my $dscaddfile=sub {
6013 my $md = new Digest::MD5;
6015 my $fh = new IO::File $leaf, '<' or die "$leaf $!";
6016 stat $fh or confess "$!";
6020 print $fakedsc " ".$md->hexdigest." $size $leaf\n" or confess "$!";
6023 unpack_playtree_linkorigs($upstreamversion, $dscaddfile);
6025 my @files=qw(debian/source/format debian/rules
6026 debian/control debian/changelog);
6027 foreach my $maybe (qw(debian/patches debian/source/options
6028 debian/tests/control)) {
6029 next unless stat_exists "$maindir/$maybe";
6030 push @files, $maybe;
6033 my $debtar= srcfn $fakeversion,'.debian.tar.gz';
6034 runcmd qw(env GZIP=-1n tar -zcf), "./$debtar", qw(-C), $maindir, @files;
6036 $dscaddfile->($debtar);
6037 close $fakedsc or confess "$!";
6040 sub quilt_fakedsc2unapplied ($$) {
6041 my ($headref, $upstreamversion) = @_;
6042 # must be run in the playground
6043 # quilt_need_fake_dsc must have been called
6045 quilt_need_fake_dsc($upstreamversion);
6047 'exec dpkg-source --no-check --skip-patches -x fake.dsc >/dev/null';
6049 my $fakexdir= $package.'-'.(stripepoch $upstreamversion);
6050 rename $fakexdir, "fake" or die "$fakexdir $!";
6054 remove_stray_gits(__ "source package");
6055 mktree_in_ud_here();
6059 rmtree 'debian'; # git checkout commitish paths does not delete!
6060 runcmd @git, qw(checkout -f), $headref, qw(-- debian);
6061 my $unapplied=git_add_write_tree();
6062 printdebug "fake orig tree object $unapplied\n";
6066 sub quilt_check_splitbrain_cache ($$) {
6067 my ($headref, $upstreamversion) = @_;
6068 # Called only if we are in (potentially) split brain mode.
6069 # Called in playground.
6070 # Computes the cache key and looks in the cache.
6071 # Returns ($dgit_view_commitid, $cachekey) or (undef, $cachekey)
6073 quilt_need_fake_dsc($upstreamversion);
6075 my $splitbrain_cachekey;
6078 "dgit: split brain (separate dgit view) may be needed (--quilt=%s).",
6080 # we look in the reflog of dgit-intern/quilt-cache
6081 # we look for an entry whose message is the key for the cache lookup
6082 my @cachekey = (qw(dgit), $our_version);
6083 push @cachekey, $upstreamversion;
6084 push @cachekey, $quilt_mode;
6085 push @cachekey, $headref;
6086 push @cachekey, $quilt_upstream_commitish // '-';
6088 push @cachekey, hashfile('fake.dsc');
6090 my $srcshash = Digest::SHA->new(256);
6091 my %sfs = ( %INC, '$0(dgit)' => $0 );
6092 foreach my $sfk (sort keys %sfs) {
6093 next unless $sfk =~ m/^\$0\b/ || $sfk =~ m{^Debian/Dgit\b};
6094 $srcshash->add($sfk," ");
6095 $srcshash->add(hashfile($sfs{$sfk}));
6096 $srcshash->add("\n");
6098 push @cachekey, $srcshash->hexdigest();
6099 $splitbrain_cachekey = "@cachekey";
6101 printdebug "splitbrain cachekey $splitbrain_cachekey\n";
6103 my $cachehit = reflog_cache_lookup
6104 "refs/$splitbraincache", $splitbrain_cachekey;
6107 unpack_playtree_need_cd_work($headref);
6108 my $saved = maybe_split_brain_save $headref, $cachehit, "cache-hit";
6109 if ($cachehit ne $headref) {
6110 progress f_ "dgit view: found cached (%s)", $saved;
6111 runcmd @git, qw(checkout -q -b dgit-view), $cachehit;
6112 $made_split_brain = 1;
6113 return ($cachehit, $splitbrain_cachekey);
6115 progress __ "dgit view: found cached, no changes required";
6116 return ($headref, $splitbrain_cachekey);
6119 printdebug "splitbrain cache miss\n";
6120 return (undef, $splitbrain_cachekey);
6123 sub baredebian_origtarballs_scan ($$$) {
6124 my ($fakedfi, $upstreamversion, $dir) = @_;
6125 if (!opendir OD, $dir) {
6126 return if $! == ENOENT;
6127 fail "opendir $dir (origs): $!";
6130 while ($!=0, defined(my $leaf = readdir OD)) {
6132 local ($debuglevel) = $debuglevel-1;
6133 printdebug "BDOS $dir $leaf ?\n";
6135 next unless is_orig_file_of_vsn $leaf, $upstreamversion;
6136 next if grep { $_->{Filename} eq $leaf } @$fakedfi;
6139 Path => "$dir/$leaf",
6143 die "$dir; $!" if $!;
6147 sub quilt_fixup_multipatch ($$$) {
6148 my ($clogp, $headref, $upstreamversion, $splitbrain_cachekey) = @_;
6150 progress f_ "examining quilt state (multiple patches, %s mode)",
6154 # - honour any existing .pc in case it has any strangeness
6155 # - determine the git commit corresponding to the tip of
6156 # the patch stack (if there is one)
6157 # - if there is such a git commit, convert each subsequent
6158 # git commit into a quilt patch with dpkg-source --commit
6159 # - otherwise convert all the differences in the tree into
6160 # a single git commit
6164 # Our git tree doesn't necessarily contain .pc. (Some versions of
6165 # dgit would include the .pc in the git tree.) If there isn't
6166 # one, we need to generate one by unpacking the patches that we
6169 # We first look for a .pc in the git tree. If there is one, we
6170 # will use it. (This is not the normal case.)
6172 # Otherwise need to regenerate .pc so that dpkg-source --commit
6173 # can work. We do this as follows:
6174 # 1. Collect all relevant .orig from parent directory
6175 # 2. Generate a debian.tar.gz out of
6176 # debian/{patches,rules,source/format,source/options}
6177 # 3. Generate a fake .dsc containing just these fields:
6178 # Format Source Version Files
6179 # 4. Extract the fake .dsc
6180 # Now the fake .dsc has a .pc directory.
6181 # (In fact we do this in every case, because in future we will
6182 # want to search for a good base commit for generating patches.)
6184 # Then we can actually do the dpkg-source --commit
6185 # 1. Make a new working tree with the same object
6186 # store as our main tree and check out the main
6188 # 2. Copy .pc from the fake's extraction, if necessary
6189 # 3. Run dpkg-source --commit
6190 # 4. If the result has changes to debian/, then
6191 # - git add them them
6192 # - git add .pc if we had a .pc in-tree
6194 # 5. If we had a .pc in-tree, delete it, and git commit
6195 # 6. Back in the main tree, fast forward to the new HEAD
6197 # Another situation we may have to cope with is gbp-style
6198 # patches-unapplied trees.
6200 # We would want to detect these, so we know to escape into
6201 # quilt_fixup_gbp. However, this is in general not possible.
6202 # Consider a package with a one patch which the dgit user reverts
6203 # (with git revert or the moral equivalent).
6205 # That is indistinguishable in contents from a patches-unapplied
6206 # tree. And looking at the history to distinguish them is not
6207 # useful because the user might have made a confusing-looking git
6208 # history structure (which ought to produce an error if dgit can't
6209 # cope, not a silent reintroduction of an unwanted patch).
6211 # So gbp users will have to pass an option. But we can usually
6212 # detect their failure to do so: if the tree is not a clean
6213 # patches-applied tree, quilt linearisation fails, but the tree
6214 # _is_ a clean patches-unapplied tree, we can suggest that maybe
6215 # they want --quilt=unapplied.
6217 # To help detect this, when we are extracting the fake dsc, we
6218 # first extract it with --skip-patches, and then apply the patches
6219 # afterwards with dpkg-source --before-build. That lets us save a
6220 # tree object corresponding to .origs.
6222 if ($quilt_mode eq 'linear'
6223 && branch_is_gdr($headref)) {
6224 # This is much faster. It also makes patches that gdr
6225 # likes better for future updates without laundering.
6227 # However, it can fail in some casses where we would
6228 # succeed: if there are existing patches, which correspond
6229 # to a prefix of the branch, but are not in gbp/gdr
6230 # format, gdr will fail (exiting status 7), but we might
6231 # be able to figure out where to start linearising. That
6232 # will be slower so hopefully there's not much to do.
6234 unpack_playtree_need_cd_work $headref;
6236 my @cmd = (@git_debrebase,
6237 qw(--noop-ok -funclean-mixed -funclean-ordering
6238 make-patches --quiet-would-amend));
6239 # We tolerate soe snags that gdr wouldn't, by default.
6245 and not ($? == 7*256 or
6246 $? == -1 && $!==ENOENT);
6250 $headref = git_rev_parse('HEAD');
6255 my $unapplied=quilt_fakedsc2unapplied($headref, $upstreamversion);
6259 my @bbcmd = (qw(sh -ec), 'exec dpkg-source --before-build . >/dev/null');
6261 if (system @bbcmd) {
6262 failedcmd @bbcmd if $? < 0;
6264 failed to apply your git tree's patch stack (from debian/patches/) to
6265 the corresponding upstream tarball(s). Your source tree and .orig
6266 are probably too inconsistent. dgit can only fix up certain kinds of
6267 anomaly (depending on the quilt mode). Please see --quilt= in dgit(1).
6273 unpack_playtree_need_cd_work($headref);
6276 if (stat_exists ".pc") {
6278 progress __ "Tree already contains .pc - will use it then delete it.";
6281 rename '../fake/.pc','.pc' or confess "$!";
6284 changedir '../fake';
6286 my $oldtiptree=git_add_write_tree();
6287 printdebug "fake o+d/p tree object $unapplied\n";
6288 changedir '../work';
6291 # We calculate some guesswork now about what kind of tree this might
6292 # be. This is mostly for error reporting.
6294 my $tentries = cmdoutput @git, qw(ls-tree --name-only -z), $headref;
6295 my $onlydebian = $tentries eq "debian\0";
6297 my $uheadref = $headref;
6298 my $uhead_whatshort = 'HEAD';
6300 if ($quilt_mode =~ m/baredebian\+tarball/) {
6301 # We need to make a tarball import. Yuk.
6302 # We want to do this here so that we have a $uheadref value
6305 baredebian_origtarballs_scan \@fakedfi, $upstreamversion, bpd_abs();
6306 baredebian_origtarballs_scan \@fakedfi, $upstreamversion,
6307 "$maindir/.." unless $buildproductsdir eq '..';
6310 my @tartrees = import_tarball_tartrees $upstreamversion, \@fakedfi;
6312 fail __ "baredebian quilt fixup: could not find any origs"
6316 my ($authline, $r1authline, $clogp,) =
6317 import_tarball_commits \@tartrees, $upstreamversion;
6319 if (@tartrees == 1) {
6320 $uheadref = $tartrees[0]{Commit};
6321 # TRANSLATORS: this translation must fit in the ASCII art
6322 # quilt differences display. The untranslated display
6323 # says %9.9s, so with that display it must be at most 9
6325 $uhead_whatshort = __ 'tarball';
6327 # on .dsc import we do not make a separate commit, but
6328 # here we need to do so
6329 rm_subdir_cached '.';
6331 foreach my $ti (@tartrees) {
6332 my $c = $ti->{Commit};
6333 if ($ti->{OrigPart} eq 'orig') {
6334 runcmd qw(git read-tree), $c;
6335 } elsif ($ti->{OrigPart} =~ m/orig-/) {
6336 read_tree_subdir $', $c;
6338 confess "$ti->OrigPart} ?"
6340 $parents .= "parent $c\n";
6342 my $tree = git_write_tree();
6343 my $mbody = f_ 'Combine orig tarballs for %s %s',
6344 $package, $upstreamversion;
6345 $uheadref = hash_commit_text <<END;
6347 ${parents}author $r1authline
6348 committer $r1authline
6352 [dgit import tarballs combine $package $upstreamversion]
6354 # TRANSLATORS: this translation must fit in the ASCII art
6355 # quilt differences display. The untranslated display
6356 # says %9.9s, so with that display it must be at most 9
6357 # characters. This fragmentt is referring to multiple
6358 # orig tarballs in a source package.
6359 $uhead_whatshort = __ 'tarballs';
6361 runcmd @git, qw(reset -q);
6363 $quilt_upstream_commitish = $uheadref;
6364 $quilt_upstream_commitish_used = '*orig*';
6365 $quilt_upstream_commitish_message = '';
6367 if ($quilt_mode =~ m/baredebian$/) {
6368 $uheadref = $quilt_upstream_commitish;
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
6373 $uhead_whatshort = __ 'upstream';
6380 # O = orig, without patches applied
6381 # A = "applied", ie orig with H's debian/patches applied
6382 O2H => quiltify_trees_differ($unapplied,$uheadref, 1,
6383 \%editedignores, \@unrepres),
6384 H2A => quiltify_trees_differ($uheadref, $oldtiptree,1),
6385 O2A => quiltify_trees_differ($unapplied,$oldtiptree,1),
6389 foreach my $bits (qw(01 02)) {
6390 foreach my $v (qw(O2H O2A H2A)) {
6391 push @dl, ($diffbits->{$v} & $bits) ? '##' : '==';
6394 printdebug "differences \@dl @dl.\n";
6397 "%s: base trees orig=%.20s o+d/p=%.20s",
6398 $us, $unapplied, $oldtiptree;
6399 # TRANSLATORS: Try to keep this ascii-art layout right. The 0s in
6400 # %9.00009s will be ignored and are there to make the format the
6401 # same length (9 characters) as the output it generates. If you
6402 # change the value 9, your translations of "upstream" and
6403 # 'tarball' must fit into the new length, and you should change
6404 # the number of 0s. Do not reduce it below 4 as HEAD has to fit
6407 "%s: quilt differences: src: %s orig %s gitignores: %s orig %s\n".
6408 "%s: quilt differences: %9.00009s %s o+d/p %9.00009s %s o+d/p",
6409 $us, $dl[0], $dl[1], $dl[3], $dl[4],
6410 $us, $uhead_whatshort, $dl[2], $uhead_whatshort, $dl[5];
6412 if (@unrepres && $quilt_mode !~ m/baredebian/) {
6413 # With baredebian, even if the upstream commitish has this
6414 # problem, we don't want to print this message, as nothing
6415 # is going to try to make a patch out of it anyway.
6416 print STDERR f_ "dgit: cannot represent change: %s: %s\n",
6419 forceable_fail [qw(unrepresentable)], __ <<END;
6420 HEAD has changes to .orig[s] which are not representable by `3.0 (quilt)'
6426 push @failsuggestion, [ 'onlydebian', __
6427 "This has only a debian/ directory; you probably want --quilt=bare debian." ]
6428 unless $quilt_mode =~ m/baredebian/;
6429 } elsif (!($diffbits->{O2H} & $diffbits->{O2A})) {
6430 push @failsuggestion, [ 'unapplied', __
6431 "This might be a patches-unapplied branch." ];
6432 } elsif (!($diffbits->{H2A} & $diffbits->{O2A})) {
6433 push @failsuggestion, [ 'applied', __
6434 "This might be a patches-applied branch." ];
6436 push @failsuggestion, [ 'quilt-mode', __
6437 "Maybe you need one of --[quilt=]gbp --[quilt=]dpm --quilt=unapplied ?" ];
6439 push @failsuggestion, [ 'gitattrs', __
6440 "Warning: Tree has .gitattributes. See GITATTRIBUTES in dgit(7)." ]
6441 if stat_exists '.gitattributes';
6443 push @failsuggestion, [ 'origs', __
6444 "Maybe orig tarball(s) are not identical to git representation?" ]
6445 unless $onlydebian && $quilt_mode !~ m/baredebian/;
6446 # ^ in that case, we didn't really look properly
6448 if (quiltmode_splitting()) {
6449 quiltify_splitting($clogp, $unapplied, $headref, $oldtiptree,
6450 $diffbits, \%editedignores,
6451 $splitbrain_cachekey);
6455 progress f_ "starting quiltify (multiple patches, %s mode)", $quilt_mode;
6456 quiltify($clogp,$headref,$oldtiptree,\@failsuggestion);
6457 runcmd @git, qw(checkout -q), (qw(master dgit-view)[do_split_brain()]);
6459 if (!open P, '>>', ".pc/applied-patches") {
6460 $!==&ENOENT or confess "$!";
6465 commit_quilty_patch();
6467 if ($mustdeletepc) {
6468 quilt_fixup_delete_pc();
6472 sub quilt_fixup_editor () {
6473 my $descfn = $ENV{$fakeeditorenv};
6474 my $editing = $ARGV[$#ARGV];
6475 open I1, '<', $descfn or confess "$descfn: $!";
6476 open I2, '<', $editing or confess "$editing: $!";
6477 unlink $editing or confess "$editing: $!";
6478 open O, '>', $editing or confess "$editing: $!";
6479 while (<I1>) { print O or confess "$!"; } I1->error and confess "$!";
6482 $copying ||= m/^\-\-\- /;
6483 next unless $copying;
6484 print O or confess "$!";
6486 I2->error and confess "$!";
6491 sub maybe_apply_patches_dirtily () {
6492 return unless $quilt_mode =~ m/gbp|unapplied|baredebian/;
6493 print STDERR __ <<END or confess "$!";
6495 dgit: Building, or cleaning with rules target, in patches-unapplied tree.
6496 dgit: Have to apply the patches - making the tree dirty.
6497 dgit: (Consider specifying --clean=git and (or) using dgit sbuild.)
6500 $patches_applied_dirtily = 01;
6501 $patches_applied_dirtily |= 02 unless stat_exists '.pc';
6502 runcmd qw(dpkg-source --before-build .);
6505 sub maybe_unapply_patches_again () {
6506 progress __ "dgit: Unapplying patches again to tidy up the tree."
6507 if $patches_applied_dirtily;
6508 runcmd qw(dpkg-source --after-build .)
6509 if $patches_applied_dirtily & 01;
6511 if $patches_applied_dirtily & 02;
6512 $patches_applied_dirtily = 0;
6515 #----- other building -----
6517 sub clean_tree_check_git ($$$) {
6518 my ($honour_ignores, $message, $ignmessage) = @_;
6519 my @cmd = (@git, qw(clean -dn));
6520 push @cmd, qw(-x) unless $honour_ignores;
6521 my $leftovers = cmdoutput @cmd;
6522 if (length $leftovers) {
6523 print STDERR $leftovers, "\n" or confess "$!";
6524 $message .= $ignmessage if $honour_ignores;
6529 sub clean_tree_check_git_wd ($) {
6531 return if $cleanmode =~ m{no-check};
6532 return if $patches_applied_dirtily; # yuk
6533 clean_tree_check_git +($cleanmode !~ m{all-check}),
6534 $message, "\n".__ <<END;
6535 If this is just missing .gitignore entries, use a different clean
6536 mode, eg --clean=dpkg-source,no-check (-wdn/-wddn) to ignore them
6537 or --clean=git (-wg/-wgf) to use \`git clean' instead.
6541 sub clean_tree_check () {
6542 # This function needs to not care about modified but tracked files.
6543 # That was done by check_not_dirty, and by now we may have run
6544 # the rules clean target which might modify tracked files (!)
6545 if ($cleanmode =~ m{^check}) {
6546 clean_tree_check_git +($cleanmode =~ m{ignores}), __
6547 "tree contains uncommitted files and --clean=check specified", '';
6548 } elsif ($cleanmode =~ m{^dpkg-source}) {
6549 clean_tree_check_git_wd __
6550 "tree contains uncommitted files (NB dgit didn't run rules clean)";
6551 } elsif ($cleanmode =~ m{^git}) {
6552 clean_tree_check_git 1, __
6553 "tree contains uncommited, untracked, unignored files\n".
6554 "You can use --clean=git[-ff],always (-wga/-wgfa) to delete them.", '';
6555 } elsif ($cleanmode eq 'none') {
6557 confess "$cleanmode ?";
6562 # We always clean the tree ourselves, rather than leave it to the
6563 # builder (dpkg-source, or soemthing which calls dpkg-source).
6564 if ($quilt_mode =~ m/baredebian/ and $cleanmode =~ m/git/) {
6565 fail f_ <<END, $quilt_mode, $cleanmode;
6566 quilt mode %s (generally needs untracked upstream files)
6567 contradicts clean mode %s (which would delete them)
6569 # This is not 100% true: dgit build-source and push-source
6570 # (for example) could operate just fine with no upstream
6571 # source in the working tree. But it doesn't seem likely that
6572 # the user wants dgit to proactively delete such things.
6573 # -wn, for example, would produce identical output without
6574 # deleting anything from the working tree.
6576 if ($cleanmode =~ m{^dpkg-source}) {
6577 my @cmd = @dpkgbuildpackage;
6578 push @cmd, qw(-d) if $cleanmode =~ m{^dpkg-source-d};
6579 push @cmd, qw(-T clean);
6580 maybe_apply_patches_dirtily();
6581 runcmd_ordryrun_local @cmd;
6582 clean_tree_check_git_wd __
6583 "tree contains uncommitted files (after running rules clean)";
6584 } elsif ($cleanmode =~ m{^git(?!-)}) {
6585 runcmd_ordryrun_local @git, qw(clean -xdf);
6586 } elsif ($cleanmode =~ m{^git-ff}) {
6587 runcmd_ordryrun_local @git, qw(clean -xdff);
6588 } elsif ($cleanmode =~ m{^check}) {
6590 } elsif ($cleanmode eq 'none') {
6592 confess "$cleanmode ?";
6597 badusage __ "clean takes no additional arguments" if @ARGV;
6600 maybe_unapply_patches_again();
6603 # return values from massage_dbp_args are one or both of these flags
6604 sub WANTSRC_SOURCE () { 01; } # caller should build source (separately)
6605 sub WANTSRC_BUILDER () { 02; } # caller should run dpkg-buildpackage
6607 sub build_or_push_prep_early () {
6608 our $build_or_push_prep_early_done //= 0;
6609 return if $build_or_push_prep_early_done++;
6610 my $clogp = parsechangelog();
6611 $isuite = getfield $clogp, 'Distribution';
6612 my $gotpackage = getfield $clogp, 'Source';
6613 $version = getfield $clogp, 'Version';
6614 $package //= $gotpackage;
6615 if ($package ne $gotpackage) {
6616 fail f_ "-p specified package %s, but changelog says %s",
6617 $package, $gotpackage;
6619 $dscfn = dscfn($version);
6622 sub build_or_push_prep_modes () {
6623 my ($format) = get_source_format();
6624 determine_whether_split_brain($format);
6626 fail __ "dgit: --include-dirty is not supported with split view".
6627 " (including with view-splitting quilt modes)"
6628 if do_split_brain() && $includedirty;
6630 if (madformat_wantfixup $format and $quilt_mode =~ m/baredebian$/) {
6631 ($quilt_upstream_commitish, $quilt_upstream_commitish_used,
6632 $quilt_upstream_commitish_message)
6633 = resolve_upstream_version
6634 $quilt_upstream_commitish, upstreamversion $version;
6635 progress f_ "dgit: --quilt=%s, %s", $quilt_mode,
6636 $quilt_upstream_commitish_message;
6637 } elsif (defined $quilt_upstream_commitish) {
6639 "dgit: --upstream-commitish only makes sense with --quilt=baredebian"
6643 sub build_prep_early () {
6644 build_or_push_prep_early();
6646 build_or_push_prep_modes();
6650 sub build_prep ($) {
6654 if (!building_source_in_playtree() || ($wantsrc & WANTSRC_BUILDER)
6655 # Clean the tree because we're going to use the contents of
6656 # $maindir. (We trying to include dirty changes in the source
6657 # package, or we are running the builder in $maindir.)
6658 || $cleanmode =~ m{always}) {
6659 # Or because the user asked us to.
6662 # We don't actually need to do anything in $maindir, but we
6663 # should do some kind of cleanliness check because (i) the
6664 # user may have forgotten a `git add', and (ii) if the user
6665 # said -wc we should still do the check.
6668 build_check_quilt_splitbrain();
6670 my $pat = changespat $version;
6671 foreach my $f (glob "$buildproductsdir/$pat") {
6674 fail f_ "remove old changes file %s: %s", $f, $!;
6676 progress f_ "would remove %s", $f;
6682 sub maybe_warn_opt_confusion ($$$) {
6683 my ($subcommand, $willrun, $optsref) = @_;
6684 foreach (@$optsref) {
6685 if (m/^(?: --dry-run $
6687 | --clean= | -w[gcnd]
6688 | --(?:include|ignore)-dirty$
6689 | --quilt= | --gbp$ | --dpm$ | --baredebian
6691 | --build-products-dir=
6693 print STDERR f_ <<END, $&, $subcommand or die $!;
6694 warning: dgit option %s must be passed before %s on dgit command line
6700 print STDERR f_ <<END, $&, $subcommand, $willrun or die $!;
6701 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
6707 sub changesopts_initial () {
6708 my @opts =@changesopts[1..$#changesopts];
6711 sub changesopts_version () {
6712 if (!defined $changes_since_version) {
6715 @vsns = archive_query('archive_query');
6716 my @quirk = access_quirk();
6717 if ($quirk[0] eq 'backports') {
6718 local $isuite = $quirk[2];
6720 canonicalise_suite();
6721 push @vsns, archive_query('archive_query');
6727 "archive query failed (queried because --since-version not specified)";
6730 @vsns = map { $_->[0] } @vsns;
6731 @vsns = sort { -version_compare($a, $b) } @vsns;
6732 $changes_since_version = $vsns[0];
6733 progress f_ "changelog will contain changes since %s", $vsns[0];
6735 $changes_since_version = '_';
6736 progress __ "package seems new, not specifying -v<version>";
6739 if ($changes_since_version ne '_') {
6740 return ("-v$changes_since_version");
6746 sub changesopts () {
6747 return (changesopts_initial(), changesopts_version());
6750 sub massage_dbp_args ($;$) {
6751 my ($cmd,$xargs) = @_;
6752 # Since we split the source build out so we can do strange things
6753 # to it, massage the arguments to dpkg-buildpackage so that the
6754 # main build doessn't build source (or add an argument to stop it
6755 # building source by default).
6756 debugcmd '#massaging#', @$cmd if $debuglevel>1;
6757 # -nc has the side effect of specifying -b if nothing else specified
6758 # and some combinations of -S, -b, et al, are errors, rather than
6759 # later simply overriding earlie. So we need to:
6760 # - search the command line for these options
6761 # - pick the last one
6762 # - perhaps add our own as a default
6763 # - perhaps adjust it to the corresponding non-source-building version
6765 foreach my $l ($cmd, $xargs) {
6767 @$l = grep { !(m/^-[SgGFABb]$|^--build=/s and $dmode=$_) } @$l;
6770 #print STDERR "MASS1 ",Dumper($cmd, $xargs, $dmode);
6771 my $r = WANTSRC_BUILDER;
6772 printdebug "massage split $dmode.\n";
6773 if ($dmode =~ s/^--build=//) {
6775 my @d = split /,/, $dmode;
6776 $r |= WANTSRC_SOURCE if grep { s/^full$/binary/ } @d;
6777 $r |= WANTSRC_SOURCE if grep { s/^source$// } @d;
6778 $r |= WANTSRC_BUILDER if grep { m/./ } @d;
6779 fail __ "Wanted to build nothing!" unless $r;
6780 $dmode = '--build='. join ',', grep m/./, @d;
6783 $dmode =~ m/[S]/ ? WANTSRC_SOURCE :
6784 $dmode =~ y/gGF/ABb/ ? WANTSRC_SOURCE | WANTSRC_BUILDER :
6785 $dmode =~ m/[ABb]/ ? WANTSRC_BUILDER :
6788 printdebug "massage done $r $dmode.\n";
6790 #print STDERR "MASS2 ",Dumper($cmd, $xargs, $r);
6796 my $wasdir = must_getcwd();
6797 changedir $buildproductsdir;
6802 # this sub must run with CWD=$buildproductsdir (eg in in_bpd)
6803 sub postbuild_mergechanges ($) {
6804 my ($msg_if_onlyone) = @_;
6805 # If there is only one .changes file, fail with $msg_if_onlyone,
6806 # or if that is undef, be a no-op.
6807 # Returns the changes file to report to the user.
6808 my $pat = changespat $version;
6809 my @changesfiles = grep { !m/_multi\.changes/ } glob $pat;
6810 @changesfiles = sort {
6811 ($b =~ m/_source\.changes$/ <=> $a =~ m/_source\.changes$/)
6815 if (@changesfiles==1) {
6816 fail +(f_ <<END, "@changesfiles").$msg_if_onlyone
6817 only one changes file from build (%s)
6819 if defined $msg_if_onlyone;
6820 $result = $changesfiles[0];
6821 } elsif (@changesfiles==2) {
6822 my $binchanges = parsecontrol($changesfiles[1], "binary changes file");
6823 foreach my $l (split /\n/, getfield $binchanges, 'Files') {
6824 fail f_ "%s found in binaries changes file %s", $l, $binchanges
6827 runcmd_ordryrun_local @mergechanges, @changesfiles;
6828 my $multichanges = changespat $version,'multi';
6830 stat_exists $multichanges or fail f_
6831 "%s unexpectedly not created by build", $multichanges;
6832 foreach my $cf (glob $pat) {
6833 next if $cf eq $multichanges;
6834 rename "$cf", "$cf.inmulti" or fail f_
6835 "install new changes %s\{,.inmulti}: %s", $cf, $!;
6838 $result = $multichanges;
6840 fail f_ "wrong number of different changes files (%s)",
6843 printdone f_ "build successful, results in %s\n", $result
6847 sub midbuild_checkchanges () {
6848 my $pat = changespat $version;
6849 return if $rmchanges;
6850 my @unwanted = map { s#.*/##; $_; } glob "$bpd_glob/$pat";
6852 $_ ne changespat $version,'source' and
6853 $_ ne changespat $version,'multi'
6855 fail +(f_ <<END, $pat, "@unwanted")
6856 changes files other than source matching %s already present; building would result in ambiguity about the intended results.
6857 Suggest you delete %s.
6862 sub midbuild_checkchanges_vanilla ($) {
6864 midbuild_checkchanges() if $wantsrc == (WANTSRC_SOURCE|WANTSRC_BUILDER);
6867 sub postbuild_mergechanges_vanilla ($) {
6869 if ($wantsrc == (WANTSRC_SOURCE|WANTSRC_BUILDER)) {
6871 postbuild_mergechanges(undef);
6874 printdone __ "build successful\n";
6880 maybe_warn_opt_confusion 'build', 'dpkg-buildpackage', \@ARGV;
6881 $buildproductsdir eq '..' or print STDERR +(f_ <<END, $us, $us);
6882 %s: warning: build-products-dir set, but not supported by dpkg-buildpackage
6883 %s: warning: build-products-dir will be ignored; files will go to ..
6885 $buildproductsdir = '..';
6886 my @dbp = (@dpkgbuildpackage, qw(-us -uc), changesopts_initial(), @ARGV);
6887 my $wantsrc = massage_dbp_args \@dbp;
6888 build_prep($wantsrc);
6889 if ($wantsrc & WANTSRC_SOURCE) {
6891 midbuild_checkchanges_vanilla $wantsrc;
6893 if ($wantsrc & WANTSRC_BUILDER) {
6894 push @dbp, changesopts_version();
6895 maybe_apply_patches_dirtily();
6896 runcmd_ordryrun_local @dbp;
6898 maybe_unapply_patches_again();
6899 postbuild_mergechanges_vanilla $wantsrc;
6903 $quilt_mode //= 'gbp';
6908 maybe_warn_opt_confusion 'gbp-build', 'gbp buildpackage', \@ARGV;
6910 # gbp can make .origs out of thin air. In my tests it does this
6911 # even for a 1.0 format package, with no origs present. So I
6912 # guess it keys off just the version number. We don't know
6913 # exactly what .origs ought to exist, but let's assume that we
6914 # should run gbp if: the version has an upstream part and the main
6916 my $upstreamversion = upstreamversion $version;
6917 my $origfnpat = srcfn $upstreamversion, '.orig.tar.*';
6918 my $gbp_make_orig = $version =~ m/-/ && !(() = glob "$bpd_glob/$origfnpat");
6920 if ($gbp_make_orig) {
6922 $cleanmode = 'none'; # don't do it again
6925 my @dbp = @dpkgbuildpackage;
6927 my $wantsrc = massage_dbp_args \@dbp, \@ARGV;
6929 if (!length $gbp_build[0]) {
6930 if (length executable_on_path('git-buildpackage')) {
6931 $gbp_build[0] = qw(git-buildpackage);
6933 $gbp_build[0] = 'gbp buildpackage';
6936 my @cmd = opts_opt_multi_cmd [], @gbp_build;
6938 push @cmd, (qw(-us -uc --git-no-sign-tags),
6939 "--git-builder=".(shellquote @dbp));
6941 if ($gbp_make_orig) {
6942 my $priv = dgit_privdir();
6943 my $ok = "$priv/origs-gen-ok";
6944 unlink $ok or $!==&ENOENT or confess "$!";
6945 my @origs_cmd = @cmd;
6946 push @origs_cmd, qw(--git-cleaner=true);
6947 push @origs_cmd, "--git-prebuild=".
6948 "touch ".(shellquote $ok)." ".(shellquote "$priv/no-such-dir/ok");
6949 push @origs_cmd, @ARGV;
6951 debugcmd @origs_cmd;
6953 do { local $!; stat_exists $ok; }
6954 or failedcmd @origs_cmd;
6956 dryrun_report @origs_cmd;
6960 build_prep($wantsrc);
6961 if ($wantsrc & WANTSRC_SOURCE) {
6963 midbuild_checkchanges_vanilla $wantsrc;
6965 push @cmd, '--git-cleaner=true';
6967 maybe_unapply_patches_again();
6968 if ($wantsrc & WANTSRC_BUILDER) {
6969 push @cmd, changesopts();
6970 runcmd_ordryrun_local @cmd, @ARGV;
6972 postbuild_mergechanges_vanilla $wantsrc;
6974 sub cmd_git_build { cmd_gbp_build(); } # compatibility with <= 1.0
6976 sub building_source_in_playtree {
6977 # If $includedirty, we have to build the source package from the
6978 # working tree, not a playtree, so that uncommitted changes are
6979 # included (copying or hardlinking them into the playtree could
6982 # Note that if we are building a source package in split brain
6983 # mode we do not support including uncommitted changes, because
6984 # that makes quilt fixup too hard. I.e. ($made_split_brain && (dgit is
6985 # building a source package)) => !$includedirty
6986 return !$includedirty;
6990 $sourcechanges = changespat $version,'source';
6992 unlink "$buildproductsdir/$sourcechanges" or $!==ENOENT
6993 or fail f_ "remove %s: %s", $sourcechanges, $!;
6995 # confess unless !!$made_split_brain == do_split_brain();
6997 my @cmd = (@dpkgsource, qw(-b --));
6999 if (building_source_in_playtree()) {
7001 my $headref = git_rev_parse('HEAD');
7002 # If we are in split brain, there is already a playtree with
7003 # the thing we should package into a .dsc (thanks to quilt
7004 # fixup). If not, make a playtree
7005 prep_ud() unless $made_split_brain;
7006 changedir $playground;
7007 unless ($made_split_brain) {
7008 my $upstreamversion = upstreamversion $version;
7009 unpack_playtree_linkorigs($upstreamversion, sub { });
7010 unpack_playtree_need_cd_work($headref);
7014 $leafdir = basename $maindir;
7016 if ($buildproductsdir ne '..') {
7017 # Well, we are going to run dpkg-source -b which consumes
7018 # origs from .. and generates output there. To make this
7019 # work when the bpd is not .. , we would have to (i) link
7020 # origs from bpd to .. , (ii) check for files that
7021 # dpkg-source -b would/might overwrite, and afterwards
7022 # (iii) move all the outputs back to the bpd (iv) except
7023 # for the origs which should be deleted from .. if they
7024 # weren't there beforehand. And if there is an error and
7025 # we don't run to completion we would necessarily leave a
7026 # mess. This is too much. The real way to fix this
7027 # is for dpkg-source to have bpd support.
7028 confess unless $includedirty;
7030 "--include-dirty not supported with --build-products-dir, sorry";
7035 runcmd_ordryrun_local @cmd, $leafdir;
7038 runcmd_ordryrun_local qw(sh -ec),
7039 'exec >../$1; shift; exec "$@"','x', $sourcechanges,
7040 @dpkggenchanges, qw(-S), changesopts();
7043 printdebug "moving $dscfn, $sourcechanges, etc. to ".bpd_abs()."\n";
7044 $dsc = parsecontrol($dscfn, "source package");
7048 printdebug " renaming ($why) $l\n";
7049 rename_link_xf 0, "$l", bpd_abs()."/$l"
7050 or fail f_ "put in place new built file (%s): %s", $l, $@;
7052 foreach my $l (split /\n/, getfield $dsc, 'Files') {
7053 $l =~ m/\S+$/ or next;
7056 $mv->('dsc', $dscfn);
7057 $mv->('changes', $sourcechanges);
7062 sub cmd_build_source {
7063 badusage __ "build-source takes no additional arguments" if @ARGV;
7064 build_prep(WANTSRC_SOURCE);
7066 maybe_unapply_patches_again();
7067 printdone f_ "source built, results in %s and %s",
7068 $dscfn, $sourcechanges;
7071 sub cmd_push_source {
7074 "dgit push-source: --include-dirty/--ignore-dirty does not make".
7075 "sense with push-source!"
7077 build_check_quilt_splitbrain();
7079 my $changes = parsecontrol("$buildproductsdir/$changesfile",
7080 __ "source changes file");
7081 unless (test_source_only_changes($changes)) {
7082 fail __ "user-specified changes file is not source-only";
7085 # Building a source package is very fast, so just do it
7087 confess "er, patches are applied dirtily but shouldn't be.."
7088 if $patches_applied_dirtily;
7089 $changesfile = $sourcechanges;
7094 sub binary_builder {
7095 my ($bbuilder, $pbmc_msg, @args) = @_;
7096 build_prep(WANTSRC_SOURCE);
7098 midbuild_checkchanges();
7101 stat_exists $dscfn or fail f_
7102 "%s (in build products dir): %s", $dscfn, $!;
7103 stat_exists $sourcechanges or fail f_
7104 "%s (in build products dir): %s", $sourcechanges, $!;
7106 runcmd_ordryrun_local @$bbuilder, @args;
7108 maybe_unapply_patches_again();
7110 postbuild_mergechanges($pbmc_msg);
7116 maybe_warn_opt_confusion 'sbuild', 'sbuild', \@ARGV;
7117 binary_builder(\@sbuild, (__ <<END), qw(-d), $isuite, @ARGV, $dscfn);
7118 perhaps you need to pass -A ? (sbuild's default is to build only
7119 arch-specific binaries; dgit 1.4 used to override that.)
7124 my ($pbuilder) = @_;
7126 maybe_warn_opt_confusion 'pbuilder', 'pbuilder', \@ARGV;
7127 # @ARGV is allowed to contain only things that should be passed to
7128 # pbuilder under debbuildopts; just massage those
7129 my $wantsrc = massage_dbp_args \@ARGV;
7131 "you asked for a builder but your debbuildopts didn't ask for".
7132 " any binaries -- is this really what you meant?"
7133 unless $wantsrc & WANTSRC_BUILDER;
7135 "we must build a .dsc to pass to the builder but your debbuiltopts".
7136 " forbids the building of a source package; cannot continue"
7137 unless $wantsrc & WANTSRC_SOURCE;
7138 # We do not want to include the verb "build" in @pbuilder because
7139 # the user can customise @pbuilder and they shouldn't be required
7140 # to include "build" in their customised value. However, if the
7141 # user passes any additional args to pbuilder using the dgit
7142 # option --pbuilder:foo, such args need to come after the "build"
7143 # verb. opts_opt_multi_cmd does all of that.
7144 binary_builder([opts_opt_multi_cmd ["build"], @$pbuilder], undef,
7145 qw(--debbuildopts), "@ARGV", qw(--distribution), $isuite,
7150 pbuilder(\@pbuilder);
7153 sub cmd_cowbuilder {
7154 pbuilder(\@cowbuilder);
7157 sub cmd_quilt_fixup {
7158 badusage "incorrect arguments to dgit quilt-fixup" if @ARGV;
7161 build_maybe_quilt_fixup();
7164 sub cmd_print_unapplied_treeish {
7165 badusage __ "incorrect arguments to dgit print-unapplied-treeish"
7167 my $headref = git_rev_parse('HEAD');
7168 my $clogp = commit_getclogp $headref;
7169 $package = getfield $clogp, 'Source';
7170 $version = getfield $clogp, 'Version';
7171 $isuite = getfield $clogp, 'Distribution';
7172 $csuite = $isuite; # we want this to be offline!
7176 changedir $playground;
7177 my $uv = upstreamversion $version;
7178 my $u = quilt_fakedsc2unapplied($headref, $uv);
7179 print $u, "\n" or confess "$!";
7182 sub import_dsc_result {
7183 my ($dstref, $newhash, $what_log, $what_msg) = @_;
7184 my @cmd = (git_update_ref_cmd $what_log, $dstref, $newhash);
7186 check_gitattrs($newhash, __ "source tree");
7188 progress f_ "dgit: import-dsc: %s", $what_msg;
7191 sub cmd_import_dsc {
7195 last unless $ARGV[0] =~ m/^-/;
7198 if (m/^--require-valid-signature$/) {
7201 badusage f_ "unknown dgit import-dsc sub-option \`%s'", $_;
7205 badusage __ "usage: dgit import-dsc .../PATH/TO/.DSC BRANCH"
7207 my ($dscfn, $dstbranch) = @ARGV;
7209 badusage __ "dry run makes no sense with import-dsc"
7212 my $force = $dstbranch =~ s/^\+// ? +1 :
7213 $dstbranch =~ s/^\.\.// ? -1 :
7215 my $info = $force ? " $&" : '';
7216 $info = "$dscfn$info";
7218 my $specbranch = $dstbranch;
7219 $dstbranch = "refs/heads/$dstbranch" unless $dstbranch =~ m#^refs/#;
7220 $dstbranch = cmdoutput @git, qw(check-ref-format --normalize), $dstbranch;
7222 my @symcmd = (@git, qw(symbolic-ref -q HEAD));
7223 my $chead = cmdoutput_errok @symcmd;
7224 defined $chead or $?==256 or failedcmd @symcmd;
7226 fail f_ "%s is checked out - will not update it", $dstbranch
7227 if defined $chead and $chead eq $dstbranch;
7229 my $oldhash = git_get_ref $dstbranch;
7231 open D, "<", $dscfn or fail f_ "open import .dsc (%s): %s", $dscfn, $!;
7232 $dscdata = do { local $/ = undef; <D>; };
7233 D->error and fail f_ "read %s: %s", $dscfn, $!;
7236 # we don't normally need this so import it here
7237 use Dpkg::Source::Package;
7238 my $dp = new Dpkg::Source::Package filename => $dscfn,
7239 require_valid_signature => $needsig;
7241 local $SIG{__WARN__} = sub {
7243 return unless $needsig;
7244 fail __ "import-dsc signature check failed";
7246 if (!$dp->is_signed()) {
7247 warn f_ "%s: warning: importing unsigned .dsc\n", $us;
7249 my $r = $dp->check_signature();
7250 confess "->check_signature => $r" if $needsig && $r;
7256 $package = getfield $dsc, 'Source';
7258 parse_dsc_field($dsc, __ "Dgit metadata in .dsc")
7259 unless forceing [qw(import-dsc-with-dgit-field)];
7260 parse_dsc_field_def_dsc_distro();
7262 $isuite = 'DGIT-IMPORT-DSC';
7263 $idistro //= $dsc_distro;
7267 if (defined $dsc_hash) {
7269 "dgit: import-dsc of .dsc with Dgit field, using git hash";
7270 resolve_dsc_field_commit undef, undef;
7272 if (defined $dsc_hash) {
7273 my @cmd = (qw(sh -ec),
7274 "echo $dsc_hash | git cat-file --batch-check");
7275 my $objgot = cmdoutput @cmd;
7276 if ($objgot =~ m#^\w+ missing\b#) {
7277 fail f_ <<END, $dsc_hash
7278 .dsc contains Dgit field referring to object %s
7279 Your git tree does not have that object. Try `git fetch' from a
7280 plausible server (browse.dgit.d.o? salsa?), and try the import-dsc again.
7283 if ($oldhash && !is_fast_fwd $oldhash, $dsc_hash) {
7285 progress __ "Not fast forward, forced update.";
7287 fail f_ "Not fast forward to %s", $dsc_hash;
7290 import_dsc_result $dstbranch, $dsc_hash,
7291 "dgit import-dsc (Dgit): $info",
7292 f_ "updated git ref %s", $dstbranch;
7296 fail f_ <<END, $dstbranch, $specbranch, $specbranch
7297 Branch %s already exists
7298 Specify ..%s for a pseudo-merge, binding in existing history
7299 Specify +%s to overwrite, discarding existing history
7301 if $oldhash && !$force;
7303 my @dfi = dsc_files_info();
7304 foreach my $fi (@dfi) {
7305 my $f = $fi->{Filename};
7306 # We transfer all the pieces of the dsc to the bpd, not just
7307 # origs. This is by analogy with dgit fetch, which wants to
7308 # keep them somewhere to avoid downloading them again.
7309 # We make symlinks, though. If the user wants copies, then
7310 # they can copy the parts of the dsc to the bpd using dcmd,
7312 my $here = "$buildproductsdir/$f";
7317 fail f_ "lstat %s works but stat gives %s !", $here, $!;
7319 fail f_ "stat %s: %s", $here, $! unless $! == ENOENT;
7320 printdebug "not in bpd, $f ...\n";
7321 # $f does not exist in bpd, we need to transfer it
7323 $there =~ s{[^/]+$}{$f} or confess "$there ?";
7324 # $there is file we want, relative to user's cwd, or abs
7325 printdebug "not in bpd, $f, test $there ...\n";
7326 stat $there or fail f_
7327 "import %s requires %s, but: %s", $dscfn, $there, $!;
7328 if ($there =~ m#^(?:\./+)?\.\./+#) {
7329 # $there is relative to user's cwd
7330 my $there_from_parent = $';
7331 if ($buildproductsdir !~ m{^/}) {
7332 # abs2rel, despite its name, can take two relative paths
7333 $there = File::Spec->abs2rel($there,$buildproductsdir);
7334 # now $there is relative to bpd, great
7335 printdebug "not in bpd, $f, abs2rel, $there ...\n";
7337 $there = (dirname $maindir)."/$there_from_parent";
7338 # now $there is absoute
7339 printdebug "not in bpd, $f, rel2rel, $there ...\n";
7341 } elsif ($there =~ m#^/#) {
7342 # $there is absolute already
7343 printdebug "not in bpd, $f, abs, $there ...\n";
7346 "cannot import %s which seems to be inside working tree!",
7349 symlink $there, $here or fail f_
7350 "symlink %s to %s: %s", $there, $here, $!;
7351 progress f_ "made symlink %s -> %s", $here, $there;
7352 # print STDERR Dumper($fi);
7354 my @mergeinputs = generate_commits_from_dsc();
7355 die unless @mergeinputs == 1;
7357 my $newhash = $mergeinputs[0]{Commit};
7362 "Import, forced update - synthetic orphan git history.";
7363 } elsif ($force < 0) {
7364 progress __ "Import, merging.";
7365 my $tree = cmdoutput @git, qw(rev-parse), "$newhash:";
7366 my $version = getfield $dsc, 'Version';
7367 my $clogp = commit_getclogp $newhash;
7368 my $authline = clogp_authline $clogp;
7369 $newhash = hash_commit_text <<ENDU
7377 .(f_ <<END, $package, $version, $dstbranch);
7378 Merge %s (%s) import into %s
7381 die; # caught earlier
7385 import_dsc_result $dstbranch, $newhash,
7386 "dgit import-dsc: $info",
7387 f_ "results are in git ref %s", $dstbranch;
7390 sub pre_archive_api_query () {
7391 not_necessarily_a_tree();
7393 sub cmd_archive_api_query {
7394 badusage __ "need only 1 subpath argument" unless @ARGV==1;
7395 my ($subpath) = @ARGV;
7396 local $isuite = 'DGIT-API-QUERY-CMD';
7397 my $json = api_query_raw $subpath;
7398 print $json or die "$!";
7401 sub repos_server_url () {
7402 $package = '_dgit-repos-server';
7403 local $access_forpush = 1;
7404 local $isuite = 'DGIT-REPOS-SERVER';
7405 my $url = access_giturl();
7408 sub pre_clone_dgit_repos_server () {
7409 not_necessarily_a_tree();
7411 sub cmd_clone_dgit_repos_server {
7412 badusage __ "need destination argument" unless @ARGV==1;
7413 my ($destdir) = @ARGV;
7414 my $url = repos_server_url();
7415 my @cmd = (@git, qw(clone), $url, $destdir);
7417 exec @cmd or fail f_ "exec git clone: %s\n", $!;
7420 sub pre_print_dgit_repos_server_source_url () {
7421 not_necessarily_a_tree();
7423 sub cmd_print_dgit_repos_server_source_url {
7425 "no arguments allowed to dgit print-dgit-repos-server-source-url"
7427 my $url = repos_server_url();
7428 print $url, "\n" or confess "$!";
7431 sub pre_print_dpkg_source_ignores {
7432 not_necessarily_a_tree();
7434 sub cmd_print_dpkg_source_ignores {
7436 "no arguments allowed to dgit print-dpkg-source-ignores"
7438 print "@dpkg_source_ignores\n" or confess "$!";
7441 sub cmd_setup_mergechangelogs {
7442 badusage __ "no arguments allowed to dgit setup-mergechangelogs"
7444 local $isuite = 'DGIT-SETUP-TREE';
7445 setup_mergechangelogs(1);
7448 sub cmd_setup_useremail {
7449 badusage __ "no arguments allowed to dgit setup-useremail" if @ARGV;
7450 local $isuite = 'DGIT-SETUP-TREE';
7454 sub cmd_setup_gitattributes {
7455 badusage __ "no arguments allowed to dgit setup-useremail" if @ARGV;
7456 local $isuite = 'DGIT-SETUP-TREE';
7460 sub cmd_setup_new_tree {
7461 badusage __ "no arguments allowed to dgit setup-tree" if @ARGV;
7462 local $isuite = 'DGIT-SETUP-TREE';
7466 #---------- argument parsing and main program ----------
7469 print "dgit version $our_version\n" or confess "$!";
7473 our (%valopts_long, %valopts_short);
7474 our (%funcopts_long);
7476 our (@modeopt_cfgs);
7478 sub defvalopt ($$$$) {
7479 my ($long,$short,$val_re,$how) = @_;
7480 my $oi = { Long => $long, Short => $short, Re => $val_re, How => $how };
7481 $valopts_long{$long} = $oi;
7482 $valopts_short{$short} = $oi;
7483 # $how subref should:
7484 # do whatever assignemnt or thing it likes with $_[0]
7485 # if the option should not be passed on to remote, @rvalopts=()
7486 # or $how can be a scalar ref, meaning simply assign the value
7489 defvalopt '--since-version', '-v', '[^_]+|_', \$changes_since_version;
7490 defvalopt '--distro', '-d', '.+', \$idistro;
7491 defvalopt '', '-k', '.+', \$keyid;
7492 defvalopt '--existing-package','', '.*', \$existing_package;
7493 defvalopt '--build-products-dir','','.*', \$buildproductsdir;
7494 defvalopt '--clean', '', $cleanmode_re, \$cleanmode;
7495 defvalopt '--package', '-p', $package_re, \$package;
7496 defvalopt '--quilt', '', $quilt_modes_re, \$quilt_mode;
7498 defvalopt '', '-C', '.+', sub {
7499 ($changesfile) = (@_);
7500 if ($changesfile =~ s#^(.*)/##) {
7501 $buildproductsdir = $1;
7505 defvalopt '--initiator-tempdir','','.*', sub {
7506 ($initiator_tempdir) = (@_);
7507 $initiator_tempdir =~ m#^/# or
7508 badusage __ "--initiator-tempdir must be used specify an".
7509 " absolute, not relative, directory."
7512 sub defoptmodes ($@) {
7513 my ($varref, $cfgkey, $default, %optmap) = @_;
7515 while (my ($opt,$val) = each %optmap) {
7516 $funcopts_long{$opt} = sub { $$varref = $val; };
7517 $permit{$val} = $val;
7519 push @modeopt_cfgs, {
7522 Default => $default,
7527 defoptmodes \$dodep14tag, qw( dep14tag want
7530 --always-dep14tag always );
7535 if (defined $ENV{'DGIT_SSH'}) {
7536 @ssh = string_to_ssh $ENV{'DGIT_SSH'};
7537 } elsif (defined $ENV{'GIT_SSH'}) {
7538 @ssh = ($ENV{'GIT_SSH'});
7546 if (!defined $val) {
7547 badusage f_ "%s needs a value", $what unless @ARGV;
7549 push @rvalopts, $val;
7551 badusage f_ "bad value \`%s' for %s", $val, $what unless
7552 $val =~ m/^$oi->{Re}$(?!\n)/s;
7553 my $how = $oi->{How};
7554 if (ref($how) eq 'SCALAR') {
7559 push @ropts, @rvalopts;
7563 last unless $ARGV[0] =~ m/^-/;
7567 if (m/^--dry-run$/) {
7570 } elsif (m/^--damp-run$/) {
7573 } elsif (m/^--no-sign$/) {
7576 } elsif (m/^--help$/) {
7578 } elsif (m/^--version$/) {
7580 } elsif (m/^--new$/) {
7583 } elsif (m/^--([-0-9a-z]+)=(.+)/s &&
7584 ($om = $opts_opt_map{$1}) &&
7588 } elsif (m/^--([-0-9a-z]+):(.*)/s &&
7589 !$opts_opt_cmdonly{$1} &&
7590 ($om = $opts_opt_map{$1})) {
7593 } elsif (m/^--([-0-9a-z]+)\!:(.*)/s &&
7594 !$opts_opt_cmdonly{$1} &&
7595 ($om = $opts_opt_map{$1})) {
7597 my $cmd = shift @$om;
7598 @$om = ($cmd, grep { $_ ne $2 } @$om);
7599 } elsif (m/^--($quilt_options_re)$/s) {
7600 push @ropts, "--quilt=$1";
7602 } elsif (m/^--(?:ignore|include)-dirty$/s) {
7605 } elsif (m/^--no-quilt-fixup$/s) {
7607 $quilt_mode = 'nocheck';
7608 } elsif (m/^--no-rm-on-error$/s) {
7611 } elsif (m/^--no-chase-dsc-distro$/s) {
7613 $chase_dsc_distro = 0;
7614 } elsif (m/^--overwrite$/s) {
7616 $overwrite_version = '';
7617 } elsif (m/^--split-(?:view|brain)$/s) {
7619 $splitview_mode = 'always';
7620 } elsif (m/^--split-(?:view|brain)=($splitview_modes_re)$/s) {
7622 $splitview_mode = $1;
7623 } elsif (m/^--overwrite=(.+)$/s) {
7625 $overwrite_version = $1;
7626 } elsif (m/^--delayed=(\d+)$/s) {
7629 } elsif (m/^--upstream-commitish=(.+)$/s) {
7631 $quilt_upstream_commitish = $1;
7632 } elsif (m/^--save-(dgit-view)=(.+)$/s ||
7633 m/^--(dgit-view)-save=(.+)$/s
7635 my ($k,$v) = ($1,$2);
7637 $v =~ s#^(?!refs/)#refs/heads/#;
7638 $internal_object_save{$k} = $v;
7639 } elsif (m/^--(no-)?rm-old-changes$/s) {
7642 } elsif (m/^--deliberately-($deliberately_re)$/s) {
7644 push @deliberatelies, $&;
7645 } elsif (m/^--force-(.*)/ && defined $forceopts{$1}) {
7649 } elsif (m/^--force-/) {
7651 f_ "%s: warning: ignoring unknown force option %s\n",
7654 } elsif (m/^--for-push$/s) {
7656 $access_forpush = 1;
7657 } elsif (m/^--config-lookup-explode=(.+)$/s) {
7658 # undocumented, for testing
7660 $gitcfgs{cmdline}{$1} = 'CONFIG-LOOKUP-EXPLODE';
7661 # ^ it's supposed to be an array ref
7662 } elsif (m/^(--[-0-9a-z]+)(=|$)/ && ($oi = $valopts_long{$1})) {
7663 $val = $2 ? $' : undef; #';
7664 $valopt->($oi->{Long});
7665 } elsif ($funcopts_long{$_}) {
7667 $funcopts_long{$_}();
7669 badusage f_ "unknown long option \`%s'", $_;
7676 } elsif (s/^-L/-/) {
7679 } elsif (s/^-h/-/) {
7681 } elsif (s/^-D/-/) {
7685 } elsif (s/^-N/-/) {
7690 push @changesopts, $_;
7692 } elsif (s/^-wn$//s) {
7694 $cleanmode = 'none';
7695 } elsif (s/^-wg(f?)(a?)$//s) {
7698 $cleanmode .= '-ff' if $1;
7699 $cleanmode .= ',always' if $2;
7700 } elsif (s/^-wd(d?)([na]?)$//s) {
7702 $cleanmode = 'dpkg-source';
7703 $cleanmode .= '-d' if $1;
7704 $cleanmode .= ',no-check' if $2 eq 'n';
7705 $cleanmode .= ',all-check' if $2 eq 'a';
7706 } elsif (s/^-wc$//s) {
7708 $cleanmode = 'check';
7709 } elsif (s/^-wci$//s) {
7711 $cleanmode = 'check,ignores';
7712 } elsif (s/^-c([^=]*)\=(.*)$//s) {
7713 push @git, '-c', $&;
7714 $gitcfgs{cmdline}{$1} = [ $2 ];
7715 } elsif (s/^-c([^=]+)$//s) {
7716 push @git, '-c', $&;
7717 $gitcfgs{cmdline}{$1} = [ 'true' ];
7718 } elsif (m/^-[a-zA-Z]/ && ($oi = $valopts_short{$&})) {
7720 $val = undef unless length $val;
7721 $valopt->($oi->{Short});
7724 badusage f_ "unknown short option \`%s'", $_;
7731 sub check_env_sanity () {
7732 my $blocked = new POSIX::SigSet;
7733 sigprocmask SIG_UNBLOCK, $blocked, $blocked or confess "$!";
7736 foreach my $name (qw(PIPE CHLD)) {
7737 my $signame = "SIG$name";
7738 my $signum = eval "POSIX::$signame" // die;
7739 die f_ "%s is set to something other than SIG_DFL\n",
7741 if defined $SIG{$name} and $SIG{$name} ne 'DEFAULT';
7742 $blocked->ismember($signum) and
7743 die f_ "%s is blocked\n", $signame;
7749 On entry to dgit, %s
7750 This is a bug produced by something in your execution environment.
7756 sub parseopts_late_defaults () {
7757 $isuite //= cfg("dgit-distro.$idistro.default-suite", 'RETURN-UNDEF')
7758 if defined $idistro;
7759 $isuite //= cfg('dgit.default.default-suite');
7761 foreach my $k (keys %opts_opt_map) {
7762 my $om = $opts_opt_map{$k};
7764 my $v = access_cfg("cmd-$k", 'RETURN-UNDEF');
7766 badcfg f_ "cannot set command for %s", $k
7767 unless length $om->[0];
7771 foreach my $c (access_cfg_cfgs("opts-$k")) {
7773 map { $_ ? @$_ : () }
7774 map { $gitcfgs{$_}{$c} }
7775 reverse @gitcfgsources;
7776 printdebug "CL $c ", (join " ", map { shellquote } @vl),
7777 "\n" if $debuglevel >= 4;
7779 badcfg f_ "cannot configure options for %s", $k
7780 if $opts_opt_cmdonly{$k};
7781 my $insertpos = $opts_cfg_insertpos{$k};
7782 @$om = ( @$om[0..$insertpos-1],
7784 @$om[$insertpos..$#$om] );
7788 if (!defined $rmchanges) {
7789 local $access_forpush;
7790 $rmchanges = access_cfg_bool(0, 'rm-old-changes');
7793 if (!defined $quilt_mode) {
7794 local $access_forpush;
7795 $quilt_mode = cfg('dgit.force.quilt-mode', 'RETURN-UNDEF')
7796 // access_cfg('quilt-mode', 'RETURN-UNDEF')
7798 $quilt_mode =~ m/^($quilt_modes_re)$/
7799 or badcfg f_ "unknown quilt-mode \`%s'", $quilt_mode;
7802 $quilt_mode =~ s/^(baredebian)\+git$/$1/;
7804 foreach my $moc (@modeopt_cfgs) {
7805 local $access_forpush;
7806 my $vr = $moc->{Var};
7807 next if defined $$vr;
7808 $$vr = access_cfg($moc->{Key}, 'RETURN-UNDEF') // $moc->{Default};
7809 my $v = $moc->{Vals}{$$vr};
7810 badcfg f_ "unknown %s setting \`%s'", $moc->{Key}, $$vr
7816 local $access_forpush;
7817 default_from_access_cfg(\$cleanmode, 'clean-mode', 'dpkg-source',
7821 $buildproductsdir //= access_cfg('build-products-dir', 'RETURN-UNDEF');
7822 $buildproductsdir //= '..';
7823 $bpd_glob = $buildproductsdir;
7824 $bpd_glob =~ s#[][\\{}*?~]#\\$&#g;
7827 setlocale(LC_MESSAGES, "");
7830 if ($ENV{$fakeeditorenv}) {
7832 quilt_fixup_editor();
7838 print STDERR __ "DRY RUN ONLY\n" if $dryrun_level > 1;
7839 print STDERR __ "DAMP RUN - WILL MAKE LOCAL (UNSIGNED) CHANGES\n"
7840 if $dryrun_level == 1;
7842 print STDERR __ $helpmsg or confess "$!";
7845 $cmd = $subcommand = shift @ARGV;
7848 my $pre_fn = ${*::}{"pre_$cmd"};
7849 $pre_fn->() if $pre_fn;
7851 if ($invoked_in_git_tree) {
7852 changedir_git_toplevel();
7857 my $fn = ${*::}{"cmd_$cmd"};
7858 $fn or badusage f_ "unknown operation %s", $cmd;