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);
49 use Fcntl qw(:DEFAULT :flock);
54 our $our_version = 'UNRELEASED'; ###substituted###
55 our $absurdity = undef; ###substituted###
57 $SIG{INT} = 'DEFAULT'; # work around #932841
59 our @rpushprotovsn_support = qw(6 5 4); # Reverse order!
70 our $dryrun_level = 0;
72 our $buildproductsdir;
75 our $includedirty = 0;
79 our $existing_package = 'dpkg';
81 our $changes_since_version;
83 our $overwrite_version; # undef: not specified; '': check changelog
85 our $quilt_upstream_commitish;
86 our $quilt_upstream_commitish_used;
87 our $quilt_upstream_commitish_message;
88 our $quilt_options_re = 'gbp|dpm|baredebian(?:\+tarball|\+git)?';
89 our $quilt_modes_re = "linear|smash|auto|nofix|nocheck|unapplied|$quilt_options_re";
91 our $splitview_modes_re = qr{auto|always|never};
93 our %internal_object_save;
94 our $we_are_responder;
95 our $we_are_initiator;
96 our $initiator_tempdir;
97 our $patches_applied_dirtily = 00;
98 our $chase_dsc_distro=1;
100 our %forceopts = map { $_=>0 }
101 qw(unrepresentable unsupported-source-format
102 dsc-changes-mismatch changes-origs-exactly
103 uploading-binaries uploading-source-only
104 import-gitapply-absurd
105 import-gitapply-no-absurd
106 import-dsc-with-dgit-field);
108 our %format_ok = map { $_=>1 } ("1.0","3.0 (native)","3.0 (quilt)");
110 our $cleanmode_re = qr{(?: dpkg-source (?: -d )? (?: ,no-check | ,all-check )?
111 | (?: git | git-ff ) (?: ,always )?
112 | check (?: ,ignores )?
116 our $git_authline_re = '^([^<>]+) \<(\S+)\> (\d+ [-+]\d+)$';
117 our $splitbraincache = 'dgit-intern/quilt-cache';
118 our $rewritemap = 'dgit-rewrite/map';
120 our @dpkg_source_ignores = qw(-i(?:^|/)\.git(?:/|$) -I.git);
122 our (@dget) = qw(dget);
123 our (@curl) = (qw(curl --proto-redir), '-all,http,https', qw(-L));
124 our (@dput) = qw(dput);
125 our (@debsign) = qw(debsign);
126 our (@gpg) = qw(gpg);
127 our (@sbuild) = (qw(sbuild --no-source));
129 our (@dgit) = qw(dgit);
130 our (@git_debrebase) = qw(git-debrebase);
131 our (@aptget) = qw(apt-get);
132 our (@aptcache) = qw(apt-cache);
133 our (@dpkgbuildpackage) = (qw(dpkg-buildpackage), @dpkg_source_ignores);
134 our (@dpkgsource) = (qw(dpkg-source), @dpkg_source_ignores);
135 our (@dpkggenchanges) = qw(dpkg-genchanges);
136 our (@mergechanges) = qw(mergechanges -f);
137 our (@gbp_build) = ('');
138 our (@gbp_pq) = ('gbp pq');
139 our (@changesopts) = ('');
140 our (@pbuilder) = ("sudo -E pbuilder","--no-source-only-changes");
141 our (@cowbuilder) = ("sudo -E cowbuilder","--no-source-only-changes");
143 our %opts_opt_map = ('dget' => \@dget, # accept for compatibility
146 'debsign' => \@debsign,
148 'sbuild' => \@sbuild,
152 'git-debrebase' => \@git_debrebase,
153 'apt-get' => \@aptget,
154 'apt-cache' => \@aptcache,
155 'dpkg-source' => \@dpkgsource,
156 'dpkg-buildpackage' => \@dpkgbuildpackage,
157 'dpkg-genchanges' => \@dpkggenchanges,
158 'gbp-build' => \@gbp_build,
159 'gbp-pq' => \@gbp_pq,
160 'ch' => \@changesopts,
161 'mergechanges' => \@mergechanges,
162 'pbuilder' => \@pbuilder,
163 'cowbuilder' => \@cowbuilder);
165 our %opts_opt_cmdonly = ('gpg' => 1, 'git' => 1);
166 our %opts_cfg_insertpos = map {
168 scalar @{ $opts_opt_map{$_} }
169 } keys %opts_opt_map;
171 sub parseopts_late_defaults();
172 sub quiltify_trees_differ ($$;$$$);
173 sub setup_gitattrs(;$);
174 sub check_gitattrs($$);
181 our $supplementary_message = '';
182 our $made_split_brain = 0;
185 # Interactions between quilt mode and split brain
186 # (currently, split brain only implemented iff
187 # madformat_wantfixup && quiltmode_splitting)
189 # source format sane `3.0 (quilt)'
190 # madformat_wantfixup()
192 # quilt mode normal quiltmode
193 # (eg linear) _splitbrain
195 # ------------ ------------------------------------------------
197 # no split no q cache no q cache forbidden,
198 # brain PM on master q fixup on master prevented
199 # !do_split_brain() PM on master
201 # split brain no q cache q fixup cached, to dgit view
202 # PM in dgit view PM in dgit view
204 # PM = pseudomerge to make ff, due to overwrite (or split view)
205 # "no q cache" = do not record in cache on build, do not check cache
206 # `3.0 (quilt)' with --quilt=nocheck is treated as sane format
210 return unless forkcheck_mainprocess();
211 print STDERR "! $_\n" foreach $supplementary_message =~ m/^.+$/mg;
214 our $remotename = 'dgit';
215 our @ourdscfield = qw(Dgit Vcs-Dgit-Master);
219 if (!defined $absurdity) {
221 $absurdity =~ s{/[^/]+$}{/absurd} or die;
224 sub madformat ($) { $_[0] eq '3.0 (quilt)' }
226 sub lbranch () { return "$branchprefix/$csuite"; }
227 my $lbranch_re = '^refs/heads/'.$branchprefix.'/([^/.]+)$';
228 sub lref () { return "refs/heads/".lbranch(); }
229 sub lrref () { return "refs/remotes/$remotename/".server_branch($csuite); }
230 sub rrref () { return server_ref($csuite); }
233 my ($vsn, $sfx) = @_;
234 return &source_file_leafname($package, $vsn, $sfx);
236 sub is_orig_file_of_vsn ($$) {
237 my ($f, $upstreamvsn) = @_;
238 return is_orig_file_of_p_v($f, $package, $upstreamvsn);
243 return srcfn($vsn,".dsc");
246 sub changespat ($;$) {
247 my ($vsn, $arch) = @_;
248 return "${package}_".(stripepoch $vsn)."_".($arch//'*').".changes";
257 return unless forkcheck_mainprocess();
258 foreach my $f (@end) {
260 print STDERR "$us: cleanup: $@" if length $@;
265 print STDERR f_ "%s: invalid configuration: %s\n", $us, "@_";
269 sub forceable_fail ($$) {
270 my ($forceoptsl, $msg) = @_;
271 fail $msg unless grep { $forceopts{$_} } @$forceoptsl;
272 print STDERR +(__ "warning: overriding problem due to --force:\n"). $msg;
276 my ($forceoptsl) = @_;
277 my @got = grep { $forceopts{$_} } @$forceoptsl;
278 return 0 unless @got;
280 "warning: skipping checks or functionality due to --force-%s\n",
284 sub no_such_package () {
285 print STDERR f_ "%s: source package %s does not exist in suite %s\n",
286 $us, $package, $isuite;
290 sub deliberately ($) {
292 return !!grep { $_ eq "--deliberately-$enquiry" } @deliberatelies;
295 sub deliberately_not_fast_forward () {
296 foreach (qw(not-fast-forward fresh-repo)) {
297 return 1 if deliberately($_) || deliberately("TEST-dgit-only-$_");
301 sub quiltmode_splitting () {
302 $quilt_mode =~ m/gbp|dpm|unapplied|baredebian/;
304 sub format_quiltmode_splitting ($) {
306 return madformat_wantfixup($format) && quiltmode_splitting();
309 sub do_split_brain () { !!($do_split_brain // confess) }
311 sub opts_opt_multi_cmd {
314 push @cmd, split /\s+/, shift @_;
321 return opts_opt_multi_cmd [], @gbp_pq;
324 sub dgit_privdir () {
325 our $dgit_privdir_made //= ensure_a_playground 'dgit';
329 my $r = $buildproductsdir;
330 $r = "$maindir/$r" unless $r =~ m{^/};
334 sub get_tree_of_commit ($) {
335 my ($commitish) = @_;
336 my $cdata = cmdoutput @git, qw(cat-file commit), $commitish;
337 $cdata =~ m/\n\n/; $cdata = $`;
338 $cdata =~ m/^tree (\w+)$/m or confess "cdata $cdata ?";
342 sub branch_gdr_info ($$) {
343 my ($symref, $head) = @_;
344 my ($status, $msg, $current, $ffq_prev, $gdrlast) =
345 gdr_ffq_prev_branchinfo($symref);
346 return () unless $status eq 'branch';
347 $ffq_prev = git_get_ref $ffq_prev;
348 $gdrlast = git_get_ref $gdrlast;
349 $gdrlast &&= is_fast_fwd $gdrlast, $head;
350 return ($ffq_prev, $gdrlast);
353 sub branch_is_gdr_unstitched_ff ($$$) {
354 my ($symref, $head, $ancestor) = @_;
355 my ($ffq_prev, $gdrlast) = branch_gdr_info($symref, $head);
356 return 0 unless $ffq_prev;
357 return 0 unless !defined $ancestor or is_fast_fwd $ancestor, $ffq_prev;
361 sub branch_is_gdr ($) {
363 # This is quite like git-debrebase's keycommits.
364 # We have our own implementation because:
365 # - our algorighm can do fewer tests so is faster
366 # - it saves testing to see if gdr is installed
368 # NB we use this jsut for deciding whether to run gdr make-patches
369 # Before reusing this algorithm for somthing else, its
370 # suitability should be reconsidered.
373 local $Debian::Dgit::debugcmd_when_debuglevel = 3;
374 printdebug "branch_is_gdr $head...\n";
375 my $get_patches = sub {
376 my $t = git_cat_file "$_[0]:debian/patches", [qw(missing tree)];
379 my $tip_patches = $get_patches->($head);
382 my $cdata = git_cat_file $walk, 'commit';
383 my ($hdrs,$msg) = $cdata =~ m{\n\n} ? ($`,$') : ($cdata,'');
384 if ($msg =~ m{^\[git-debrebase\ (
385 anchor | changelog | make-patches |
386 merged-breakwater | pseudomerge
388 # no need to analyse this - it's sufficient
389 # (gdr classifications: Anchor, MergedBreakwaters)
390 # (made by gdr: Pseudomerge, Changelog)
391 printdebug "branch_is_gdr $walk gdr $1 YES\n";
394 my @parents = ($hdrs =~ m/^parent (\w+)$/gm);
396 my $walk_tree = get_tree_of_commit $walk;
397 foreach my $p (@parents) {
398 my $p_tree = get_tree_of_commit $p;
399 if ($p_tree eq $walk_tree) { # pseudomerge contriburor
400 # (gdr classification: Pseudomerge; not made by gdr)
401 printdebug "branch_is_gdr $walk unmarked pseudomerge\n"
407 # some other non-gdr merge
408 # (gdr classification: VanillaMerge, DgitImportUnpatched, ?)
409 printdebug "branch_is_gdr $walk ?-2-merge NO\n";
413 # (gdr classification: ?)
414 printdebug "branch_is_gdr $walk ?-octopus NO\n";
418 printdebug "branch_is_gdr $walk origin\n";
421 if ($get_patches->($walk) ne $tip_patches) {
422 # Our parent added, removed, or edited patches, and wasn't
423 # a gdr make-patches commit. gdr make-patches probably
424 # won't do that well, then.
425 # (gdr classification of parent: AddPatches or ?)
426 printdebug "branch_is_gdr $walk ?-patches NO\n";
429 if ($tip_patches eq '' and
430 !defined git_cat_file "$walk~:debian" and
431 !quiltify_trees_differ "$walk~", $walk
433 # (gdr classification of parent: BreakwaterStart
434 printdebug "branch_is_gdr $walk unmarked BreakwaterStart YES\n";
437 # (gdr classification: Upstream Packaging Mixed Changelog)
438 printdebug "branch_is_gdr $walk plain\n"
444 #---------- remote protocol support, common ----------
446 # remote push initiator/responder protocol:
447 # $ dgit remote-push-build-host <n-rargs> <rargs>... <push-args>...
448 # where <rargs> is <push-host-dir> <supported-proto-vsn>,... ...
449 # < dgit-remote-push-ready <actual-proto-vsn>
456 # > supplementary-message NBYTES
461 # > file parsed-changelog
462 # [indicates that output of dpkg-parsechangelog follows]
463 # > data-block NBYTES
464 # > [NBYTES bytes of data (no newline)]
465 # [maybe some more blocks]
474 # > param head DGIT-VIEW-HEAD
475 # > param csuite SUITE
476 # > param tagformat new # $protovsn == 4
477 # > param splitbrain 0|1 # $protovsn >= 6
478 # > param maint-view MAINT-VIEW-HEAD
480 # > param buildinfo-filename P_V_X.buildinfo # zero or more times
481 # > file buildinfo # for buildinfos to sign
483 # > previously REFNAME=OBJNAME # if --deliberately-not-fast-forward
484 # # goes into tag, for replay prevention
487 # [indicates that signed tag is wanted]
488 # < data-block NBYTES
489 # < [NBYTES bytes of data (no newline)]
490 # [maybe some more blocks]
494 # > want signed-dsc-changes
495 # < data-block NBYTES [transfer of signed dsc]
497 # < data-block NBYTES [transfer of signed changes]
499 # < data-block NBYTES [transfer of each signed buildinfo
500 # [etc] same number and order as "file buildinfo"]
508 sub i_child_report () {
509 # Sees if our child has died, and reap it if so. Returns a string
510 # describing how it died if it failed, or undef otherwise.
511 return undef unless $i_child_pid;
512 my $got = waitpid $i_child_pid, WNOHANG;
513 return undef if $got <= 0;
514 die unless $got == $i_child_pid;
515 $i_child_pid = undef;
516 return undef unless $?;
517 return f_ "build host child %s", waitstatusmsg();
522 fail f_ "connection lost: %s", $! if $fh->error;
523 fail f_ "protocol violation; %s not expected", $m;
526 sub badproto_badread ($$) {
528 fail f_ "connection lost: %s", $! if $!;
529 my $report = i_child_report();
530 fail $report if defined $report;
531 badproto $fh, f_ "eof (reading %s)", $wh;
534 sub protocol_expect (&$) {
535 my ($match, $fh) = @_;
538 defined && chomp or badproto_badread $fh, __ "protocol message";
546 badproto $fh, f_ "\`%s'", $_;
549 sub protocol_send_file ($$) {
550 my ($fh, $ourfn) = @_;
551 open PF, "<", $ourfn or die "$ourfn: $!";
554 my $got = read PF, $d, 65536;
555 die "$ourfn: $!" unless defined $got;
557 print $fh "data-block ".length($d)."\n" or confess "$!";
558 print $fh $d or confess "$!";
560 PF->error and die "$ourfn $!";
561 print $fh "data-end\n" or confess "$!";
565 sub protocol_read_bytes ($$) {
566 my ($fh, $nbytes) = @_;
567 $nbytes =~ m/^[1-9]\d{0,5}$|^0$/ or badproto \*RO, __ "bad byte count";
569 my $got = read $fh, $d, $nbytes;
570 $got==$nbytes or badproto_badread $fh, __ "data block";
574 sub protocol_receive_file ($$) {
575 my ($fh, $ourfn) = @_;
576 printdebug "() $ourfn\n";
577 open PF, ">", $ourfn or die "$ourfn: $!";
579 my ($y,$l) = protocol_expect {
580 m/^data-block (.*)$/ ? (1,$1) :
581 m/^data-end$/ ? (0,) :
585 my $d = protocol_read_bytes $fh, $l;
586 print PF $d or confess "$!";
588 close PF or confess "$!";
591 #---------- remote protocol support, responder ----------
593 sub responder_send_command ($) {
595 return unless $we_are_responder;
596 # called even without $we_are_responder
597 printdebug ">> $command\n";
598 print PO $command, "\n" or confess "$!";
601 sub responder_send_file ($$) {
602 my ($keyword, $ourfn) = @_;
603 return unless $we_are_responder;
604 printdebug "]] $keyword $ourfn\n";
605 responder_send_command "file $keyword";
606 protocol_send_file \*PO, $ourfn;
609 sub responder_receive_files ($@) {
610 my ($keyword, @ourfns) = @_;
611 die unless $we_are_responder;
612 printdebug "[[ $keyword @ourfns\n";
613 responder_send_command "want $keyword";
614 foreach my $fn (@ourfns) {
615 protocol_receive_file \*PI, $fn;
618 protocol_expect { m/^files-end$/ } \*PI;
621 #---------- remote protocol support, initiator ----------
623 sub initiator_expect (&) {
625 protocol_expect { &$match } \*RO;
628 #---------- end remote code ----------
631 if ($we_are_responder) {
633 responder_send_command "progress ".length($m) or confess "$!";
634 print PO $m or confess "$!";
644 $ua = LWP::UserAgent->new();
648 progress "downloading $what...";
649 my $r = $ua->get(@_) or confess "$!";
650 return undef if $r->code == 404;
651 $r->is_success or fail f_ "failed to fetch %s: %s",
652 $what, $r->status_line;
653 return $r->decoded_content(charset => 'none');
656 our ($dscdata,$dscurl,$dsc,$dsc_checked,$skew_warning_vsn);
658 sub act_local () { return $dryrun_level <= 1; }
659 sub act_scary () { return !$dryrun_level; }
662 if (!$dryrun_level) {
663 progress f_ "%s ok: %s", $us, "@_";
665 progress f_ "would be ok: %s (but dry run only)", "@_";
670 printcmd(\*STDERR,$debugprefix."#",@_);
673 sub runcmd_ordryrun {
681 sub runcmd_ordryrun_local {
689 our $helpmsg = i_ <<END;
691 dgit [dgit-opts] clone [dgit-opts] package [suite] [./dir|/dir]
692 dgit [dgit-opts] fetch|pull [dgit-opts] [suite]
693 dgit [dgit-opts] build [dpkg-buildpackage-opts]
694 dgit [dgit-opts] sbuild [sbuild-opts]
695 dgit [dgit-opts] pbuilder|cowbuilder [debbuildopts]
696 dgit [dgit-opts] push [dgit-opts] [suite]
697 dgit [dgit-opts] push-source [dgit-opts] [suite]
698 dgit [dgit-opts] rpush build-host:build-dir ...
699 important dgit options:
700 -k<keyid> sign tag and package with <keyid> instead of default
701 --dry-run -n do not change anything, but go through the motions
702 --damp-run -L like --dry-run but make local changes, without signing
703 --new -N allow introducing a new package
704 --debug -D increase debug level
705 -c<name>=<value> set git config option (used directly by dgit too)
708 our $later_warning_msg = i_ <<END;
709 Perhaps the upload is stuck in incoming. Using the version from git.
713 print STDERR f_ "%s: %s\n%s", $us, "@_", __ $helpmsg or confess "$!";
718 @ARGV or badusage __ "too few arguments";
719 return scalar shift @ARGV;
723 not_necessarily_a_tree();
726 print __ $helpmsg or confess "$!";
730 our $td = $ENV{DGIT_TEST_DUMMY_DIR} || "DGIT_TEST_DUMMY_DIR-unset";
732 our %defcfg = ('dgit.default.distro' => 'debian',
733 'dgit.default.default-suite' => 'unstable',
734 'dgit.default.old-dsc-distro' => 'debian',
735 'dgit-suite.*-security.distro' => 'debian-security',
736 'dgit.default.username' => '',
737 'dgit.default.archive-query-default-component' => 'main',
738 'dgit.default.ssh' => 'ssh',
739 'dgit.default.archive-query' => 'madison:',
740 'dgit.default.sshpsql-dbname' => 'service=projectb',
741 'dgit.default.aptget-components' => 'main',
742 'dgit.default.source-only-uploads' => 'ok',
743 'dgit.dsc-url-proto-ok.http' => 'true',
744 'dgit.dsc-url-proto-ok.https' => 'true',
745 'dgit.dsc-url-proto-ok.git' => 'true',
746 'dgit.vcs-git.suites', => 'sid', # ;-separated
747 'dgit.default.dsc-url-proto-ok' => 'false',
748 # old means "repo server accepts pushes with old dgit tags"
749 # new means "repo server accepts pushes with new dgit tags"
750 # maint means "repo server accepts split brain pushes"
751 # hist means "repo server may have old pushes without new tag"
752 # ("hist" is implied by "old")
753 'dgit-distro.debian.archive-query' => 'ftpmasterapi:',
754 'dgit-distro.debian.git-check' => 'url',
755 'dgit-distro.debian.git-check-suffix' => '/info/refs',
756 'dgit-distro.debian.new-private-pushers' => 't',
757 'dgit-distro.debian.source-only-uploads' => 'not-wholly-new',
758 'dgit-distro.debian/push.git-url' => '',
759 'dgit-distro.debian/push.git-host' => 'push.dgit.debian.org',
760 'dgit-distro.debian/push.git-user-force' => 'dgit',
761 'dgit-distro.debian/push.git-proto' => 'git+ssh://',
762 'dgit-distro.debian/push.git-path' => '/dgit/debian/repos',
763 'dgit-distro.debian/push.git-create' => 'true',
764 'dgit-distro.debian/push.git-check' => 'ssh-cmd',
765 'dgit-distro.debian.archive-query-url', 'https://api.ftp-master.debian.org/',
766 # 'dgit-distro.debian.archive-query-tls-key',
767 # '/etc/ssl/certs/%HOST%.pem:/etc/dgit/%HOST%.pem',
768 # ^ this does not work because curl is broken nowadays
769 # Fixing #790093 properly will involve providing providing the key
770 # in some pacagke and maybe updating these paths.
772 # 'dgit-distro.debian.archive-query-tls-curl-args',
773 # '--ca-path=/etc/ssl/ca-debian',
774 # ^ this is a workaround but works (only) on DSA-administered machines
775 'dgit-distro.debian.git-url' => 'https://git.dgit.debian.org',
776 'dgit-distro.debian.git-url-suffix' => '',
777 'dgit-distro.debian.upload-host' => 'ftp-master', # for dput
778 'dgit-distro.debian.mirror' => 'http://ftp.debian.org/debian/',
779 'dgit-distro.debian-security.archive-query' => 'aptget:',
780 'dgit-distro.debian-security.mirror' => 'http://security.debian.org/debian-security/',
781 'dgit-distro.debian-security.aptget-suite-map' => 's#-security$#/updates#',
782 'dgit-distro.debian-security.aptget-suite-rmap' => 's#$#-security#',
783 'dgit-distro.debian-security.nominal-distro' => 'debian',
784 'dgit-distro.debian.backports-quirk' => '(squeeze)-backports*',
785 'dgit-distro.debian-backports.mirror' => 'http://backports.debian.org/debian-backports/',
786 'dgit-distro.ubuntu.git-check' => 'false',
787 'dgit-distro.ubuntu.mirror' => 'http://archive.ubuntu.com/ubuntu',
788 'dgit-distro.ubuntucloud.git-check' => 'false',
789 'dgit-distro.ubuntucloud.nominal-distro' => 'ubuntu',
790 'dgit-distro.ubuntucloud.archive-query' => 'aptget:',
791 'dgit-distro.ubuntucloud.mirror' => 'http://ubuntu-cloud.archive.canonical.com/ubuntu',
792 'dgit-distro.ubuntucloud.aptget-suite-map' => 's#^([^-]+):([^:]+)$#${1}-updates/$2#; s#^(.+)-(.+):(.+)#$1-$2/$3#;',
793 'dgit-distro.ubuntucloud.aptget-suite-rmap' => 's#/(.+)$#-$1#',
794 'dgit-distro.test-dummy.ssh' => "$td/ssh",
795 'dgit-distro.test-dummy.username' => "alice",
796 'dgit-distro.test-dummy.git-check' => "ssh-cmd",
797 'dgit-distro.test-dummy.git-create' => "ssh-cmd",
798 'dgit-distro.test-dummy.git-url' => "$td/git",
799 'dgit-distro.test-dummy.git-host' => "git",
800 'dgit-distro.test-dummy.git-path' => "$td/git",
801 'dgit-distro.test-dummy.archive-query' => "dummycatapi:",
802 'dgit-distro.test-dummy.archive-query-url' => "file://$td/aq/",
803 'dgit-distro.test-dummy.mirror' => "file://$td/mirror/",
804 'dgit-distro.test-dummy.upload-host' => 'test-dummy',
808 our @gitcfgsources = qw(cmdline local global system);
809 our $invoked_in_git_tree = 1;
811 sub git_slurp_config () {
812 # This algoritm is a bit subtle, but this is needed so that for
813 # options which we want to be single-valued, we allow the
814 # different config sources to override properly. See #835858.
815 foreach my $src (@gitcfgsources) {
816 next if $src eq 'cmdline';
817 # we do this ourselves since git doesn't handle it
819 $gitcfgs{$src} = git_slurp_config_src $src;
823 sub git_get_config ($) {
825 foreach my $src (@gitcfgsources) {
826 my $l = $gitcfgs{$src}{$c};
827 confess "internal error ($l $c)" if $l && !ref $l;
828 printdebug"C $c ".(defined $l ?
829 join " ", map { messagequote "'$_'" } @$l :
834 f_ "multiple values for %s (in %s git config)", $c, $src
836 $l->[0] =~ m/\n/ and badcfg f_
837 "value for config option %s (in %s git config) contains newline(s)!",
846 return undef if $c =~ /RETURN-UNDEF/;
847 printdebug "C? $c\n" if $debuglevel >= 5;
848 my $v = git_get_config($c);
849 return $v if defined $v;
850 my $dv = $defcfg{$c};
852 printdebug "CD $c $dv\n" if $debuglevel >= 4;
857 "need value for one of: %s\n".
858 "%s: distro or suite appears not to be (properly) supported",
862 sub not_necessarily_a_tree () {
863 # needs to be called from pre_*
864 @gitcfgsources = grep { $_ ne 'local' } @gitcfgsources;
865 $invoked_in_git_tree = 0;
868 sub access_basedistro__noalias () {
869 if (defined $idistro) {
872 my $def = cfg("dgit-suite.$isuite.distro", 'RETURN-UNDEF');
873 return $def if defined $def;
874 foreach my $src (@gitcfgsources, 'internal') {
875 my $kl = $src eq 'internal' ? \%defcfg : $gitcfgs{$src};
877 foreach my $k (keys %$kl) {
878 next unless $k =~ m#^dgit-suite\.(.*)\.distro$#;
880 next unless match_glob $dpat, $isuite;
884 return cfg("dgit.default.distro");
888 sub access_basedistro () {
889 my $noalias = access_basedistro__noalias();
890 my $canon = cfg("dgit-distro.$noalias.alias-canon",'RETURN-UNDEF');
891 return $canon // $noalias;
894 sub access_nomdistro () {
895 my $base = access_basedistro();
896 my $r = cfg("dgit-distro.$base.nominal-distro",'RETURN-UNDEF') // $base;
897 $r =~ m/^$distro_re$/ or badcfg
898 f_ "bad syntax for (nominal) distro \`%s' (does not match %s)",
899 $r, "/^$distro_re$/";
903 sub access_quirk () {
904 # returns (quirk name, distro to use instead or undef, quirk-specific info)
905 my $basedistro = access_basedistro();
906 my $backports_quirk = cfg("dgit-distro.$basedistro.backports-quirk",
908 if (defined $backports_quirk) {
909 my $re = $backports_quirk;
910 $re =~ s/[^-0-9a-z_\%*()]/\\$&/ig;
912 $re =~ s/\%/([-0-9a-z_]+)/
913 or $re =~ m/[()]/ or badcfg __ "backports-quirk needs \% or ( )";
914 if ($isuite =~ m/^$re$/) {
915 return ('backports',"$basedistro-backports",$1);
918 return ('none',undef);
923 sub parse_cfg_bool ($$$) {
924 my ($what,$def,$v) = @_;
927 $v =~ m/^[ty1]/ ? 1 :
928 $v =~ m/^[fn0]/ ? 0 :
929 badcfg f_ "%s needs t (true, y, 1) or f (false, n, 0) not \`%s'",
933 sub access_forpush_config () {
934 my $d = access_basedistro();
938 parse_cfg_bool('new-private-pushers', 0,
939 cfg("dgit-distro.$d.new-private-pushers",
942 my $v = cfg("dgit-distro.$d.readonly", 'RETURN-UNDEF');
945 $v =~ m/^[ty1]/ ? 0 : # force readonly, forpush = 0
946 $v =~ m/^[fn0]/ ? 1 : # force nonreadonly, forpush = 1
947 $v =~ m/^[a]/ ? '' : # auto, forpush = ''
949 "readonly needs t (true, y, 1) or f (false, n, 0) or a (auto)";
952 sub access_forpush () {
953 $access_forpush //= access_forpush_config();
954 return $access_forpush;
957 sub default_from_access_cfg ($$$;$) {
958 my ($var, $keybase, $defval, $permit_re) = @_;
959 return if defined $$var;
961 $$var = access_cfg("$keybase-newer", 'RETURN-UNDEF');
962 $$var = undef if $$var && $$var !~ m/^$permit_re$/;
964 $$var //= access_cfg($keybase, 'RETURN-UNDEF');
967 badcfg f_ "unknown %s \`%s'", $keybase, $$var
968 if defined $permit_re and $$var !~ m/$permit_re/;
972 confess +(__ 'internal error').' '.Dumper($access_forpush)," ?" if
973 defined $access_forpush and !$access_forpush;
974 badcfg __ "pushing but distro is configured readonly"
975 if access_forpush_config() eq '0';
977 $supplementary_message = __ <<'END' unless $we_are_responder;
978 Push failed, before we got started.
979 You can retry the push, after fixing the problem, if you like.
981 parseopts_late_defaults();
985 parseopts_late_defaults();
988 sub determine_whether_split_brain ($) {
991 local $access_forpush;
992 default_from_access_cfg(\$splitview_mode, 'split-view', 'auto',
993 $splitview_modes_re);
994 $do_split_brain = 1 if $splitview_mode eq 'always';
997 printdebug "format $format, quilt mode $quilt_mode\n";
999 if (format_quiltmode_splitting $format) {
1000 $splitview_mode ne 'never' or
1001 fail f_ "dgit: quilt mode \`%s' (for format \`%s')".
1002 " implies split view, but split-view set to \`%s'",
1003 $quilt_mode, $format, $splitview_mode;
1004 $do_split_brain = 1;
1006 $do_split_brain //= 0;
1009 sub supplementary_message ($) {
1011 if (!$we_are_responder) {
1012 $supplementary_message = $msg;
1015 responder_send_command "supplementary-message ".length($msg)
1017 print PO $msg or confess "$!";
1021 sub access_distros () {
1022 # Returns list of distros to try, in order
1025 # 0. `instead of' distro name(s) we have been pointed to
1026 # 1. the access_quirk distro, if any
1027 # 2a. the user's specified distro, or failing that } basedistro
1028 # 2b. the distro calculated from the suite }
1029 my @l = access_basedistro();
1031 my (undef,$quirkdistro) = access_quirk();
1032 unshift @l, $quirkdistro;
1033 unshift @l, $instead_distro;
1034 @l = grep { defined } @l;
1036 push @l, access_nomdistro();
1038 if (access_forpush()) {
1039 @l = map { ("$_/push", $_) } @l;
1044 sub access_cfg_cfgs (@) {
1047 # The nesting of these loops determines the search order. We put
1048 # the key loop on the outside so that we search all the distros
1049 # for each key, before going on to the next key. That means that
1050 # if access_cfg is called with a more specific, and then a less
1051 # specific, key, an earlier distro can override the less specific
1052 # without necessarily overriding any more specific keys. (If the
1053 # distro wants to override the more specific keys it can simply do
1054 # so; whereas if we did the loop the other way around, it would be
1055 # impossible to for an earlier distro to override a less specific
1056 # key but not the more specific ones without restating the unknown
1057 # values of the more specific keys.
1060 # We have to deal with RETURN-UNDEF specially, so that we don't
1061 # terminate the search prematurely.
1063 if (m/RETURN-UNDEF/) { push @rundef, $_; last; }
1066 foreach my $d (access_distros()) {
1067 push @cfgs, map { "dgit-distro.$d.$_" } @realkeys;
1069 push @cfgs, map { "dgit.default.$_" } @realkeys;
1070 push @cfgs, @rundef;
1074 sub access_cfg (@) {
1076 my (@cfgs) = access_cfg_cfgs(@keys);
1077 my $value = cfg(@cfgs);
1081 sub access_cfg_bool ($$) {
1082 my ($def, @keys) = @_;
1083 parse_cfg_bool($keys[0], $def, access_cfg(@keys, 'RETURN-UNDEF'));
1086 sub string_to_ssh ($) {
1088 if ($spec =~ m/\s/) {
1089 return qw(sh -ec), 'exec '.$spec.' "$@"', 'x';
1095 sub access_cfg_ssh () {
1096 my $gitssh = access_cfg('ssh', 'RETURN-UNDEF');
1097 if (!defined $gitssh) {
1100 return string_to_ssh $gitssh;
1104 sub access_runeinfo ($) {
1106 return ": dgit ".access_basedistro()." $info ;";
1109 sub access_someuserhost ($) {
1111 my $user = access_cfg("$some-user-force", 'RETURN-UNDEF');
1112 defined($user) && length($user) or
1113 $user = access_cfg("$some-user",'username');
1114 my $host = access_cfg("$some-host");
1115 return length($user) ? "$user\@$host" : $host;
1118 sub access_gituserhost () {
1119 return access_someuserhost('git');
1122 sub access_giturl (;$) {
1123 my ($optional) = @_;
1124 my $url = access_cfg('git-url','RETURN-UNDEF');
1127 my $proto = access_cfg('git-proto', 'RETURN-UNDEF');
1128 return undef unless defined $proto;
1131 access_gituserhost().
1132 access_cfg('git-path');
1134 $suffix = access_cfg('git-url-suffix','RETURN-UNDEF');
1137 return "$url/$package$suffix";
1140 sub commit_getclogp ($) {
1141 # Returns the parsed changelog hashref for a particular commit
1143 our %commit_getclogp_memo;
1144 my $memo = $commit_getclogp_memo{$objid};
1145 return $memo if $memo;
1147 my $mclog = dgit_privdir()."clog";
1148 runcmd shell_cmd "exec >$mclog", @git, qw(cat-file blob),
1149 "$objid:debian/changelog";
1150 $commit_getclogp_memo{$objid} = parsechangelog("-l$mclog");
1153 sub parse_dscdata () {
1154 my $dscfh = new IO::File \$dscdata, '<' or confess "$!";
1155 printdebug Dumper($dscdata) if $debuglevel>1;
1156 $dsc = parsecontrolfh($dscfh,$dscurl,1);
1157 printdebug Dumper($dsc) if $debuglevel>1;
1162 sub archive_query ($;@) {
1163 my ($method) = shift @_;
1164 fail __ "this operation does not support multiple comma-separated suites"
1166 my $query = access_cfg('archive-query','RETURN-UNDEF');
1167 $query =~ s/^(\w+):// or badcfg "invalid archive-query method \`$query'";
1170 { no strict qw(refs); &{"${method}_${proto}"}($proto,$data,@_); }
1173 sub archive_query_prepend_mirror {
1174 my $m = access_cfg('mirror');
1175 return map { [ $_->[0], $m.$_->[1], @$_[2..$#$_] ] } @_;
1178 sub pool_dsc_subpath ($$) {
1179 my ($vsn,$component) = @_; # $package is implict arg
1180 my $prefix = substr($package, 0, $package =~ m/^l/ ? 4 : 1);
1181 return "/pool/$component/$prefix/$package/".dscfn($vsn);
1184 sub cfg_apply_map ($$$) {
1185 my ($varref, $what, $mapspec) = @_;
1186 return unless $mapspec;
1188 printdebug "config $what EVAL{ $mapspec; }\n";
1190 eval "package Dgit::Config; $mapspec;";
1195 sub url_fetch ($;@) {
1196 my ($url, %xopts) = @_;
1197 # Ok404 => 1 means give undef for 404
1198 # AccessBase => 'archive-query' (eg)
1200 use WWW::Curl::Easy;
1202 my $curl = WWW::Curl::Easy->new;
1205 my $x = $curl->setopt($k, $v);
1206 confess "$k $v ".$curl->strerror($x)." ?" if $x;
1210 $setopt->(CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS|CURLPROTO_HTTP);
1211 $setopt->(CURLOPT_URL, $url);
1212 $setopt->(CURLOPT_NOSIGNAL, 1);
1213 $setopt->(CURLOPT_WRITEDATA, \$response_body);
1215 if ($xopts{AccessBase} && $url =~ m#^https://([-.0-9a-z]+)/#) {
1216 foreach my $k ("$xopts{AccessBase}-tls-key",
1217 "$xopts{AccessBase}-tls-curl-ca-args") {
1218 fail "config option $k is obsolete and no longer supported"
1219 if defined access_cfg($k, 'RETURN-UNDEF');
1223 printdebug "query: fetching $url...\n";
1225 local $SIG{PIPE} = 'IGNORE';
1227 my $x = $curl->perform();
1228 fail f_ "fetch of %s failed (%s): %s",
1229 $url, $curl->strerror($x), $curl->errbuf
1232 my $code = $curl->getinfo(CURLINFO_HTTP_CODE);
1233 if ($code eq '404' && $xopts{Ok404}) { return undef; }
1235 fail f_ "fetch of %s gave HTTP code %s", $url, $code
1236 unless $url =~ m#^file://# or $code =~ m/^2/;
1237 return $response_body;
1240 #---------- `ftpmasterapi' archive query method (nascent) ----------
1242 sub api_query_raw ($;$) {
1243 my ($subpath, $ok404) = @_;
1244 my $url = access_cfg('archive-query-url');
1246 return url_fetch $url,
1248 AccessBase => 'archive-query';
1251 sub api_query ($$;$) {
1252 my ($data, $subpath, $ok404) = @_;
1254 badcfg __ "ftpmasterapi archive query method takes no data part"
1256 my $json = api_query_raw $subpath, $ok404;
1257 return undef unless defined $json;
1258 return decode_json($json);
1261 sub canonicalise_suite_ftpmasterapi {
1262 my ($proto,$data) = @_;
1263 my $suites = api_query($data, 'suites');
1265 foreach my $entry (@$suites) {
1267 my $v = $entry->{$_};
1268 defined $v && $v eq $isuite;
1269 } qw(codename name);
1270 push @matched, $entry;
1272 fail f_ "unknown suite %s, maybe -d would help", $isuite
1276 @matched==1 or die f_ "multiple matches for suite %s\n", $isuite;
1277 $cn = "$matched[0]{codename}";
1278 defined $cn or die f_ "suite %s info has no codename\n", $isuite;
1279 $cn =~ m/^$suite_re$/
1280 or die f_ "suite %s maps to bad codename\n", $isuite;
1282 die +(__ "bad ftpmaster api response: ")."$@\n".Dumper(\@matched)
1287 sub archive_query_ftpmasterapi {
1288 my ($proto,$data) = @_;
1289 my $info = api_query($data, "dsc_in_suite/$isuite/$package");
1291 my $digester = Digest::SHA->new(256);
1292 foreach my $entry (@$info) {
1294 my $vsn = "$entry->{version}";
1295 my ($ok,$msg) = version_check $vsn;
1296 die f_ "bad version: %s\n", $msg unless $ok;
1297 my $component = "$entry->{component}";
1298 $component =~ m/^$component_re$/ or die __ "bad component";
1299 my $filename = "$entry->{filename}";
1300 $filename && $filename !~ m#[^-+:._~0-9a-zA-Z/]|^[/.]|/[/.]#
1301 or die __ "bad filename";
1302 my $sha256sum = "$entry->{sha256sum}";
1303 $sha256sum =~ m/^[0-9a-f]+$/ or die __ "bad sha256sum";
1304 push @rows, [ $vsn, "/pool/$component/$filename",
1305 $digester, $sha256sum ];
1307 die +(__ "bad ftpmaster api response: ")."$@\n".Dumper($entry)
1310 @rows = sort { -version_compare($a->[0],$b->[0]) } @rows;
1311 return archive_query_prepend_mirror @rows;
1314 sub file_in_archive_ftpmasterapi {
1315 my ($proto,$data,$filename) = @_;
1316 my $pat = $filename;
1319 $pat =~ s#[^-+_.0-9a-z/]# sprintf '%%%02x', ord $& #ge;
1320 my $info = api_query($data, "file_in_archive/$pat", 1);
1323 sub package_not_wholly_new_ftpmasterapi {
1324 my ($proto,$data,$pkg) = @_;
1325 my $info = api_query($data,"madison?package=${pkg}&f=json");
1329 #---------- `aptget' archive query method ----------
1332 our $aptget_releasefile;
1333 our $aptget_configpath;
1335 sub aptget_aptget () { return @aptget, qw(-c), $aptget_configpath; }
1336 sub aptget_aptcache () { return @aptcache, qw(-c), $aptget_configpath; }
1338 sub aptget_cache_clean {
1339 runcmd_ordryrun_local qw(sh -ec),
1340 'cd "$1"; find -atime +30 -type f -print0 | xargs -0r rm --',
1344 sub aptget_lock_acquire () {
1345 my $lockfile = "$aptget_base/lock";
1346 open APTGET_LOCK, '>', $lockfile or confess "open $lockfile: $!";
1347 flock APTGET_LOCK, LOCK_EX or confess "lock $lockfile: $!";
1350 sub aptget_prep ($) {
1352 return if defined $aptget_base;
1354 badcfg __ "aptget archive query method takes no data part"
1357 my $cache = $ENV{XDG_CACHE_DIR} // "$ENV{HOME}/.cache";
1360 ensuredir "$cache/dgit";
1362 access_cfg('aptget-cachekey','RETURN-UNDEF')
1363 // access_nomdistro();
1365 $aptget_base = "$cache/dgit/aptget";
1366 ensuredir $aptget_base;
1368 my $quoted_base = $aptget_base;
1369 confess "$quoted_base contains bad chars, cannot continue"
1370 if $quoted_base =~ m/["\\]/; # apt.conf(5) says no escaping :-/
1372 ensuredir $aptget_base;
1374 aptget_lock_acquire();
1376 aptget_cache_clean();
1378 $aptget_configpath = "$aptget_base/apt.conf#$cachekey";
1379 my $sourceslist = "source.list#$cachekey";
1381 my $aptsuites = $isuite;
1382 cfg_apply_map(\$aptsuites, 'suite map',
1383 access_cfg('aptget-suite-map', 'RETURN-UNDEF'));
1385 open SRCS, ">", "$aptget_base/$sourceslist" or confess "$!";
1386 printf SRCS "deb-src %s %s %s\n",
1387 access_cfg('mirror'),
1389 access_cfg('aptget-components')
1392 ensuredir "$aptget_base/cache";
1393 ensuredir "$aptget_base/lists";
1395 open CONF, ">", $aptget_configpath or confess "$!";
1397 Debug::NoLocking "true";
1398 APT::Get::List-Cleanup "false";
1399 #clear APT::Update::Post-Invoke-Success;
1400 Dir::Etc::SourceList "$quoted_base/$sourceslist";
1401 Dir::State::Lists "$quoted_base/lists";
1402 Dir::Etc::preferences "$quoted_base/preferences";
1403 Dir::Cache::srcpkgcache "$quoted_base/cache/srcs#$cachekey";
1404 Dir::Cache::pkgcache "$quoted_base/cache/pkgs#$cachekey";
1407 foreach my $key (qw(
1410 Dir::Cache::Archives
1411 Dir::Etc::SourceParts
1412 Dir::Etc::preferencesparts
1414 ensuredir "$aptget_base/$key";
1415 print CONF "$key \"$quoted_base/$key\";\n" or confess "$!";
1418 my $oldatime = (time // confess "$!") - 1;
1419 foreach my $oldlist (<$aptget_base/lists/*Release>) {
1420 next unless stat_exists $oldlist;
1421 my ($mtime) = (stat _)[9];
1422 utime $oldatime, $mtime, $oldlist or die "$oldlist $!";
1425 runcmd_ordryrun_local aptget_aptget(), qw(update);
1428 foreach my $oldlist (<$aptget_base/lists/*Release>) {
1429 next unless stat_exists $oldlist;
1430 my ($atime) = (stat _)[8];
1431 next if $atime == $oldatime;
1432 push @releasefiles, $oldlist;
1434 my @inreleasefiles = grep { m#/InRelease$# } @releasefiles;
1435 @releasefiles = @inreleasefiles if @inreleasefiles;
1436 if (!@releasefiles) {
1437 fail f_ <<END, $isuite, $cache;
1438 apt seemed to not to update dgit's cached Release files for %s.
1440 is on a filesystem mounted `noatime'; if so, please use `relatime'.)
1443 confess "apt updated too many Release files (@releasefiles), erk"
1444 unless @releasefiles == 1;
1446 ($aptget_releasefile) = @releasefiles;
1449 sub canonicalise_suite_aptget {
1450 my ($proto,$data) = @_;
1453 my $release = parsecontrol $aptget_releasefile, "Release file", 1;
1455 foreach my $name (qw(Codename Suite)) {
1456 my $val = $release->{$name};
1458 printdebug "release file $name: $val\n";
1459 cfg_apply_map(\$val, 'suite rmap',
1460 access_cfg('aptget-suite-rmap', 'RETURN-UNDEF'));
1461 $val =~ m/^$suite_re$/o or fail f_
1462 "Release file (%s) specifies intolerable %s",
1463 $aptget_releasefile, $name;
1470 sub archive_query_aptget {
1471 my ($proto,$data) = @_;
1474 ensuredir "$aptget_base/source";
1475 foreach my $old (<$aptget_base/source/*.dsc>) {
1476 unlink $old or die "$old: $!";
1479 my $showsrc = cmdoutput aptget_aptcache(), qw(showsrc), $package;
1480 return () unless $showsrc =~ m/^package:\s*\Q$package\E\s*$/mi;
1481 # avoids apt-get source failing with ambiguous error code
1483 runcmd_ordryrun_local
1484 shell_cmd 'cd "$1"/source; shift', $aptget_base,
1485 aptget_aptget(), qw(--download-only --only-source source), $package;
1487 my @dscs = <$aptget_base/source/*.dsc>;
1488 fail __ "apt-get source did not produce a .dsc" unless @dscs;
1489 fail f_ "apt-get source produced several .dscs (%s)", "@dscs"
1492 my $pre_dsc = parsecontrol $dscs[0], $dscs[0], 1;
1495 my $uri = "file://". uri_escape $dscs[0];
1496 $uri =~ s{\%2f}{/}gi;
1497 return [ (getfield $pre_dsc, 'Version'), $uri ];
1500 sub file_in_archive_aptget () { return undef; }
1501 sub package_not_wholly_new_aptget () { return undef; }
1503 #---------- `dummyapicat' archive query method ----------
1504 # (untranslated, because this is for testing purposes etc.)
1506 sub archive_query_dummycatapi { archive_query_ftpmasterapi @_; }
1507 sub canonicalise_suite_dummycatapi { canonicalise_suite_ftpmasterapi @_; }
1509 sub dummycatapi_run_in_mirror ($@) {
1510 # runs $fn with FIA open onto rune
1511 my ($rune, $argl, $fn) = @_;
1513 my $mirror = access_cfg('mirror');
1514 $mirror =~ s#^file://#/# or die "$mirror ?";
1515 my @cmd = (qw(sh -ec), 'cd "$1"; shift'."\n".$rune,
1516 qw(x), $mirror, @$argl);
1517 debugcmd "-|", @cmd;
1518 open FIA, "-|", @cmd or confess "$!";
1520 close FIA or ($!==0 && $?==141) or die failedcmd @cmd;
1524 sub file_in_archive_dummycatapi ($$$) {
1525 my ($proto,$data,$filename) = @_;
1527 dummycatapi_run_in_mirror '
1528 find -name "$1" -print0 |
1530 ', [$filename], sub {
1533 printdebug "| $_\n";
1534 m/^(\w+) (\S+)$/ or die "$_ ?";
1535 push @out, { sha256sum => $1, filename => $2 };
1541 sub package_not_wholly_new_dummycatapi {
1542 my ($proto,$data,$pkg) = @_;
1543 dummycatapi_run_in_mirror "
1544 find -name ${pkg}_*.dsc
1551 #---------- `madison' archive query method ----------
1553 sub archive_query_madison {
1554 return archive_query_prepend_mirror
1555 map { [ @$_[0..1] ] } madison_get_parse(@_);
1558 sub madison_get_parse {
1559 my ($proto,$data) = @_;
1560 die unless $proto eq 'madison';
1561 if (!length $data) {
1562 $data= access_cfg('madison-distro','RETURN-UNDEF');
1563 $data //= access_basedistro();
1565 $rmad{$proto,$data,$package} ||= cmdoutput
1566 qw(rmadison -asource),"-s$isuite","-u$data",$package;
1567 my $rmad = $rmad{$proto,$data,$package};
1570 foreach my $l (split /\n/, $rmad) {
1571 $l =~ m{^ \s*( [^ \t|]+ )\s* \|
1572 \s*( [^ \t|]+ )\s* \|
1573 \s*( [^ \t|/]+ )(?:/([^ \t|/]+))? \s* \|
1574 \s*( [^ \t|]+ )\s* }x or die "$rmad ?";
1575 $1 eq $package or die "$rmad $package ?";
1582 $component = access_cfg('archive-query-default-component');
1584 $5 eq 'source' or die "$rmad ?";
1585 push @out, [$vsn,pool_dsc_subpath($vsn,$component),$newsuite];
1587 return sort { -version_compare($a->[0],$b->[0]); } @out;
1590 sub canonicalise_suite_madison {
1591 # madison canonicalises for us
1592 my @r = madison_get_parse(@_);
1594 "unable to canonicalise suite using package %s".
1595 " which does not appear to exist in suite %s;".
1596 " --existing-package may help",
1601 sub file_in_archive_madison { return undef; }
1602 sub package_not_wholly_new_madison { return undef; }
1604 #---------- `sshpsql' archive query method ----------
1605 # (untranslated, because this is obsolete)
1608 my ($data,$runeinfo,$sql) = @_;
1609 if (!length $data) {
1610 $data= access_someuserhost('sshpsql').':'.
1611 access_cfg('sshpsql-dbname');
1613 $data =~ m/:/ or badcfg "invalid sshpsql method string \`$data'";
1614 my ($userhost,$dbname) = ($`,$'); #';
1616 my @cmd = (access_cfg_ssh, $userhost,
1617 access_runeinfo("ssh-psql $runeinfo").
1618 " export LC_MESSAGES=C; export LC_CTYPE=C;".
1619 " ".shellquote qw(psql -A), $dbname, qw(-c), $sql);
1621 open P, "-|", @cmd or confess "$!";
1624 printdebug(">|$_|\n");
1627 $!=0; $?=0; close P or failedcmd @cmd;
1629 my $nrows = pop @rows;
1630 $nrows =~ s/^\((\d+) rows?\)$/$1/ or die "$nrows ?";
1631 @rows == $nrows+1 or die "$nrows ".(scalar @rows)." ?";
1632 @rows = map { [ split /\|/, $_ ] } @rows;
1633 my $ncols = scalar @{ shift @rows };
1634 die if grep { scalar @$_ != $ncols } @rows;
1638 sub sql_injection_check {
1639 foreach (@_) { die "$_ $& ?" if m{[^-+=:_.,/0-9a-zA-Z]}; }
1642 sub archive_query_sshpsql ($$) {
1643 my ($proto,$data) = @_;
1644 sql_injection_check $isuite, $package;
1645 my @rows = sshpsql($data, "archive-query $isuite $package", <<END);
1646 SELECT source.version, component.name, files.filename, files.sha256sum
1648 JOIN src_associations ON source.id = src_associations.source
1649 JOIN suite ON suite.id = src_associations.suite
1650 JOIN dsc_files ON dsc_files.source = source.id
1651 JOIN files_archive_map ON files_archive_map.file_id = dsc_files.file
1652 JOIN component ON component.id = files_archive_map.component_id
1653 JOIN files ON files.id = dsc_files.file
1654 WHERE ( suite.suite_name='$isuite' OR suite.codename='$isuite' )
1655 AND source.source='$package'
1656 AND files.filename LIKE '%.dsc';
1658 @rows = sort { -version_compare($a->[0],$b->[0]) } @rows;
1659 my $digester = Digest::SHA->new(256);
1661 my ($vsn,$component,$filename,$sha256sum) = @$_;
1662 [ $vsn, "/pool/$component/$filename",$digester,$sha256sum ];
1664 return archive_query_prepend_mirror @rows;
1667 sub canonicalise_suite_sshpsql ($$) {
1668 my ($proto,$data) = @_;
1669 sql_injection_check $isuite;
1670 my @rows = sshpsql($data, "canonicalise-suite $isuite", <<END);
1671 SELECT suite.codename
1672 FROM suite where suite_name='$isuite' or codename='$isuite';
1674 @rows = map { $_->[0] } @rows;
1675 fail "unknown suite $isuite" unless @rows;
1676 die "ambiguous $isuite: @rows ?" if @rows>1;
1680 sub file_in_archive_sshpsql ($$$) { return undef; }
1681 sub package_not_wholly_new_sshpsql ($$$) { return undef; }
1683 #---------- `dummycat' archive query method ----------
1684 # (untranslated, because this is for testing purposes etc.)
1686 sub canonicalise_suite_dummycat ($$) {
1687 my ($proto,$data) = @_;
1688 my $dpath = "$data/suite.$isuite";
1689 if (!open C, "<", $dpath) {
1690 $!==ENOENT or die "$dpath: $!";
1691 printdebug "dummycat canonicalise_suite $isuite $dpath ENOENT\n";
1695 chomp or die "$dpath: $!";
1697 printdebug "dummycat canonicalise_suite $isuite $dpath = $_\n";
1701 sub archive_query_dummycat ($$) {
1702 my ($proto,$data) = @_;
1703 canonicalise_suite();
1704 my $dpath = "$data/package.$csuite.$package";
1705 if (!open C, "<", $dpath) {
1706 $!==ENOENT or die "$dpath: $!";
1707 printdebug "dummycat query $csuite $package $dpath ENOENT\n";
1715 printdebug "dummycat query $csuite $package $dpath | $_\n";
1716 my @row = split /\s+/, $_;
1717 @row==2 or die "$dpath: $_ ?";
1720 C->error and die "$dpath: $!";
1722 return archive_query_prepend_mirror
1723 sort { -version_compare($a->[0],$b->[0]); } @rows;
1726 sub file_in_archive_dummycat () { return undef; }
1727 sub package_not_wholly_new_dummycat () { return undef; }
1729 #---------- archive query entrypoints and rest of program ----------
1731 sub canonicalise_suite () {
1732 return if defined $csuite;
1733 fail f_ "cannot operate on %s suite", $isuite if $isuite eq 'UNRELEASED';
1734 $csuite = archive_query('canonicalise_suite');
1735 if ($isuite ne $csuite) {
1736 progress f_ "canonical suite name for %s is %s", $isuite, $csuite;
1738 progress f_ "canonical suite name is %s", $csuite;
1742 sub get_archive_dsc () {
1743 canonicalise_suite();
1744 my @vsns = archive_query('archive_query');
1745 foreach my $vinfo (@vsns) {
1746 my ($vsn,$vsn_dscurl,$digester,$digest) = @$vinfo;
1747 $dscurl = $vsn_dscurl;
1748 $dscdata = url_get($dscurl);
1750 $skew_warning_vsn = $vsn if !defined $skew_warning_vsn;
1755 $digester->add($dscdata);
1756 my $got = $digester->hexdigest();
1758 fail f_ "%s has hash %s but archive told us to expect %s",
1759 $dscurl, $got, $digest;
1762 my $fmt = getfield $dsc, 'Format';
1763 $format_ok{$fmt} or forceable_fail [qw(unsupported-source-format)],
1764 f_ "unsupported source format %s, sorry", $fmt;
1766 $dsc_checked = !!$digester;
1767 printdebug "get_archive_dsc: Version ".(getfield $dsc, 'Version')."\n";
1771 printdebug "get_archive_dsc: nothing in archive, returning undef\n";
1774 sub check_for_git ();
1775 sub check_for_git () {
1777 my $how = access_cfg('git-check');
1778 if ($how eq 'ssh-cmd') {
1780 (access_cfg_ssh, access_gituserhost(),
1781 access_runeinfo("git-check $package").
1782 " set -e; cd ".access_cfg('git-path').";".
1783 " if test -d $package.git; then echo 1; else echo 0; fi");
1784 my $r= cmdoutput @cmd;
1785 if (defined $r and $r =~ m/^divert (\w+)$/) {
1787 my ($usedistro,) = access_distros();
1788 # NB that if we are pushing, $usedistro will be $distro/push
1789 $instead_distro= cfg("dgit-distro.$usedistro.diverts.$divert");
1790 $instead_distro =~ s{^/}{ access_basedistro()."/" }e;
1791 progress f_ "diverting to %s (using config for %s)",
1792 $divert, $instead_distro;
1793 return check_for_git();
1795 failedcmd @cmd unless defined $r and $r =~ m/^[01]$/;
1797 } elsif ($how eq 'url') {
1798 my $prefix = access_cfg('git-check-url','git-url');
1799 my $suffix = access_cfg('git-check-suffix','git-suffix',
1800 'RETURN-UNDEF') // '.git';
1801 my $url = "$prefix/$package$suffix";
1802 my @cmd = (@curl, qw(-sS -I), $url);
1803 my $result = cmdoutput @cmd;
1804 $result =~ s/^\S+ 200 .*\n\r?\n//;
1805 # curl -sS -I with https_proxy prints
1806 # HTTP/1.0 200 Connection established
1807 $result =~ m/^\S+ (404|200) /s or
1808 fail +(__ "unexpected results from git check query - ").
1809 Dumper($prefix, $result);
1811 if ($code eq '404') {
1813 } elsif ($code eq '200') {
1818 } elsif ($how eq 'true') {
1820 } elsif ($how eq 'false') {
1823 badcfg f_ "unknown git-check \`%s'", $how;
1827 sub create_remote_git_repo () {
1828 my $how = access_cfg('git-create');
1829 if ($how eq 'ssh-cmd') {
1831 (access_cfg_ssh, access_gituserhost(),
1832 access_runeinfo("git-create $package").
1833 "set -e; cd ".access_cfg('git-path').";".
1834 " cp -a _template $package.git");
1835 } elsif ($how eq 'true') {
1838 badcfg f_ "unknown git-create \`%s'", $how;
1842 our ($dsc_hash,$lastpush_mergeinput);
1843 our ($dsc_distro, $dsc_hint_tag, $dsc_hint_url);
1847 dgit_privdir(); # ensures that $dgit_privdir_made is based on $maindir
1848 $playground = fresh_playground 'dgit/unpack';
1851 sub mktree_in_ud_here () {
1855 sub git_write_tree () {
1856 my $tree = cmdoutput @git, qw(write-tree);
1857 $tree =~ m/^\w+$/ or die "$tree ?";
1861 sub git_add_write_tree () {
1862 runcmd @git, qw(add -Af .);
1863 return git_write_tree();
1866 sub remove_stray_gits ($) {
1868 my @gitscmd = qw(find -name .git -prune -print0);
1869 debugcmd "|",@gitscmd;
1870 open GITS, "-|", @gitscmd or confess "$!";
1875 print STDERR f_ "%s: warning: removing from %s: %s\n",
1876 $us, $what, (messagequote $_);
1880 $!=0; $?=0; close GITS or failedcmd @gitscmd;
1883 sub mktree_in_ud_from_only_subdir ($;$) {
1884 my ($what,$raw) = @_;
1885 # changes into the subdir
1888 confess "expected one subdir but found @dirs ?" unless @dirs==1;
1889 $dirs[0] =~ m#^([^/]+)/\.$# or die;
1893 remove_stray_gits($what);
1894 mktree_in_ud_here();
1896 my ($format, $fopts) = get_source_format();
1897 if (madformat($format)) {
1902 my $tree=git_add_write_tree();
1903 return ($tree,$dir);
1906 our @files_csum_info_fields =
1907 (['Checksums-Sha256','Digest::SHA', 'new(256)', 'sha256sum'],
1908 ['Checksums-Sha1', 'Digest::SHA', 'new(1)', 'sha1sum'],
1909 ['Files', 'Digest::MD5', 'new()', 'md5sum']);
1911 sub dsc_files_info () {
1912 foreach my $csumi (@files_csum_info_fields) {
1913 my ($fname, $module, $method) = @$csumi;
1914 my $field = $dsc->{$fname};
1915 next unless defined $field;
1916 eval "use $module; 1;" or die $@;
1918 foreach (split /\n/, $field) {
1920 m/^(\w+) (\d+) (\S+)$/ or
1921 fail f_ "could not parse .dsc %s line \`%s'", $fname, $_;
1922 my $digester = eval "$module"."->$method;" or die $@;
1927 Digester => $digester,
1932 fail f_ "missing any supported Checksums-* or Files field in %s",
1933 $dsc->get_option('name');
1937 map { $_->{Filename} } dsc_files_info();
1940 sub files_compare_inputs (@) {
1945 my $showinputs = sub {
1946 return join "; ", map { $_->get_option('name') } @$inputs;
1949 foreach my $in (@$inputs) {
1951 my $in_name = $in->get_option('name');
1953 printdebug "files_compare_inputs $in_name\n";
1955 foreach my $csumi (@files_csum_info_fields) {
1956 my ($fname) = @$csumi;
1957 printdebug "files_compare_inputs $in_name $fname\n";
1959 my $field = $in->{$fname};
1960 next unless defined $field;
1963 foreach (split /\n/, $field) {
1966 my ($info, $f) = m/^(\w+ \d+) (?:\S+ \S+ )?(\S+)$/ or
1967 fail "could not parse $in_name $fname line \`$_'";
1969 printdebug "files_compare_inputs $in_name $fname $f\n";
1973 my $re = \ $record{$f}{$fname};
1975 $fchecked{$f}{$in_name} = 1;
1978 "hash or size of %s varies in %s fields (between: %s)",
1979 $f, $fname, $showinputs->();
1984 @files = sort @files;
1985 $expected_files //= \@files;
1986 "@$expected_files" eq "@files" or
1987 fail f_ "file list in %s varies between hash fields!",
1991 fail f_ "%s has no files list field(s)", $in_name;
1993 printdebug "files_compare_inputs ".Dumper(\%fchecked, \%record)
1996 grep { keys %$_ == @$inputs-1 } values %fchecked
1997 or fail f_ "no file appears in all file lists (looked in: %s)",
2001 sub is_orig_file_in_dsc ($$) {
2002 my ($f, $dsc_files_info) = @_;
2003 return 0 if @$dsc_files_info <= 1;
2004 # One file means no origs, and the filename doesn't have a "what
2005 # part of dsc" component. (Consider versions ending `.orig'.)
2006 return 0 unless $f =~ m/\.$orig_f_tail_re$/o;
2010 # This function determines whether a .changes file is source-only from
2011 # the point of view of dak. Thus, it permits *_source.buildinfo
2014 # It does not, however, permit any other buildinfo files. After a
2015 # source-only upload, the buildds will try to upload files like
2016 # foo_1.2.3_amd64.buildinfo. If the package maintainer included files
2017 # named like this in their (otherwise) source-only upload, the uploads
2018 # of the buildd can be rejected by dak. Fixing the resultant
2019 # situation can require manual intervention. So we block such
2020 # .buildinfo files when the user tells us to perform a source-only
2021 # upload (such as when using the push-source subcommand with the -C
2022 # option, which calls this function).
2024 # Note, though, that when dgit is told to prepare a source-only
2025 # upload, such as when subcommands like build-source and push-source
2026 # without -C are used, dgit has a more restrictive notion of
2027 # source-only .changes than dak: such uploads will never include
2028 # *_source.buildinfo files. This is because there is no use for such
2029 # files when using a tool like dgit to produce the source package, as
2030 # dgit ensures the source is identical to git HEAD.
2031 sub test_source_only_changes ($) {
2033 foreach my $l (split /\n/, getfield $changes, 'Files') {
2034 $l =~ m/\S+$/ or next;
2035 # \.tar\.[a-z0-9]+ covers orig.tar and the tarballs in native packages
2036 unless ($& =~ m/(?:\.dsc|\.diff\.gz|\.tar\.[a-z0-9]+|_source\.buildinfo)$/) {
2037 print f_ "purportedly source-only changes polluted by %s\n", $&;
2044 sub changes_update_origs_from_dsc ($$$$) {
2045 my ($dsc, $changes, $upstreamvsn, $changesfile) = @_;
2047 printdebug "checking origs needed ($upstreamvsn)...\n";
2048 $_ = getfield $changes, 'Files';
2049 m/^\w+ \d+ (\S+ \S+) \S+$/m or
2050 fail __ "cannot find section/priority from .changes Files field";
2051 my $placementinfo = $1;
2053 printdebug "checking origs needed placement '$placementinfo'...\n";
2054 foreach my $l (split /\n/, getfield $dsc, 'Files') {
2055 $l =~ m/\S+$/ or next;
2057 printdebug "origs $file | $l\n";
2058 next unless is_orig_file_of_vsn $file, $upstreamvsn;
2059 printdebug "origs $file is_orig\n";
2060 my $have = archive_query('file_in_archive', $file);
2061 if (!defined $have) {
2062 print STDERR __ <<END;
2063 archive does not support .orig check; hope you used --ch:--sa/-sd if needed
2069 printdebug "origs $file \$#\$have=$#$have\n";
2070 foreach my $h (@$have) {
2073 foreach my $csumi (@files_csum_info_fields) {
2074 my ($fname, $module, $method, $archivefield) = @$csumi;
2075 next unless defined $h->{$archivefield};
2076 $_ = $dsc->{$fname};
2077 next unless defined;
2078 m/^(\w+) .* \Q$file\E$/m or
2079 fail f_ ".dsc %s missing entry for %s", $fname, $file;
2080 if ($h->{$archivefield} eq $1) {
2084 "%s: %s (archive) != %s (local .dsc)",
2085 $archivefield, $h->{$archivefield}, $1;
2088 confess "$file ".Dumper($h)." ?!" if $same && @differ;
2092 f_ "archive %s: %s", $h->{filename}, join "; ", @differ
2095 printdebug "origs $file f.same=$found_same".
2096 " #f._differ=$#found_differ\n";
2097 if (@found_differ && !$found_same) {
2099 (f_ "archive contains %s with different checksum", $file),
2102 # Now we edit the changes file to add or remove it
2103 foreach my $csumi (@files_csum_info_fields) {
2104 my ($fname, $module, $method, $archivefield) = @$csumi;
2105 next unless defined $changes->{$fname};
2107 # in archive, delete from .changes if it's there
2108 $changed{$file} = "removed" if
2109 $changes->{$fname} =~ s/\n.* \Q$file\E$(?:)$//m;
2110 } elsif ($changes->{$fname} =~ m/^.* \Q$file\E$(?:)$/m) {
2111 # not in archive, but it's here in the .changes
2113 my $dsc_data = getfield $dsc, $fname;
2114 $dsc_data =~ m/^(.* \Q$file\E$)$/m or die "$dsc_data $file ?";
2116 $extra =~ s/ \d+ /$&$placementinfo /
2117 or confess "$fname $extra >$dsc_data< ?"
2118 if $fname eq 'Files';
2119 $changes->{$fname} .= "\n". $extra;
2120 $changed{$file} = "added";
2125 foreach my $file (keys %changed) {
2127 "edited .changes for archive .orig contents: %s %s",
2128 $changed{$file}, $file;
2130 my $chtmp = "$changesfile.tmp";
2131 $changes->save($chtmp);
2133 rename $chtmp,$changesfile or die "$changesfile $!";
2135 progress f_ "[new .changes left in %s]", $changesfile;
2138 progress f_ "%s already has appropriate .orig(s) (if any)",
2143 sub clogp_authline ($) {
2145 my $author = getfield $clogp, 'Maintainer';
2146 if ($author =~ m/^[^"\@]+\,/) {
2147 # single entry Maintainer field with unquoted comma
2148 $author = ($& =~ y/,//rd).$'; # strip the comma
2150 # git wants a single author; any remaining commas in $author
2151 # are by now preceded by @ (or "). It seems safer to punt on
2152 # "..." for now rather than attempting to dequote or something.
2153 $author =~ s#,.*##ms unless $author =~ m/"/;
2154 my $date = cmdoutput qw(date), '+%s %z', qw(-d), getfield($clogp,'Date');
2155 my $authline = "$author $date";
2156 $authline =~ m/$git_authline_re/o or
2157 fail f_ "unexpected commit author line format \`%s'".
2158 " (was generated from changelog Maintainer field)",
2160 return ($1,$2,$3) if wantarray;
2164 sub vendor_patches_distro ($$) {
2165 my ($checkdistro, $what) = @_;
2166 return unless defined $checkdistro;
2168 my $series = "debian/patches/\L$checkdistro\E.series";
2169 printdebug "checking for vendor-specific $series ($what)\n";
2171 if (!open SERIES, "<", $series) {
2172 confess "$series $!" unless $!==ENOENT;
2179 print STDERR __ <<END;
2181 Unfortunately, this source package uses a feature of dpkg-source where
2182 the same source package unpacks to different source code on different
2183 distros. dgit cannot safely operate on such packages on affected
2184 distros, because the meaning of source packages is not stable.
2186 Please ask the distro/maintainer to remove the distro-specific series
2187 files and use a different technique (if necessary, uploading actually
2188 different packages, if different distros are supposed to have
2192 fail f_ "Found active distro-specific series file for".
2193 " %s (%s): %s, cannot continue",
2194 $checkdistro, $what, $series;
2196 die "$series $!" if SERIES->error;
2200 sub check_for_vendor_patches () {
2201 # This dpkg-source feature doesn't seem to be documented anywhere!
2202 # But it can be found in the changelog (reformatted):
2204 # commit 4fa01b70df1dc4458daee306cfa1f987b69da58c
2205 # Author: Raphael Hertzog <hertzog@debian.org>
2206 # Date: Sun Oct 3 09:36:48 2010 +0200
2208 # dpkg-source: correctly create .pc/.quilt_series with alternate
2211 # If you have debian/patches/ubuntu.series and you were
2212 # unpacking the source package on ubuntu, quilt was still
2213 # directed to debian/patches/series instead of
2214 # debian/patches/ubuntu.series.
2216 # debian/changelog | 3 +++
2217 # scripts/Dpkg/Source/Package/V3/quilt.pm | 4 +++-
2218 # 2 files changed, 6 insertions(+), 1 deletion(-)
2221 vendor_patches_distro($ENV{DEB_VENDOR}, "DEB_VENDOR");
2222 vendor_patches_distro(Dpkg::Vendor::get_current_vendor(),
2223 __ "Dpkg::Vendor \`current vendor'");
2224 vendor_patches_distro(access_basedistro(),
2225 __ "(base) distro being accessed");
2226 vendor_patches_distro(access_nomdistro(),
2227 __ "(nominal) distro being accessed");
2230 sub check_bpd_exists () {
2231 stat $buildproductsdir
2232 or fail f_ "build-products-dir %s is not accessible: %s\n",
2233 $buildproductsdir, $!;
2236 sub dotdot_bpd_transfer_origs ($$$) {
2237 my ($bpd_abs, $upstreamversion, $wanted) = @_;
2238 # checks is_orig_file_of_vsn and if
2239 # calls $wanted->{$leaf} and expects boolish
2241 return if $buildproductsdir eq '..';
2244 my $dotdot = $maindir;
2245 $dotdot =~ s{/[^/]+$}{};
2246 opendir DD, $dotdot or fail "opendir .. ($dotdot): $!";
2247 while ($!=0, defined(my $leaf = readdir DD)) {
2249 local ($debuglevel) = $debuglevel-1;
2250 printdebug "DD_BPD $leaf ?\n";
2252 next unless is_orig_file_of_vsn $leaf, $upstreamversion;
2253 next unless $wanted->($leaf);
2254 next if lstat "$bpd_abs/$leaf";
2257 "%s: found orig(s) in .. missing from build-products-dir, transferring:\n",
2260 $! == &ENOENT or fail f_
2261 "check orig file %s in bpd %s: %s", $leaf, $bpd_abs, $!;
2262 lstat "$dotdot/$leaf" or fail f_
2263 "check orig file %s in ..: %s", $leaf, $!;
2265 stat "$dotdot/$leaf" or fail f_
2266 "check target of orig symlink %s in ..: %s", $leaf, $!;
2267 my $ltarget = readlink "$dotdot/$leaf" or
2268 die "readlink $dotdot/$leaf: $!";
2269 if ($ltarget !~ m{^/}) {
2270 $ltarget = "$dotdot/$ltarget";
2272 symlink $ltarget, "$bpd_abs/$leaf"
2273 or die "$ltarget $bpd_abs $leaf: $!";
2275 "%s: cloned orig symlink from ..: %s\n",
2277 } elsif (link "$dotdot/$leaf", "$bpd_abs/$leaf") {
2279 "%s: hardlinked orig from ..: %s\n",
2281 } elsif ($! != EXDEV) {
2282 fail f_ "failed to make %s a hardlink to %s: %s",
2283 "$bpd_abs/$leaf", "$dotdot/$leaf", $!;
2285 symlink "$bpd_abs/$leaf", "$dotdot/$leaf"
2286 or die "$bpd_abs $dotdot $leaf $!";
2288 "%s: symmlinked orig from .. on other filesystem: %s\n",
2292 die "$dotdot; $!" if $!;
2296 sub import_tarball_tartrees ($$) {
2297 my ($upstreamv, $dfi) = @_;
2298 # cwd should be the playground
2300 # We unpack and record the orig tarballs first, so that we only
2301 # need disk space for one private copy of the unpacked source.
2302 # But we can't make them into commits until we have the metadata
2303 # from the debian/changelog, so we record the tree objects now and
2304 # make them into commits later.
2306 my $orig_f_base = srcfn $upstreamv, '';
2308 foreach my $fi (@$dfi) {
2309 # We actually import, and record as a commit, every tarball
2310 # (unless there is only one file, in which case there seems
2313 my $f = $fi->{Filename};
2314 printdebug "import considering $f ";
2315 (printdebug "not tar\n"), next unless $f =~ m/\.tar(\.\w+)?$/;
2316 (printdebug "signature\n"), next if $f =~ m/$orig_f_sig_re$/o;
2320 $f =~ m/^\Q$orig_f_base\E\.([^._]+)?\.tar(?:\.\w+)?$/;
2322 printdebug "Y ", (join ' ', map { $_//"(none)" }
2323 $compr_ext, $orig_f_part
2326 my $path = $fi->{Path} // $f;
2327 my $input = new IO::File $f, '<' or die "$f $!";
2331 if (defined $compr_ext) {
2333 Dpkg::Compression::compression_guess_from_filename $f;
2334 fail "Dpkg::Compression cannot handle file $f in source package"
2335 if defined $compr_ext && !defined $cname;
2337 new Dpkg::Compression::Process compression => $cname;
2338 @compr_cmd = $compr_proc->get_uncompress_cmdline();
2339 my $compr_fh = new IO::Handle;
2340 my $compr_pid = open $compr_fh, "-|" // confess "$!";
2342 open STDIN, "<&", $input or confess "$!";
2344 die "dgit (child): exec $compr_cmd[0]: $!\n";
2349 rmtree "_unpack-tar";
2350 mkdir "_unpack-tar" or confess "$!";
2351 my @tarcmd = qw(tar -x -f -
2352 --no-same-owner --no-same-permissions
2353 --no-acls --no-xattrs --no-selinux);
2354 my $tar_pid = fork // confess "$!";
2356 chdir "_unpack-tar" or confess "$!";
2357 open STDIN, "<&", $input or confess "$!";
2359 die f_ "dgit (child): exec %s: %s", $tarcmd[0], $!;
2361 $!=0; (waitpid $tar_pid, 0) == $tar_pid or confess "$!";
2362 !$? or failedcmd @tarcmd;
2365 (@compr_cmd ? ($?==SIGPIPE || failedcmd @compr_cmd)
2367 # finally, we have the results in "tarball", but maybe
2368 # with the wrong permissions
2370 runcmd qw(chmod -R +rwX _unpack-tar);
2371 changedir "_unpack-tar";
2372 remove_stray_gits($f);
2373 mktree_in_ud_here();
2375 my ($tree) = git_add_write_tree();
2376 my $tentries = cmdoutput @git, qw(ls-tree -z), $tree;
2377 if ($tentries =~ m/^\d+ tree (\w+)\t[^\000]+\000$/s) {
2379 printdebug "one subtree $1\n";
2381 printdebug "multiple subtrees\n";
2384 rmtree "_unpack-tar";
2386 my $ent = [ $f, $tree ];
2388 Orig => !!$orig_f_part,
2389 Sort => (!$orig_f_part ? 2 :
2390 $orig_f_part =~ m/-/g ? 1 :
2392 OrigPart => $orig_f_part, # 'orig', 'orig-XXX', or undef
2399 # put any without "_" first (spec is not clear whether files
2400 # are always in the usual order). Tarballs without "_" are
2401 # the main orig or the debian tarball.
2402 $a->{Sort} <=> $b->{Sort} or
2409 sub import_tarball_commits ($$) {
2410 my ($tartrees, $upstreamv) = @_;
2411 # cwd should be a playtree which has a relevant debian/changelog
2412 # fills in $tt->{Commit} for each one
2414 my $any_orig = grep { $_->{Orig} } @$tartrees;
2416 my @clogcmd = qw(dpkg-parsechangelog --format rfc822 --all);
2420 printdebug "import clog search...\n";
2421 parsechangelog_loop \@clogcmd, (__ "package changelog"), sub {
2422 my ($thisstanza, $desc) = @_;
2423 no warnings qw(exiting);
2425 $clogp //= $thisstanza;
2427 printdebug "import clog $thisstanza->{version} $desc...\n";
2429 last if !$any_orig; # we don't need $r1clogp
2431 # We look for the first (most recent) changelog entry whose
2432 # version number is lower than the upstream version of this
2433 # package. Then the last (least recent) previous changelog
2434 # entry is treated as the one which introduced this upstream
2435 # version and used for the synthetic commits for the upstream
2438 # One might think that a more sophisticated algorithm would be
2439 # necessary. But: we do not want to scan the whole changelog
2440 # file. Stopping when we see an earlier version, which
2441 # necessarily then is an earlier upstream version, is the only
2442 # realistic way to do that. Then, either the earliest
2443 # changelog entry we have seen so far is indeed the earliest
2444 # upload of this upstream version; or there are only changelog
2445 # entries relating to later upstream versions (which is not
2446 # possible unless the changelog and .dsc disagree about the
2447 # version). Then it remains to choose between the physically
2448 # last entry in the file, and the one with the lowest version
2449 # number. If these are not the same, we guess that the
2450 # versions were created in a non-monotonic order rather than
2451 # that the changelog entries have been misordered.
2453 printdebug "import clog $thisstanza->{version} vs $upstreamv...\n";
2455 last if version_compare($thisstanza->{version}, $upstreamv) < 0;
2456 $r1clogp = $thisstanza;
2458 printdebug "import clog $r1clogp->{version} becomes r1\n";
2461 $clogp or fail __ "package changelog has no entries!";
2463 my $authline = clogp_authline $clogp;
2464 my $changes = getfield $clogp, 'Changes';
2465 $changes =~ s/^\n//; # Changes: \n
2466 my $cversion = getfield $clogp, 'Version';
2470 $r1clogp //= $clogp; # maybe there's only one entry;
2471 $r1authline = clogp_authline $r1clogp;
2472 # Strictly, r1authline might now be wrong if it's going to be
2473 # unused because !$any_orig. Whatever.
2475 printdebug "import tartrees authline $authline\n";
2476 printdebug "import tartrees r1authline $r1authline\n";
2478 foreach my $tt (@$tartrees) {
2479 printdebug "import tartree $tt->{F} $tt->{Tree}\n";
2481 # untranslated so that different people's imports are identical
2482 my $mbody = sprintf "Import %s", $tt->{F};
2483 $tt->{Commit} = hash_commit_text($tt->{Orig} ? <<END_O : <<END_T);
2486 committer $r1authline
2490 [dgit import orig $tt->{F}]
2498 [dgit import tarball $package $cversion $tt->{F}]
2503 return ($authline, $r1authline, $clogp, $changes);
2506 sub generate_commits_from_dsc () {
2507 # See big comment in fetch_from_archive, below.
2508 # See also README.dsc-import.
2510 changedir $playground;
2512 my $bpd_abs = bpd_abs();
2513 my $upstreamv = upstreamversion $dsc->{version};
2514 my @dfi = dsc_files_info();
2516 dotdot_bpd_transfer_origs $bpd_abs, $upstreamv,
2517 sub { grep { $_->{Filename} eq $_[0] } @dfi };
2519 foreach my $fi (@dfi) {
2520 my $f = $fi->{Filename};
2521 die "$f ?" if $f =~ m#/|^\.|\.dsc$|\.tmp$#;
2522 my $upper_f = "$bpd_abs/$f";
2524 printdebug "considering reusing $f: ";
2526 if (link_ltarget "$upper_f,fetch", $f) {
2527 printdebug "linked (using ...,fetch).\n";
2528 } elsif ((printdebug "($!) "),
2530 fail f_ "accessing %s: %s", "$buildproductsdir/$f,fetch", $!;
2531 } elsif (link_ltarget $upper_f, $f) {
2532 printdebug "linked.\n";
2533 } elsif ((printdebug "($!) "),
2535 fail f_ "accessing %s: %s", "$buildproductsdir/$f", $!;
2537 printdebug "absent.\n";
2541 complete_file_from_dsc('.', $fi, \$refetched)
2544 printdebug "considering saving $f: ";
2546 if (rename_link_xf 1, $f, $upper_f) {
2547 printdebug "linked.\n";
2548 } elsif ((printdebug "($@) "),
2550 fail f_ "saving %s: %s", "$buildproductsdir/$f", $@;
2551 } elsif (!$refetched) {
2552 printdebug "no need.\n";
2553 } elsif (rename_link_xf 1, $f, "$upper_f,fetch") {
2554 printdebug "linked (using ...,fetch).\n";
2555 } elsif ((printdebug "($@) "),
2557 fail f_ "saving %s: %s", "$buildproductsdir/$f,fetch", $@;
2559 printdebug "cannot.\n";
2564 @tartrees = import_tarball_tartrees($upstreamv, \@dfi)
2565 unless @dfi == 1; # only one file in .dsc
2567 my $dscfn = "$package.dsc";
2569 my $treeimporthow = 'package';
2571 open D, ">", $dscfn or die "$dscfn: $!";
2572 print D $dscdata or die "$dscfn: $!";
2573 close D or die "$dscfn: $!";
2574 my @cmd = qw(dpkg-source);
2575 push @cmd, '--no-check' if $dsc_checked;
2576 if (madformat $dsc->{format}) {
2577 push @cmd, '--skip-patches';
2578 $treeimporthow = 'unpatched';
2580 push @cmd, qw(-x --), $dscfn;
2583 my ($tree,$dir) = mktree_in_ud_from_only_subdir(__ "source package");
2584 if (madformat $dsc->{format}) {
2585 check_for_vendor_patches();
2589 if (madformat $dsc->{format}) {
2590 my @pcmd = qw(dpkg-source --before-build .);
2591 runcmd shell_cmd 'exec >/dev/null', @pcmd;
2593 $dappliedtree = git_add_write_tree();
2596 my ($authline, $r1authline, $clogp, $changes) =
2597 import_tarball_commits(\@tartrees, $upstreamv);
2599 my $cversion = getfield $clogp, 'Version';
2601 printdebug "import main commit\n";
2603 open C, ">../commit.tmp" or confess "$!";
2604 print C <<END or confess "$!";
2607 print C <<END or confess "$!" foreach @tartrees;
2610 print C <<END or confess "$!";
2616 [dgit import $treeimporthow $package $cversion]
2619 close C or confess "$!";
2620 my $rawimport_hash = hash_commit qw(../commit.tmp);
2622 if (madformat $dsc->{format}) {
2623 printdebug "import apply patches...\n";
2625 # regularise the state of the working tree so that
2626 # the checkout of $rawimport_hash works nicely.
2627 my $dappliedcommit = hash_commit_text(<<END);
2634 runcmd @git, qw(checkout -q -b dapplied), $dappliedcommit;
2636 runcmd @git, qw(checkout -q -b unpa), $rawimport_hash;
2638 # We need the answers to be reproducible
2639 my @authline = clogp_authline($clogp);
2640 local $ENV{GIT_COMMITTER_NAME} = $authline[0];
2641 local $ENV{GIT_COMMITTER_EMAIL} = $authline[1];
2642 local $ENV{GIT_COMMITTER_DATE} = $authline[2];
2643 local $ENV{GIT_AUTHOR_NAME} = $authline[0];
2644 local $ENV{GIT_AUTHOR_EMAIL} = $authline[1];
2645 local $ENV{GIT_AUTHOR_DATE} = $authline[2];
2647 my $path = $ENV{PATH} or die;
2649 # we use ../../gbp-pq-output, which (given that we are in
2650 # $playground/PLAYTREE, and $playground is .git/dgit/unpack,
2653 foreach my $use_absurd (qw(0 1)) {
2654 runcmd @git, qw(checkout -q unpa);
2655 runcmd @git, qw(update-ref -d refs/heads/patch-queue/unpa);
2656 local $ENV{PATH} = $path;
2659 progress "warning: $@";
2660 $path = "$absurdity:$path";
2661 progress f_ "%s: trying slow absurd-git-apply...", $us;
2662 rename "../../gbp-pq-output","../../gbp-pq-output.0"
2667 die "forbid absurd git-apply\n" if $use_absurd
2668 && forceing [qw(import-gitapply-no-absurd)];
2669 die "only absurd git-apply!\n" if !$use_absurd
2670 && forceing [qw(import-gitapply-absurd)];
2672 local $ENV{DGIT_ABSURD_DEBUG} = $debuglevel if $use_absurd;
2673 local $ENV{PATH} = $path if $use_absurd;
2675 my @showcmd = (gbp_pq, qw(import));
2676 my @realcmd = shell_cmd
2677 'exec >/dev/null 2>>../../gbp-pq-output', @showcmd;
2678 debugcmd "+",@realcmd;
2679 if (system @realcmd) {
2680 die f_ "%s failed: %s\n",
2681 +(shellquote @showcmd),
2682 failedcmd_waitstatus();
2685 my $gapplied = git_rev_parse('HEAD');
2686 my $gappliedtree = cmdoutput @git, qw(rev-parse HEAD:);
2687 $gappliedtree eq $dappliedtree or
2688 fail f_ <<END, $gapplied, $gappliedtree, $dappliedtree;
2689 gbp-pq import and dpkg-source disagree!
2690 gbp-pq import gave commit %s
2691 gbp-pq import gave tree %s
2692 dpkg-source --before-build gave tree %s
2694 $rawimport_hash = $gapplied;
2699 { local $@; eval { runcmd qw(cat ../../gbp-pq-output); }; }
2704 progress f_ "synthesised git commit from .dsc %s", $cversion;
2706 my $rawimport_mergeinput = {
2707 Commit => $rawimport_hash,
2708 Info => __ "Import of source package",
2710 my @output = ($rawimport_mergeinput);
2712 if ($lastpush_mergeinput) {
2713 my $oldclogp = mergeinfo_getclogp($lastpush_mergeinput);
2714 my $oversion = getfield $oldclogp, 'Version';
2716 version_compare($oversion, $cversion);
2718 @output = ($rawimport_mergeinput, $lastpush_mergeinput,
2719 { ReverseParents => 1,
2720 # untranslated so that different people's pseudomerges
2721 # are not needlessly different (although they will
2722 # still differ if the series of pulls is different)
2723 Message => (sprintf <<END, $package, $cversion, $csuite) });
2724 Record %s (%s) in archive suite %s
2726 } elsif ($vcmp > 0) {
2727 print STDERR f_ <<END, $cversion, $oversion,
2729 Version actually in archive: %s (older)
2730 Last version pushed with dgit: %s (newer or same)
2733 __ $later_warning_msg or confess "$!";
2734 @output = $lastpush_mergeinput;
2736 # Same version. Use what's in the server git branch,
2737 # discarding our own import. (This could happen if the
2738 # server automatically imports all packages into git.)
2739 @output = $lastpush_mergeinput;
2747 sub complete_file_from_dsc ($$;$) {
2748 our ($dstdir, $fi, $refetched) = @_;
2749 # Ensures that we have, in $dstdir, the file $fi, with the correct
2750 # contents. (Downloading it from alongside $dscurl if necessary.)
2751 # If $refetched is defined, can overwrite "$dstdir/$fi->{Filename}"
2752 # and will set $$refetched=1 if it did so (or tried to).
2754 my $f = $fi->{Filename};
2755 my $tf = "$dstdir/$f";
2759 my $checkhash = sub {
2760 open F, "<", "$tf" or die "$tf: $!";
2761 $fi->{Digester}->reset();
2762 $fi->{Digester}->addfile(*F);
2763 F->error and confess "$!";
2764 $got = $fi->{Digester}->hexdigest();
2765 return $got eq $fi->{Hash};
2768 if (stat_exists $tf) {
2769 if ($checkhash->()) {
2770 progress f_ "using existing %s", $f;
2774 fail f_ "file %s has hash %s but .dsc demands hash %s".
2775 " (perhaps you should delete this file?)",
2776 $f, $got, $fi->{Hash};
2778 progress f_ "need to fetch correct version of %s", $f;
2779 unlink $tf or die "$tf $!";
2782 printdebug "$tf does not exist, need to fetch\n";
2786 $furl =~ s{/[^/]+$}{};
2788 die "$f ?" unless $f =~ m/^\Q${package}\E_/;
2789 die "$f ?" if $f =~ m#/#;
2790 runcmd_ordryrun_local @curl,qw(-f -o),$tf,'--',"$furl";
2791 return 0 if !act_local();
2794 fail f_ "file %s has hash %s but .dsc demands hash %s".
2795 " (got wrong file from archive!)",
2796 $f, $got, $fi->{Hash};
2801 sub ensure_we_have_orig () {
2802 my @dfi = dsc_files_info();
2803 foreach my $fi (@dfi) {
2804 my $f = $fi->{Filename};
2805 next unless is_orig_file_in_dsc($f, \@dfi);
2806 complete_file_from_dsc($buildproductsdir, $fi)
2811 #---------- git fetch ----------
2813 sub lrfetchrefs () { return "refs/dgit-fetch/".access_basedistro(); }
2814 sub lrfetchref () { return lrfetchrefs.'/'.server_branch($csuite); }
2816 # We fetch some parts of lrfetchrefs/*. Ideally we delete these
2817 # locally fetched refs because they have unhelpful names and clutter
2818 # up gitk etc. So we track whether we have "used up" head ref (ie,
2819 # whether we have made another local ref which refers to this object).
2821 # (If we deleted them unconditionally, then we might end up
2822 # re-fetching the same git objects each time dgit fetch was run.)
2824 # So, each use of lrfetchrefs needs to be accompanied by arrangements
2825 # in git_fetch_us to fetch the refs in question, and possibly a call
2826 # to lrfetchref_used.
2828 our (%lrfetchrefs_f, %lrfetchrefs_d);
2829 # $lrfetchrefs_X{lrfetchrefs."/heads/whatever"} = $objid
2831 sub lrfetchref_used ($) {
2832 my ($fullrefname) = @_;
2833 my $objid = $lrfetchrefs_f{$fullrefname};
2834 $lrfetchrefs_d{$fullrefname} = $objid if defined $objid;
2837 sub git_lrfetch_sane {
2838 my ($url, $supplementary, @specs) = @_;
2839 # Make a 'refs/'.lrfetchrefs.'/*' be just like on server,
2840 # at least as regards @specs. Also leave the results in
2841 # %lrfetchrefs_f, and arrange for lrfetchref_used to be
2842 # able to clean these up.
2844 # With $supplementary==1, @specs must not contain wildcards
2845 # and we add to our previous fetches (non-atomically).
2847 # This is rather miserable:
2848 # When git fetch --prune is passed a fetchspec ending with a *,
2849 # it does a plausible thing. If there is no * then:
2850 # - it matches subpaths too, even if the supplied refspec
2851 # starts refs, and behaves completely madly if the source
2852 # has refs/refs/something. (See, for example, Debian #NNNN.)
2853 # - if there is no matching remote ref, it bombs out the whole
2855 # We want to fetch a fixed ref, and we don't know in advance
2856 # if it exists, so this is not suitable.
2858 # Our workaround is to use git ls-remote. git ls-remote has its
2859 # own qairks. Notably, it has the absurd multi-tail-matching
2860 # behaviour: git ls-remote R refs/foo can report refs/foo AND
2861 # refs/refs/foo etc.
2863 # Also, we want an idempotent snapshot, but we have to make two
2864 # calls to the remote: one to git ls-remote and to git fetch. The
2865 # solution is use git ls-remote to obtain a target state, and
2866 # git fetch to try to generate it. If we don't manage to generate
2867 # the target state, we try again.
2869 printdebug "git_lrfetch_sane suppl=$supplementary specs @specs\n";
2871 my $specre = join '|', map {
2874 my $wildcard = $x =~ s/\\\*$/.*/;
2875 die if $wildcard && $supplementary;
2878 printdebug "git_lrfetch_sane specre=$specre\n";
2879 my $wanted_rref = sub {
2881 return m/^(?:$specre)$/;
2884 my $fetch_iteration = 0;
2887 printdebug "git_lrfetch_sane iteration $fetch_iteration\n";
2888 if (++$fetch_iteration > 10) {
2889 fail __ "too many iterations trying to get sane fetch!";
2892 my @look = map { "refs/$_" } @specs;
2893 my @lcmd = (@git, qw(ls-remote -q --refs), $url, @look);
2897 open GITLS, "-|", @lcmd or confess "$!";
2899 printdebug "=> ", $_;
2900 m/^(\w+)\s+(\S+)\n/ or die "ls-remote $_ ?";
2901 my ($objid,$rrefname) = ($1,$2);
2902 if (!$wanted_rref->($rrefname)) {
2903 print STDERR f_ <<END, "@look", $rrefname;
2904 warning: git ls-remote %s reported %s; this is silly, ignoring it.
2908 $wantr{$rrefname} = $objid;
2911 close GITLS or failedcmd @lcmd;
2913 # OK, now %want is exactly what we want for refs in @specs
2915 !m/\*$/ && !exists $wantr{"refs/$_"} ? () :
2916 "+refs/$_:".lrfetchrefs."/$_";
2919 printdebug "git_lrfetch_sane fspecs @fspecs\n";
2921 my @fcmd = (@git, qw(fetch -p -n -q), $url, @fspecs);
2922 runcmd_ordryrun_local @fcmd if @fspecs;
2924 if (!$supplementary) {
2925 %lrfetchrefs_f = ();
2929 git_for_each_ref(lrfetchrefs, sub {
2930 my ($objid,$objtype,$lrefname,$reftail) = @_;
2931 $lrfetchrefs_f{$lrefname} = $objid;
2932 $objgot{$objid} = 1;
2935 if ($supplementary) {
2939 foreach my $lrefname (sort keys %lrfetchrefs_f) {
2940 my $rrefname = 'refs'.substr($lrefname, length lrfetchrefs);
2941 if (!exists $wantr{$rrefname}) {
2942 if ($wanted_rref->($rrefname)) {
2944 git-fetch @fspecs created $lrefname which git ls-remote @look didn't list.
2947 print STDERR f_ <<END, "@fspecs", $lrefname
2948 warning: git fetch %s created %s; this is silly, deleting it.
2951 runcmd_ordryrun_local @git, qw(update-ref -d), $lrefname;
2952 delete $lrfetchrefs_f{$lrefname};
2956 foreach my $rrefname (sort keys %wantr) {
2957 my $lrefname = lrfetchrefs.substr($rrefname, 4);
2958 my $got = $lrfetchrefs_f{$lrefname} // '<none>';
2959 my $want = $wantr{$rrefname};
2960 next if $got eq $want;
2961 if (!defined $objgot{$want}) {
2962 fail __ <<END unless act_local();
2963 --dry-run specified but we actually wanted the results of git fetch,
2964 so this is not going to work. Try running dgit fetch first,
2965 or using --damp-run instead of --dry-run.
2967 print STDERR f_ <<END, $lrefname, $want;
2968 warning: git ls-remote suggests we want %s
2969 warning: and it should refer to %s
2970 warning: but git fetch didn't fetch that object to any relevant ref.
2971 warning: This may be due to a race with someone updating the server.
2972 warning: Will try again...
2974 next FETCH_ITERATION;
2977 git-fetch @fspecs made $lrefname=$got but want git ls-remote @look says $want
2979 runcmd_ordryrun_local @git, qw(update-ref -m),
2980 "dgit fetch git fetch fixup", $lrefname, $want;
2981 $lrfetchrefs_f{$lrefname} = $want;
2986 if (defined $csuite) {
2987 printdebug "git_lrfetch_sane: tidying any old suite lrfetchrefs\n";
2988 git_for_each_ref("refs/dgit-fetch/$csuite", sub {
2989 my ($objid,$objtype,$lrefname,$reftail) = @_;
2990 next if $lrfetchrefs_f{$lrefname}; # $csuite eq $distro ?
2991 runcmd_ordryrun_local @git, qw(update-ref -d), $lrefname;
2995 printdebug "git_lrfetch_sane: git fetch --no-insane emulation complete\n",
2996 Dumper(\%lrfetchrefs_f);
2999 sub git_fetch_us () {
3000 # Want to fetch only what we are going to use, unless
3001 # deliberately-not-ff, in which case we must fetch everything.
3003 my @specs = deliberately_not_fast_forward ? qw(tags/*) :
3004 map { "tags/$_" } debiantags('*',access_nomdistro);
3005 push @specs, server_branch($csuite);
3006 push @specs, $rewritemap;
3007 push @specs, qw(heads/*) if deliberately_not_fast_forward;
3009 my $url = access_giturl();
3010 git_lrfetch_sane $url, 0, @specs;
3013 my @tagpats = debiantags('*',access_nomdistro);
3015 git_for_each_ref([map { "refs/tags/$_" } @tagpats], sub {
3016 my ($objid,$objtype,$fullrefname,$reftail) = @_;
3017 printdebug "currently $fullrefname=$objid\n";
3018 $here{$fullrefname} = $objid;
3020 git_for_each_ref([map { lrfetchrefs."/tags/".$_ } @tagpats], sub {
3021 my ($objid,$objtype,$fullrefname,$reftail) = @_;
3022 my $lref = "refs".substr($fullrefname, length(lrfetchrefs));
3023 printdebug "offered $lref=$objid\n";
3024 if (!defined $here{$lref}) {
3025 my @upd = (@git, qw(update-ref), $lref, $objid, '');
3026 runcmd_ordryrun_local @upd;
3027 lrfetchref_used $fullrefname;
3028 } elsif ($here{$lref} eq $objid) {
3029 lrfetchref_used $fullrefname;
3031 print STDERR f_ "Not updating %s from %s to %s.\n",
3032 $lref, $here{$lref}, $objid;
3037 #---------- dsc and archive handling ----------
3039 sub mergeinfo_getclogp ($) {
3040 # Ensures thit $mi->{Clogp} exists and returns it
3042 $mi->{Clogp} = commit_getclogp($mi->{Commit});
3045 sub mergeinfo_version ($) {
3046 return getfield( (mergeinfo_getclogp $_[0]), 'Version' );
3049 sub fetch_from_archive_record_1 ($) {
3051 runcmd git_update_ref_cmd "dgit fetch $csuite", 'DGIT_ARCHIVE', $hash;
3052 cmdoutput @git, qw(log -n2), $hash;
3053 # ... gives git a chance to complain if our commit is malformed
3056 sub fetch_from_archive_record_2 ($) {
3058 my @upd_cmd = (git_update_ref_cmd 'dgit fetch', lrref(), $hash);
3062 dryrun_report @upd_cmd;
3066 sub parse_dsc_field_def_dsc_distro () {
3067 $dsc_distro //= cfg qw(dgit.default.old-dsc-distro
3068 dgit.default.distro);
3071 sub parse_dsc_field ($$) {
3072 my ($dsc, $what) = @_;
3074 foreach my $field (@ourdscfield) {
3075 $f = $dsc->{$field};
3080 progress f_ "%s: NO git hash", $what;
3081 parse_dsc_field_def_dsc_distro();
3082 } elsif (($dsc_hash, $dsc_distro, $dsc_hint_tag, $dsc_hint_url)
3083 = $f =~ m/^(\w+)\s+($distro_re)\s+($versiontag_re)\s+(\S+)(?:\s|$)/) {
3084 progress f_ "%s: specified git info (%s)", $what, $dsc_distro;
3085 $dsc_hint_tag = [ $dsc_hint_tag ];
3086 } elsif ($f =~ m/^\w+\s*$/) {
3088 parse_dsc_field_def_dsc_distro();
3089 $dsc_hint_tag = [ debiantags +(getfield $dsc, 'Version'),
3091 progress f_ "%s: specified git hash", $what;
3093 fail f_ "%s: invalid Dgit info", $what;
3097 sub resolve_dsc_field_commit ($$) {
3098 my ($already_distro, $already_mapref) = @_;
3100 return unless defined $dsc_hash;
3103 defined $already_mapref &&
3104 ($already_distro eq $dsc_distro || !$chase_dsc_distro)
3105 ? $already_mapref : undef;
3109 my ($what, @fetch) = @_;
3111 local $idistro = $dsc_distro;
3112 my $lrf = lrfetchrefs;
3114 if (!$chase_dsc_distro) {
3115 progress f_ "not chasing .dsc distro %s: not fetching %s",
3120 progress f_ ".dsc names distro %s: fetching %s", $dsc_distro, $what;
3122 my $url = access_giturl();
3123 if (!defined $url) {
3124 defined $dsc_hint_url or fail f_ <<END, $dsc_distro;
3125 .dsc Dgit metadata is in context of distro %s
3126 for which we have no configured url and .dsc provides no hint
3129 $dsc_hint_url =~ m#^([-+0-9a-zA-Z]+):# ? $1 :
3130 $dsc_hint_url =~ m#^/# ? 'file' : 'bad-syntax';
3131 parse_cfg_bool "dsc-url-proto-ok", 'false',
3132 cfg("dgit.dsc-url-proto-ok.$proto",
3133 "dgit.default.dsc-url-proto-ok")
3134 or fail f_ <<END, $dsc_distro, $proto;
3135 .dsc Dgit metadata is in context of distro %s
3136 for which we have no configured url;
3137 .dsc provides hinted url with protocol %s which is unsafe.
3138 (can be overridden by config - consult documentation)
3140 $url = $dsc_hint_url;
3143 git_lrfetch_sane $url, 1, @fetch;
3148 my $rewrite_enable = do {
3149 local $idistro = $dsc_distro;
3150 access_cfg('rewrite-map-enable', 'RETURN-UNDEF');
3153 if (parse_cfg_bool 'rewrite-map-enable', 'true', $rewrite_enable) {
3154 if (!defined $mapref) {
3155 my $lrf = $do_fetch->((__ "rewrite map"), $rewritemap) or return;
3156 $mapref = $lrf.'/'.$rewritemap;
3158 my $rewritemapdata = git_cat_file $mapref.':map';
3159 if (defined $rewritemapdata
3160 && $rewritemapdata =~ m/^$dsc_hash(?:[ \t](\w+))/m) {
3162 "server's git history rewrite map contains a relevant entry!";
3165 if (defined $dsc_hash) {
3166 progress __ "using rewritten git hash in place of .dsc value";
3168 progress __ "server data says .dsc hash is to be disregarded";
3173 if (!defined git_cat_file $dsc_hash) {
3174 my @tags = map { "tags/".$_ } @$dsc_hint_tag;
3175 my $lrf = $do_fetch->((__ "additional commits"), @tags) &&
3176 defined git_cat_file $dsc_hash
3177 or fail f_ <<END, $dsc_hash;
3178 .dsc Dgit metadata requires commit %s
3179 but we could not obtain that object anywhere.
3181 foreach my $t (@tags) {
3182 my $fullrefname = $lrf.'/'.$t;
3183 # print STDERR "CHK $t $fullrefname ".Dumper(\%lrfetchrefs_f);
3184 next unless $lrfetchrefs_f{$fullrefname};
3185 next unless is_fast_fwd "$fullrefname~0", $dsc_hash;
3186 lrfetchref_used $fullrefname;
3191 sub fetch_from_archive () {
3193 ensure_setup_existing_tree();
3195 # Ensures that lrref() is what is actually in the archive, one way
3196 # or another, according to us - ie this client's
3197 # appropritaely-updated archive view. Also returns the commit id.
3198 # If there is nothing in the archive, leaves lrref alone and
3199 # returns undef. git_fetch_us must have already been called.
3203 parse_dsc_field($dsc, __ 'last upload to archive');
3204 resolve_dsc_field_commit access_basedistro,
3205 lrfetchrefs."/".$rewritemap
3207 progress __ "no version available from the archive";
3210 # If the archive's .dsc has a Dgit field, there are three
3211 # relevant git commitids we need to choose between and/or merge
3213 # 1. $dsc_hash: the Dgit field from the archive
3214 # 2. $lastpush_hash: the suite branch on the dgit git server
3215 # 3. $lastfetch_hash: our local tracking brach for the suite
3217 # These may all be distinct and need not be in any fast forward
3220 # If the dsc was pushed to this suite, then the server suite
3221 # branch will have been updated; but it might have been pushed to
3222 # a different suite and copied by the archive. Conversely a more
3223 # recent version may have been pushed with dgit but not appeared
3224 # in the archive (yet).
3226 # $lastfetch_hash may be awkward because archive imports
3227 # (particularly, imports of Dgit-less .dscs) are performed only as
3228 # needed on individual clients, so different clients may perform a
3229 # different subset of them - and these imports are only made
3230 # public during push. So $lastfetch_hash may represent a set of
3231 # imports different to a subsequent upload by a different dgit
3234 # Our approach is as follows:
3236 # As between $dsc_hash and $lastpush_hash: if $lastpush_hash is a
3237 # descendant of $dsc_hash, then it was pushed by a dgit user who
3238 # had based their work on $dsc_hash, so we should prefer it.
3239 # Otherwise, $dsc_hash was installed into this suite in the
3240 # archive other than by a dgit push, and (necessarily) after the
3241 # last dgit push into that suite (since a dgit push would have
3242 # been descended from the dgit server git branch); thus, in that
3243 # case, we prefer the archive's version (and produce a
3244 # pseudo-merge to overwrite the dgit server git branch).
3246 # (If there is no Dgit field in the archive's .dsc then
3247 # generate_commit_from_dsc uses the version numbers to decide
3248 # whether the suite branch or the archive is newer. If the suite
3249 # branch is newer it ignores the archive's .dsc; otherwise it
3250 # generates an import of the .dsc, and produces a pseudo-merge to
3251 # overwrite the suite branch with the archive contents.)
3253 # The outcome of that part of the algorithm is the `public view',
3254 # and is same for all dgit clients: it does not depend on any
3255 # unpublished history in the local tracking branch.
3257 # As between the public view and the local tracking branch: The
3258 # local tracking branch is only updated by dgit fetch, and
3259 # whenever dgit fetch runs it includes the public view in the
3260 # local tracking branch. Therefore if the public view is not
3261 # descended from the local tracking branch, the local tracking
3262 # branch must contain history which was imported from the archive
3263 # but never pushed; and, its tip is now out of date. So, we make
3264 # a pseudo-merge to overwrite the old imports and stitch the old
3267 # Finally: we do not necessarily reify the public view (as
3268 # described above). This is so that we do not end up stacking two
3269 # pseudo-merges. So what we actually do is figure out the inputs
3270 # to any public view pseudo-merge and put them in @mergeinputs.
3273 # $mergeinputs[]{Commit}
3274 # $mergeinputs[]{Info}
3275 # $mergeinputs[0] is the one whose tree we use
3276 # @mergeinputs is in the order we use in the actual commit)
3279 # $mergeinputs[]{Message} is a commit message to use
3280 # $mergeinputs[]{ReverseParents} if def specifies that parent
3281 # list should be in opposite order
3282 # Such an entry has no Commit or Info. It applies only when found
3283 # in the last entry. (This ugliness is to support making
3284 # identical imports to previous dgit versions.)
3286 my $lastpush_hash = git_get_ref(lrfetchref());
3287 printdebug "previous reference hash=$lastpush_hash\n";
3288 $lastpush_mergeinput = $lastpush_hash && {
3289 Commit => $lastpush_hash,
3290 Info => (__ "dgit suite branch on dgit git server"),
3293 my $lastfetch_hash = git_get_ref(lrref());
3294 printdebug "fetch_from_archive: lastfetch=$lastfetch_hash\n";
3295 my $lastfetch_mergeinput = $lastfetch_hash && {
3296 Commit => $lastfetch_hash,
3297 Info => (__ "dgit client's archive history view"),
3300 my $dsc_mergeinput = $dsc_hash && {
3301 Commit => $dsc_hash,
3302 Info => (__ "Dgit field in .dsc from archive"),
3306 my $del_lrfetchrefs = sub {
3309 printdebug "del_lrfetchrefs...\n";
3310 foreach my $fullrefname (sort keys %lrfetchrefs_d) {
3311 my $objid = $lrfetchrefs_d{$fullrefname};
3312 printdebug "del_lrfetchrefs: $objid $fullrefname\n";
3314 $gur ||= new IO::Handle;
3315 open $gur, "|-", qw(git update-ref --stdin) or confess "$!";
3317 printf $gur "delete %s %s\n", $fullrefname, $objid;
3320 close $gur or failedcmd "git update-ref delete lrfetchrefs";
3324 if (defined $dsc_hash) {
3325 ensure_we_have_orig();
3326 if (!$lastpush_hash || $dsc_hash eq $lastpush_hash) {
3327 @mergeinputs = $dsc_mergeinput
3328 } elsif (is_fast_fwd($dsc_hash,$lastpush_hash)) {
3329 print STDERR f_ <<END, $dsc_hash, $lastpush_hash,
3331 Git commit in archive is behind the last version allegedly pushed/uploaded.
3332 Commit referred to by archive: %s
3333 Last version pushed with dgit: %s
3336 __ $later_warning_msg or confess "$!";
3337 @mergeinputs = ($lastpush_mergeinput);
3339 # Archive has .dsc which is not a descendant of the last dgit
3340 # push. This can happen if the archive moves .dscs about.
3341 # Just follow its lead.
3342 if (is_fast_fwd($lastpush_hash,$dsc_hash)) {
3343 progress __ "archive .dsc names newer git commit";
3344 @mergeinputs = ($dsc_mergeinput);
3346 progress __ "archive .dsc names other git commit, fixing up";
3347 @mergeinputs = ($dsc_mergeinput, $lastpush_mergeinput);
3351 @mergeinputs = generate_commits_from_dsc();
3352 # We have just done an import. Now, our import algorithm might
3353 # have been improved. But even so we do not want to generate
3354 # a new different import of the same package. So if the
3355 # version numbers are the same, just use our existing version.
3356 # If the version numbers are different, the archive has changed
3357 # (perhaps, rewound).
3358 if ($lastfetch_mergeinput &&
3359 !version_compare( (mergeinfo_version $lastfetch_mergeinput),
3360 (mergeinfo_version $mergeinputs[0]) )) {
3361 @mergeinputs = ($lastfetch_mergeinput);
3363 } elsif ($lastpush_hash) {
3364 # only in git, not in the archive yet
3365 @mergeinputs = ($lastpush_mergeinput);
3366 print STDERR f_ <<END,
3368 Package not found in the archive, but has allegedly been pushed using dgit.
3371 __ $later_warning_msg or confess "$!";
3373 printdebug "nothing found!\n";
3374 if (defined $skew_warning_vsn) {
3375 print STDERR f_ <<END, $skew_warning_vsn or confess "$!";
3377 Warning: relevant archive skew detected.
3378 Archive allegedly contains %s
3379 But we were not able to obtain any version from the archive or git.
3383 unshift @end, $del_lrfetchrefs;
3387 if ($lastfetch_hash &&
3389 my $h = $_->{Commit};
3390 $h and is_fast_fwd($lastfetch_hash, $h);
3391 # If true, one of the existing parents of this commit
3392 # is a descendant of the $lastfetch_hash, so we'll
3393 # be ff from that automatically.
3397 push @mergeinputs, $lastfetch_mergeinput;
3400 printdebug "fetch mergeinfos:\n";
3401 foreach my $mi (@mergeinputs) {
3403 printdebug " commit $mi->{Commit} $mi->{Info}\n";
3405 printdebug sprintf " ReverseParents=%d Message=%s",
3406 $mi->{ReverseParents}, $mi->{Message};
3410 my $compat_info= pop @mergeinputs
3411 if $mergeinputs[$#mergeinputs]{Message};
3413 @mergeinputs = grep { defined $_->{Commit} } @mergeinputs;
3416 if (@mergeinputs > 1) {
3418 my $tree_commit = $mergeinputs[0]{Commit};
3420 my $tree = get_tree_of_commit $tree_commit;;
3422 # We use the changelog author of the package in question the
3423 # author of this pseudo-merge. This is (roughly) correct if
3424 # this commit is simply representing aa non-dgit upload.
3425 # (Roughly because it does not record sponsorship - but we
3426 # don't have sponsorship info because that's in the .changes,
3427 # which isn't in the archivw.)
3429 # But, it might be that we are representing archive history
3430 # updates (including in-archive copies). These are not really
3431 # the responsibility of the person who created the .dsc, but
3432 # there is no-one whose name we should better use. (The
3433 # author of the .dsc-named commit is clearly worse.)
3435 my $useclogp = mergeinfo_getclogp $mergeinputs[0];
3436 my $author = clogp_authline $useclogp;
3437 my $cversion = getfield $useclogp, 'Version';
3439 my $mcf = dgit_privdir()."/mergecommit";
3440 open MC, ">", $mcf or die "$mcf $!";
3441 print MC <<END or confess "$!";
3445 my @parents = grep { $_->{Commit} } @mergeinputs;
3446 @parents = reverse @parents if $compat_info->{ReverseParents};
3447 print MC <<END or confess "$!" foreach @parents;
3451 print MC <<END or confess "$!";
3457 if (defined $compat_info->{Message}) {
3458 print MC $compat_info->{Message} or confess "$!";
3460 print MC f_ <<END, $package, $cversion, $csuite or confess "$!";
3461 Record %s (%s) in archive suite %s
3465 my $message_add_info = sub {
3467 my $mversion = mergeinfo_version $mi;
3468 printf MC " %-20s %s\n", $mversion, $mi->{Info}
3472 $message_add_info->($mergeinputs[0]);
3473 print MC __ <<END or confess "$!";
3474 should be treated as descended from
3476 $message_add_info->($_) foreach @mergeinputs[1..$#mergeinputs];
3479 close MC or confess "$!";
3480 $hash = hash_commit $mcf;
3482 $hash = $mergeinputs[0]{Commit};
3484 printdebug "fetch hash=$hash\n";
3487 my ($lasth, $what) = @_;
3488 return unless $lasth;
3489 confess "$lasth $hash $what ?" unless is_fast_fwd($lasth, $hash);
3492 $chkff->($lastpush_hash, __ 'dgit repo server tip (last push)')
3494 $chkff->($lastfetch_hash, __ 'local tracking tip (last fetch)');
3496 fetch_from_archive_record_1($hash);
3498 if (defined $skew_warning_vsn) {
3499 printdebug "SKEW CHECK WANT $skew_warning_vsn\n";
3500 my $gotclogp = commit_getclogp($hash);
3501 my $got_vsn = getfield $gotclogp, 'Version';
3502 printdebug "SKEW CHECK GOT $got_vsn\n";
3503 if (version_compare($got_vsn, $skew_warning_vsn) < 0) {
3504 print STDERR f_ <<END, $skew_warning_vsn, $got_vsn or confess "$!";
3506 Warning: archive skew detected. Using the available version:
3507 Archive allegedly contains %s
3508 We were able to obtain only %s
3514 if ($lastfetch_hash ne $hash) {
3515 fetch_from_archive_record_2($hash);
3518 lrfetchref_used lrfetchref();
3520 check_gitattrs($hash, __ "fetched source tree");
3522 unshift @end, $del_lrfetchrefs;
3526 sub set_local_git_config ($$) {
3528 runcmd @git, qw(config), $k, $v;
3531 sub setup_mergechangelogs (;$) {
3533 return unless $always || access_cfg_bool(1, 'setup-mergechangelogs');
3535 my $driver = 'dpkg-mergechangelogs';
3536 my $cb = "merge.$driver";
3537 confess unless defined $maindir;
3538 my $attrs = "$maindir_gitcommon/info/attributes";
3539 ensuredir "$maindir_gitcommon/info";
3541 open NATTRS, ">", "$attrs.new" or die "$attrs.new $!";
3542 if (!open ATTRS, "<", $attrs) {
3543 $!==ENOENT or die "$attrs: $!";
3547 next if m{^debian/changelog\s};
3548 print NATTRS $_, "\n" or confess "$!";
3550 ATTRS->error and confess "$!";
3553 print NATTRS "debian/changelog merge=$driver\n" or confess "$!";
3556 set_local_git_config "$cb.name", __ 'debian/changelog merge driver';
3557 set_local_git_config "$cb.driver", 'dpkg-mergechangelogs -m %O %A %B %A';
3559 rename "$attrs.new", "$attrs" or die "$attrs: $!";
3562 sub setup_useremail (;$) {
3564 return unless $always || access_cfg_bool(1, 'setup-useremail');
3567 my ($k, $envvar) = @_;
3568 my $v = access_cfg("user-$k", 'RETURN-UNDEF') // $ENV{$envvar};
3569 return unless defined $v;
3570 set_local_git_config "user.$k", $v;
3573 $setup->('email', 'DEBEMAIL');
3574 $setup->('name', 'DEBFULLNAME');
3577 sub ensure_setup_existing_tree () {
3578 my $k = "remote.$remotename.skipdefaultupdate";
3579 my $c = git_get_config $k;
3580 return if defined $c;
3581 set_local_git_config $k, 'true';
3584 sub open_main_gitattrs () {
3585 confess 'internal error no maindir' unless defined $maindir;
3586 my $gai = new IO::File "$maindir_gitcommon/info/attributes"
3588 or die "open $maindir_gitcommon/info/attributes: $!";
3592 our $gitattrs_ourmacro_re = qr{^\[attr\]dgit-defuse-attrs\s};
3594 sub is_gitattrs_setup () {
3597 # 1: gitattributes set up and should be left alone
3599 # 0: there is a dgit-defuse-attrs but it needs fixing
3600 # undef: there is none
3601 my $gai = open_main_gitattrs();
3602 return 0 unless $gai;
3604 next unless m{$gitattrs_ourmacro_re};
3605 return 1 if m{\s-working-tree-encoding\s};
3606 printdebug "is_gitattrs_setup: found old macro\n";
3609 $gai->error and confess "$!";
3610 printdebug "is_gitattrs_setup: found nothing\n";
3614 sub setup_gitattrs (;$) {
3616 return unless $always || access_cfg_bool(1, 'setup-gitattributes');
3618 my $already = is_gitattrs_setup();
3621 [attr]dgit-defuse-attrs already found, and proper, in .git/info/attributes
3622 not doing further gitattributes setup
3626 my $new = "[attr]dgit-defuse-attrs $negate_harmful_gitattrs";
3627 my $af = "$maindir_gitcommon/info/attributes";
3628 ensuredir "$maindir_gitcommon/info";
3630 open GAO, "> $af.new" or confess "$!";
3631 print GAO <<END, __ <<ENDT or confess "$!" unless defined $already;
3635 # ^ see GITATTRIBUTES in dgit(7) and dgit setup-new-tree in dgit(1)
3637 my $gai = open_main_gitattrs();
3640 if (m{$gitattrs_ourmacro_re}) {
3641 die unless defined $already;
3645 print GAO $_, "\n" or confess "$!";
3647 $gai->error and confess "$!";
3649 close GAO or confess "$!";
3650 rename "$af.new", "$af" or fail f_ "install %s: %s", $af, $!;
3653 sub setup_new_tree () {
3654 setup_mergechangelogs();
3659 sub check_gitattrs ($$) {
3660 my ($treeish, $what) = @_;
3662 return if is_gitattrs_setup;
3665 my @cmd = (@git, qw(ls-tree -lrz --), "${treeish}:");
3667 my $gafl = new IO::File;
3668 open $gafl, "-|", @cmd or confess "$!";
3671 s/^\d+\s+\w+\s+\w+\s+(\d+)\t// or die;
3673 next unless m{(?:^|/)\.gitattributes$};
3675 # oh dear, found one
3676 print STDERR f_ <<END, $what;
3677 dgit: warning: %s contains .gitattributes
3678 dgit: .gitattributes not (fully) defused. Recommended: dgit setup-new-tree.
3683 # tree contains no .gitattributes files
3684 $?=0; $!=0; close $gafl or failedcmd @cmd;
3688 sub multisuite_suite_child ($$$) {
3689 my ($tsuite, $mergeinputs, $fn) = @_;
3690 # in child, sets things up, calls $fn->(), and returns undef
3691 # in parent, returns canonical suite name for $tsuite
3692 my $canonsuitefh = IO::File::new_tmpfile;
3693 my $pid = fork // confess "$!";
3697 $us .= " [$isuite]";
3698 $debugprefix .= " ";
3699 progress f_ "fetching %s...", $tsuite;
3700 canonicalise_suite();
3701 print $canonsuitefh $csuite, "\n" or confess "$!";
3702 close $canonsuitefh or confess "$!";
3706 waitpid $pid,0 == $pid or confess "$!";
3707 fail f_ "failed to obtain %s: %s", $tsuite, waitstatusmsg()
3709 seek $canonsuitefh,0,0 or confess "$!";
3710 local $csuite = <$canonsuitefh>;
3711 confess "$!" unless defined $csuite && chomp $csuite;
3713 printdebug "multisuite $tsuite missing\n";
3716 printdebug "multisuite $tsuite ok (canon=$csuite)\n";
3717 push @$mergeinputs, {
3724 sub fork_for_multisuite ($) {
3725 my ($before_fetch_merge) = @_;
3726 # if nothing unusual, just returns ''
3729 # returns 0 to caller in child, to do first of the specified suites
3730 # in child, $csuite is not yet set
3732 # returns 1 to caller in parent, to finish up anything needed after
3733 # in parent, $csuite is set to canonicalised portmanteau
3735 my $org_isuite = $isuite;
3736 my @suites = split /\,/, $isuite;
3737 return '' unless @suites > 1;
3738 printdebug "fork_for_multisuite: @suites\n";
3742 my $cbasesuite = multisuite_suite_child($suites[0], \@mergeinputs,
3744 return 0 unless defined $cbasesuite;
3746 fail f_ "package %s missing in (base suite) %s", $package, $cbasesuite
3747 unless @mergeinputs;
3749 my @csuites = ($cbasesuite);
3751 $before_fetch_merge->();
3753 foreach my $tsuite (@suites[1..$#suites]) {
3754 $tsuite =~ s/^-/$cbasesuite-/;
3755 my $csubsuite = multisuite_suite_child($tsuite, \@mergeinputs,
3762 $csubsuite =~ s/^\Q$cbasesuite\E-/-/;
3763 push @csuites, $csubsuite;
3766 foreach my $mi (@mergeinputs) {
3767 my $ref = git_get_ref $mi->{Ref};
3768 die "$mi->{Ref} ?" unless length $ref;
3769 $mi->{Commit} = $ref;
3772 $csuite = join ",", @csuites;
3774 my $previous = git_get_ref lrref;
3776 unshift @mergeinputs, {
3777 Commit => $previous,
3778 Info => (__ "local combined tracking branch"),
3780 "archive seems to have rewound: local tracking branch is ahead!"),
3784 foreach my $ix (0..$#mergeinputs) {
3785 $mergeinputs[$ix]{Index} = $ix;
3788 @mergeinputs = sort {
3789 -version_compare(mergeinfo_version $a,
3790 mergeinfo_version $b) # highest version first
3792 $a->{Index} <=> $b->{Index}; # earliest in spec first
3798 foreach my $mi (@mergeinputs) {
3799 printdebug "multisuite merge check $mi->{Info}\n";
3800 foreach my $previous (@needed) {
3801 next unless is_fast_fwd $mi->{Commit}, $previous->{Commit};
3802 printdebug "multisuite merge un-needed $previous->{Info}\n";
3806 printdebug "multisuite merge this-needed\n";
3807 $mi->{Character} = '+';
3810 $needed[0]{Character} = '*';
3812 my $output = $needed[0]{Commit};
3815 printdebug "multisuite merge nontrivial\n";
3816 my $tree = cmdoutput qw(git rev-parse), $needed[0]{Commit}.':';
3818 my $commit = "tree $tree\n";
3819 my $msg = f_ "Combine archive branches %s [dgit]\n\n".
3820 "Input branches:\n",
3823 foreach my $mi (sort { $a->{Index} <=> $b->{Index} } @mergeinputs) {
3824 printdebug "multisuite merge include $mi->{Info}\n";
3825 $mi->{Character} //= ' ';
3826 $commit .= "parent $mi->{Commit}\n";
3827 $msg .= sprintf " %s %-25s %s\n",
3829 (mergeinfo_version $mi),
3832 my $authline = clogp_authline mergeinfo_getclogp $needed[0];
3833 $msg .= __ "\nKey\n".
3834 " * marks the highest version branch, which choose to use\n".
3835 " + marks each branch which was not already an ancestor\n\n";
3837 "[dgit multi-suite $csuite]\n";
3839 "author $authline\n".
3840 "committer $authline\n\n";
3841 $output = hash_commit_text $commit.$msg;
3842 printdebug "multisuite merge generated $output\n";
3845 fetch_from_archive_record_1($output);
3846 fetch_from_archive_record_2($output);
3848 progress f_ "calculated combined tracking suite %s", $csuite;
3853 sub clone_set_head () {
3854 open H, "> .git/HEAD" or confess "$!";
3855 print H "ref: ".lref()."\n" or confess "$!";
3856 close H or confess "$!";
3858 sub clone_finish ($) {
3860 runcmd @git, qw(reset --hard), lrref();
3861 runcmd qw(bash -ec), <<'END';
3863 git ls-tree -r --name-only -z HEAD | \
3864 xargs -0r touch -h -r . --
3866 printdone f_ "ready for work in %s", $dstdir;
3870 # in multisuite, returns twice!
3871 # once in parent after first suite fetched,
3872 # and then again in child after everything is finished
3874 badusage __ "dry run makes no sense with clone" unless act_local();
3876 my $multi_fetched = fork_for_multisuite(sub {
3877 printdebug "multi clone before fetch merge\n";
3881 if ($multi_fetched) {
3882 printdebug "multi clone after fetch merge\n";
3884 clone_finish($dstdir);
3887 printdebug "clone main body\n";
3889 mkdir $dstdir or fail f_ "create \`%s': %s", $dstdir, $!;
3893 canonicalise_suite();
3894 my $hasgit = check_for_git();
3896 runcmd @git, qw(init -q);
3901 progress __ "fetching existing git history";
3904 progress __ "starting new git history";
3906 fetch_from_archive() or no_such_package;
3907 my $vcsgiturl = $dsc->{'Vcs-Git'};
3908 if (length $vcsgiturl) {
3909 $vcsgiturl =~ s/\s+-b\s+\S+//g;
3910 runcmd @git, qw(remote add vcs-git), $vcsgiturl;
3912 clone_finish($dstdir);
3916 canonicalise_suite();
3917 if (check_for_git()) {
3920 fetch_from_archive() or no_such_package();
3922 my $vcsgiturl = $dsc && $dsc->{'Vcs-Git'};
3923 if (length $vcsgiturl and
3924 (grep { $csuite eq $_ }
3926 cfg 'dgit.vcs-git.suites')) {
3927 my $current = cfg 'remote.vcs-git.url', 'RETURN-UNDEF';
3928 if (defined $current && $current ne $vcsgiturl) {
3929 print STDERR f_ <<END, $csuite;
3930 FYI: Vcs-Git in %s has different url to your vcs-git remote.
3931 Your vcs-git remote url may be out of date. Use dgit update-vcs-git ?
3935 printdone f_ "fetched into %s", lrref();
3939 my $multi_fetched = fork_for_multisuite(sub { });
3940 fetch_one() unless $multi_fetched; # parent
3941 finish 0 if $multi_fetched eq '0'; # child
3946 runcmd_ordryrun_local @git, qw(merge -m),
3947 (f_ "Merge from %s [dgit]", $csuite),
3949 printdone f_ "fetched to %s and merged into HEAD", lrref();
3952 sub check_not_dirty () {
3953 my @forbid = qw(local-options local-patch-header);
3954 @forbid = map { "debian/source/$_" } @forbid;
3955 foreach my $f (@forbid) {
3956 if (stat_exists $f) {
3957 fail f_ "git tree contains %s", $f;
3961 my @cmd = (@git, qw(status -uall --ignored --porcelain));
3962 push @cmd, qw(debian/source/format debian/source/options);
3965 my $bad = cmdoutput @cmd;
3968 "you have uncommitted changes to critical files, cannot continue:\n").
3972 return if $includedirty;
3974 git_check_unmodified();
3977 sub commit_admin ($) {
3980 runcmd_ordryrun_local @git, qw(commit -m), $m;
3983 sub quiltify_nofix_bail ($$) {
3984 my ($headinfo, $xinfo) = @_;
3985 if ($quilt_mode eq 'nofix') {
3987 "quilt fixup required but quilt mode is \`nofix'\n".
3988 "HEAD commit%s differs from tree implied by debian/patches%s",
3993 sub commit_quilty_patch () {
3994 my $output = cmdoutput @git, qw(status --ignored --porcelain);
3996 foreach my $l (split /\n/, $output) {
3997 next unless $l =~ m/\S/;
3998 if ($l =~ m{^(?:[?!][?!]| [MADRC]) (.pc|debian/patches)}) {
4002 delete $adds{'.pc'}; # if there wasn't one before, don't add it
4004 progress __ "nothing quilty to commit, ok.";
4007 quiltify_nofix_bail "", __ " (wanted to commit patch update)";
4008 my @adds = map { s/[][*?\\]/\\$&/g; $_; } sort keys %adds;
4009 runcmd_ordryrun_local @git, qw(add -f), @adds;
4010 commit_admin +(__ <<ENDT).<<END
4011 Commit Debian 3.0 (quilt) metadata
4014 [dgit ($our_version) quilt-fixup]
4018 sub get_source_format () {
4020 if (open F, "debian/source/options") {
4024 s/\s+$//; # ignore missing final newline
4026 my ($k, $v) = ($`, $'); #');
4027 $v =~ s/^"(.*)"$/$1/;
4033 F->error and confess "$!";
4036 confess "$!" unless $!==&ENOENT;
4039 if (!open F, "debian/source/format") {
4040 confess "$!" unless $!==&ENOENT;
4044 F->error and confess "$!";
4046 return ($_, \%options);
4049 sub madformat_wantfixup ($) {
4051 return 0 unless $format eq '3.0 (quilt)';
4052 our $quilt_mode_warned;
4053 if ($quilt_mode eq 'nocheck') {
4054 progress f_ "Not doing any fixup of \`%s'".
4055 " due to ----no-quilt-fixup or --quilt=nocheck", $format
4056 unless $quilt_mode_warned++;
4059 progress f_ "Format \`%s', need to check/update patch stack", $format
4060 unless $quilt_mode_warned++;
4064 sub maybe_split_brain_save ($$$) {
4065 my ($headref, $dgitview, $msg) = @_;
4066 # => message fragment "$saved" describing disposition of $dgitview
4067 # (used inside parens, in the English texts)
4068 my $save = $internal_object_save{'dgit-view'};
4069 return f_ "commit id %s", $dgitview unless defined $save;
4070 my @cmd = (shell_cmd 'cd "$1"; shift', $maindir,
4072 "dgit --dgit-view-save $msg HEAD=$headref",
4075 return f_ "and left in %s", $save;
4078 # An "infopair" is a tuple [ $thing, $what ]
4079 # (often $thing is a commit hash; $what is a description)
4081 sub infopair_cond_equal ($$) {
4083 $x->[0] eq $y->[0] or fail <<END;
4084 $x->[1] ($x->[0]) not equal to $y->[1] ($y->[0])
4088 sub infopair_lrf_tag_lookup ($$) {
4089 my ($tagnames, $what) = @_;
4090 # $tagname may be an array ref
4091 my @tagnames = ref $tagnames ? @$tagnames : ($tagnames);
4092 printdebug "infopair_lrfetchref_tag_lookup $what @tagnames\n";
4093 foreach my $tagname (@tagnames) {
4094 my $lrefname = lrfetchrefs."/tags/$tagname";
4095 my $tagobj = $lrfetchrefs_f{$lrefname};
4096 next unless defined $tagobj;
4097 printdebug "infopair_lrfetchref_tag_lookup $tagobj $tagname $what\n";
4098 return [ git_rev_parse($tagobj), $what ];
4100 fail @tagnames==1 ? (f_ <<END, $what, "@tagnames")
4101 Wanted tag %s (%s) on dgit server, but not found
4103 : (f_ <<END, $what, "@tagnames");
4104 Wanted tag %s (one of: %s) on dgit server, but not found
4108 sub infopair_cond_ff ($$) {
4109 my ($anc,$desc) = @_;
4110 is_fast_fwd($anc->[0], $desc->[0]) or
4111 fail f_ <<END, $anc->[1], $anc->[0], $desc->[1], $desc->[0];
4112 %s (%s) .. %s (%s) is not fast forward
4116 sub pseudomerge_version_check ($$) {
4117 my ($clogp, $archive_hash) = @_;
4119 my $arch_clogp = commit_getclogp $archive_hash;
4120 my $i_arch_v = [ (getfield $arch_clogp, 'Version'),
4121 __ 'version currently in archive' ];
4122 if (defined $overwrite_version) {
4123 if (length $overwrite_version) {
4124 infopair_cond_equal([ $overwrite_version,
4125 '--overwrite= version' ],
4128 my $v = $i_arch_v->[0];
4130 "Checking package changelog for archive version %s ...", $v;
4133 my @xa = ("-f$v", "-t$v");
4134 my $vclogp = parsechangelog @xa;
4137 [ (getfield $vclogp, $fn),
4138 (f_ "%s field from dpkg-parsechangelog %s",
4141 my $cv = $gf->('Version');
4142 infopair_cond_equal($i_arch_v, $cv);
4143 $cd = $gf->('Distribution');
4147 $@ =~ s/^dgit: //gm;
4149 f_ "Perhaps debian/changelog does not mention %s ?", $v;
4151 fail f_ <<END, $cd->[1], $cd->[0], $v
4153 Your tree seems to based on earlier (not uploaded) %s.
4155 if $cd->[0] =~ m/UNRELEASED/;
4159 printdebug "pseudomerge_version_check i_arch_v @$i_arch_v\n";
4163 sub pseudomerge_hash_commit ($$$$ $$) {
4164 my ($clogp, $dgitview, $archive_hash, $i_arch_v,
4165 $msg_cmd, $msg_msg) = @_;
4166 progress f_ "Declaring that HEAD includes all changes in %s...",
4169 my $tree = cmdoutput qw(git rev-parse), "${dgitview}:";
4170 my $authline = clogp_authline $clogp;
4174 !defined $overwrite_version ? ""
4175 : !length $overwrite_version ? " --overwrite"
4176 : " --overwrite=".$overwrite_version;
4178 # Contributing parent is the first parent - that makes
4179 # git rev-list --first-parent DTRT.
4180 my $pmf = dgit_privdir()."/pseudomerge";
4181 open MC, ">", $pmf or die "$pmf $!";
4182 print MC <<END or confess "$!";
4185 parent $archive_hash
4193 close MC or confess "$!";
4195 return hash_commit($pmf);
4198 sub splitbrain_pseudomerge ($$$$) {
4199 my ($clogp, $maintview, $dgitview, $archive_hash) = @_;
4200 # => $merged_dgitview
4201 printdebug "splitbrain_pseudomerge...\n";
4203 # We: debian/PREVIOUS HEAD($maintview)
4204 # expect: o ----------------- o
4207 # a/d/PREVIOUS $dgitview
4210 # we do: `------------------ o
4214 return $dgitview unless defined $archive_hash;
4215 return $dgitview if deliberately_not_fast_forward();
4217 printdebug "splitbrain_pseudomerge...\n";
4219 my $i_arch_v = pseudomerge_version_check($clogp, $archive_hash);
4221 if (!defined $overwrite_version) {
4222 progress __ "Checking that HEAD includes all changes in archive...";
4225 return $dgitview if is_fast_fwd $archive_hash, $dgitview;
4227 if (defined $overwrite_version) {
4229 my $t_dep14 = debiantag_maintview $i_arch_v->[0], access_nomdistro;
4230 my $i_dep14 = infopair_lrf_tag_lookup($t_dep14,
4231 __ "maintainer view tag");
4232 my $t_dgit = debiantag_new $i_arch_v->[0], access_nomdistro;
4233 my $i_dgit = infopair_lrf_tag_lookup($t_dgit, __ "dgit view tag");
4234 my $i_archive = [ $archive_hash, __ "current archive contents" ];
4236 printdebug "splitbrain_pseudomerge i_archive @$i_archive\n";
4238 infopair_cond_equal($i_dgit, $i_archive);
4239 infopair_cond_ff($i_dep14, $i_dgit);
4240 infopair_cond_ff($i_dep14, [ $maintview, 'HEAD' ]);
4243 $@ =~ s/^\n//; chomp $@;
4244 print STDERR <<END.(__ <<ENDT);
4247 | Not fast forward; maybe --overwrite is needed ? Please see dgit(1).
4252 my $arch_v = $i_arch_v->[0];
4253 my $r = pseudomerge_hash_commit
4254 $clogp, $dgitview, $archive_hash, $i_arch_v,
4255 "dgit --quilt=$quilt_mode",
4256 (defined $overwrite_version
4257 ? f_ "Declare fast forward from %s\n", $arch_v
4258 : f_ "Make fast forward from %s\n", $arch_v);
4260 maybe_split_brain_save $maintview, $r, "pseudomerge";
4262 progress f_ "Made pseudo-merge of %s into dgit view.", $arch_v;
4266 sub plain_overwrite_pseudomerge ($$$) {
4267 my ($clogp, $head, $archive_hash) = @_;
4269 printdebug "plain_overwrite_pseudomerge...";
4271 my $i_arch_v = pseudomerge_version_check($clogp, $archive_hash);
4273 return $head if is_fast_fwd $archive_hash, $head;
4275 my $m = f_ "Declare fast forward from %s", $i_arch_v->[0];
4277 my $r = pseudomerge_hash_commit
4278 $clogp, $head, $archive_hash, $i_arch_v,
4281 runcmd git_update_ref_cmd $m, 'HEAD', $r, $head;
4283 progress f_ "Make pseudo-merge of %s into your HEAD.", $i_arch_v->[0];
4287 sub push_parse_changelog ($) {
4290 my $clogp = Dpkg::Control::Hash->new();
4291 $clogp->load($clogpfn) or die;
4293 my $clogpackage = getfield $clogp, 'Source';
4294 $package //= $clogpackage;
4295 fail f_ "-p specified %s but changelog specified %s",
4296 $package, $clogpackage
4297 unless $package eq $clogpackage;
4298 my $cversion = getfield $clogp, 'Version';
4300 if (!$we_are_initiator) {
4301 # rpush initiator can't do this because it doesn't have $isuite yet
4302 my $tag = debiantag_new($cversion, access_nomdistro);
4303 runcmd @git, qw(check-ref-format), $tag;
4306 my $dscfn = dscfn($cversion);
4308 return ($clogp, $cversion, $dscfn);
4311 sub push_parse_dsc ($$$) {
4312 my ($dscfn,$dscfnwhat, $cversion) = @_;
4313 $dsc = parsecontrol($dscfn,$dscfnwhat);
4314 my $dversion = getfield $dsc, 'Version';
4315 my $dscpackage = getfield $dsc, 'Source';
4316 ($dscpackage eq $package && $dversion eq $cversion) or
4317 fail f_ "%s is for %s %s but debian/changelog is for %s %s",
4318 $dscfn, $dscpackage, $dversion,
4319 $package, $cversion;
4322 sub push_tagwants ($$$$) {
4323 my ($cversion, $dgithead, $maintviewhead, $tfbase) = @_;
4326 TagFn => \&debiantag_new,
4331 if (defined $maintviewhead) {
4333 TagFn => \&debiantag_maintview,
4334 Objid => $maintviewhead,
4335 TfSuffix => '-maintview',
4338 } elsif ($dodep14tag ne 'no') {
4340 TagFn => \&debiantag_maintview,
4342 TfSuffix => '-dgit',
4346 foreach my $tw (@tagwants) {
4347 $tw->{Tag} = $tw->{TagFn}($cversion, access_nomdistro);
4348 $tw->{Tfn} = sub { $tfbase.$tw->{TfSuffix}.$_[0]; };
4350 printdebug 'push_tagwants: ', Dumper(\@_, \@tagwants);
4354 sub push_mktags ($$ $$ $) {
4356 $changesfile,$changesfilewhat,
4359 die unless $tagwants->[0]{View} eq 'dgit';
4361 my $declaredistro = access_nomdistro();
4362 my $reader_giturl = do { local $access_forpush=0; access_giturl(); };
4363 $dsc->{$ourdscfield[0]} = join " ",
4364 $tagwants->[0]{Objid}, $declaredistro, $tagwants->[0]{Tag},
4366 $dsc->save("$dscfn.tmp") or confess "$!";
4368 my $changes = parsecontrol($changesfile,$changesfilewhat);
4369 foreach my $field (qw(Source Distribution Version)) {
4370 $changes->{$field} eq $clogp->{$field} or
4371 fail f_ "changes field %s \`%s' does not match changelog \`%s'",
4372 $field, $changes->{$field}, $clogp->{$field};
4375 my $cversion = getfield $clogp, 'Version';
4376 my $clogsuite = getfield $clogp, 'Distribution';
4377 my $format = getfield $dsc, 'Format';
4379 # We make the git tag by hand because (a) that makes it easier
4380 # to control the "tagger" (b) we can do remote signing
4381 my $authline = clogp_authline $clogp;
4385 my $tfn = $tw->{Tfn};
4386 my $head = $tw->{Objid};
4387 my $tag = $tw->{Tag};
4389 open TO, '>', $tfn->('.tmp') or confess "$!";
4390 print TO <<END or confess "$!";
4398 my @dtxinfo = @deliberatelies;
4399 unshift @dtxinfo, "--quilt=$quilt_mode" if madformat($format);
4400 unshift @dtxinfo, do_split_brain() ? "split" : "no-split"
4401 # rpush protocol 5 and earlier don't tell us
4402 unless $we_are_initiator && $protovsn < 6;
4403 my $dtxinfo = join(" ", "",@dtxinfo);
4404 my $tag_metadata = <<END;
4405 [dgit distro=$declaredistro$dtxinfo]
4407 foreach my $ref (sort keys %previously) {
4408 $tag_metadata .= <<END or confess "$!";
4409 [dgit previously:$ref=$previously{$ref}]
4413 if ($tw->{View} eq 'dgit') {
4414 print TO sprintf <<ENDT, $package, $cversion, $clogsuite, $csuite
4415 %s release %s for %s (%s) [dgit]
4418 } elsif ($tw->{View} eq 'maint') {
4419 print TO sprintf <<END, $package, $cversion, $clogsuite, $csuite;
4420 %s release %s for %s (%s)
4424 (maintainer view tag generated by dgit --quilt=%s)
4429 confess Dumper($tw)."?";
4431 print TO "\n", $tag_metadata;
4433 close TO or confess "$!";
4435 my $tagobjfn = $tfn->('.tmp');
4437 if (!defined $keyid) {
4438 $keyid = access_cfg('keyid','RETURN-UNDEF');
4440 if (!defined $keyid) {
4441 $keyid = getfield $clogp, 'Maintainer';
4443 unlink $tfn->('.tmp.asc') or $!==&ENOENT or confess "$!";
4444 my @sign_cmd = (@gpg, qw(--detach-sign --armor));
4445 push @sign_cmd, qw(-u),$keyid if defined $keyid;
4446 push @sign_cmd, $tfn->('.tmp');
4447 runcmd_ordryrun @sign_cmd;
4449 $tagobjfn = $tfn->('.signed.tmp');
4450 runcmd shell_cmd "exec >$tagobjfn", qw(cat --),
4451 $tfn->('.tmp'), $tfn->('.tmp.asc');
4457 my @r = map { $mktag->($_); } @$tagwants;
4461 sub sign_changes ($) {
4462 my ($changesfile) = @_;
4464 my @debsign_cmd = @debsign;
4465 push @debsign_cmd, "-k$keyid" if defined $keyid;
4466 push @debsign_cmd, "-p$gpg[0]" if $gpg[0] ne 'gpg';
4467 push @debsign_cmd, $changesfile;
4468 runcmd_ordryrun @debsign_cmd;
4473 printdebug "actually entering push\n";
4475 supplementary_message(__ <<'END');
4476 Push failed, while checking state of the archive.
4477 You can retry the push, after fixing the problem, if you like.
4479 if (check_for_git()) {
4482 my $archive_hash = fetch_from_archive();
4483 if (!$archive_hash) {
4485 fail __ "package appears to be new in this suite;".
4486 " if this is intentional, use --new";
4489 supplementary_message(__ <<'END');
4490 Push failed, while preparing your push.
4491 You can retry the push, after fixing the problem, if you like.
4496 access_giturl(); # check that success is vaguely likely
4497 rpush_handle_protovsn_bothends() if $we_are_initiator;
4499 my $clogpfn = dgit_privdir()."/changelog.822.tmp";
4500 runcmd shell_cmd "exec >$clogpfn", qw(dpkg-parsechangelog);
4502 responder_send_file('parsed-changelog', $clogpfn);
4504 my ($clogp, $cversion, $dscfn) =
4505 push_parse_changelog("$clogpfn");
4507 my $dscpath = "$buildproductsdir/$dscfn";
4508 stat_exists $dscpath or
4509 fail f_ "looked for .dsc %s, but %s; maybe you forgot to build",
4512 responder_send_file('dsc', $dscpath);
4514 push_parse_dsc($dscpath, $dscfn, $cversion);
4516 my $format = getfield $dsc, 'Format';
4518 my $symref = git_get_symref();
4519 my $actualhead = git_rev_parse('HEAD');
4521 if (branch_is_gdr_unstitched_ff($symref, $actualhead, $archive_hash)) {
4522 if (quiltmode_splitting()) {
4523 my ($ffq_prev, $gdrlast) = branch_gdr_info($symref, $actualhead);
4524 fail f_ <<END, $ffq_prev, $quilt_mode;
4525 Branch is managed by git-debrebase (%s
4526 exists), but quilt mode (%s) implies a split view.
4527 Pass the right --quilt option or adjust your git config.
4528 Or, maybe, run git-debrebase forget-was-ever-debrebase.
4531 runcmd_ordryrun_local @git_debrebase, 'stitch';
4532 $actualhead = git_rev_parse('HEAD');
4535 my $dgithead = $actualhead;
4536 my $maintviewhead = undef;
4538 my $upstreamversion = upstreamversion $clogp->{Version};
4540 if (madformat_wantfixup($format)) {
4541 # user might have not used dgit build, so maybe do this now:
4542 if (do_split_brain()) {
4543 changedir $playground;
4545 ($dgithead, $cachekey) =
4546 quilt_check_splitbrain_cache($actualhead, $upstreamversion);
4547 $dgithead or fail f_
4548 "--quilt=%s but no cached dgit view:
4549 perhaps HEAD changed since dgit build[-source] ?",
4552 if (!do_split_brain()) {
4553 # In split brain mode, do not attempt to incorporate dirty
4554 # stuff from the user's working tree. That would be mad.
4555 commit_quilty_patch();
4558 if (do_split_brain()) {
4559 $made_split_brain = 1;
4560 $dgithead = splitbrain_pseudomerge($clogp,
4561 $actualhead, $dgithead,
4563 $maintviewhead = $actualhead;
4565 prep_ud(); # so _only_subdir() works, below
4568 if (defined $overwrite_version && !defined $maintviewhead
4570 $dgithead = plain_overwrite_pseudomerge($clogp,
4578 if ($archive_hash) {
4579 if (is_fast_fwd($archive_hash, $dgithead)) {
4581 } elsif (deliberately_not_fast_forward) {
4584 fail __ "dgit push: HEAD is not a descendant".
4585 " of the archive's version.\n".
4586 "To overwrite the archive's contents,".
4587 " pass --overwrite[=VERSION].\n".
4588 "To rewind history, if permitted by the archive,".
4589 " use --deliberately-not-fast-forward.";
4593 confess unless !!$made_split_brain == do_split_brain();
4595 changedir $playground;
4596 progress f_ "checking that %s corresponds to HEAD", $dscfn;
4597 runcmd qw(dpkg-source -x --),
4598 $dscpath =~ m#^/# ? $dscpath : "$maindir/$dscpath";
4599 my ($tree,$dir) = mktree_in_ud_from_only_subdir("source package");
4600 check_for_vendor_patches() if madformat($dsc->{format});
4602 my @diffcmd = (@git, qw(diff --quiet), $tree, $dgithead);
4603 debugcmd "+",@diffcmd;
4605 my $r = system @diffcmd;
4608 my $referent = $made_split_brain ? $dgithead : 'HEAD';
4609 my $diffs = cmdoutput @git, qw(diff --stat), $tree, $dgithead;
4612 my $raw = cmdoutput @git,
4613 qw(diff --no-renames -z -r --raw), $tree, $dgithead;
4615 foreach (split /\0/, $raw) {
4616 if (defined $changed) {
4617 push @mode_changes, "$changed: $_\n" if $changed;
4620 } elsif (m/^:0+ 0+ /) {
4622 } elsif (m/^:(?:10*)?(\d+) (?:10*)?(\d+) /) {
4623 $changed = "Mode change from $1 to $2"
4628 if (@mode_changes) {
4629 fail +(f_ <<ENDT, $dscfn).<<END
4630 HEAD specifies a different tree to %s:
4634 .(join '', @mode_changes)
4635 .(f_ <<ENDT, $tree, $referent);
4636 There is a problem with your source tree (see dgit(7) for some hints).
4637 To see a full diff, run git diff %s %s
4641 fail +(f_ <<ENDT, $dscfn).<<END.(f_ <<ENDT, $tree, $referent);
4642 HEAD specifies a different tree to %s:
4646 Perhaps you forgot to build. Or perhaps there is a problem with your
4647 source tree (see dgit(7) for some hints). To see a full diff, run
4654 if (!$changesfile) {
4655 my $pat = changespat $cversion;
4656 my @cs = glob "$buildproductsdir/$pat";
4657 fail f_ "failed to find unique changes file".
4658 " (looked for %s in %s);".
4659 " perhaps you need to use dgit -C",
4660 $pat, $buildproductsdir
4662 ($changesfile) = @cs;
4664 $changesfile = "$buildproductsdir/$changesfile";
4667 # Check that changes and .dsc agree enough
4668 $changesfile =~ m{[^/]*$};
4669 my $changes = parsecontrol($changesfile,$&);
4670 files_compare_inputs($dsc, $changes)
4671 unless forceing [qw(dsc-changes-mismatch)];
4673 # Check whether this is a source only upload
4674 my $hasdebs = $changes->{Files} =~ m{\.deb$}m;
4675 my $sourceonlypolicy = access_cfg 'source-only-uploads';
4676 if ($sourceonlypolicy eq 'ok') {
4677 } elsif ($sourceonlypolicy eq 'always') {
4678 forceable_fail [qw(uploading-binaries)],
4679 __ "uploading binaries, although distro policy is source only"
4681 } elsif ($sourceonlypolicy eq 'never') {
4682 forceable_fail [qw(uploading-source-only)],
4683 __ "source-only upload, although distro policy requires .debs"
4685 } elsif ($sourceonlypolicy eq 'not-wholly-new') {
4686 forceable_fail [qw(uploading-source-only)],
4687 f_ "source-only upload, even though package is entirely NEW\n".
4688 "(this is contrary to policy in %s)",
4692 && !(archive_query('package_not_wholly_new', $package) // 1);
4694 badcfg f_ "unknown source-only-uploads policy \`%s'",
4698 # Perhaps adjust .dsc to contain right set of origs
4699 changes_update_origs_from_dsc($dsc, $changes, $upstreamversion,
4701 unless forceing [qw(changes-origs-exactly)];
4703 # Checks complete, we're going to try and go ahead:
4705 responder_send_file('changes',$changesfile);
4706 responder_send_command("param head $dgithead");
4707 responder_send_command("param csuite $csuite");
4708 responder_send_command("param isuite $isuite");
4709 responder_send_command("param tagformat new"); # needed in $protovsn==4
4710 responder_send_command("param splitbrain $do_split_brain");
4711 if (defined $maintviewhead) {
4712 responder_send_command("param maint-view $maintviewhead");
4715 # Perhaps send buildinfo(s) for signing
4716 my $changes_files = getfield $changes, 'Files';
4717 my @buildinfos = ($changes_files =~ m/ .* (\S+\.buildinfo)$/mg);
4718 foreach my $bi (@buildinfos) {
4719 responder_send_command("param buildinfo-filename $bi");
4720 responder_send_file('buildinfo', "$buildproductsdir/$bi");
4723 if (deliberately_not_fast_forward) {
4724 git_for_each_ref(lrfetchrefs, sub {
4725 my ($objid,$objtype,$lrfetchrefname,$reftail) = @_;
4726 my $rrefname= substr($lrfetchrefname, length(lrfetchrefs) + 1);
4727 responder_send_command("previously $rrefname=$objid");
4728 $previously{$rrefname} = $objid;
4732 my @tagwants = push_tagwants($cversion, $dgithead, $maintviewhead,
4733 dgit_privdir()."/tag");
4736 supplementary_message(__ <<'END');
4737 Push failed, while signing the tag.
4738 You can retry the push, after fixing the problem, if you like.
4740 # If we manage to sign but fail to record it anywhere, it's fine.
4741 if ($we_are_responder) {
4742 @tagobjfns = map { $_->{Tfn}('.signed-tmp') } @tagwants;
4743 responder_receive_files('signed-tag', @tagobjfns);
4745 @tagobjfns = push_mktags($clogp,$dscpath,
4746 $changesfile,$changesfile,
4749 supplementary_message(__ <<'END');
4750 Push failed, *after* signing the tag.
4751 If you want to try again, you should use a new version number.
4754 pairwise { $a->{TagObjFn} = $b } @tagwants, @tagobjfns;
4756 foreach my $tw (@tagwants) {
4757 my $tag = $tw->{Tag};
4758 my $tagobjfn = $tw->{TagObjFn};
4760 cmdoutput @git, qw(hash-object -w -t tag), $tagobjfn;
4761 runcmd_ordryrun @git, qw(verify-tag), $tag_obj_hash;
4762 runcmd_ordryrun_local
4763 @git, qw(update-ref), "refs/tags/$tag", $tag_obj_hash;
4766 supplementary_message(__ <<'END');
4767 Push failed, while updating the remote git repository - see messages above.
4768 If you want to try again, you should use a new version number.
4770 if (!check_for_git()) {
4771 create_remote_git_repo();
4774 my @pushrefs = $forceflag.$dgithead.":".rrref();
4775 foreach my $tw (@tagwants) {
4776 push @pushrefs, $forceflag."refs/tags/$tw->{Tag}";
4779 runcmd_ordryrun @git,
4780 qw(-c push.followTags=false push), access_giturl(), @pushrefs;
4781 runcmd_ordryrun git_update_ref_cmd 'dgit push', lrref(), $dgithead;
4783 supplementary_message(__ <<'END');
4784 Push failed, while obtaining signatures on the .changes and .dsc.
4785 If it was just that the signature failed, you may try again by using
4786 debsign by hand to sign the changes file (see the command dgit tried,
4787 above), and then dput that changes file to complete the upload.
4788 If you need to change the package, you must use a new version number.
4790 if ($we_are_responder) {
4791 my $dryrunsuffix = act_local() ? "" : ".tmp";
4792 my @rfiles = ($dscpath, $changesfile);
4793 push @rfiles, map { "$buildproductsdir/$_" } @buildinfos;
4794 responder_receive_files('signed-dsc-changes',
4795 map { "$_$dryrunsuffix" } @rfiles);
4798 rename "$dscpath.tmp",$dscpath or die "$dscfn $!";
4800 progress f_ "[new .dsc left in %s.tmp]", $dscpath;
4802 sign_changes $changesfile;
4805 supplementary_message(f_ <<END, $changesfile);
4806 Push failed, while uploading package(s) to the archive server.
4807 You can retry the upload of exactly these same files with dput of:
4809 If that .changes file is broken, you will need to use a new version
4810 number for your next attempt at the upload.
4812 my $host = access_cfg('upload-host','RETURN-UNDEF');
4813 my @hostarg = defined($host) ? ($host,) : ();
4814 runcmd_ordryrun @dput, @hostarg, $changesfile;
4815 printdone f_ "pushed and uploaded %s", $cversion;
4817 supplementary_message('');
4818 responder_send_command("complete");
4822 not_necessarily_a_tree();
4827 badusage __ "-p is not allowed with clone; specify as argument instead"
4828 if defined $package;
4831 } elsif (@ARGV==2 && $ARGV[1] =~ m#^\w#) {
4832 ($package,$isuite) = @ARGV;
4833 } elsif (@ARGV==2 && $ARGV[1] =~ m#^[./]#) {
4834 ($package,$dstdir) = @ARGV;
4835 } elsif (@ARGV==3) {
4836 ($package,$isuite,$dstdir) = @ARGV;
4838 badusage __ "incorrect arguments to dgit clone";
4842 $dstdir ||= "$package";
4843 if (stat_exists $dstdir) {
4844 fail f_ "%s already exists", $dstdir;
4848 if ($rmonerror && !$dryrun_level) {
4849 $cwd_remove= getcwd();
4851 return unless defined $cwd_remove;
4852 if (!chdir "$cwd_remove") {
4853 return if $!==&ENOENT;
4854 confess "chdir $cwd_remove: $!";
4856 printdebug "clone rmonerror removing $dstdir\n";
4858 rmtree($dstdir) or fail f_ "remove %s: %s\n", $dstdir, $!;
4859 } elsif (grep { $! == $_ }
4860 (ENOENT, ENOTDIR, EACCES, EPERM, ELOOP)) {
4862 print STDERR f_ "check whether to remove %s: %s\n",
4869 $cwd_remove = undef;
4872 sub branchsuite () {
4873 my $branch = git_get_symref();
4874 if (defined $branch && $branch =~ m#$lbranch_re#o) {
4881 sub package_from_d_control () {
4882 if (!defined $package) {
4883 my $sourcep = parsecontrol('debian/control','debian/control');
4884 $package = getfield $sourcep, 'Source';
4888 sub fetchpullargs () {
4889 package_from_d_control();
4891 $isuite = branchsuite();
4893 my $clogp = parsechangelog();
4894 my $clogsuite = getfield $clogp, 'Distribution';
4895 $isuite= $clogsuite if $clogsuite ne 'UNRELEASED';
4897 } elsif (@ARGV==1) {
4900 badusage __ "incorrect arguments to dgit fetch or dgit pull";
4914 determine_whether_split_brain get_source_format();
4915 if (do_split_brain()) {
4916 my ($format, $fopts) = get_source_format();
4917 madformat($format) and fail f_ <<END, $quilt_mode
4918 dgit pull not yet supported in split view mode (including with view-splitting quilt modes)
4926 package_from_d_control();
4927 @ARGV==1 or badusage __ "dgit checkout needs a suite argument";
4931 foreach my $canon (qw(0 1)) {
4936 canonicalise_suite();
4938 if (length git_get_ref lref()) {
4939 # local branch already exists, yay
4942 if (!length git_get_ref lrref()) {
4950 runcmd (@git, qw(update-ref), lref(), lrref(), '');
4953 local $ENV{GIT_REFLOG_ACTION} = git_reflog_action_msg
4954 "dgit checkout $isuite";
4955 runcmd (@git, qw(checkout), lbranch());
4958 sub cmd_update_vcs_git () {
4960 if (@ARGV==0 || $ARGV[0] =~ m/^-/) {
4961 ($specsuite,) = split /\;/, cfg 'dgit.vcs-git.suites';
4963 ($specsuite) = (@ARGV);
4968 if ($ARGV[0] eq '-') {
4970 } elsif ($ARGV[0] eq '-') {
4975 package_from_d_control();
4977 if ($specsuite eq '.') {
4978 $ctrl = parsecontrol 'debian/control', 'debian/control';
4980 $isuite = $specsuite;
4984 my $url = getfield $ctrl, 'Vcs-Git';
4987 my $orgurl = cfg 'remote.vcs-git.url', 'RETURN-UNDEF';
4988 if (!defined $orgurl) {
4989 print STDERR f_ "setting up vcs-git: %s\n", $url;
4990 @cmd = (@git, qw(remote add vcs-git), $url);
4991 } elsif ($orgurl eq $url) {
4992 print STDERR f_ "vcs git already configured: %s\n", $url;
4994 print STDERR f_ "changing vcs-git url to: %s\n", $url;
4995 @cmd = (@git, qw(remote set-url vcs-git), $url);
4997 runcmd_ordryrun_local @cmd;
4999 print f_ "fetching (%s)\n", "@ARGV";
5000 runcmd_ordryrun_local @git, qw(fetch vcs-git), @ARGV;
5006 build_or_push_prep_early();
5008 build_or_push_prep_modes();
5012 } elsif (@ARGV==1) {
5013 ($specsuite) = (@ARGV);
5015 badusage f_ "incorrect arguments to dgit %s", $subcommand;
5018 local ($package) = $existing_package; # this is a hack
5019 canonicalise_suite();
5021 canonicalise_suite();
5023 if (defined $specsuite &&
5024 $specsuite ne $isuite &&
5025 $specsuite ne $csuite) {
5026 fail f_ "dgit %s: changelog specifies %s (%s)".
5027 " but command line specifies %s",
5028 $subcommand, $isuite, $csuite, $specsuite;
5037 #---------- remote commands' implementation ----------
5039 sub pre_remote_push_build_host {
5040 my ($nrargs) = shift @ARGV;
5041 my (@rargs) = @ARGV[0..$nrargs-1];
5042 @ARGV = @ARGV[$nrargs..$#ARGV];
5044 my ($dir,$vsnwant) = @rargs;
5045 # vsnwant is a comma-separated list; we report which we have
5046 # chosen in our ready response (so other end can tell if they
5049 $we_are_responder = 1;
5050 $us .= " (build host)";
5052 open PI, "<&STDIN" or confess "$!";
5053 open STDIN, "/dev/null" or confess "$!";
5054 open PO, ">&STDOUT" or confess "$!";
5056 open STDOUT, ">&STDERR" or confess "$!";
5060 ($protovsn) = grep {
5061 $vsnwant =~ m{^(?:.*,)?$_(?:,.*)?$}
5062 } @rpushprotovsn_support;
5064 fail f_ "build host has dgit rpush protocol versions %s".
5065 " but invocation host has %s",
5066 (join ",", @rpushprotovsn_support), $vsnwant
5067 unless defined $protovsn;
5071 sub cmd_remote_push_build_host {
5072 responder_send_command("dgit-remote-push-ready $protovsn");
5076 sub pre_remote_push_responder { pre_remote_push_build_host(); }
5077 sub cmd_remote_push_responder { cmd_remote_push_build_host(); }
5078 # ... for compatibility with proto vsn.1 dgit (just so that user gets
5079 # a good error message)
5081 sub rpush_handle_protovsn_bothends () {
5088 my $report = i_child_report();
5089 if (defined $report) {
5090 printdebug "($report)\n";
5091 } elsif ($i_child_pid) {
5092 printdebug "(killing build host child $i_child_pid)\n";
5093 kill 15, $i_child_pid;
5095 if (defined $i_tmp && !defined $initiator_tempdir) {
5097 eval { rmtree $i_tmp; };
5102 return unless forkcheck_mainprocess();
5107 my ($base,$selector,@args) = @_;
5108 $selector =~ s/\-/_/g;
5109 { no strict qw(refs); &{"${base}_${selector}"}(@args); }
5113 not_necessarily_a_tree();
5118 if ($host =~ m/^((?:[^][]|\[[^][]*\])*)\:/) {
5126 push @rargs, join ",", @rpushprotovsn_support;
5129 push @rdgit, @ropts;
5130 push @rdgit, qw(remote-push-build-host), (scalar @rargs), @rargs;
5132 my @cmd = (@ssh, $host, shellquote @rdgit);
5135 $we_are_initiator=1;
5137 if (defined $initiator_tempdir) {
5138 rmtree $initiator_tempdir;
5139 mkdir $initiator_tempdir, 0700
5140 or fail f_ "create %s: %s", $initiator_tempdir, $!;
5141 $i_tmp = $initiator_tempdir;
5145 $i_child_pid = open2(\*RO, \*RI, @cmd);
5147 ($protovsn) = initiator_expect { m/^dgit-remote-push-ready (\S+)/ };
5148 die "$protovsn ?" unless grep { $_ eq $protovsn } @rpushprotovsn_support;
5151 my ($icmd,$iargs) = initiator_expect {
5152 m/^(\S+)(?: (.*))?$/;
5155 i_method "i_resp", $icmd, $iargs;
5159 sub i_resp_progress ($) {
5161 my $msg = protocol_read_bytes \*RO, $rhs;
5165 sub i_resp_supplementary_message ($) {
5167 $supplementary_message = protocol_read_bytes \*RO, $rhs;
5170 sub i_resp_complete {
5171 my $pid = $i_child_pid;
5172 $i_child_pid = undef; # prevents killing some other process with same pid
5173 printdebug "waiting for build host child $pid...\n";
5174 my $got = waitpid $pid, 0;
5175 confess "$!" unless $got == $pid;
5176 fail f_ "build host child failed: %s", waitstatusmsg() if $?;
5179 printdebug __ "all done\n";
5183 sub i_resp_file ($) {
5185 my $localname = i_method "i_localname", $keyword;
5186 my $localpath = "$i_tmp/$localname";
5187 stat_exists $localpath and
5188 badproto \*RO, f_ "file %s (%s) twice", $keyword, $localpath;
5189 protocol_receive_file \*RO, $localpath;
5190 i_method "i_file", $keyword;
5195 sub i_resp_param ($) {
5196 $_[0] =~ m/^(\S+) (.*)$/ or badproto \*RO, __ "bad param spec";
5200 sub i_resp_previously ($) {
5201 $_[0] =~ m#^(refs/tags/\S+)=(\w+)$#
5202 or badproto \*RO, __ "bad previously spec";
5203 my $r = system qw(git check-ref-format), $1;
5204 confess "bad previously ref spec ($r)" if $r;
5205 $previously{$1} = $2;
5209 our ($i_clogp, $i_version, $i_dscfn, $i_changesfn, @i_buildinfos);
5211 sub i_resp_want ($) {
5213 die "$keyword ?" if $i_wanted{$keyword}++;
5215 defined $i_param{'csuite'} or badproto \*RO, "premature desire, no csuite";
5216 $isuite = $i_param{'isuite'} // $i_param{'csuite'};
5217 die unless $isuite =~ m/^$suite_re$/;
5219 if (!defined $dsc) {
5221 rpush_handle_protovsn_bothends();
5222 push_parse_dsc $i_dscfn, 'remote dsc', $i_version;
5223 if ($protovsn >= 6) {
5224 determine_whether_split_brain getfield $dsc, 'Format';
5225 $do_split_brain eq ($i_param{'splitbrain'} // '<unsent>')
5227 "split brain mismatch, $do_split_brain != $i_param{'split_brain'}";
5228 printdebug "rpush split brain $do_split_brain\n";
5232 my @localpaths = i_method "i_want", $keyword;
5233 printdebug "[[ $keyword @localpaths\n";
5234 foreach my $localpath (@localpaths) {
5235 protocol_send_file \*RI, $localpath;
5237 print RI "files-end\n" or confess "$!";
5240 sub i_localname_parsed_changelog {
5241 return "remote-changelog.822";
5243 sub i_file_parsed_changelog {
5244 ($i_clogp, $i_version, $i_dscfn) =
5245 push_parse_changelog "$i_tmp/remote-changelog.822";
5246 die if $i_dscfn =~ m#/|^\W#;
5249 sub i_localname_dsc {
5250 defined $i_dscfn or badproto \*RO, "dsc (before parsed-changelog)";
5255 sub i_localname_buildinfo ($) {
5256 my $bi = $i_param{'buildinfo-filename'};
5257 defined $bi or badproto \*RO, "buildinfo before filename";
5258 defined $i_changesfn or badproto \*RO, "buildinfo before changes";
5259 $bi =~ m{^\Q$package\E_[!-.0-~]*\.buildinfo$}s
5260 or badproto \*RO, "improper buildinfo filename";
5263 sub i_file_buildinfo {
5264 my $bi = $i_param{'buildinfo-filename'};
5265 my $bd = parsecontrol "$i_tmp/$bi", $bi;
5266 my $ch = parsecontrol "$i_tmp/$i_changesfn", 'changes';
5267 if (!forceing [qw(buildinfo-changes-mismatch)]) {
5268 files_compare_inputs($bd, $ch);
5269 (getfield $bd, $_) eq (getfield $ch, $_) or
5270 fail f_ "buildinfo mismatch in field %s", $_
5271 foreach qw(Source Version);
5272 !defined $bd->{$_} or
5273 fail f_ "buildinfo contains forbidden field %s", $_
5274 foreach qw(Changes Changed-by Distribution);
5276 push @i_buildinfos, $bi;
5277 delete $i_param{'buildinfo-filename'};
5280 sub i_localname_changes {
5281 defined $i_dscfn or badproto \*RO, "dsc (before parsed-changelog)";
5282 $i_changesfn = $i_dscfn;
5283 $i_changesfn =~ s/\.dsc$/_dgit.changes/ or die;
5284 return $i_changesfn;
5286 sub i_file_changes { }
5288 sub i_want_signed_tag {
5289 printdebug Dumper(\%i_param, $i_dscfn);
5290 defined $i_param{'head'} && defined $i_dscfn && defined $i_clogp
5291 && defined $i_param{'csuite'}
5292 or badproto \*RO, "premature desire for signed-tag";
5293 my $head = $i_param{'head'};
5294 die if $head =~ m/[^0-9a-f]/ || $head !~ m/^../;
5296 my $maintview = $i_param{'maint-view'};
5297 die if defined $maintview && $maintview =~ m/[^0-9a-f]/;
5299 if ($protovsn == 4) {
5300 my $p = $i_param{'tagformat'} // '<undef>';
5302 or badproto \*RO, "tag format mismatch: $p vs. new";
5305 die unless $i_param{'csuite'} =~ m/^$suite_re$/;
5307 defined $dsc or badproto \*RO, "dsc (before parsed-changelog)";
5309 my @tagwants = push_tagwants $i_version, $head, $maintview, "tag";
5312 push_mktags $i_clogp, $i_dscfn,
5313 $i_changesfn, (__ 'remote changes file'),
5317 sub i_want_signed_dsc_changes {
5318 rename "$i_dscfn.tmp","$i_dscfn" or die "$i_dscfn $!";
5319 sign_changes $i_changesfn;
5320 return ($i_dscfn, $i_changesfn, @i_buildinfos);
5323 #---------- building etc. ----------
5329 #----- `3.0 (quilt)' handling -----
5331 our $fakeeditorenv = 'DGIT_FAKE_EDITOR_QUILT';
5333 sub quiltify_dpkg_commit ($$$;$) {
5334 my ($patchname,$author,$msg, $xinfo) = @_;
5337 mkpath '.git/dgit'; # we are in playtree
5338 my $descfn = ".git/dgit/quilt-description.tmp";
5339 open O, '>', $descfn or confess "$descfn: $!";
5340 $msg =~ s/\n+/\n\n/;
5341 print O <<END or confess "$!";
5343 ${xinfo}Subject: $msg
5347 close O or confess "$!";
5350 local $ENV{'EDITOR'} = cmdoutput qw(realpath --), $0;
5351 local $ENV{'VISUAL'} = $ENV{'EDITOR'};
5352 local $ENV{$fakeeditorenv} = cmdoutput qw(realpath --), $descfn;
5353 runcmd @dpkgsource, qw(--commit --include-removal .), $patchname;
5357 sub quiltify_trees_differ ($$;$$$) {
5358 my ($x,$y,$finegrained,$ignorenamesr,$unrepres) = @_;
5359 # returns true iff the two tree objects differ other than in debian/
5360 # with $finegrained,
5361 # returns bitmask 01 - differ in upstream files except .gitignore
5362 # 02 - differ in .gitignore
5363 # if $ignorenamesr is defined, $ingorenamesr->{$fn}
5364 # is set for each modified .gitignore filename $fn
5365 # if $unrepres is defined, array ref to which is appeneded
5366 # a list of unrepresentable changes (removals of upstream files
5369 my @cmd = (@git, qw(diff-tree -z --no-renames));
5370 push @cmd, qw(--name-only) unless $unrepres;
5371 push @cmd, qw(-r) if $finegrained || $unrepres;
5373 my $diffs= cmdoutput @cmd;
5376 foreach my $f (split /\0/, $diffs) {
5377 if ($unrepres && !@lmodes) {
5378 @lmodes = $f =~ m/^\:(\w+) (\w+) \w+ \w+ / or die "$_ ?";
5381 my ($oldmode,$newmode) = @lmodes;
5384 next if $f =~ m#^debian(?:/.*)?$#s;
5388 die __ "not a plain file or symlink\n"
5389 unless $newmode =~ m/^(?:10|12)\d{4}$/ ||
5390 $oldmode =~ m/^(?:10|12)\d{4}$/;
5391 if ($oldmode =~ m/[^0]/ &&
5392 $newmode =~ m/[^0]/) {
5393 # both old and new files exist
5394 die __ "mode or type changed\n" if $oldmode ne $newmode;
5395 die __ "modified symlink\n" unless $newmode =~ m/^10/;
5396 } elsif ($oldmode =~ m/[^0]/) {
5398 die __ "deletion of symlink\n"
5399 unless $oldmode =~ m/^10/;
5402 die __ "creation with non-default mode\n"
5403 unless $newmode =~ m/^100644$/ or
5404 $newmode =~ m/^120000$/;
5408 local $/="\n"; chomp $@;
5409 push @$unrepres, [ $f, "$@ ($oldmode->$newmode)" ];
5413 my $isignore = $f =~ m#^(?:.*/)?.gitignore$#s;
5414 $r |= $isignore ? 02 : 01;
5415 $ignorenamesr->{$f}=1 if $ignorenamesr && $isignore;
5417 printdebug "quiltify_trees_differ $x $y => $r\n";
5421 sub quiltify_tree_sentinelfiles ($) {
5422 # lists the `sentinel' files present in the tree
5424 my $r = cmdoutput @git, qw(ls-tree --name-only), $x,
5425 qw(-- debian/rules debian/control);
5430 sub quiltify_splitting ($$$$$$$) {
5431 my ($clogp, $unapplied, $headref, $oldtiptree, $diffbits,
5432 $editedignores, $cachekey) = @_;
5433 my $gitignore_special = 1;
5434 if ($quilt_mode !~ m/gbp|dpm|baredebian/) {
5435 # treat .gitignore just like any other upstream file
5436 $diffbits = { %$diffbits };
5437 $_ = !!$_ foreach values %$diffbits;
5438 $gitignore_special = 0;
5440 # We would like any commits we generate to be reproducible
5441 my @authline = clogp_authline($clogp);
5442 local $ENV{GIT_COMMITTER_NAME} = $authline[0];
5443 local $ENV{GIT_COMMITTER_EMAIL} = $authline[1];
5444 local $ENV{GIT_COMMITTER_DATE} = $authline[2];
5445 local $ENV{GIT_AUTHOR_NAME} = $authline[0];
5446 local $ENV{GIT_AUTHOR_EMAIL} = $authline[1];
5447 local $ENV{GIT_AUTHOR_DATE} = $authline[2];
5449 confess unless do_split_brain();
5451 my $fulldiffhint = sub {
5453 my $cmd = "git diff $x $y -- :/ ':!debian'";
5454 $cmd .= " ':!/.gitignore' ':!*/.gitignore'" if $gitignore_special;
5455 return f_ "\nFor full diff showing the problem(s), type:\n %s\n",
5459 if ($quilt_mode =~ m/gbp|unapplied|baredebian/ &&
5460 ($diffbits->{O2H} & 01)) {
5462 "--quilt=%s specified, implying patches-unapplied git tree\n".
5463 " but git tree differs from orig in upstream files.",
5465 $msg .= $fulldiffhint->($unapplied, 'HEAD');
5466 if (!stat_exists "debian/patches" and $quilt_mode !~ m/baredebian/) {
5468 "\n ... debian/patches is missing; perhaps this is a patch queue branch?";
5472 if ($quilt_mode =~ m/dpm/ &&
5473 ($diffbits->{H2A} & 01)) {
5474 fail +(f_ <<END, $quilt_mode). $fulldiffhint->($oldtiptree,'HEAD');
5475 --quilt=%s specified, implying patches-applied git tree
5476 but git tree differs from result of applying debian/patches to upstream
5479 if ($quilt_mode =~ m/baredebian/) {
5480 # We need to construct a merge which has upstream files from
5481 # upstream and debian/ files from HEAD.
5483 read_tree_upstream $quilt_upstream_commitish, 1, $headref;
5484 my $version = getfield $clogp, 'Version';
5485 my $upsversion = upstreamversion $version;
5486 my $merge = make_commit
5487 [ $headref, $quilt_upstream_commitish ],
5488 [ +(f_ <<ENDT, $upsversion), $quilt_upstream_commitish_message, <<ENDU ];
5489 Combine debian/ with upstream source for %s
5491 [dgit ($our_version) baredebian-merge $version $quilt_upstream_commitish_used]
5493 runcmd @git, qw(reset -q --hard), $merge;
5495 if ($quilt_mode =~ m/gbp|unapplied|baredebian/ &&
5496 ($diffbits->{O2A} & 01)) { # some patches
5497 progress __ "dgit view: creating patches-applied version using gbp pq";
5498 runcmd shell_cmd 'exec >/dev/null', gbp_pq, qw(import);
5499 # gbp pq import creates a fresh branch; push back to dgit-view
5500 runcmd @git, qw(update-ref refs/heads/dgit-view HEAD);
5501 runcmd @git, qw(checkout -q dgit-view);
5503 if ($quilt_mode =~ m/gbp|dpm/ &&
5504 ($diffbits->{O2A} & 02)) {
5505 fail f_ <<END, $quilt_mode;
5506 --quilt=%s specified, implying that HEAD is for use with a
5507 tool which does not create patches for changes to upstream
5508 .gitignores: but, such patches exist in debian/patches.
5511 if (($diffbits->{O2H} & 02) && # user has modified .gitignore
5512 !($diffbits->{O2A} & 02)) { # patches do not change .gitignore
5514 "dgit view: creating patch to represent .gitignore changes";
5515 ensuredir "debian/patches";
5516 my $gipatch = "debian/patches/auto-gitignore";
5517 open GIPATCH, ">>", "$gipatch" or confess "$gipatch: $!";
5518 stat GIPATCH or confess "$gipatch: $!";
5519 fail f_ "%s already exists; but want to create it".
5520 " to record .gitignore changes",
5523 print GIPATCH +(__ <<END).<<ENDU or die "$gipatch: $!";
5524 Subject: Update .gitignore from Debian packaging branch
5526 The Debian packaging git branch contains these updates to the upstream
5527 .gitignore file(s). This patch is autogenerated, to provide these
5528 updates to users of the official Debian archive view of the package.
5531 [dgit ($our_version) update-gitignore]
5534 close GIPATCH or die "$gipatch: $!";
5535 runcmd shell_cmd "exec >>$gipatch", @git, qw(diff),
5536 $unapplied, $headref, "--", sort keys %$editedignores;
5537 open SERIES, "+>>", "debian/patches/series" or confess "$!";
5538 defined seek SERIES, -1, 2 or $!==EINVAL or confess "$!";
5540 defined read SERIES, $newline, 1 or confess "$!";
5541 print SERIES "\n" or confess "$!" unless $newline eq "\n";
5542 print SERIES "auto-gitignore\n" or confess "$!";
5543 close SERIES or die $!;
5544 runcmd @git, qw(add -f -- debian/patches/series), $gipatch;
5545 commit_admin +(__ <<END).<<ENDU
5546 Commit patch to update .gitignore
5549 [dgit ($our_version) update-gitignore-quilt-fixup]
5554 sub quiltify ($$$$) {
5555 my ($clogp,$target,$oldtiptree,$failsuggestion) = @_;
5557 # Quilt patchification algorithm
5559 # We search backwards through the history of the main tree's HEAD
5560 # (T) looking for a start commit S whose tree object is identical
5561 # to to the patch tip tree (ie the tree corresponding to the
5562 # current dpkg-committed patch series). For these purposes
5563 # `identical' disregards anything in debian/ - this wrinkle is
5564 # necessary because dpkg-source treates debian/ specially.
5566 # We can only traverse edges where at most one of the ancestors'
5567 # trees differs (in changes outside in debian/). And we cannot
5568 # handle edges which change .pc/ or debian/patches. To avoid
5569 # going down a rathole we avoid traversing edges which introduce
5570 # debian/rules or debian/control. And we set a limit on the
5571 # number of edges we are willing to look at.
5573 # If we succeed, we walk forwards again. For each traversed edge
5574 # PC (with P parent, C child) (starting with P=S and ending with
5575 # C=T) to we do this:
5577 # - dpkg-source --commit with a patch name and message derived from C
5578 # After traversing PT, we git commit the changes which
5579 # should be contained within debian/patches.
5581 # The search for the path S..T is breadth-first. We maintain a
5582 # todo list containing search nodes. A search node identifies a
5583 # commit, and looks something like this:
5585 # Commit => $git_commit_id,
5586 # Child => $c, # or undef if P=T
5587 # Whynot => $reason_edge_PC_unsuitable, # in @nots only
5588 # Nontrivial => true iff $p..$c has relevant changes
5595 my %considered; # saves being exponential on some weird graphs
5597 my $t_sentinels = quiltify_tree_sentinelfiles $target;
5600 my ($search,$whynot) = @_;
5601 printdebug " search NOT $search->{Commit} $whynot\n";
5602 $search->{Whynot} = $whynot;
5603 push @nots, $search;
5604 no warnings qw(exiting);
5613 my $c = shift @todo;
5614 next if $considered{$c->{Commit}}++;
5616 $not->($c, __ "maximum search space exceeded") if --$max_work <= 0;
5618 printdebug "quiltify investigate $c->{Commit}\n";
5621 if (!quiltify_trees_differ $c->{Commit}, $oldtiptree) {
5622 printdebug " search finished hooray!\n";
5627 quiltify_nofix_bail " $c->{Commit}", " (tree object $oldtiptree)";
5628 if ($quilt_mode eq 'smash') {
5629 printdebug " search quitting smash\n";
5633 my $c_sentinels = quiltify_tree_sentinelfiles $c->{Commit};
5634 $not->($c, f_ "has %s not %s", $c_sentinels, $t_sentinels)
5635 if $c_sentinels ne $t_sentinels;
5637 my $commitdata = cmdoutput @git, qw(cat-file commit), $c->{Commit};
5638 $commitdata =~ m/\n\n/;
5640 my @parents = ($commitdata =~ m/^parent (\w+)$/gm);
5641 @parents = map { { Commit => $_, Child => $c } } @parents;
5643 $not->($c, __ "root commit") if !@parents;
5645 foreach my $p (@parents) {
5646 $p->{Nontrivial}= quiltify_trees_differ $p->{Commit},$c->{Commit};
5648 my $ndiffers = grep { $_->{Nontrivial} } @parents;
5649 $not->($c, f_ "merge (%s nontrivial parents)", $ndiffers)
5652 foreach my $p (@parents) {
5653 printdebug "considering C=$c->{Commit} P=$p->{Commit}\n";
5655 my @cmd= (@git, qw(diff-tree -r --name-only),
5656 $p->{Commit},$c->{Commit},
5657 qw(-- debian/patches .pc debian/source/format));
5658 my $patchstackchange = cmdoutput @cmd;
5659 if (length $patchstackchange) {
5660 $patchstackchange =~ s/\n/,/g;
5661 $not->($p, f_ "changed %s", $patchstackchange);
5664 printdebug " search queue P=$p->{Commit} ",
5665 ($p->{Nontrivial} ? "NT" : "triv"),"\n";
5671 printdebug "quiltify want to smash\n";
5674 my $x = $_[0]{Commit};
5675 $x =~ s/(.*?[0-9a-z]{8})[0-9a-z]*$/$1/;
5678 if ($quilt_mode eq 'linear') {
5680 "\n%s: error: quilt fixup cannot be linear. Stopped at:\n",
5682 my $all_gdr = !!@nots;
5683 foreach my $notp (@nots) {
5684 my $c = $notp->{Child};
5685 my $cprange = $abbrev->($notp);
5686 $cprange .= "..".$abbrev->($c) if $c;
5687 print STDERR f_ "%s: %s: %s\n",
5688 $us, $cprange, $notp->{Whynot};
5689 $all_gdr &&= $notp->{Child} &&
5690 (git_cat_file $notp->{Child}{Commit}, 'commit')
5691 =~ m{^\[git-debrebase(?! split[: ]).*\]$}m;
5695 [ grep { $_->[0] ne 'quilt-mode' } @$failsuggestion ]
5697 print STDERR "$us: $_->[1]\n" foreach @$failsuggestion;
5699 "quilt history linearisation failed. Search \`quilt fixup' in dgit(7).\n";
5700 } elsif ($quilt_mode eq 'smash') {
5701 } elsif ($quilt_mode eq 'auto') {
5702 progress __ "quilt fixup cannot be linear, smashing...";
5704 confess "$quilt_mode ?";
5707 my $time = $ENV{'GIT_COMMITTER_DATE'} || time;
5708 $time =~ s/\s.*//; # trim timezone from GIT_COMMITTER_DATE
5710 my $msg = cmdoutput @git, qw(log), "-n$ncommits";
5712 quiltify_dpkg_commit "auto-$version-$target-$time",
5713 (getfield $clogp, 'Maintainer'),
5714 (f_ "Automatically generated patch (%s)\n".
5715 "Last (up to) %s git changes, FYI:\n\n",
5716 $clogp->{Version}, $ncommits).
5721 progress __ "quiltify linearisation planning successful, executing...";
5723 for (my $p = $sref_S;
5724 my $c = $p->{Child};
5726 printdebug "quiltify traverse $p->{Commit}..$c->{Commit}\n";
5727 next unless $p->{Nontrivial};
5729 my $cc = $c->{Commit};
5731 my $commitdata = cmdoutput @git, qw(cat-file commit), $cc;
5732 $commitdata =~ m/\n\n/ or die "$c ?";
5735 $commitdata =~ m/^author (.*) \d+ [-+0-9]+$/m or die "$cc ?";
5738 my $commitdate = cmdoutput
5739 @git, qw(log -n1 --pretty=format:%aD), $cc;
5741 $msg =~ s/^(.*)\n*/$1\n/ or die "$cc $msg ?";
5743 my $strip_nls = sub { $msg =~ s/\n+$//; $msg .= "\n"; };
5750 my $gbp_check_suitable = sub {
5755 die __ "contains unexpected slashes\n" if m{//} || m{/$};
5756 die __ "contains leading punctuation\n" if m{^\W} || m{/\W};
5757 die __ "contains bad character(s)\n" if m{[^-a-z0-9_.+=~/]}i;
5758 die __ "is series file\n" if m{$series_filename_re}o;
5759 die __ "too long\n" if length > 200;
5761 return $_ unless $@;
5763 "quiltifying commit %s: ignoring/dropping Gbp-Pq %s: %s",
5768 if ($msg =~ s/^ (?: gbp(?:-pq)? : \s* name \s+ |
5770 (\S+) \s* \n //ixm) {
5771 $patchname = $gbp_check_suitable->($1, 'Name');
5773 if ($msg =~ s/^ (?: gbp(?:-pq)? : \s* topic \s+ |
5775 (\S+) \s* \n //ixm) {
5776 $patchdir = $gbp_check_suitable->($1, 'Topic');
5781 if (!defined $patchname) {
5782 $patchname = $title;
5783 $patchname =~ s/[.:]$//;
5786 my $converter = new Text::Iconv qw(UTF-8 ASCII//TRANSLIT);
5787 my $translitname = $converter->convert($patchname);
5788 die unless defined $translitname;
5789 $patchname = $translitname;
5792 +(f_ "dgit: patch title transliteration error: %s", $@)
5794 $patchname =~ y/ A-Z/-a-z/;
5795 $patchname =~ y/-a-z0-9_.+=~//cd;
5796 $patchname =~ s/^\W/x-$&/;
5797 $patchname = substr($patchname,0,40);
5798 $patchname .= ".patch";
5800 if (!defined $patchdir) {
5803 if (length $patchdir) {
5804 $patchname = "$patchdir/$patchname";
5806 if ($patchname =~ m{^(.*)/}) {
5807 mkpath "debian/patches/$1";
5812 stat "debian/patches/$patchname$index";
5814 $!==ENOENT or confess "$patchname$index $!";
5816 runcmd @git, qw(checkout -q), $cc;
5818 # We use the tip's changelog so that dpkg-source doesn't
5819 # produce complaining messages from dpkg-parsechangelog. None
5820 # of the information dpkg-source gets from the changelog is
5821 # actually relevant - it gets put into the original message
5822 # which dpkg-source provides our stunt editor, and then
5824 runcmd @git, qw(checkout -q), $target, qw(debian/changelog);
5826 quiltify_dpkg_commit "$patchname$index", $author, $msg,
5827 "Date: $commitdate\n".
5828 "X-Dgit-Generated: $clogp->{Version} $cc\n";
5830 runcmd @git, qw(checkout -q), $cc, qw(debian/changelog);
5834 sub build_maybe_quilt_fixup () {
5835 my ($format,$fopts) = get_source_format;
5836 return unless madformat_wantfixup $format;
5839 check_for_vendor_patches();
5841 my $clogp = parsechangelog();
5842 my $headref = git_rev_parse('HEAD');
5843 my $symref = git_get_symref();
5844 my $upstreamversion = upstreamversion $version;
5847 changedir $playground;
5849 my $splitbrain_cachekey;
5851 if (do_split_brain()) {
5853 ($cachehit, $splitbrain_cachekey) =
5854 quilt_check_splitbrain_cache($headref, $upstreamversion);
5861 unpack_playtree_need_cd_work($headref);
5862 if (do_split_brain()) {
5863 runcmd @git, qw(checkout -q -b dgit-view);
5864 # so long as work is not deleted, its current branch will
5865 # remain dgit-view, rather than master, so subsequent calls to
5866 # unpack_playtree_need_cd_work
5867 # will DTRT, resetting dgit-view.
5868 confess if $made_split_brain;
5869 $made_split_brain = 1;
5873 if ($fopts->{'single-debian-patch'}) {
5875 "quilt mode %s does not make sense (or is not supported) with single-debian-patch",
5877 if quiltmode_splitting();
5878 quilt_fixup_singlepatch($clogp, $headref, $upstreamversion);
5880 quilt_fixup_multipatch($clogp, $headref, $upstreamversion,
5881 $splitbrain_cachekey);
5884 if (do_split_brain()) {
5885 my $dgitview = git_rev_parse 'HEAD';
5888 reflog_cache_insert "refs/$splitbraincache",
5889 $splitbrain_cachekey, $dgitview;
5891 changedir "$playground/work";
5893 my $saved = maybe_split_brain_save $headref, $dgitview, __ "converted";
5894 progress f_ "dgit view: created (%s)", $saved;
5898 runcmd_ordryrun_local
5899 @git, qw(pull --ff-only -q), "$playground/work", qw(master);
5902 sub build_check_quilt_splitbrain () {
5903 build_maybe_quilt_fixup();
5906 sub unpack_playtree_need_cd_work ($) {
5909 # prep_ud() must have been called already.
5910 if (!chdir "work") {
5911 # Check in the filesystem because sometimes we run prep_ud
5912 # in between multiple calls to unpack_playtree_need_cd_work.
5913 confess "$!" unless $!==ENOENT;
5914 mkdir "work" or confess "$!";
5916 mktree_in_ud_here();
5918 runcmd @git, qw(reset -q --hard), $headref;
5921 sub unpack_playtree_linkorigs ($$) {
5922 my ($upstreamversion, $fn) = @_;
5923 # calls $fn->($leafname);
5925 my $bpd_abs = bpd_abs();
5927 dotdot_bpd_transfer_origs $bpd_abs, $upstreamversion, sub { 1 };
5929 opendir QFD, $bpd_abs or fail "buildproductsdir: $bpd_abs: $!";
5930 while ($!=0, defined(my $leaf = readdir QFD)) {
5931 my $f = bpd_abs()."/".$leaf;
5933 local ($debuglevel) = $debuglevel-1;
5934 printdebug "QF linkorigs bpd $leaf, $f ?\n";
5936 next unless is_orig_file_of_vsn $leaf, $upstreamversion;
5937 printdebug "QF linkorigs $leaf, $f Y\n";
5938 link_ltarget $f, $leaf or die "$leaf $!";
5941 die "$buildproductsdir: $!" if $!;
5945 sub quilt_fixup_delete_pc () {
5946 runcmd @git, qw(rm -rqf .pc);
5947 commit_admin +(__ <<END).<<ENDU
5948 Commit removal of .pc (quilt series tracking data)
5951 [dgit ($our_version) upgrade quilt-remove-pc]
5955 sub quilt_fixup_singlepatch ($$$) {
5956 my ($clogp, $headref, $upstreamversion) = @_;
5958 progress __ "starting quiltify (single-debian-patch)";
5960 # dpkg-source --commit generates new patches even if
5961 # single-debian-patch is in debian/source/options. In order to
5962 # get it to generate debian/patches/debian-changes, it is
5963 # necessary to build the source package.
5965 unpack_playtree_linkorigs($upstreamversion, sub { });
5966 unpack_playtree_need_cd_work($headref);
5968 rmtree("debian/patches");
5970 runcmd @dpkgsource, qw(-b .);
5972 runcmd @dpkgsource, qw(-x), (srcfn $version, ".dsc");
5973 rename srcfn("$upstreamversion", "/debian/patches"),
5974 "work/debian/patches"
5976 or confess "install d/patches: $!";
5979 commit_quilty_patch();
5982 sub quilt_need_fake_dsc ($) {
5983 # cwd should be playground
5984 my ($upstreamversion) = @_;
5986 return if stat_exists "fake.dsc";
5987 # ^ OK to test this as a sentinel because if we created it
5988 # we must either have done the rest too, or crashed.
5990 my $fakeversion="$upstreamversion-~~DGITFAKE";
5992 my $fakedsc=new IO::File 'fake.dsc', '>' or confess "$!";
5993 print $fakedsc <<END or confess "$!";
5996 Version: $fakeversion
6000 my $dscaddfile=sub {
6003 my $md = new Digest::MD5;
6005 my $fh = new IO::File $leaf, '<' or die "$leaf $!";
6006 stat $fh or confess "$!";
6010 print $fakedsc " ".$md->hexdigest." $size $leaf\n" or confess "$!";
6013 unpack_playtree_linkorigs($upstreamversion, $dscaddfile);
6015 my @files=qw(debian/source/format debian/rules
6016 debian/control debian/changelog);
6017 foreach my $maybe (qw(debian/patches debian/source/options
6018 debian/tests/control)) {
6019 next unless stat_exists "$maindir/$maybe";
6020 push @files, $maybe;
6023 my $debtar= srcfn $fakeversion,'.debian.tar.gz';
6024 runcmd qw(env GZIP=-1n tar -zcf), "./$debtar", qw(-C), $maindir, @files;
6026 $dscaddfile->($debtar);
6027 close $fakedsc or confess "$!";
6030 sub quilt_fakedsc2unapplied ($$) {
6031 my ($headref, $upstreamversion) = @_;
6032 # must be run in the playground
6033 # quilt_need_fake_dsc must have been called
6035 quilt_need_fake_dsc($upstreamversion);
6037 'exec dpkg-source --no-check --skip-patches -x fake.dsc >/dev/null';
6039 my $fakexdir= $package.'-'.(stripepoch $upstreamversion);
6040 rename $fakexdir, "fake" or die "$fakexdir $!";
6044 remove_stray_gits(__ "source package");
6045 mktree_in_ud_here();
6049 rmtree 'debian'; # git checkout commitish paths does not delete!
6050 runcmd @git, qw(checkout -f), $headref, qw(-- debian);
6051 my $unapplied=git_add_write_tree();
6052 printdebug "fake orig tree object $unapplied\n";
6056 sub quilt_check_splitbrain_cache ($$) {
6057 my ($headref, $upstreamversion) = @_;
6058 # Called only if we are in (potentially) split brain mode.
6059 # Called in playground.
6060 # Computes the cache key and looks in the cache.
6061 # Returns ($dgit_view_commitid, $cachekey) or (undef, $cachekey)
6063 quilt_need_fake_dsc($upstreamversion);
6065 my $splitbrain_cachekey;
6068 "dgit: split brain (separate dgit view) may be needed (--quilt=%s).",
6070 # we look in the reflog of dgit-intern/quilt-cache
6071 # we look for an entry whose message is the key for the cache lookup
6072 my @cachekey = (qw(dgit), $our_version);
6073 push @cachekey, $upstreamversion;
6074 push @cachekey, $quilt_mode;
6075 push @cachekey, $headref;
6076 push @cachekey, $quilt_upstream_commitish // '-';
6078 push @cachekey, hashfile('fake.dsc');
6080 my $srcshash = Digest::SHA->new(256);
6081 my %sfs = ( %INC, '$0(dgit)' => $0 );
6082 foreach my $sfk (sort keys %sfs) {
6083 next unless $sfk =~ m/^\$0\b/ || $sfk =~ m{^Debian/Dgit\b};
6084 $srcshash->add($sfk," ");
6085 $srcshash->add(hashfile($sfs{$sfk}));
6086 $srcshash->add("\n");
6088 push @cachekey, $srcshash->hexdigest();
6089 $splitbrain_cachekey = "@cachekey";
6091 printdebug "splitbrain cachekey $splitbrain_cachekey\n";
6093 my $cachehit = reflog_cache_lookup
6094 "refs/$splitbraincache", $splitbrain_cachekey;
6097 unpack_playtree_need_cd_work($headref);
6098 my $saved = maybe_split_brain_save $headref, $cachehit, "cache-hit";
6099 if ($cachehit ne $headref) {
6100 progress f_ "dgit view: found cached (%s)", $saved;
6101 runcmd @git, qw(checkout -q -b dgit-view), $cachehit;
6102 $made_split_brain = 1;
6103 return ($cachehit, $splitbrain_cachekey);
6105 progress __ "dgit view: found cached, no changes required";
6106 return ($headref, $splitbrain_cachekey);
6109 printdebug "splitbrain cache miss\n";
6110 return (undef, $splitbrain_cachekey);
6113 sub baredebian_origtarballs_scan ($$$) {
6114 my ($fakedfi, $upstreamversion, $dir) = @_;
6115 if (!opendir OD, $dir) {
6116 return if $! == ENOENT;
6117 fail "opendir $dir (origs): $!";
6120 while ($!=0, defined(my $leaf = readdir OD)) {
6122 local ($debuglevel) = $debuglevel-1;
6123 printdebug "BDOS $dir $leaf ?\n";
6125 next unless is_orig_file_of_vsn $leaf, $upstreamversion;
6126 next if grep { $_->{Filename} eq $leaf } @$fakedfi;
6129 Path => "$dir/$leaf",
6133 die "$dir; $!" if $!;
6137 sub quilt_fixup_multipatch ($$$) {
6138 my ($clogp, $headref, $upstreamversion, $splitbrain_cachekey) = @_;
6140 progress f_ "examining quilt state (multiple patches, %s mode)",
6144 # - honour any existing .pc in case it has any strangeness
6145 # - determine the git commit corresponding to the tip of
6146 # the patch stack (if there is one)
6147 # - if there is such a git commit, convert each subsequent
6148 # git commit into a quilt patch with dpkg-source --commit
6149 # - otherwise convert all the differences in the tree into
6150 # a single git commit
6154 # Our git tree doesn't necessarily contain .pc. (Some versions of
6155 # dgit would include the .pc in the git tree.) If there isn't
6156 # one, we need to generate one by unpacking the patches that we
6159 # We first look for a .pc in the git tree. If there is one, we
6160 # will use it. (This is not the normal case.)
6162 # Otherwise need to regenerate .pc so that dpkg-source --commit
6163 # can work. We do this as follows:
6164 # 1. Collect all relevant .orig from parent directory
6165 # 2. Generate a debian.tar.gz out of
6166 # debian/{patches,rules,source/format,source/options}
6167 # 3. Generate a fake .dsc containing just these fields:
6168 # Format Source Version Files
6169 # 4. Extract the fake .dsc
6170 # Now the fake .dsc has a .pc directory.
6171 # (In fact we do this in every case, because in future we will
6172 # want to search for a good base commit for generating patches.)
6174 # Then we can actually do the dpkg-source --commit
6175 # 1. Make a new working tree with the same object
6176 # store as our main tree and check out the main
6178 # 2. Copy .pc from the fake's extraction, if necessary
6179 # 3. Run dpkg-source --commit
6180 # 4. If the result has changes to debian/, then
6181 # - git add them them
6182 # - git add .pc if we had a .pc in-tree
6184 # 5. If we had a .pc in-tree, delete it, and git commit
6185 # 6. Back in the main tree, fast forward to the new HEAD
6187 # Another situation we may have to cope with is gbp-style
6188 # patches-unapplied trees.
6190 # We would want to detect these, so we know to escape into
6191 # quilt_fixup_gbp. However, this is in general not possible.
6192 # Consider a package with a one patch which the dgit user reverts
6193 # (with git revert or the moral equivalent).
6195 # That is indistinguishable in contents from a patches-unapplied
6196 # tree. And looking at the history to distinguish them is not
6197 # useful because the user might have made a confusing-looking git
6198 # history structure (which ought to produce an error if dgit can't
6199 # cope, not a silent reintroduction of an unwanted patch).
6201 # So gbp users will have to pass an option. But we can usually
6202 # detect their failure to do so: if the tree is not a clean
6203 # patches-applied tree, quilt linearisation fails, but the tree
6204 # _is_ a clean patches-unapplied tree, we can suggest that maybe
6205 # they want --quilt=unapplied.
6207 # To help detect this, when we are extracting the fake dsc, we
6208 # first extract it with --skip-patches, and then apply the patches
6209 # afterwards with dpkg-source --before-build. That lets us save a
6210 # tree object corresponding to .origs.
6212 if ($quilt_mode eq 'linear'
6213 && branch_is_gdr($headref)) {
6214 # This is much faster. It also makes patches that gdr
6215 # likes better for future updates without laundering.
6217 # However, it can fail in some casses where we would
6218 # succeed: if there are existing patches, which correspond
6219 # to a prefix of the branch, but are not in gbp/gdr
6220 # format, gdr will fail (exiting status 7), but we might
6221 # be able to figure out where to start linearising. That
6222 # will be slower so hopefully there's not much to do.
6224 unpack_playtree_need_cd_work $headref;
6226 my @cmd = (@git_debrebase,
6227 qw(--noop-ok -funclean-mixed -funclean-ordering
6228 make-patches --quiet-would-amend));
6229 # We tolerate soe snags that gdr wouldn't, by default.
6235 and not ($? == 7*256 or
6236 $? == -1 && $!==ENOENT);
6240 $headref = git_rev_parse('HEAD');
6245 my $unapplied=quilt_fakedsc2unapplied($headref, $upstreamversion);
6249 my @bbcmd = (qw(sh -ec), 'exec dpkg-source --before-build . >/dev/null');
6251 if (system @bbcmd) {
6252 failedcmd @bbcmd if $? < 0;
6254 failed to apply your git tree's patch stack (from debian/patches/) to
6255 the corresponding upstream tarball(s). Your source tree and .orig
6256 are probably too inconsistent. dgit can only fix up certain kinds of
6257 anomaly (depending on the quilt mode). Please see --quilt= in dgit(1).
6263 unpack_playtree_need_cd_work($headref);
6266 if (stat_exists ".pc") {
6268 progress __ "Tree already contains .pc - will use it then delete it.";
6271 rename '../fake/.pc','.pc' or confess "$!";
6274 changedir '../fake';
6276 my $oldtiptree=git_add_write_tree();
6277 printdebug "fake o+d/p tree object $unapplied\n";
6278 changedir '../work';
6281 # We calculate some guesswork now about what kind of tree this might
6282 # be. This is mostly for error reporting.
6284 my $tentries = cmdoutput @git, qw(ls-tree --name-only -z), $headref;
6285 my $onlydebian = $tentries eq "debian\0";
6287 my $uheadref = $headref;
6288 my $uhead_whatshort = 'HEAD';
6290 if ($quilt_mode =~ m/baredebian\+tarball/) {
6291 # We need to make a tarball import. Yuk.
6292 # We want to do this here so that we have a $uheadref value
6295 baredebian_origtarballs_scan \@fakedfi, $upstreamversion, bpd_abs();
6296 baredebian_origtarballs_scan \@fakedfi, $upstreamversion,
6297 "$maindir/.." unless $buildproductsdir eq '..';
6300 my @tartrees = import_tarball_tartrees $upstreamversion, \@fakedfi;
6302 fail __ "baredebian quilt fixup: could not find any origs"
6306 my ($authline, $r1authline, $clogp,) =
6307 import_tarball_commits \@tartrees, $upstreamversion;
6309 if (@tartrees == 1) {
6310 $uheadref = $tartrees[0]{Commit};
6311 # TRANSLATORS: this translation must fit in the ASCII art
6312 # quilt differences display. The untranslated display
6313 # says %9.9s, so with that display it must be at most 9
6315 $uhead_whatshort = __ 'tarball';
6317 # on .dsc import we do not make a separate commit, but
6318 # here we need to do so
6319 rm_subdir_cached '.';
6321 foreach my $ti (@tartrees) {
6322 my $c = $ti->{Commit};
6323 if ($ti->{OrigPart} eq 'orig') {
6324 runcmd qw(git read-tree), $c;
6325 } elsif ($ti->{OrigPart} =~ m/orig-/) {
6326 read_tree_subdir $', $c;
6328 confess "$ti->OrigPart} ?"
6330 $parents .= "parent $c\n";
6332 my $tree = git_write_tree();
6333 my $mbody = f_ 'Combine orig tarballs for %s %s',
6334 $package, $upstreamversion;
6335 $uheadref = hash_commit_text <<END;
6337 ${parents}author $r1authline
6338 committer $r1authline
6342 [dgit import tarballs combine $package $upstreamversion]
6344 # TRANSLATORS: this translation must fit in the ASCII art
6345 # quilt differences display. The untranslated display
6346 # says %9.9s, so with that display it must be at most 9
6347 # characters. This fragmentt is referring to multiple
6348 # orig tarballs in a source package.
6349 $uhead_whatshort = __ 'tarballs';
6351 runcmd @git, qw(reset -q);
6353 $quilt_upstream_commitish = $uheadref;
6354 $quilt_upstream_commitish_used = '*orig*';
6355 $quilt_upstream_commitish_message = '';
6357 if ($quilt_mode =~ m/baredebian$/) {
6358 $uheadref = $quilt_upstream_commitish;
6359 # TRANSLATORS: this translation must fit in the ASCII art
6360 # quilt differences display. The untranslated display
6361 # says %9.9s, so with that display it must be at most 9
6363 $uhead_whatshort = __ 'upstream';
6370 # O = orig, without patches applied
6371 # A = "applied", ie orig with H's debian/patches applied
6372 O2H => quiltify_trees_differ($unapplied,$uheadref, 1,
6373 \%editedignores, \@unrepres),
6374 H2A => quiltify_trees_differ($uheadref, $oldtiptree,1),
6375 O2A => quiltify_trees_differ($unapplied,$oldtiptree,1),
6379 foreach my $bits (qw(01 02)) {
6380 foreach my $v (qw(O2H O2A H2A)) {
6381 push @dl, ($diffbits->{$v} & $bits) ? '##' : '==';
6384 printdebug "differences \@dl @dl.\n";
6387 "%s: base trees orig=%.20s o+d/p=%.20s",
6388 $us, $unapplied, $oldtiptree;
6389 # TRANSLATORS: Try to keep this ascii-art layout right. The 0s in
6390 # %9.00009s will be ignored and are there to make the format the
6391 # same length (9 characters) as the output it generates. If you
6392 # change the value 9, your translations of "upstream" and
6393 # 'tarball' must fit into the new length, and you should change
6394 # the number of 0s. Do not reduce it below 4 as HEAD has to fit
6397 "%s: quilt differences: src: %s orig %s gitignores: %s orig %s\n".
6398 "%s: quilt differences: %9.00009s %s o+d/p %9.00009s %s o+d/p",
6399 $us, $dl[0], $dl[1], $dl[3], $dl[4],
6400 $us, $uhead_whatshort, $dl[2], $uhead_whatshort, $dl[5];
6402 if (@unrepres && $quilt_mode !~ m/baredebian/) {
6403 # With baredebian, even if the upstream commitish has this
6404 # problem, we don't want to print this message, as nothing
6405 # is going to try to make a patch out of it anyway.
6406 print STDERR f_ "dgit: cannot represent change: %s: %s\n",
6409 forceable_fail [qw(unrepresentable)], __ <<END;
6410 HEAD has changes to .orig[s] which are not representable by `3.0 (quilt)'
6416 push @failsuggestion, [ 'onlydebian', __
6417 "This has only a debian/ directory; you probably want --quilt=bare debian." ]
6418 unless $quilt_mode =~ m/baredebian/;
6419 } elsif (!($diffbits->{O2H} & $diffbits->{O2A})) {
6420 push @failsuggestion, [ 'unapplied', __
6421 "This might be a patches-unapplied branch." ];
6422 } elsif (!($diffbits->{H2A} & $diffbits->{O2A})) {
6423 push @failsuggestion, [ 'applied', __
6424 "This might be a patches-applied branch." ];
6426 push @failsuggestion, [ 'quilt-mode', __
6427 "Maybe you need one of --[quilt=]gbp --[quilt=]dpm --quilt=unapplied ?" ];
6429 push @failsuggestion, [ 'gitattrs', __
6430 "Warning: Tree has .gitattributes. See GITATTRIBUTES in dgit(7)." ]
6431 if stat_exists '.gitattributes';
6433 push @failsuggestion, [ 'origs', __
6434 "Maybe orig tarball(s) are not identical to git representation?" ]
6435 unless $onlydebian && $quilt_mode !~ m/baredebian/;
6436 # ^ in that case, we didn't really look properly
6438 if (quiltmode_splitting()) {
6439 quiltify_splitting($clogp, $unapplied, $headref, $oldtiptree,
6440 $diffbits, \%editedignores,
6441 $splitbrain_cachekey);
6445 progress f_ "starting quiltify (multiple patches, %s mode)", $quilt_mode;
6446 quiltify($clogp,$headref,$oldtiptree,\@failsuggestion);
6447 runcmd @git, qw(checkout -q), (qw(master dgit-view)[do_split_brain()]);
6449 if (!open P, '>>', ".pc/applied-patches") {
6450 $!==&ENOENT or confess "$!";
6455 commit_quilty_patch();
6457 if ($mustdeletepc) {
6458 quilt_fixup_delete_pc();
6462 sub quilt_fixup_editor () {
6463 my $descfn = $ENV{$fakeeditorenv};
6464 my $editing = $ARGV[$#ARGV];
6465 open I1, '<', $descfn or confess "$descfn: $!";
6466 open I2, '<', $editing or confess "$editing: $!";
6467 unlink $editing or confess "$editing: $!";
6468 open O, '>', $editing or confess "$editing: $!";
6469 while (<I1>) { print O or confess "$!"; } I1->error and confess "$!";
6472 $copying ||= m/^\-\-\- /;
6473 next unless $copying;
6474 print O or confess "$!";
6476 I2->error and confess "$!";
6481 sub maybe_apply_patches_dirtily () {
6482 return unless $quilt_mode =~ m/gbp|unapplied|baredebian/;
6483 print STDERR __ <<END or confess "$!";
6485 dgit: Building, or cleaning with rules target, in patches-unapplied tree.
6486 dgit: Have to apply the patches - making the tree dirty.
6487 dgit: (Consider specifying --clean=git and (or) using dgit sbuild.)
6490 $patches_applied_dirtily = 01;
6491 $patches_applied_dirtily |= 02 unless stat_exists '.pc';
6492 runcmd qw(dpkg-source --before-build .);
6495 sub maybe_unapply_patches_again () {
6496 progress __ "dgit: Unapplying patches again to tidy up the tree."
6497 if $patches_applied_dirtily;
6498 runcmd qw(dpkg-source --after-build .)
6499 if $patches_applied_dirtily & 01;
6501 if $patches_applied_dirtily & 02;
6502 $patches_applied_dirtily = 0;
6505 #----- other building -----
6507 sub clean_tree_check_git ($$$) {
6508 my ($honour_ignores, $message, $ignmessage) = @_;
6509 my @cmd = (@git, qw(clean -dn));
6510 push @cmd, qw(-x) unless $honour_ignores;
6511 my $leftovers = cmdoutput @cmd;
6512 if (length $leftovers) {
6513 print STDERR $leftovers, "\n" or confess "$!";
6514 $message .= $ignmessage if $honour_ignores;
6519 sub clean_tree_check_git_wd ($) {
6521 return if $cleanmode =~ m{no-check};
6522 return if $patches_applied_dirtily; # yuk
6523 clean_tree_check_git +($cleanmode !~ m{all-check}),
6524 $message, "\n".__ <<END;
6525 If this is just missing .gitignore entries, use a different clean
6526 mode, eg --clean=dpkg-source,no-check (-wdn/-wddn) to ignore them
6527 or --clean=git (-wg/-wgf) to use \`git clean' instead.
6531 sub clean_tree_check () {
6532 # This function needs to not care about modified but tracked files.
6533 # That was done by check_not_dirty, and by now we may have run
6534 # the rules clean target which might modify tracked files (!)
6535 if ($cleanmode =~ m{^check}) {
6536 clean_tree_check_git +($cleanmode =~ m{ignores}), __
6537 "tree contains uncommitted files and --clean=check specified", '';
6538 } elsif ($cleanmode =~ m{^dpkg-source}) {
6539 clean_tree_check_git_wd __
6540 "tree contains uncommitted files (NB dgit didn't run rules clean)";
6541 } elsif ($cleanmode =~ m{^git}) {
6542 clean_tree_check_git 1, __
6543 "tree contains uncommited, untracked, unignored files\n".
6544 "You can use --clean=git[-ff],always (-wga/-wgfa) to delete them.", '';
6545 } elsif ($cleanmode eq 'none') {
6547 confess "$cleanmode ?";
6552 # We always clean the tree ourselves, rather than leave it to the
6553 # builder (dpkg-source, or soemthing which calls dpkg-source).
6554 if ($quilt_mode =~ m/baredebian/ and $cleanmode =~ m/git/) {
6555 fail f_ <<END, $quilt_mode, $cleanmode;
6556 quilt mode %s (generally needs untracked upstream files)
6557 contradicts clean mode %s (which would delete them)
6559 # This is not 100% true: dgit build-source and push-source
6560 # (for example) could operate just fine with no upstream
6561 # source in the working tree. But it doesn't seem likely that
6562 # the user wants dgit to proactively delete such things.
6563 # -wn, for example, would produce identical output without
6564 # deleting anything from the working tree.
6566 if ($cleanmode =~ m{^dpkg-source}) {
6567 my @cmd = @dpkgbuildpackage;
6568 push @cmd, qw(-d) if $cleanmode =~ m{^dpkg-source-d};
6569 push @cmd, qw(-T clean);
6570 maybe_apply_patches_dirtily();
6571 runcmd_ordryrun_local @cmd;
6572 clean_tree_check_git_wd __
6573 "tree contains uncommitted files (after running rules clean)";
6574 } elsif ($cleanmode =~ m{^git(?!-)}) {
6575 runcmd_ordryrun_local @git, qw(clean -xdf);
6576 } elsif ($cleanmode =~ m{^git-ff}) {
6577 runcmd_ordryrun_local @git, qw(clean -xdff);
6578 } elsif ($cleanmode =~ m{^check}) {
6580 } elsif ($cleanmode eq 'none') {
6582 confess "$cleanmode ?";
6587 badusage __ "clean takes no additional arguments" if @ARGV;
6590 maybe_unapply_patches_again();
6593 # return values from massage_dbp_args are one or both of these flags
6594 sub WANTSRC_SOURCE () { 01; } # caller should build source (separately)
6595 sub WANTSRC_BUILDER () { 02; } # caller should run dpkg-buildpackage
6597 sub build_or_push_prep_early () {
6598 our $build_or_push_prep_early_done //= 0;
6599 return if $build_or_push_prep_early_done++;
6600 my $clogp = parsechangelog();
6601 $isuite = getfield $clogp, 'Distribution';
6602 my $gotpackage = getfield $clogp, 'Source';
6603 $version = getfield $clogp, 'Version';
6604 $package //= $gotpackage;
6605 if ($package ne $gotpackage) {
6606 fail f_ "-p specified package %s, but changelog says %s",
6607 $package, $gotpackage;
6609 $dscfn = dscfn($version);
6612 sub build_or_push_prep_modes () {
6613 my ($format) = get_source_format();
6614 determine_whether_split_brain($format);
6616 fail __ "dgit: --include-dirty is not supported with split view".
6617 " (including with view-splitting quilt modes)"
6618 if do_split_brain() && $includedirty;
6620 if (madformat_wantfixup $format and $quilt_mode =~ m/baredebian$/) {
6621 ($quilt_upstream_commitish, $quilt_upstream_commitish_used,
6622 $quilt_upstream_commitish_message)
6623 = resolve_upstream_version
6624 $quilt_upstream_commitish, upstreamversion $version;
6625 progress f_ "dgit: --quilt=%s, %s", $quilt_mode,
6626 $quilt_upstream_commitish_message;
6627 } elsif (defined $quilt_upstream_commitish) {
6629 "dgit: --upstream-commitish only makes sense with --quilt=baredebian"
6633 sub build_prep_early () {
6634 build_or_push_prep_early();
6636 build_or_push_prep_modes();
6640 sub build_prep ($) {
6644 if (!building_source_in_playtree() || ($wantsrc & WANTSRC_BUILDER)
6645 # Clean the tree because we're going to use the contents of
6646 # $maindir. (We trying to include dirty changes in the source
6647 # package, or we are running the builder in $maindir.)
6648 || $cleanmode =~ m{always}) {
6649 # Or because the user asked us to.
6652 # We don't actually need to do anything in $maindir, but we
6653 # should do some kind of cleanliness check because (i) the
6654 # user may have forgotten a `git add', and (ii) if the user
6655 # said -wc we should still do the check.
6658 build_check_quilt_splitbrain();
6660 my $pat = changespat $version;
6661 foreach my $f (glob "$buildproductsdir/$pat") {
6664 fail f_ "remove old changes file %s: %s", $f, $!;
6666 progress f_ "would remove %s", $f;
6672 sub changesopts_initial () {
6673 my @opts =@changesopts[1..$#changesopts];
6676 sub changesopts_version () {
6677 if (!defined $changes_since_version) {
6680 @vsns = archive_query('archive_query');
6681 my @quirk = access_quirk();
6682 if ($quirk[0] eq 'backports') {
6683 local $isuite = $quirk[2];
6685 canonicalise_suite();
6686 push @vsns, archive_query('archive_query');
6692 "archive query failed (queried because --since-version not specified)";
6695 @vsns = map { $_->[0] } @vsns;
6696 @vsns = sort { -version_compare($a, $b) } @vsns;
6697 $changes_since_version = $vsns[0];
6698 progress f_ "changelog will contain changes since %s", $vsns[0];
6700 $changes_since_version = '_';
6701 progress __ "package seems new, not specifying -v<version>";
6704 if ($changes_since_version ne '_') {
6705 return ("-v$changes_since_version");
6711 sub changesopts () {
6712 return (changesopts_initial(), changesopts_version());
6715 sub massage_dbp_args ($;$) {
6716 my ($cmd,$xargs) = @_;
6717 # Since we split the source build out so we can do strange things
6718 # to it, massage the arguments to dpkg-buildpackage so that the
6719 # main build doessn't build source (or add an argument to stop it
6720 # building source by default).
6721 debugcmd '#massaging#', @$cmd if $debuglevel>1;
6722 # -nc has the side effect of specifying -b if nothing else specified
6723 # and some combinations of -S, -b, et al, are errors, rather than
6724 # later simply overriding earlie. So we need to:
6725 # - search the command line for these options
6726 # - pick the last one
6727 # - perhaps add our own as a default
6728 # - perhaps adjust it to the corresponding non-source-building version
6730 foreach my $l ($cmd, $xargs) {
6732 @$l = grep { !(m/^-[SgGFABb]$|^--build=/s and $dmode=$_) } @$l;
6735 #print STDERR "MASS1 ",Dumper($cmd, $xargs, $dmode);
6736 my $r = WANTSRC_BUILDER;
6737 printdebug "massage split $dmode.\n";
6738 if ($dmode =~ s/^--build=//) {
6740 my @d = split /,/, $dmode;
6741 $r |= WANTSRC_SOURCE if grep { s/^full$/binary/ } @d;
6742 $r |= WANTSRC_SOURCE if grep { s/^source$// } @d;
6743 $r |= WANTSRC_BUILDER if grep { m/./ } @d;
6744 fail __ "Wanted to build nothing!" unless $r;
6745 $dmode = '--build='. join ',', grep m/./, @d;
6748 $dmode =~ m/[S]/ ? WANTSRC_SOURCE :
6749 $dmode =~ y/gGF/ABb/ ? WANTSRC_SOURCE | WANTSRC_BUILDER :
6750 $dmode =~ m/[ABb]/ ? WANTSRC_BUILDER :
6753 printdebug "massage done $r $dmode.\n";
6755 #print STDERR "MASS2 ",Dumper($cmd, $xargs, $r);
6761 my $wasdir = must_getcwd();
6762 changedir $buildproductsdir;
6767 # this sub must run with CWD=$buildproductsdir (eg in in_bpd)
6768 sub postbuild_mergechanges ($) {
6769 my ($msg_if_onlyone) = @_;
6770 # If there is only one .changes file, fail with $msg_if_onlyone,
6771 # or if that is undef, be a no-op.
6772 # Returns the changes file to report to the user.
6773 my $pat = changespat $version;
6774 my @changesfiles = grep { !m/_multi\.changes/ } glob $pat;
6775 @changesfiles = sort {
6776 ($b =~ m/_source\.changes$/ <=> $a =~ m/_source\.changes$/)
6780 if (@changesfiles==1) {
6781 fail +(f_ <<END, "@changesfiles").$msg_if_onlyone
6782 only one changes file from build (%s)
6784 if defined $msg_if_onlyone;
6785 $result = $changesfiles[0];
6786 } elsif (@changesfiles==2) {
6787 my $binchanges = parsecontrol($changesfiles[1], "binary changes file");
6788 foreach my $l (split /\n/, getfield $binchanges, 'Files') {
6789 fail f_ "%s found in binaries changes file %s", $l, $binchanges
6792 runcmd_ordryrun_local @mergechanges, @changesfiles;
6793 my $multichanges = changespat $version,'multi';
6795 stat_exists $multichanges or fail f_
6796 "%s unexpectedly not created by build", $multichanges;
6797 foreach my $cf (glob $pat) {
6798 next if $cf eq $multichanges;
6799 rename "$cf", "$cf.inmulti" or fail f_
6800 "install new changes %s\{,.inmulti}: %s", $cf, $!;
6803 $result = $multichanges;
6805 fail f_ "wrong number of different changes files (%s)",
6808 printdone f_ "build successful, results in %s\n", $result
6812 sub midbuild_checkchanges () {
6813 my $pat = changespat $version;
6814 return if $rmchanges;
6815 my @unwanted = map { s#.*/##; $_; } glob "$bpd_glob/$pat";
6817 $_ ne changespat $version,'source' and
6818 $_ ne changespat $version,'multi'
6820 fail +(f_ <<END, $pat, "@unwanted")
6821 changes files other than source matching %s already present; building would result in ambiguity about the intended results.
6822 Suggest you delete %s.
6827 sub midbuild_checkchanges_vanilla ($) {
6829 midbuild_checkchanges() if $wantsrc == (WANTSRC_SOURCE|WANTSRC_BUILDER);
6832 sub postbuild_mergechanges_vanilla ($) {
6834 if ($wantsrc == (WANTSRC_SOURCE|WANTSRC_BUILDER)) {
6836 postbuild_mergechanges(undef);
6839 printdone __ "build successful\n";
6845 $buildproductsdir eq '..' or print STDERR +(f_ <<END, $us, $us);
6846 %s: warning: build-products-dir set, but not supported by dpkg-buildpackage
6847 %s: warning: build-products-dir will be ignored; files will go to ..
6849 $buildproductsdir = '..';
6850 my @dbp = (@dpkgbuildpackage, qw(-us -uc), changesopts_initial(), @ARGV);
6851 my $wantsrc = massage_dbp_args \@dbp;
6852 build_prep($wantsrc);
6853 if ($wantsrc & WANTSRC_SOURCE) {
6855 midbuild_checkchanges_vanilla $wantsrc;
6857 if ($wantsrc & WANTSRC_BUILDER) {
6858 push @dbp, changesopts_version();
6859 maybe_apply_patches_dirtily();
6860 runcmd_ordryrun_local @dbp;
6862 maybe_unapply_patches_again();
6863 postbuild_mergechanges_vanilla $wantsrc;
6867 $quilt_mode //= 'gbp';
6873 # gbp can make .origs out of thin air. In my tests it does this
6874 # even for a 1.0 format package, with no origs present. So I
6875 # guess it keys off just the version number. We don't know
6876 # exactly what .origs ought to exist, but let's assume that we
6877 # should run gbp if: the version has an upstream part and the main
6879 my $upstreamversion = upstreamversion $version;
6880 my $origfnpat = srcfn $upstreamversion, '.orig.tar.*';
6881 my $gbp_make_orig = $version =~ m/-/ && !(() = glob "$bpd_glob/$origfnpat");
6883 if ($gbp_make_orig) {
6885 $cleanmode = 'none'; # don't do it again
6888 my @dbp = @dpkgbuildpackage;
6890 my $wantsrc = massage_dbp_args \@dbp, \@ARGV;
6892 if (!length $gbp_build[0]) {
6893 if (length executable_on_path('git-buildpackage')) {
6894 $gbp_build[0] = qw(git-buildpackage);
6896 $gbp_build[0] = 'gbp buildpackage';
6899 my @cmd = opts_opt_multi_cmd [], @gbp_build;
6901 push @cmd, (qw(-us -uc --git-no-sign-tags),
6902 "--git-builder=".(shellquote @dbp));
6904 if ($gbp_make_orig) {
6905 my $priv = dgit_privdir();
6906 my $ok = "$priv/origs-gen-ok";
6907 unlink $ok or $!==&ENOENT or confess "$!";
6908 my @origs_cmd = @cmd;
6909 push @origs_cmd, qw(--git-cleaner=true);
6910 push @origs_cmd, "--git-prebuild=".
6911 "touch ".(shellquote $ok)." ".(shellquote "$priv/no-such-dir/ok");
6912 push @origs_cmd, @ARGV;
6914 debugcmd @origs_cmd;
6916 do { local $!; stat_exists $ok; }
6917 or failedcmd @origs_cmd;
6919 dryrun_report @origs_cmd;
6923 build_prep($wantsrc);
6924 if ($wantsrc & WANTSRC_SOURCE) {
6926 midbuild_checkchanges_vanilla $wantsrc;
6928 push @cmd, '--git-cleaner=true';
6930 maybe_unapply_patches_again();
6931 if ($wantsrc & WANTSRC_BUILDER) {
6932 push @cmd, changesopts();
6933 runcmd_ordryrun_local @cmd, @ARGV;
6935 postbuild_mergechanges_vanilla $wantsrc;
6937 sub cmd_git_build { cmd_gbp_build(); } # compatibility with <= 1.0
6939 sub building_source_in_playtree {
6940 # If $includedirty, we have to build the source package from the
6941 # working tree, not a playtree, so that uncommitted changes are
6942 # included (copying or hardlinking them into the playtree could
6945 # Note that if we are building a source package in split brain
6946 # mode we do not support including uncommitted changes, because
6947 # that makes quilt fixup too hard. I.e. ($made_split_brain && (dgit is
6948 # building a source package)) => !$includedirty
6949 return !$includedirty;
6953 $sourcechanges = changespat $version,'source';
6955 unlink "$buildproductsdir/$sourcechanges" or $!==ENOENT
6956 or fail f_ "remove %s: %s", $sourcechanges, $!;
6958 # confess unless !!$made_split_brain == do_split_brain();
6960 my @cmd = (@dpkgsource, qw(-b --));
6962 if (building_source_in_playtree()) {
6964 my $headref = git_rev_parse('HEAD');
6965 # If we are in split brain, there is already a playtree with
6966 # the thing we should package into a .dsc (thanks to quilt
6967 # fixup). If not, make a playtree
6968 prep_ud() unless $made_split_brain;
6969 changedir $playground;
6970 unless ($made_split_brain) {
6971 my $upstreamversion = upstreamversion $version;
6972 unpack_playtree_linkorigs($upstreamversion, sub { });
6973 unpack_playtree_need_cd_work($headref);
6977 $leafdir = basename $maindir;
6979 if ($buildproductsdir ne '..') {
6980 # Well, we are going to run dpkg-source -b which consumes
6981 # origs from .. and generates output there. To make this
6982 # work when the bpd is not .. , we would have to (i) link
6983 # origs from bpd to .. , (ii) check for files that
6984 # dpkg-source -b would/might overwrite, and afterwards
6985 # (iii) move all the outputs back to the bpd (iv) except
6986 # for the origs which should be deleted from .. if they
6987 # weren't there beforehand. And if there is an error and
6988 # we don't run to completion we would necessarily leave a
6989 # mess. This is too much. The real way to fix this
6990 # is for dpkg-source to have bpd support.
6991 confess unless $includedirty;
6993 "--include-dirty not supported with --build-products-dir, sorry";
6998 runcmd_ordryrun_local @cmd, $leafdir;
7001 runcmd_ordryrun_local qw(sh -ec),
7002 'exec >../$1; shift; exec "$@"','x', $sourcechanges,
7003 @dpkggenchanges, qw(-S), changesopts();
7006 printdebug "moving $dscfn, $sourcechanges, etc. to ".bpd_abs()."\n";
7007 $dsc = parsecontrol($dscfn, "source package");
7011 printdebug " renaming ($why) $l\n";
7012 rename_link_xf 0, "$l", bpd_abs()."/$l"
7013 or fail f_ "put in place new built file (%s): %s", $l, $@;
7015 foreach my $l (split /\n/, getfield $dsc, 'Files') {
7016 $l =~ m/\S+$/ or next;
7019 $mv->('dsc', $dscfn);
7020 $mv->('changes', $sourcechanges);
7025 sub cmd_build_source {
7026 badusage __ "build-source takes no additional arguments" if @ARGV;
7027 build_prep(WANTSRC_SOURCE);
7029 maybe_unapply_patches_again();
7030 printdone f_ "source built, results in %s and %s",
7031 $dscfn, $sourcechanges;
7034 sub cmd_push_source {
7037 "dgit push-source: --include-dirty/--ignore-dirty does not make".
7038 "sense with push-source!"
7040 build_check_quilt_splitbrain();
7042 my $changes = parsecontrol("$buildproductsdir/$changesfile",
7043 __ "source changes file");
7044 unless (test_source_only_changes($changes)) {
7045 fail __ "user-specified changes file is not source-only";
7048 # Building a source package is very fast, so just do it
7050 confess "er, patches are applied dirtily but shouldn't be.."
7051 if $patches_applied_dirtily;
7052 $changesfile = $sourcechanges;
7057 sub binary_builder {
7058 my ($bbuilder, $pbmc_msg, @args) = @_;
7059 build_prep(WANTSRC_SOURCE);
7061 midbuild_checkchanges();
7064 stat_exists $dscfn or fail f_
7065 "%s (in build products dir): %s", $dscfn, $!;
7066 stat_exists $sourcechanges or fail f_
7067 "%s (in build products dir): %s", $sourcechanges, $!;
7069 runcmd_ordryrun_local @$bbuilder, @args;
7071 maybe_unapply_patches_again();
7073 postbuild_mergechanges($pbmc_msg);
7079 binary_builder(\@sbuild, (__ <<END), qw(-d), $isuite, @ARGV, $dscfn);
7080 perhaps you need to pass -A ? (sbuild's default is to build only
7081 arch-specific binaries; dgit 1.4 used to override that.)
7086 my ($pbuilder) = @_;
7088 # @ARGV is allowed to contain only things that should be passed to
7089 # pbuilder under debbuildopts; just massage those
7090 my $wantsrc = massage_dbp_args \@ARGV;
7092 "you asked for a builder but your debbuildopts didn't ask for".
7093 " any binaries -- is this really what you meant?"
7094 unless $wantsrc & WANTSRC_BUILDER;
7096 "we must build a .dsc to pass to the builder but your debbuiltopts".
7097 " forbids the building of a source package; cannot continue"
7098 unless $wantsrc & WANTSRC_SOURCE;
7099 # We do not want to include the verb "build" in @pbuilder because
7100 # the user can customise @pbuilder and they shouldn't be required
7101 # to include "build" in their customised value. However, if the
7102 # user passes any additional args to pbuilder using the dgit
7103 # option --pbuilder:foo, such args need to come after the "build"
7104 # verb. opts_opt_multi_cmd does all of that.
7105 binary_builder([opts_opt_multi_cmd ["build"], @$pbuilder], undef,
7106 qw(--debbuildopts), "@ARGV", qw(--distribution), $isuite,
7111 pbuilder(\@pbuilder);
7114 sub cmd_cowbuilder {
7115 pbuilder(\@cowbuilder);
7118 sub cmd_quilt_fixup {
7119 badusage "incorrect arguments to dgit quilt-fixup" if @ARGV;
7122 build_maybe_quilt_fixup();
7125 sub cmd_print_unapplied_treeish {
7126 badusage __ "incorrect arguments to dgit print-unapplied-treeish"
7128 my $headref = git_rev_parse('HEAD');
7129 my $clogp = commit_getclogp $headref;
7130 $package = getfield $clogp, 'Source';
7131 $version = getfield $clogp, 'Version';
7132 $isuite = getfield $clogp, 'Distribution';
7133 $csuite = $isuite; # we want this to be offline!
7137 changedir $playground;
7138 my $uv = upstreamversion $version;
7139 my $u = quilt_fakedsc2unapplied($headref, $uv);
7140 print $u, "\n" or confess "$!";
7143 sub import_dsc_result {
7144 my ($dstref, $newhash, $what_log, $what_msg) = @_;
7145 my @cmd = (git_update_ref_cmd $what_log, $dstref, $newhash);
7147 check_gitattrs($newhash, __ "source tree");
7149 progress f_ "dgit: import-dsc: %s", $what_msg;
7152 sub cmd_import_dsc {
7156 last unless $ARGV[0] =~ m/^-/;
7159 if (m/^--require-valid-signature$/) {
7162 badusage f_ "unknown dgit import-dsc sub-option \`%s'", $_;
7166 badusage __ "usage: dgit import-dsc .../PATH/TO/.DSC BRANCH"
7168 my ($dscfn, $dstbranch) = @ARGV;
7170 badusage __ "dry run makes no sense with import-dsc"
7173 my $force = $dstbranch =~ s/^\+// ? +1 :
7174 $dstbranch =~ s/^\.\.// ? -1 :
7176 my $info = $force ? " $&" : '';
7177 $info = "$dscfn$info";
7179 my $specbranch = $dstbranch;
7180 $dstbranch = "refs/heads/$dstbranch" unless $dstbranch =~ m#^refs/#;
7181 $dstbranch = cmdoutput @git, qw(check-ref-format --normalize), $dstbranch;
7183 my @symcmd = (@git, qw(symbolic-ref -q HEAD));
7184 my $chead = cmdoutput_errok @symcmd;
7185 defined $chead or $?==256 or failedcmd @symcmd;
7187 fail f_ "%s is checked out - will not update it", $dstbranch
7188 if defined $chead and $chead eq $dstbranch;
7190 my $oldhash = git_get_ref $dstbranch;
7192 open D, "<", $dscfn or fail f_ "open import .dsc (%s): %s", $dscfn, $!;
7193 $dscdata = do { local $/ = undef; <D>; };
7194 D->error and fail f_ "read %s: %s", $dscfn, $!;
7197 # we don't normally need this so import it here
7198 use Dpkg::Source::Package;
7199 my $dp = new Dpkg::Source::Package filename => $dscfn,
7200 require_valid_signature => $needsig;
7202 local $SIG{__WARN__} = sub {
7204 return unless $needsig;
7205 fail __ "import-dsc signature check failed";
7207 if (!$dp->is_signed()) {
7208 warn f_ "%s: warning: importing unsigned .dsc\n", $us;
7210 my $r = $dp->check_signature();
7211 confess "->check_signature => $r" if $needsig && $r;
7217 $package = getfield $dsc, 'Source';
7219 parse_dsc_field($dsc, __ "Dgit metadata in .dsc")
7220 unless forceing [qw(import-dsc-with-dgit-field)];
7221 parse_dsc_field_def_dsc_distro();
7223 $isuite = 'DGIT-IMPORT-DSC';
7224 $idistro //= $dsc_distro;
7228 if (defined $dsc_hash) {
7230 "dgit: import-dsc of .dsc with Dgit field, using git hash";
7231 resolve_dsc_field_commit undef, undef;
7233 if (defined $dsc_hash) {
7234 my @cmd = (qw(sh -ec),
7235 "echo $dsc_hash | git cat-file --batch-check");
7236 my $objgot = cmdoutput @cmd;
7237 if ($objgot =~ m#^\w+ missing\b#) {
7238 fail f_ <<END, $dsc_hash
7239 .dsc contains Dgit field referring to object %s
7240 Your git tree does not have that object. Try `git fetch' from a
7241 plausible server (browse.dgit.d.o? salsa?), and try the import-dsc again.
7244 if ($oldhash && !is_fast_fwd $oldhash, $dsc_hash) {
7246 progress __ "Not fast forward, forced update.";
7248 fail f_ "Not fast forward to %s", $dsc_hash;
7251 import_dsc_result $dstbranch, $dsc_hash,
7252 "dgit import-dsc (Dgit): $info",
7253 f_ "updated git ref %s", $dstbranch;
7257 fail f_ <<END, $dstbranch, $specbranch, $specbranch
7258 Branch %s already exists
7259 Specify ..%s for a pseudo-merge, binding in existing history
7260 Specify +%s to overwrite, discarding existing history
7262 if $oldhash && !$force;
7264 my @dfi = dsc_files_info();
7265 foreach my $fi (@dfi) {
7266 my $f = $fi->{Filename};
7267 # We transfer all the pieces of the dsc to the bpd, not just
7268 # origs. This is by analogy with dgit fetch, which wants to
7269 # keep them somewhere to avoid downloading them again.
7270 # We make symlinks, though. If the user wants copies, then
7271 # they can copy the parts of the dsc to the bpd using dcmd,
7273 my $here = "$buildproductsdir/$f";
7278 fail f_ "lstat %s works but stat gives %s !", $here, $!;
7280 fail f_ "stat %s: %s", $here, $! unless $! == ENOENT;
7281 printdebug "not in bpd, $f ...\n";
7282 # $f does not exist in bpd, we need to transfer it
7284 $there =~ s{[^/]+$}{$f} or confess "$there ?";
7285 # $there is file we want, relative to user's cwd, or abs
7286 printdebug "not in bpd, $f, test $there ...\n";
7287 stat $there or fail f_
7288 "import %s requires %s, but: %s", $dscfn, $there, $!;
7289 if ($there =~ m#^(?:\./+)?\.\./+#) {
7290 # $there is relative to user's cwd
7291 my $there_from_parent = $';
7292 if ($buildproductsdir !~ m{^/}) {
7293 # abs2rel, despite its name, can take two relative paths
7294 $there = File::Spec->abs2rel($there,$buildproductsdir);
7295 # now $there is relative to bpd, great
7296 printdebug "not in bpd, $f, abs2rel, $there ...\n";
7298 $there = (dirname $maindir)."/$there_from_parent";
7299 # now $there is absoute
7300 printdebug "not in bpd, $f, rel2rel, $there ...\n";
7302 } elsif ($there =~ m#^/#) {
7303 # $there is absolute already
7304 printdebug "not in bpd, $f, abs, $there ...\n";
7307 "cannot import %s which seems to be inside working tree!",
7310 symlink $there, $here or fail f_
7311 "symlink %s to %s: %s", $there, $here, $!;
7312 progress f_ "made symlink %s -> %s", $here, $there;
7313 # print STDERR Dumper($fi);
7315 my @mergeinputs = generate_commits_from_dsc();
7316 die unless @mergeinputs == 1;
7318 my $newhash = $mergeinputs[0]{Commit};
7323 "Import, forced update - synthetic orphan git history.";
7324 } elsif ($force < 0) {
7325 progress __ "Import, merging.";
7326 my $tree = cmdoutput @git, qw(rev-parse), "$newhash:";
7327 my $version = getfield $dsc, 'Version';
7328 my $clogp = commit_getclogp $newhash;
7329 my $authline = clogp_authline $clogp;
7330 $newhash = hash_commit_text <<ENDU
7338 .(f_ <<END, $package, $version, $dstbranch);
7339 Merge %s (%s) import into %s
7342 die; # caught earlier
7346 import_dsc_result $dstbranch, $newhash,
7347 "dgit import-dsc: $info",
7348 f_ "results are in git ref %s", $dstbranch;
7351 sub pre_archive_api_query () {
7352 not_necessarily_a_tree();
7354 sub cmd_archive_api_query {
7355 badusage __ "need only 1 subpath argument" unless @ARGV==1;
7356 my ($subpath) = @ARGV;
7357 local $isuite = 'DGIT-API-QUERY-CMD';
7358 my $json = api_query_raw $subpath;
7359 print $json or die "$!";
7362 sub repos_server_url () {
7363 $package = '_dgit-repos-server';
7364 local $access_forpush = 1;
7365 local $isuite = 'DGIT-REPOS-SERVER';
7366 my $url = access_giturl();
7369 sub pre_clone_dgit_repos_server () {
7370 not_necessarily_a_tree();
7372 sub cmd_clone_dgit_repos_server {
7373 badusage __ "need destination argument" unless @ARGV==1;
7374 my ($destdir) = @ARGV;
7375 my $url = repos_server_url();
7376 my @cmd = (@git, qw(clone), $url, $destdir);
7378 exec @cmd or fail f_ "exec git clone: %s\n", $!;
7381 sub pre_print_dgit_repos_server_source_url () {
7382 not_necessarily_a_tree();
7384 sub cmd_print_dgit_repos_server_source_url {
7386 "no arguments allowed to dgit print-dgit-repos-server-source-url"
7388 my $url = repos_server_url();
7389 print $url, "\n" or confess "$!";
7392 sub pre_print_dpkg_source_ignores {
7393 not_necessarily_a_tree();
7395 sub cmd_print_dpkg_source_ignores {
7397 "no arguments allowed to dgit print-dpkg-source-ignores"
7399 print "@dpkg_source_ignores\n" or confess "$!";
7402 sub cmd_setup_mergechangelogs {
7403 badusage __ "no arguments allowed to dgit setup-mergechangelogs"
7405 local $isuite = 'DGIT-SETUP-TREE';
7406 setup_mergechangelogs(1);
7409 sub cmd_setup_useremail {
7410 badusage __ "no arguments allowed to dgit setup-useremail" if @ARGV;
7411 local $isuite = 'DGIT-SETUP-TREE';
7415 sub cmd_setup_gitattributes {
7416 badusage __ "no arguments allowed to dgit setup-useremail" if @ARGV;
7417 local $isuite = 'DGIT-SETUP-TREE';
7421 sub cmd_setup_new_tree {
7422 badusage __ "no arguments allowed to dgit setup-tree" if @ARGV;
7423 local $isuite = 'DGIT-SETUP-TREE';
7427 #---------- argument parsing and main program ----------
7430 print "dgit version $our_version\n" or confess "$!";
7434 our (%valopts_long, %valopts_short);
7435 our (%funcopts_long);
7437 our (@modeopt_cfgs);
7439 sub defvalopt ($$$$) {
7440 my ($long,$short,$val_re,$how) = @_;
7441 my $oi = { Long => $long, Short => $short, Re => $val_re, How => $how };
7442 $valopts_long{$long} = $oi;
7443 $valopts_short{$short} = $oi;
7444 # $how subref should:
7445 # do whatever assignemnt or thing it likes with $_[0]
7446 # if the option should not be passed on to remote, @rvalopts=()
7447 # or $how can be a scalar ref, meaning simply assign the value
7450 defvalopt '--since-version', '-v', '[^_]+|_', \$changes_since_version;
7451 defvalopt '--distro', '-d', '.+', \$idistro;
7452 defvalopt '', '-k', '.+', \$keyid;
7453 defvalopt '--existing-package','', '.*', \$existing_package;
7454 defvalopt '--build-products-dir','','.*', \$buildproductsdir;
7455 defvalopt '--clean', '', $cleanmode_re, \$cleanmode;
7456 defvalopt '--package', '-p', $package_re, \$package;
7457 defvalopt '--quilt', '', $quilt_modes_re, \$quilt_mode;
7459 defvalopt '', '-C', '.+', sub {
7460 ($changesfile) = (@_);
7461 if ($changesfile =~ s#^(.*)/##) {
7462 $buildproductsdir = $1;
7466 defvalopt '--initiator-tempdir','','.*', sub {
7467 ($initiator_tempdir) = (@_);
7468 $initiator_tempdir =~ m#^/# or
7469 badusage __ "--initiator-tempdir must be used specify an".
7470 " absolute, not relative, directory."
7473 sub defoptmodes ($@) {
7474 my ($varref, $cfgkey, $default, %optmap) = @_;
7476 while (my ($opt,$val) = each %optmap) {
7477 $funcopts_long{$opt} = sub { $$varref = $val; };
7478 $permit{$val} = $val;
7480 push @modeopt_cfgs, {
7483 Default => $default,
7488 defoptmodes \$dodep14tag, qw( dep14tag want
7491 --always-dep14tag always );
7496 if (defined $ENV{'DGIT_SSH'}) {
7497 @ssh = string_to_ssh $ENV{'DGIT_SSH'};
7498 } elsif (defined $ENV{'GIT_SSH'}) {
7499 @ssh = ($ENV{'GIT_SSH'});
7507 if (!defined $val) {
7508 badusage f_ "%s needs a value", $what unless @ARGV;
7510 push @rvalopts, $val;
7512 badusage f_ "bad value \`%s' for %s", $val, $what unless
7513 $val =~ m/^$oi->{Re}$(?!\n)/s;
7514 my $how = $oi->{How};
7515 if (ref($how) eq 'SCALAR') {
7520 push @ropts, @rvalopts;
7524 last unless $ARGV[0] =~ m/^-/;
7528 if (m/^--dry-run$/) {
7531 } elsif (m/^--damp-run$/) {
7534 } elsif (m/^--no-sign$/) {
7537 } elsif (m/^--help$/) {
7539 } elsif (m/^--version$/) {
7541 } elsif (m/^--new$/) {
7544 } elsif (m/^--([-0-9a-z]+)=(.+)/s &&
7545 ($om = $opts_opt_map{$1}) &&
7549 } elsif (m/^--([-0-9a-z]+):(.*)/s &&
7550 !$opts_opt_cmdonly{$1} &&
7551 ($om = $opts_opt_map{$1})) {
7554 } elsif (m/^--([-0-9a-z]+)\!:(.*)/s &&
7555 !$opts_opt_cmdonly{$1} &&
7556 ($om = $opts_opt_map{$1})) {
7558 my $cmd = shift @$om;
7559 @$om = ($cmd, grep { $_ ne $2 } @$om);
7560 } elsif (m/^--($quilt_options_re)$/s) {
7561 push @ropts, "--quilt=$1";
7563 } elsif (m/^--(?:ignore|include)-dirty$/s) {
7566 } elsif (m/^--no-quilt-fixup$/s) {
7568 $quilt_mode = 'nocheck';
7569 } elsif (m/^--no-rm-on-error$/s) {
7572 } elsif (m/^--no-chase-dsc-distro$/s) {
7574 $chase_dsc_distro = 0;
7575 } elsif (m/^--overwrite$/s) {
7577 $overwrite_version = '';
7578 } elsif (m/^--split-(?:view|brain)$/s) {
7580 $splitview_mode = 'always';
7581 } elsif (m/^--split-(?:view|brain)=($splitview_modes_re)$/s) {
7583 $splitview_mode = $1;
7584 } elsif (m/^--overwrite=(.+)$/s) {
7586 $overwrite_version = $1;
7587 } elsif (m/^--delayed=(\d+)$/s) {
7590 } elsif (m/^--upstream-commitish=(.+)$/s) {
7592 $quilt_upstream_commitish = $1;
7593 } elsif (m/^--save-(dgit-view)=(.+)$/s ||
7594 m/^--(dgit-view)-save=(.+)$/s
7596 my ($k,$v) = ($1,$2);
7598 $v =~ s#^(?!refs/)#refs/heads/#;
7599 $internal_object_save{$k} = $v;
7600 } elsif (m/^--(no-)?rm-old-changes$/s) {
7603 } elsif (m/^--deliberately-($deliberately_re)$/s) {
7605 push @deliberatelies, $&;
7606 } elsif (m/^--force-(.*)/ && defined $forceopts{$1}) {
7610 } elsif (m/^--force-/) {
7612 f_ "%s: warning: ignoring unknown force option %s\n",
7615 } elsif (m/^--for-push$/s) {
7617 $access_forpush = 1;
7618 } elsif (m/^--config-lookup-explode=(.+)$/s) {
7619 # undocumented, for testing
7621 $gitcfgs{cmdline}{$1} = 'CONFIG-LOOKUP-EXPLODE';
7622 # ^ it's supposed to be an array ref
7623 } elsif (m/^(--[-0-9a-z]+)(=|$)/ && ($oi = $valopts_long{$1})) {
7624 $val = $2 ? $' : undef; #';
7625 $valopt->($oi->{Long});
7626 } elsif ($funcopts_long{$_}) {
7628 $funcopts_long{$_}();
7630 badusage f_ "unknown long option \`%s'", $_;
7637 } elsif (s/^-L/-/) {
7640 } elsif (s/^-h/-/) {
7642 } elsif (s/^-D/-/) {
7646 } elsif (s/^-N/-/) {
7651 push @changesopts, $_;
7653 } elsif (s/^-wn$//s) {
7655 $cleanmode = 'none';
7656 } elsif (s/^-wg(f?)(a?)$//s) {
7659 $cleanmode .= '-ff' if $1;
7660 $cleanmode .= ',always' if $2;
7661 } elsif (s/^-wd(d?)([na]?)$//s) {
7663 $cleanmode = 'dpkg-source';
7664 $cleanmode .= '-d' if $1;
7665 $cleanmode .= ',no-check' if $2 eq 'n';
7666 $cleanmode .= ',all-check' if $2 eq 'a';
7667 } elsif (s/^-wc$//s) {
7669 $cleanmode = 'check';
7670 } elsif (s/^-wci$//s) {
7672 $cleanmode = 'check,ignores';
7673 } elsif (s/^-c([^=]*)\=(.*)$//s) {
7674 push @git, '-c', $&;
7675 $gitcfgs{cmdline}{$1} = [ $2 ];
7676 } elsif (s/^-c([^=]+)$//s) {
7677 push @git, '-c', $&;
7678 $gitcfgs{cmdline}{$1} = [ 'true' ];
7679 } elsif (m/^-[a-zA-Z]/ && ($oi = $valopts_short{$&})) {
7681 $val = undef unless length $val;
7682 $valopt->($oi->{Short});
7685 badusage f_ "unknown short option \`%s'", $_;
7692 sub check_env_sanity () {
7693 my $blocked = new POSIX::SigSet;
7694 sigprocmask SIG_UNBLOCK, $blocked, $blocked or confess "$!";
7697 foreach my $name (qw(PIPE CHLD)) {
7698 my $signame = "SIG$name";
7699 my $signum = eval "POSIX::$signame" // die;
7700 die f_ "%s is set to something other than SIG_DFL\n",
7702 if defined $SIG{$name} and $SIG{$name} ne 'DEFAULT';
7703 $blocked->ismember($signum) and
7704 die f_ "%s is blocked\n", $signame;
7710 On entry to dgit, %s
7711 This is a bug produced by something in your execution environment.
7717 sub parseopts_late_defaults () {
7718 $isuite //= cfg("dgit-distro.$idistro.default-suite", 'RETURN-UNDEF')
7719 if defined $idistro;
7720 $isuite //= cfg('dgit.default.default-suite');
7722 foreach my $k (keys %opts_opt_map) {
7723 my $om = $opts_opt_map{$k};
7725 my $v = access_cfg("cmd-$k", 'RETURN-UNDEF');
7727 badcfg f_ "cannot set command for %s", $k
7728 unless length $om->[0];
7732 foreach my $c (access_cfg_cfgs("opts-$k")) {
7734 map { $_ ? @$_ : () }
7735 map { $gitcfgs{$_}{$c} }
7736 reverse @gitcfgsources;
7737 printdebug "CL $c ", (join " ", map { shellquote } @vl),
7738 "\n" if $debuglevel >= 4;
7740 badcfg f_ "cannot configure options for %s", $k
7741 if $opts_opt_cmdonly{$k};
7742 my $insertpos = $opts_cfg_insertpos{$k};
7743 @$om = ( @$om[0..$insertpos-1],
7745 @$om[$insertpos..$#$om] );
7749 if (!defined $rmchanges) {
7750 local $access_forpush;
7751 $rmchanges = access_cfg_bool(0, 'rm-old-changes');
7754 if (!defined $quilt_mode) {
7755 local $access_forpush;
7756 $quilt_mode = cfg('dgit.force.quilt-mode', 'RETURN-UNDEF')
7757 // access_cfg('quilt-mode', 'RETURN-UNDEF')
7759 $quilt_mode =~ m/^($quilt_modes_re)$/
7760 or badcfg f_ "unknown quilt-mode \`%s'", $quilt_mode;
7763 $quilt_mode =~ s/^(baredebian)\+git$/$1/;
7765 foreach my $moc (@modeopt_cfgs) {
7766 local $access_forpush;
7767 my $vr = $moc->{Var};
7768 next if defined $$vr;
7769 $$vr = access_cfg($moc->{Key}, 'RETURN-UNDEF') // $moc->{Default};
7770 my $v = $moc->{Vals}{$$vr};
7771 badcfg f_ "unknown %s setting \`%s'", $moc->{Key}, $$vr
7777 local $access_forpush;
7778 default_from_access_cfg(\$cleanmode, 'clean-mode', 'dpkg-source',
7782 $buildproductsdir //= access_cfg('build-products-dir', 'RETURN-UNDEF');
7783 $buildproductsdir //= '..';
7784 $bpd_glob = $buildproductsdir;
7785 $bpd_glob =~ s#[][\\{}*?~]#\\$&#g;
7788 setlocale(LC_MESSAGES, "");
7791 if ($ENV{$fakeeditorenv}) {
7793 quilt_fixup_editor();
7799 print STDERR __ "DRY RUN ONLY\n" if $dryrun_level > 1;
7800 print STDERR __ "DAMP RUN - WILL MAKE LOCAL (UNSIGNED) CHANGES\n"
7801 if $dryrun_level == 1;
7803 print STDERR __ $helpmsg or confess "$!";
7806 $cmd = $subcommand = shift @ARGV;
7809 my $pre_fn = ${*::}{"pre_$cmd"};
7810 $pre_fn->() if $pre_fn;
7812 if ($invoked_in_git_tree) {
7813 changedir_git_toplevel();
7818 my $fn = ${*::}{"cmd_$cmd"};
7819 $fn or badusage f_ "unknown operation %s", $cmd;