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 #---------- `ftpmasterapi' archive query method (nascent) ----------
1197 sub archive_api_query_curl ($) {
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 ($url =~ m#^https://([-.0-9a-z]+)/#) {
1216 foreach my $k (qw(archive-query-tls-key
1217 archive-query-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 "archive api 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 return $curl->getinfo(CURLINFO_HTTP_CODE), $response_body;
1235 sub api_query_raw ($;$) {
1236 my ($subpath, $ok404) = @_;
1237 my $url = access_cfg('archive-query-url');
1239 my ($code,$json) = archive_api_query_curl($url);
1240 return undef if $code eq '404' && $ok404;
1241 fail f_ "fetch of %s gave HTTP code %s", $url, $code
1242 unless $url =~ m#^file://# or $code =~ m/^2/;
1246 sub api_query ($$;$) {
1247 my ($data, $subpath, $ok404) = @_;
1249 badcfg __ "ftpmasterapi archive query method takes no data part"
1251 my $json = api_query_raw $subpath, $ok404;
1252 return undef unless defined $json;
1253 return decode_json($json);
1256 sub canonicalise_suite_ftpmasterapi {
1257 my ($proto,$data) = @_;
1258 my $suites = api_query($data, 'suites');
1260 foreach my $entry (@$suites) {
1262 my $v = $entry->{$_};
1263 defined $v && $v eq $isuite;
1264 } qw(codename name);
1265 push @matched, $entry;
1267 fail f_ "unknown suite %s, maybe -d would help", $isuite
1271 @matched==1 or die f_ "multiple matches for suite %s\n", $isuite;
1272 $cn = "$matched[0]{codename}";
1273 defined $cn or die f_ "suite %s info has no codename\n", $isuite;
1274 $cn =~ m/^$suite_re$/
1275 or die f_ "suite %s maps to bad codename\n", $isuite;
1277 die +(__ "bad ftpmaster api response: ")."$@\n".Dumper(\@matched)
1282 sub archive_query_ftpmasterapi {
1283 my ($proto,$data) = @_;
1284 my $info = api_query($data, "dsc_in_suite/$isuite/$package");
1286 my $digester = Digest::SHA->new(256);
1287 foreach my $entry (@$info) {
1289 my $vsn = "$entry->{version}";
1290 my ($ok,$msg) = version_check $vsn;
1291 die f_ "bad version: %s\n", $msg unless $ok;
1292 my $component = "$entry->{component}";
1293 $component =~ m/^$component_re$/ or die __ "bad component";
1294 my $filename = "$entry->{filename}";
1295 $filename && $filename !~ m#[^-+:._~0-9a-zA-Z/]|^[/.]|/[/.]#
1296 or die __ "bad filename";
1297 my $sha256sum = "$entry->{sha256sum}";
1298 $sha256sum =~ m/^[0-9a-f]+$/ or die __ "bad sha256sum";
1299 push @rows, [ $vsn, "/pool/$component/$filename",
1300 $digester, $sha256sum ];
1302 die +(__ "bad ftpmaster api response: ")."$@\n".Dumper($entry)
1305 @rows = sort { -version_compare($a->[0],$b->[0]) } @rows;
1306 return archive_query_prepend_mirror @rows;
1309 sub file_in_archive_ftpmasterapi {
1310 my ($proto,$data,$filename) = @_;
1311 my $pat = $filename;
1314 $pat =~ s#[^-+_.0-9a-z/]# sprintf '%%%02x', ord $& #ge;
1315 my $info = api_query($data, "file_in_archive/$pat", 1);
1318 sub package_not_wholly_new_ftpmasterapi {
1319 my ($proto,$data,$pkg) = @_;
1320 my $info = api_query($data,"madison?package=${pkg}&f=json");
1324 #---------- `aptget' archive query method ----------
1327 our $aptget_releasefile;
1328 our $aptget_configpath;
1330 sub aptget_aptget () { return @aptget, qw(-c), $aptget_configpath; }
1331 sub aptget_aptcache () { return @aptcache, qw(-c), $aptget_configpath; }
1333 sub aptget_cache_clean {
1334 runcmd_ordryrun_local qw(sh -ec),
1335 'cd "$1"; find -atime +30 -type f -print0 | xargs -0r rm --',
1339 sub aptget_lock_acquire () {
1340 my $lockfile = "$aptget_base/lock";
1341 open APTGET_LOCK, '>', $lockfile or confess "open $lockfile: $!";
1342 flock APTGET_LOCK, LOCK_EX or confess "lock $lockfile: $!";
1345 sub aptget_prep ($) {
1347 return if defined $aptget_base;
1349 badcfg __ "aptget archive query method takes no data part"
1352 my $cache = $ENV{XDG_CACHE_DIR} // "$ENV{HOME}/.cache";
1355 ensuredir "$cache/dgit";
1357 access_cfg('aptget-cachekey','RETURN-UNDEF')
1358 // access_nomdistro();
1360 $aptget_base = "$cache/dgit/aptget";
1361 ensuredir $aptget_base;
1363 my $quoted_base = $aptget_base;
1364 confess "$quoted_base contains bad chars, cannot continue"
1365 if $quoted_base =~ m/["\\]/; # apt.conf(5) says no escaping :-/
1367 ensuredir $aptget_base;
1369 aptget_lock_acquire();
1371 aptget_cache_clean();
1373 $aptget_configpath = "$aptget_base/apt.conf#$cachekey";
1374 my $sourceslist = "source.list#$cachekey";
1376 my $aptsuites = $isuite;
1377 cfg_apply_map(\$aptsuites, 'suite map',
1378 access_cfg('aptget-suite-map', 'RETURN-UNDEF'));
1380 open SRCS, ">", "$aptget_base/$sourceslist" or confess "$!";
1381 printf SRCS "deb-src %s %s %s\n",
1382 access_cfg('mirror'),
1384 access_cfg('aptget-components')
1387 ensuredir "$aptget_base/cache";
1388 ensuredir "$aptget_base/lists";
1390 open CONF, ">", $aptget_configpath or confess "$!";
1392 Debug::NoLocking "true";
1393 APT::Get::List-Cleanup "false";
1394 #clear APT::Update::Post-Invoke-Success;
1395 Dir::Etc::SourceList "$quoted_base/$sourceslist";
1396 Dir::State::Lists "$quoted_base/lists";
1397 Dir::Etc::preferences "$quoted_base/preferences";
1398 Dir::Cache::srcpkgcache "$quoted_base/cache/srcs#$cachekey";
1399 Dir::Cache::pkgcache "$quoted_base/cache/pkgs#$cachekey";
1402 foreach my $key (qw(
1405 Dir::Cache::Archives
1406 Dir::Etc::SourceParts
1407 Dir::Etc::preferencesparts
1409 ensuredir "$aptget_base/$key";
1410 print CONF "$key \"$quoted_base/$key\";\n" or confess "$!";
1413 my $oldatime = (time // confess "$!") - 1;
1414 foreach my $oldlist (<$aptget_base/lists/*Release>) {
1415 next unless stat_exists $oldlist;
1416 my ($mtime) = (stat _)[9];
1417 utime $oldatime, $mtime, $oldlist or die "$oldlist $!";
1420 runcmd_ordryrun_local aptget_aptget(), qw(update);
1423 foreach my $oldlist (<$aptget_base/lists/*Release>) {
1424 next unless stat_exists $oldlist;
1425 my ($atime) = (stat _)[8];
1426 next if $atime == $oldatime;
1427 push @releasefiles, $oldlist;
1429 my @inreleasefiles = grep { m#/InRelease$# } @releasefiles;
1430 @releasefiles = @inreleasefiles if @inreleasefiles;
1431 if (!@releasefiles) {
1432 fail f_ <<END, $isuite, $cache;
1433 apt seemed to not to update dgit's cached Release files for %s.
1435 is on a filesystem mounted `noatime'; if so, please use `relatime'.)
1438 confess "apt updated too many Release files (@releasefiles), erk"
1439 unless @releasefiles == 1;
1441 ($aptget_releasefile) = @releasefiles;
1444 sub canonicalise_suite_aptget {
1445 my ($proto,$data) = @_;
1448 my $release = parsecontrol $aptget_releasefile, "Release file", 1;
1450 foreach my $name (qw(Codename Suite)) {
1451 my $val = $release->{$name};
1453 printdebug "release file $name: $val\n";
1454 cfg_apply_map(\$val, 'suite rmap',
1455 access_cfg('aptget-suite-rmap', 'RETURN-UNDEF'));
1456 $val =~ m/^$suite_re$/o or fail f_
1457 "Release file (%s) specifies intolerable %s",
1458 $aptget_releasefile, $name;
1465 sub archive_query_aptget {
1466 my ($proto,$data) = @_;
1469 ensuredir "$aptget_base/source";
1470 foreach my $old (<$aptget_base/source/*.dsc>) {
1471 unlink $old or die "$old: $!";
1474 my $showsrc = cmdoutput aptget_aptcache(), qw(showsrc), $package;
1475 return () unless $showsrc =~ m/^package:\s*\Q$package\E\s*$/mi;
1476 # avoids apt-get source failing with ambiguous error code
1478 runcmd_ordryrun_local
1479 shell_cmd 'cd "$1"/source; shift', $aptget_base,
1480 aptget_aptget(), qw(--download-only --only-source source), $package;
1482 my @dscs = <$aptget_base/source/*.dsc>;
1483 fail __ "apt-get source did not produce a .dsc" unless @dscs;
1484 fail f_ "apt-get source produced several .dscs (%s)", "@dscs"
1487 my $pre_dsc = parsecontrol $dscs[0], $dscs[0], 1;
1490 my $uri = "file://". uri_escape $dscs[0];
1491 $uri =~ s{\%2f}{/}gi;
1492 return [ (getfield $pre_dsc, 'Version'), $uri ];
1495 sub file_in_archive_aptget () { return undef; }
1496 sub package_not_wholly_new_aptget () { return undef; }
1498 #---------- `dummyapicat' archive query method ----------
1499 # (untranslated, because this is for testing purposes etc.)
1501 sub archive_query_dummycatapi { archive_query_ftpmasterapi @_; }
1502 sub canonicalise_suite_dummycatapi { canonicalise_suite_ftpmasterapi @_; }
1504 sub dummycatapi_run_in_mirror ($@) {
1505 # runs $fn with FIA open onto rune
1506 my ($rune, $argl, $fn) = @_;
1508 my $mirror = access_cfg('mirror');
1509 $mirror =~ s#^file://#/# or die "$mirror ?";
1510 my @cmd = (qw(sh -ec), 'cd "$1"; shift'."\n".$rune,
1511 qw(x), $mirror, @$argl);
1512 debugcmd "-|", @cmd;
1513 open FIA, "-|", @cmd or confess "$!";
1515 close FIA or ($!==0 && $?==141) or die failedcmd @cmd;
1519 sub file_in_archive_dummycatapi ($$$) {
1520 my ($proto,$data,$filename) = @_;
1522 dummycatapi_run_in_mirror '
1523 find -name "$1" -print0 |
1525 ', [$filename], sub {
1528 printdebug "| $_\n";
1529 m/^(\w+) (\S+)$/ or die "$_ ?";
1530 push @out, { sha256sum => $1, filename => $2 };
1536 sub package_not_wholly_new_dummycatapi {
1537 my ($proto,$data,$pkg) = @_;
1538 dummycatapi_run_in_mirror "
1539 find -name ${pkg}_*.dsc
1546 #---------- `madison' archive query method ----------
1548 sub archive_query_madison {
1549 return archive_query_prepend_mirror
1550 map { [ @$_[0..1] ] } madison_get_parse(@_);
1553 sub madison_get_parse {
1554 my ($proto,$data) = @_;
1555 die unless $proto eq 'madison';
1556 if (!length $data) {
1557 $data= access_cfg('madison-distro','RETURN-UNDEF');
1558 $data //= access_basedistro();
1560 $rmad{$proto,$data,$package} ||= cmdoutput
1561 qw(rmadison -asource),"-s$isuite","-u$data",$package;
1562 my $rmad = $rmad{$proto,$data,$package};
1565 foreach my $l (split /\n/, $rmad) {
1566 $l =~ m{^ \s*( [^ \t|]+ )\s* \|
1567 \s*( [^ \t|]+ )\s* \|
1568 \s*( [^ \t|/]+ )(?:/([^ \t|/]+))? \s* \|
1569 \s*( [^ \t|]+ )\s* }x or die "$rmad ?";
1570 $1 eq $package or die "$rmad $package ?";
1577 $component = access_cfg('archive-query-default-component');
1579 $5 eq 'source' or die "$rmad ?";
1580 push @out, [$vsn,pool_dsc_subpath($vsn,$component),$newsuite];
1582 return sort { -version_compare($a->[0],$b->[0]); } @out;
1585 sub canonicalise_suite_madison {
1586 # madison canonicalises for us
1587 my @r = madison_get_parse(@_);
1589 "unable to canonicalise suite using package %s".
1590 " which does not appear to exist in suite %s;".
1591 " --existing-package may help",
1596 sub file_in_archive_madison { return undef; }
1597 sub package_not_wholly_new_madison { return undef; }
1599 #---------- `sshpsql' archive query method ----------
1600 # (untranslated, because this is obsolete)
1603 my ($data,$runeinfo,$sql) = @_;
1604 if (!length $data) {
1605 $data= access_someuserhost('sshpsql').':'.
1606 access_cfg('sshpsql-dbname');
1608 $data =~ m/:/ or badcfg "invalid sshpsql method string \`$data'";
1609 my ($userhost,$dbname) = ($`,$'); #';
1611 my @cmd = (access_cfg_ssh, $userhost,
1612 access_runeinfo("ssh-psql $runeinfo").
1613 " export LC_MESSAGES=C; export LC_CTYPE=C;".
1614 " ".shellquote qw(psql -A), $dbname, qw(-c), $sql);
1616 open P, "-|", @cmd or confess "$!";
1619 printdebug(">|$_|\n");
1622 $!=0; $?=0; close P or failedcmd @cmd;
1624 my $nrows = pop @rows;
1625 $nrows =~ s/^\((\d+) rows?\)$/$1/ or die "$nrows ?";
1626 @rows == $nrows+1 or die "$nrows ".(scalar @rows)." ?";
1627 @rows = map { [ split /\|/, $_ ] } @rows;
1628 my $ncols = scalar @{ shift @rows };
1629 die if grep { scalar @$_ != $ncols } @rows;
1633 sub sql_injection_check {
1634 foreach (@_) { die "$_ $& ?" if m{[^-+=:_.,/0-9a-zA-Z]}; }
1637 sub archive_query_sshpsql ($$) {
1638 my ($proto,$data) = @_;
1639 sql_injection_check $isuite, $package;
1640 my @rows = sshpsql($data, "archive-query $isuite $package", <<END);
1641 SELECT source.version, component.name, files.filename, files.sha256sum
1643 JOIN src_associations ON source.id = src_associations.source
1644 JOIN suite ON suite.id = src_associations.suite
1645 JOIN dsc_files ON dsc_files.source = source.id
1646 JOIN files_archive_map ON files_archive_map.file_id = dsc_files.file
1647 JOIN component ON component.id = files_archive_map.component_id
1648 JOIN files ON files.id = dsc_files.file
1649 WHERE ( suite.suite_name='$isuite' OR suite.codename='$isuite' )
1650 AND source.source='$package'
1651 AND files.filename LIKE '%.dsc';
1653 @rows = sort { -version_compare($a->[0],$b->[0]) } @rows;
1654 my $digester = Digest::SHA->new(256);
1656 my ($vsn,$component,$filename,$sha256sum) = @$_;
1657 [ $vsn, "/pool/$component/$filename",$digester,$sha256sum ];
1659 return archive_query_prepend_mirror @rows;
1662 sub canonicalise_suite_sshpsql ($$) {
1663 my ($proto,$data) = @_;
1664 sql_injection_check $isuite;
1665 my @rows = sshpsql($data, "canonicalise-suite $isuite", <<END);
1666 SELECT suite.codename
1667 FROM suite where suite_name='$isuite' or codename='$isuite';
1669 @rows = map { $_->[0] } @rows;
1670 fail "unknown suite $isuite" unless @rows;
1671 die "ambiguous $isuite: @rows ?" if @rows>1;
1675 sub file_in_archive_sshpsql ($$$) { return undef; }
1676 sub package_not_wholly_new_sshpsql ($$$) { return undef; }
1678 #---------- `dummycat' archive query method ----------
1679 # (untranslated, because this is for testing purposes etc.)
1681 sub canonicalise_suite_dummycat ($$) {
1682 my ($proto,$data) = @_;
1683 my $dpath = "$data/suite.$isuite";
1684 if (!open C, "<", $dpath) {
1685 $!==ENOENT or die "$dpath: $!";
1686 printdebug "dummycat canonicalise_suite $isuite $dpath ENOENT\n";
1690 chomp or die "$dpath: $!";
1692 printdebug "dummycat canonicalise_suite $isuite $dpath = $_\n";
1696 sub archive_query_dummycat ($$) {
1697 my ($proto,$data) = @_;
1698 canonicalise_suite();
1699 my $dpath = "$data/package.$csuite.$package";
1700 if (!open C, "<", $dpath) {
1701 $!==ENOENT or die "$dpath: $!";
1702 printdebug "dummycat query $csuite $package $dpath ENOENT\n";
1710 printdebug "dummycat query $csuite $package $dpath | $_\n";
1711 my @row = split /\s+/, $_;
1712 @row==2 or die "$dpath: $_ ?";
1715 C->error and die "$dpath: $!";
1717 return archive_query_prepend_mirror
1718 sort { -version_compare($a->[0],$b->[0]); } @rows;
1721 sub file_in_archive_dummycat () { return undef; }
1722 sub package_not_wholly_new_dummycat () { return undef; }
1724 #---------- archive query entrypoints and rest of program ----------
1726 sub canonicalise_suite () {
1727 return if defined $csuite;
1728 fail f_ "cannot operate on %s suite", $isuite if $isuite eq 'UNRELEASED';
1729 $csuite = archive_query('canonicalise_suite');
1730 if ($isuite ne $csuite) {
1731 progress f_ "canonical suite name for %s is %s", $isuite, $csuite;
1733 progress f_ "canonical suite name is %s", $csuite;
1737 sub get_archive_dsc () {
1738 canonicalise_suite();
1739 my @vsns = archive_query('archive_query');
1740 foreach my $vinfo (@vsns) {
1741 my ($vsn,$vsn_dscurl,$digester,$digest) = @$vinfo;
1742 $dscurl = $vsn_dscurl;
1743 $dscdata = url_get($dscurl);
1745 $skew_warning_vsn = $vsn if !defined $skew_warning_vsn;
1750 $digester->add($dscdata);
1751 my $got = $digester->hexdigest();
1753 fail f_ "%s has hash %s but archive told us to expect %s",
1754 $dscurl, $got, $digest;
1757 my $fmt = getfield $dsc, 'Format';
1758 $format_ok{$fmt} or forceable_fail [qw(unsupported-source-format)],
1759 f_ "unsupported source format %s, sorry", $fmt;
1761 $dsc_checked = !!$digester;
1762 printdebug "get_archive_dsc: Version ".(getfield $dsc, 'Version')."\n";
1766 printdebug "get_archive_dsc: nothing in archive, returning undef\n";
1769 sub check_for_git ();
1770 sub check_for_git () {
1772 my $how = access_cfg('git-check');
1773 if ($how eq 'ssh-cmd') {
1775 (access_cfg_ssh, access_gituserhost(),
1776 access_runeinfo("git-check $package").
1777 " set -e; cd ".access_cfg('git-path').";".
1778 " if test -d $package.git; then echo 1; else echo 0; fi");
1779 my $r= cmdoutput @cmd;
1780 if (defined $r and $r =~ m/^divert (\w+)$/) {
1782 my ($usedistro,) = access_distros();
1783 # NB that if we are pushing, $usedistro will be $distro/push
1784 $instead_distro= cfg("dgit-distro.$usedistro.diverts.$divert");
1785 $instead_distro =~ s{^/}{ access_basedistro()."/" }e;
1786 progress f_ "diverting to %s (using config for %s)",
1787 $divert, $instead_distro;
1788 return check_for_git();
1790 failedcmd @cmd unless defined $r and $r =~ m/^[01]$/;
1792 } elsif ($how eq 'url') {
1793 my $prefix = access_cfg('git-check-url','git-url');
1794 my $suffix = access_cfg('git-check-suffix','git-suffix',
1795 'RETURN-UNDEF') // '.git';
1796 my $url = "$prefix/$package$suffix";
1797 my @cmd = (@curl, qw(-sS -I), $url);
1798 my $result = cmdoutput @cmd;
1799 $result =~ s/^\S+ 200 .*\n\r?\n//;
1800 # curl -sS -I with https_proxy prints
1801 # HTTP/1.0 200 Connection established
1802 $result =~ m/^\S+ (404|200) /s or
1803 fail +(__ "unexpected results from git check query - ").
1804 Dumper($prefix, $result);
1806 if ($code eq '404') {
1808 } elsif ($code eq '200') {
1813 } elsif ($how eq 'true') {
1815 } elsif ($how eq 'false') {
1818 badcfg f_ "unknown git-check \`%s'", $how;
1822 sub create_remote_git_repo () {
1823 my $how = access_cfg('git-create');
1824 if ($how eq 'ssh-cmd') {
1826 (access_cfg_ssh, access_gituserhost(),
1827 access_runeinfo("git-create $package").
1828 "set -e; cd ".access_cfg('git-path').";".
1829 " cp -a _template $package.git");
1830 } elsif ($how eq 'true') {
1833 badcfg f_ "unknown git-create \`%s'", $how;
1837 our ($dsc_hash,$lastpush_mergeinput);
1838 our ($dsc_distro, $dsc_hint_tag, $dsc_hint_url);
1842 dgit_privdir(); # ensures that $dgit_privdir_made is based on $maindir
1843 $playground = fresh_playground 'dgit/unpack';
1846 sub mktree_in_ud_here () {
1850 sub git_write_tree () {
1851 my $tree = cmdoutput @git, qw(write-tree);
1852 $tree =~ m/^\w+$/ or die "$tree ?";
1856 sub git_add_write_tree () {
1857 runcmd @git, qw(add -Af .);
1858 return git_write_tree();
1861 sub remove_stray_gits ($) {
1863 my @gitscmd = qw(find -name .git -prune -print0);
1864 debugcmd "|",@gitscmd;
1865 open GITS, "-|", @gitscmd or confess "$!";
1870 print STDERR f_ "%s: warning: removing from %s: %s\n",
1871 $us, $what, (messagequote $_);
1875 $!=0; $?=0; close GITS or failedcmd @gitscmd;
1878 sub mktree_in_ud_from_only_subdir ($;$) {
1879 my ($what,$raw) = @_;
1880 # changes into the subdir
1883 confess "expected one subdir but found @dirs ?" unless @dirs==1;
1884 $dirs[0] =~ m#^([^/]+)/\.$# or die;
1888 remove_stray_gits($what);
1889 mktree_in_ud_here();
1891 my ($format, $fopts) = get_source_format();
1892 if (madformat($format)) {
1897 my $tree=git_add_write_tree();
1898 return ($tree,$dir);
1901 our @files_csum_info_fields =
1902 (['Checksums-Sha256','Digest::SHA', 'new(256)', 'sha256sum'],
1903 ['Checksums-Sha1', 'Digest::SHA', 'new(1)', 'sha1sum'],
1904 ['Files', 'Digest::MD5', 'new()', 'md5sum']);
1906 sub dsc_files_info () {
1907 foreach my $csumi (@files_csum_info_fields) {
1908 my ($fname, $module, $method) = @$csumi;
1909 my $field = $dsc->{$fname};
1910 next unless defined $field;
1911 eval "use $module; 1;" or die $@;
1913 foreach (split /\n/, $field) {
1915 m/^(\w+) (\d+) (\S+)$/ or
1916 fail f_ "could not parse .dsc %s line \`%s'", $fname, $_;
1917 my $digester = eval "$module"."->$method;" or die $@;
1922 Digester => $digester,
1927 fail f_ "missing any supported Checksums-* or Files field in %s",
1928 $dsc->get_option('name');
1932 map { $_->{Filename} } dsc_files_info();
1935 sub files_compare_inputs (@) {
1940 my $showinputs = sub {
1941 return join "; ", map { $_->get_option('name') } @$inputs;
1944 foreach my $in (@$inputs) {
1946 my $in_name = $in->get_option('name');
1948 printdebug "files_compare_inputs $in_name\n";
1950 foreach my $csumi (@files_csum_info_fields) {
1951 my ($fname) = @$csumi;
1952 printdebug "files_compare_inputs $in_name $fname\n";
1954 my $field = $in->{$fname};
1955 next unless defined $field;
1958 foreach (split /\n/, $field) {
1961 my ($info, $f) = m/^(\w+ \d+) (?:\S+ \S+ )?(\S+)$/ or
1962 fail "could not parse $in_name $fname line \`$_'";
1964 printdebug "files_compare_inputs $in_name $fname $f\n";
1968 my $re = \ $record{$f}{$fname};
1970 $fchecked{$f}{$in_name} = 1;
1973 "hash or size of %s varies in %s fields (between: %s)",
1974 $f, $fname, $showinputs->();
1979 @files = sort @files;
1980 $expected_files //= \@files;
1981 "@$expected_files" eq "@files" or
1982 fail f_ "file list in %s varies between hash fields!",
1986 fail f_ "%s has no files list field(s)", $in_name;
1988 printdebug "files_compare_inputs ".Dumper(\%fchecked, \%record)
1991 grep { keys %$_ == @$inputs-1 } values %fchecked
1992 or fail f_ "no file appears in all file lists (looked in: %s)",
1996 sub is_orig_file_in_dsc ($$) {
1997 my ($f, $dsc_files_info) = @_;
1998 return 0 if @$dsc_files_info <= 1;
1999 # One file means no origs, and the filename doesn't have a "what
2000 # part of dsc" component. (Consider versions ending `.orig'.)
2001 return 0 unless $f =~ m/\.$orig_f_tail_re$/o;
2005 # This function determines whether a .changes file is source-only from
2006 # the point of view of dak. Thus, it permits *_source.buildinfo
2009 # It does not, however, permit any other buildinfo files. After a
2010 # source-only upload, the buildds will try to upload files like
2011 # foo_1.2.3_amd64.buildinfo. If the package maintainer included files
2012 # named like this in their (otherwise) source-only upload, the uploads
2013 # of the buildd can be rejected by dak. Fixing the resultant
2014 # situation can require manual intervention. So we block such
2015 # .buildinfo files when the user tells us to perform a source-only
2016 # upload (such as when using the push-source subcommand with the -C
2017 # option, which calls this function).
2019 # Note, though, that when dgit is told to prepare a source-only
2020 # upload, such as when subcommands like build-source and push-source
2021 # without -C are used, dgit has a more restrictive notion of
2022 # source-only .changes than dak: such uploads will never include
2023 # *_source.buildinfo files. This is because there is no use for such
2024 # files when using a tool like dgit to produce the source package, as
2025 # dgit ensures the source is identical to git HEAD.
2026 sub test_source_only_changes ($) {
2028 foreach my $l (split /\n/, getfield $changes, 'Files') {
2029 $l =~ m/\S+$/ or next;
2030 # \.tar\.[a-z0-9]+ covers orig.tar and the tarballs in native packages
2031 unless ($& =~ m/(?:\.dsc|\.diff\.gz|\.tar\.[a-z0-9]+|_source\.buildinfo)$/) {
2032 print f_ "purportedly source-only changes polluted by %s\n", $&;
2039 sub changes_update_origs_from_dsc ($$$$) {
2040 my ($dsc, $changes, $upstreamvsn, $changesfile) = @_;
2042 printdebug "checking origs needed ($upstreamvsn)...\n";
2043 $_ = getfield $changes, 'Files';
2044 m/^\w+ \d+ (\S+ \S+) \S+$/m or
2045 fail __ "cannot find section/priority from .changes Files field";
2046 my $placementinfo = $1;
2048 printdebug "checking origs needed placement '$placementinfo'...\n";
2049 foreach my $l (split /\n/, getfield $dsc, 'Files') {
2050 $l =~ m/\S+$/ or next;
2052 printdebug "origs $file | $l\n";
2053 next unless is_orig_file_of_vsn $file, $upstreamvsn;
2054 printdebug "origs $file is_orig\n";
2055 my $have = archive_query('file_in_archive', $file);
2056 if (!defined $have) {
2057 print STDERR __ <<END;
2058 archive does not support .orig check; hope you used --ch:--sa/-sd if needed
2064 printdebug "origs $file \$#\$have=$#$have\n";
2065 foreach my $h (@$have) {
2068 foreach my $csumi (@files_csum_info_fields) {
2069 my ($fname, $module, $method, $archivefield) = @$csumi;
2070 next unless defined $h->{$archivefield};
2071 $_ = $dsc->{$fname};
2072 next unless defined;
2073 m/^(\w+) .* \Q$file\E$/m or
2074 fail f_ ".dsc %s missing entry for %s", $fname, $file;
2075 if ($h->{$archivefield} eq $1) {
2079 "%s: %s (archive) != %s (local .dsc)",
2080 $archivefield, $h->{$archivefield}, $1;
2083 confess "$file ".Dumper($h)." ?!" if $same && @differ;
2087 f_ "archive %s: %s", $h->{filename}, join "; ", @differ
2090 printdebug "origs $file f.same=$found_same".
2091 " #f._differ=$#found_differ\n";
2092 if (@found_differ && !$found_same) {
2094 (f_ "archive contains %s with different checksum", $file),
2097 # Now we edit the changes file to add or remove it
2098 foreach my $csumi (@files_csum_info_fields) {
2099 my ($fname, $module, $method, $archivefield) = @$csumi;
2100 next unless defined $changes->{$fname};
2102 # in archive, delete from .changes if it's there
2103 $changed{$file} = "removed" if
2104 $changes->{$fname} =~ s/\n.* \Q$file\E$(?:)$//m;
2105 } elsif ($changes->{$fname} =~ m/^.* \Q$file\E$(?:)$/m) {
2106 # not in archive, but it's here in the .changes
2108 my $dsc_data = getfield $dsc, $fname;
2109 $dsc_data =~ m/^(.* \Q$file\E$)$/m or die "$dsc_data $file ?";
2111 $extra =~ s/ \d+ /$&$placementinfo /
2112 or confess "$fname $extra >$dsc_data< ?"
2113 if $fname eq 'Files';
2114 $changes->{$fname} .= "\n". $extra;
2115 $changed{$file} = "added";
2120 foreach my $file (keys %changed) {
2122 "edited .changes for archive .orig contents: %s %s",
2123 $changed{$file}, $file;
2125 my $chtmp = "$changesfile.tmp";
2126 $changes->save($chtmp);
2128 rename $chtmp,$changesfile or die "$changesfile $!";
2130 progress f_ "[new .changes left in %s]", $changesfile;
2133 progress f_ "%s already has appropriate .orig(s) (if any)",
2138 sub clogp_authline ($) {
2140 my $author = getfield $clogp, 'Maintainer';
2141 if ($author =~ m/^[^"\@]+\,/) {
2142 # single entry Maintainer field with unquoted comma
2143 $author = ($& =~ y/,//rd).$'; # strip the comma
2145 # git wants a single author; any remaining commas in $author
2146 # are by now preceded by @ (or "). It seems safer to punt on
2147 # "..." for now rather than attempting to dequote or something.
2148 $author =~ s#,.*##ms unless $author =~ m/"/;
2149 my $date = cmdoutput qw(date), '+%s %z', qw(-d), getfield($clogp,'Date');
2150 my $authline = "$author $date";
2151 $authline =~ m/$git_authline_re/o or
2152 fail f_ "unexpected commit author line format \`%s'".
2153 " (was generated from changelog Maintainer field)",
2155 return ($1,$2,$3) if wantarray;
2159 sub vendor_patches_distro ($$) {
2160 my ($checkdistro, $what) = @_;
2161 return unless defined $checkdistro;
2163 my $series = "debian/patches/\L$checkdistro\E.series";
2164 printdebug "checking for vendor-specific $series ($what)\n";
2166 if (!open SERIES, "<", $series) {
2167 confess "$series $!" unless $!==ENOENT;
2174 print STDERR __ <<END;
2176 Unfortunately, this source package uses a feature of dpkg-source where
2177 the same source package unpacks to different source code on different
2178 distros. dgit cannot safely operate on such packages on affected
2179 distros, because the meaning of source packages is not stable.
2181 Please ask the distro/maintainer to remove the distro-specific series
2182 files and use a different technique (if necessary, uploading actually
2183 different packages, if different distros are supposed to have
2187 fail f_ "Found active distro-specific series file for".
2188 " %s (%s): %s, cannot continue",
2189 $checkdistro, $what, $series;
2191 die "$series $!" if SERIES->error;
2195 sub check_for_vendor_patches () {
2196 # This dpkg-source feature doesn't seem to be documented anywhere!
2197 # But it can be found in the changelog (reformatted):
2199 # commit 4fa01b70df1dc4458daee306cfa1f987b69da58c
2200 # Author: Raphael Hertzog <hertzog@debian.org>
2201 # Date: Sun Oct 3 09:36:48 2010 +0200
2203 # dpkg-source: correctly create .pc/.quilt_series with alternate
2206 # If you have debian/patches/ubuntu.series and you were
2207 # unpacking the source package on ubuntu, quilt was still
2208 # directed to debian/patches/series instead of
2209 # debian/patches/ubuntu.series.
2211 # debian/changelog | 3 +++
2212 # scripts/Dpkg/Source/Package/V3/quilt.pm | 4 +++-
2213 # 2 files changed, 6 insertions(+), 1 deletion(-)
2216 vendor_patches_distro($ENV{DEB_VENDOR}, "DEB_VENDOR");
2217 vendor_patches_distro(Dpkg::Vendor::get_current_vendor(),
2218 __ "Dpkg::Vendor \`current vendor'");
2219 vendor_patches_distro(access_basedistro(),
2220 __ "(base) distro being accessed");
2221 vendor_patches_distro(access_nomdistro(),
2222 __ "(nominal) distro being accessed");
2225 sub check_bpd_exists () {
2226 stat $buildproductsdir
2227 or fail f_ "build-products-dir %s is not accessible: %s\n",
2228 $buildproductsdir, $!;
2231 sub dotdot_bpd_transfer_origs ($$$) {
2232 my ($bpd_abs, $upstreamversion, $wanted) = @_;
2233 # checks is_orig_file_of_vsn and if
2234 # calls $wanted->{$leaf} and expects boolish
2236 return if $buildproductsdir eq '..';
2239 my $dotdot = $maindir;
2240 $dotdot =~ s{/[^/]+$}{};
2241 opendir DD, $dotdot or fail "opendir .. ($dotdot): $!";
2242 while ($!=0, defined(my $leaf = readdir DD)) {
2244 local ($debuglevel) = $debuglevel-1;
2245 printdebug "DD_BPD $leaf ?\n";
2247 next unless is_orig_file_of_vsn $leaf, $upstreamversion;
2248 next unless $wanted->($leaf);
2249 next if lstat "$bpd_abs/$leaf";
2252 "%s: found orig(s) in .. missing from build-products-dir, transferring:\n",
2255 $! == &ENOENT or fail f_
2256 "check orig file %s in bpd %s: %s", $leaf, $bpd_abs, $!;
2257 lstat "$dotdot/$leaf" or fail f_
2258 "check orig file %s in ..: %s", $leaf, $!;
2260 stat "$dotdot/$leaf" or fail f_
2261 "check target of orig symlink %s in ..: %s", $leaf, $!;
2262 my $ltarget = readlink "$dotdot/$leaf" or
2263 die "readlink $dotdot/$leaf: $!";
2264 if ($ltarget !~ m{^/}) {
2265 $ltarget = "$dotdot/$ltarget";
2267 symlink $ltarget, "$bpd_abs/$leaf"
2268 or die "$ltarget $bpd_abs $leaf: $!";
2270 "%s: cloned orig symlink from ..: %s\n",
2272 } elsif (link "$dotdot/$leaf", "$bpd_abs/$leaf") {
2274 "%s: hardlinked orig from ..: %s\n",
2276 } elsif ($! != EXDEV) {
2277 fail f_ "failed to make %s a hardlink to %s: %s",
2278 "$bpd_abs/$leaf", "$dotdot/$leaf", $!;
2280 symlink "$bpd_abs/$leaf", "$dotdot/$leaf"
2281 or die "$bpd_abs $dotdot $leaf $!";
2283 "%s: symmlinked orig from .. on other filesystem: %s\n",
2287 die "$dotdot; $!" if $!;
2291 sub import_tarball_tartrees ($$) {
2292 my ($upstreamv, $dfi) = @_;
2293 # cwd should be the playground
2295 # We unpack and record the orig tarballs first, so that we only
2296 # need disk space for one private copy of the unpacked source.
2297 # But we can't make them into commits until we have the metadata
2298 # from the debian/changelog, so we record the tree objects now and
2299 # make them into commits later.
2301 my $orig_f_base = srcfn $upstreamv, '';
2303 foreach my $fi (@$dfi) {
2304 # We actually import, and record as a commit, every tarball
2305 # (unless there is only one file, in which case there seems
2308 my $f = $fi->{Filename};
2309 printdebug "import considering $f ";
2310 (printdebug "not tar\n"), next unless $f =~ m/\.tar(\.\w+)?$/;
2311 (printdebug "signature\n"), next if $f =~ m/$orig_f_sig_re$/o;
2315 $f =~ m/^\Q$orig_f_base\E\.([^._]+)?\.tar(?:\.\w+)?$/;
2317 printdebug "Y ", (join ' ', map { $_//"(none)" }
2318 $compr_ext, $orig_f_part
2321 my $path = $fi->{Path} // $f;
2322 my $input = new IO::File $f, '<' or die "$f $!";
2326 if (defined $compr_ext) {
2328 Dpkg::Compression::compression_guess_from_filename $f;
2329 fail "Dpkg::Compression cannot handle file $f in source package"
2330 if defined $compr_ext && !defined $cname;
2332 new Dpkg::Compression::Process compression => $cname;
2333 @compr_cmd = $compr_proc->get_uncompress_cmdline();
2334 my $compr_fh = new IO::Handle;
2335 my $compr_pid = open $compr_fh, "-|" // confess "$!";
2337 open STDIN, "<&", $input or confess "$!";
2339 die "dgit (child): exec $compr_cmd[0]: $!\n";
2344 rmtree "_unpack-tar";
2345 mkdir "_unpack-tar" or confess "$!";
2346 my @tarcmd = qw(tar -x -f -
2347 --no-same-owner --no-same-permissions
2348 --no-acls --no-xattrs --no-selinux);
2349 my $tar_pid = fork // confess "$!";
2351 chdir "_unpack-tar" or confess "$!";
2352 open STDIN, "<&", $input or confess "$!";
2354 die f_ "dgit (child): exec %s: %s", $tarcmd[0], $!;
2356 $!=0; (waitpid $tar_pid, 0) == $tar_pid or confess "$!";
2357 !$? or failedcmd @tarcmd;
2360 (@compr_cmd ? ($?==SIGPIPE || failedcmd @compr_cmd)
2362 # finally, we have the results in "tarball", but maybe
2363 # with the wrong permissions
2365 runcmd qw(chmod -R +rwX _unpack-tar);
2366 changedir "_unpack-tar";
2367 remove_stray_gits($f);
2368 mktree_in_ud_here();
2370 my ($tree) = git_add_write_tree();
2371 my $tentries = cmdoutput @git, qw(ls-tree -z), $tree;
2372 if ($tentries =~ m/^\d+ tree (\w+)\t[^\000]+\000$/s) {
2374 printdebug "one subtree $1\n";
2376 printdebug "multiple subtrees\n";
2379 rmtree "_unpack-tar";
2381 my $ent = [ $f, $tree ];
2383 Orig => !!$orig_f_part,
2384 Sort => (!$orig_f_part ? 2 :
2385 $orig_f_part =~ m/-/g ? 1 :
2387 OrigPart => $orig_f_part, # 'orig', 'orig-XXX', or undef
2394 # put any without "_" first (spec is not clear whether files
2395 # are always in the usual order). Tarballs without "_" are
2396 # the main orig or the debian tarball.
2397 $a->{Sort} <=> $b->{Sort} or
2404 sub import_tarball_commits ($$) {
2405 my ($tartrees, $upstreamv) = @_;
2406 # cwd should be a playtree which has a relevant debian/changelog
2407 # fills in $tt->{Commit} for each one
2409 my $any_orig = grep { $_->{Orig} } @$tartrees;
2411 my @clogcmd = qw(dpkg-parsechangelog --format rfc822 --all);
2415 printdebug "import clog search...\n";
2416 parsechangelog_loop \@clogcmd, (__ "package changelog"), sub {
2417 my ($thisstanza, $desc) = @_;
2418 no warnings qw(exiting);
2420 $clogp //= $thisstanza;
2422 printdebug "import clog $thisstanza->{version} $desc...\n";
2424 last if !$any_orig; # we don't need $r1clogp
2426 # We look for the first (most recent) changelog entry whose
2427 # version number is lower than the upstream version of this
2428 # package. Then the last (least recent) previous changelog
2429 # entry is treated as the one which introduced this upstream
2430 # version and used for the synthetic commits for the upstream
2433 # One might think that a more sophisticated algorithm would be
2434 # necessary. But: we do not want to scan the whole changelog
2435 # file. Stopping when we see an earlier version, which
2436 # necessarily then is an earlier upstream version, is the only
2437 # realistic way to do that. Then, either the earliest
2438 # changelog entry we have seen so far is indeed the earliest
2439 # upload of this upstream version; or there are only changelog
2440 # entries relating to later upstream versions (which is not
2441 # possible unless the changelog and .dsc disagree about the
2442 # version). Then it remains to choose between the physically
2443 # last entry in the file, and the one with the lowest version
2444 # number. If these are not the same, we guess that the
2445 # versions were created in a non-monotonic order rather than
2446 # that the changelog entries have been misordered.
2448 printdebug "import clog $thisstanza->{version} vs $upstreamv...\n";
2450 last if version_compare($thisstanza->{version}, $upstreamv) < 0;
2451 $r1clogp = $thisstanza;
2453 printdebug "import clog $r1clogp->{version} becomes r1\n";
2456 $clogp or fail __ "package changelog has no entries!";
2458 my $authline = clogp_authline $clogp;
2459 my $changes = getfield $clogp, 'Changes';
2460 $changes =~ s/^\n//; # Changes: \n
2461 my $cversion = getfield $clogp, 'Version';
2465 $r1clogp //= $clogp; # maybe there's only one entry;
2466 $r1authline = clogp_authline $r1clogp;
2467 # Strictly, r1authline might now be wrong if it's going to be
2468 # unused because !$any_orig. Whatever.
2470 printdebug "import tartrees authline $authline\n";
2471 printdebug "import tartrees r1authline $r1authline\n";
2473 foreach my $tt (@$tartrees) {
2474 printdebug "import tartree $tt->{F} $tt->{Tree}\n";
2476 # untranslated so that different people's imports are identical
2477 my $mbody = sprintf "Import %s", $tt->{F};
2478 $tt->{Commit} = hash_commit_text($tt->{Orig} ? <<END_O : <<END_T);
2481 committer $r1authline
2485 [dgit import orig $tt->{F}]
2493 [dgit import tarball $package $cversion $tt->{F}]
2498 return ($authline, $r1authline, $clogp, $changes);
2501 sub generate_commits_from_dsc () {
2502 # See big comment in fetch_from_archive, below.
2503 # See also README.dsc-import.
2505 changedir $playground;
2507 my $bpd_abs = bpd_abs();
2508 my $upstreamv = upstreamversion $dsc->{version};
2509 my @dfi = dsc_files_info();
2511 dotdot_bpd_transfer_origs $bpd_abs, $upstreamv,
2512 sub { grep { $_->{Filename} eq $_[0] } @dfi };
2514 foreach my $fi (@dfi) {
2515 my $f = $fi->{Filename};
2516 die "$f ?" if $f =~ m#/|^\.|\.dsc$|\.tmp$#;
2517 my $upper_f = "$bpd_abs/$f";
2519 printdebug "considering reusing $f: ";
2521 if (link_ltarget "$upper_f,fetch", $f) {
2522 printdebug "linked (using ...,fetch).\n";
2523 } elsif ((printdebug "($!) "),
2525 fail f_ "accessing %s: %s", "$buildproductsdir/$f,fetch", $!;
2526 } elsif (link_ltarget $upper_f, $f) {
2527 printdebug "linked.\n";
2528 } elsif ((printdebug "($!) "),
2530 fail f_ "accessing %s: %s", "$buildproductsdir/$f", $!;
2532 printdebug "absent.\n";
2536 complete_file_from_dsc('.', $fi, \$refetched)
2539 printdebug "considering saving $f: ";
2541 if (rename_link_xf 1, $f, $upper_f) {
2542 printdebug "linked.\n";
2543 } elsif ((printdebug "($@) "),
2545 fail f_ "saving %s: %s", "$buildproductsdir/$f", $@;
2546 } elsif (!$refetched) {
2547 printdebug "no need.\n";
2548 } elsif (rename_link_xf 1, $f, "$upper_f,fetch") {
2549 printdebug "linked (using ...,fetch).\n";
2550 } elsif ((printdebug "($@) "),
2552 fail f_ "saving %s: %s", "$buildproductsdir/$f,fetch", $@;
2554 printdebug "cannot.\n";
2559 @tartrees = import_tarball_tartrees($upstreamv, \@dfi)
2560 unless @dfi == 1; # only one file in .dsc
2562 my $dscfn = "$package.dsc";
2564 my $treeimporthow = 'package';
2566 open D, ">", $dscfn or die "$dscfn: $!";
2567 print D $dscdata or die "$dscfn: $!";
2568 close D or die "$dscfn: $!";
2569 my @cmd = qw(dpkg-source);
2570 push @cmd, '--no-check' if $dsc_checked;
2571 if (madformat $dsc->{format}) {
2572 push @cmd, '--skip-patches';
2573 $treeimporthow = 'unpatched';
2575 push @cmd, qw(-x --), $dscfn;
2578 my ($tree,$dir) = mktree_in_ud_from_only_subdir(__ "source package");
2579 if (madformat $dsc->{format}) {
2580 check_for_vendor_patches();
2584 if (madformat $dsc->{format}) {
2585 my @pcmd = qw(dpkg-source --before-build .);
2586 runcmd shell_cmd 'exec >/dev/null', @pcmd;
2588 $dappliedtree = git_add_write_tree();
2591 my ($authline, $r1authline, $clogp, $changes) =
2592 import_tarball_commits(\@tartrees, $upstreamv);
2594 my $cversion = getfield $clogp, 'Version';
2596 printdebug "import main commit\n";
2598 open C, ">../commit.tmp" or confess "$!";
2599 print C <<END or confess "$!";
2602 print C <<END or confess "$!" foreach @tartrees;
2605 print C <<END or confess "$!";
2611 [dgit import $treeimporthow $package $cversion]
2614 close C or confess "$!";
2615 my $rawimport_hash = hash_commit qw(../commit.tmp);
2617 if (madformat $dsc->{format}) {
2618 printdebug "import apply patches...\n";
2620 # regularise the state of the working tree so that
2621 # the checkout of $rawimport_hash works nicely.
2622 my $dappliedcommit = hash_commit_text(<<END);
2629 runcmd @git, qw(checkout -q -b dapplied), $dappliedcommit;
2631 runcmd @git, qw(checkout -q -b unpa), $rawimport_hash;
2633 # We need the answers to be reproducible
2634 my @authline = clogp_authline($clogp);
2635 local $ENV{GIT_COMMITTER_NAME} = $authline[0];
2636 local $ENV{GIT_COMMITTER_EMAIL} = $authline[1];
2637 local $ENV{GIT_COMMITTER_DATE} = $authline[2];
2638 local $ENV{GIT_AUTHOR_NAME} = $authline[0];
2639 local $ENV{GIT_AUTHOR_EMAIL} = $authline[1];
2640 local $ENV{GIT_AUTHOR_DATE} = $authline[2];
2642 my $path = $ENV{PATH} or die;
2644 # we use ../../gbp-pq-output, which (given that we are in
2645 # $playground/PLAYTREE, and $playground is .git/dgit/unpack,
2648 foreach my $use_absurd (qw(0 1)) {
2649 runcmd @git, qw(checkout -q unpa);
2650 runcmd @git, qw(update-ref -d refs/heads/patch-queue/unpa);
2651 local $ENV{PATH} = $path;
2654 progress "warning: $@";
2655 $path = "$absurdity:$path";
2656 progress f_ "%s: trying slow absurd-git-apply...", $us;
2657 rename "../../gbp-pq-output","../../gbp-pq-output.0"
2662 die "forbid absurd git-apply\n" if $use_absurd
2663 && forceing [qw(import-gitapply-no-absurd)];
2664 die "only absurd git-apply!\n" if !$use_absurd
2665 && forceing [qw(import-gitapply-absurd)];
2667 local $ENV{DGIT_ABSURD_DEBUG} = $debuglevel if $use_absurd;
2668 local $ENV{PATH} = $path if $use_absurd;
2670 my @showcmd = (gbp_pq, qw(import));
2671 my @realcmd = shell_cmd
2672 'exec >/dev/null 2>>../../gbp-pq-output', @showcmd;
2673 debugcmd "+",@realcmd;
2674 if (system @realcmd) {
2675 die f_ "%s failed: %s\n",
2676 +(shellquote @showcmd),
2677 failedcmd_waitstatus();
2680 my $gapplied = git_rev_parse('HEAD');
2681 my $gappliedtree = cmdoutput @git, qw(rev-parse HEAD:);
2682 $gappliedtree eq $dappliedtree or
2683 fail f_ <<END, $gapplied, $gappliedtree, $dappliedtree;
2684 gbp-pq import and dpkg-source disagree!
2685 gbp-pq import gave commit %s
2686 gbp-pq import gave tree %s
2687 dpkg-source --before-build gave tree %s
2689 $rawimport_hash = $gapplied;
2694 { local $@; eval { runcmd qw(cat ../../gbp-pq-output); }; }
2699 progress f_ "synthesised git commit from .dsc %s", $cversion;
2701 my $rawimport_mergeinput = {
2702 Commit => $rawimport_hash,
2703 Info => __ "Import of source package",
2705 my @output = ($rawimport_mergeinput);
2707 if ($lastpush_mergeinput) {
2708 my $oldclogp = mergeinfo_getclogp($lastpush_mergeinput);
2709 my $oversion = getfield $oldclogp, 'Version';
2711 version_compare($oversion, $cversion);
2713 @output = ($rawimport_mergeinput, $lastpush_mergeinput,
2714 { ReverseParents => 1,
2715 # untranslated so that different people's pseudomerges
2716 # are not needlessly different (although they will
2717 # still differ if the series of pulls is different)
2718 Message => (sprintf <<END, $package, $cversion, $csuite) });
2719 Record %s (%s) in archive suite %s
2721 } elsif ($vcmp > 0) {
2722 print STDERR f_ <<END, $cversion, $oversion,
2724 Version actually in archive: %s (older)
2725 Last version pushed with dgit: %s (newer or same)
2728 __ $later_warning_msg or confess "$!";
2729 @output = $lastpush_mergeinput;
2731 # Same version. Use what's in the server git branch,
2732 # discarding our own import. (This could happen if the
2733 # server automatically imports all packages into git.)
2734 @output = $lastpush_mergeinput;
2742 sub complete_file_from_dsc ($$;$) {
2743 our ($dstdir, $fi, $refetched) = @_;
2744 # Ensures that we have, in $dstdir, the file $fi, with the correct
2745 # contents. (Downloading it from alongside $dscurl if necessary.)
2746 # If $refetched is defined, can overwrite "$dstdir/$fi->{Filename}"
2747 # and will set $$refetched=1 if it did so (or tried to).
2749 my $f = $fi->{Filename};
2750 my $tf = "$dstdir/$f";
2754 my $checkhash = sub {
2755 open F, "<", "$tf" or die "$tf: $!";
2756 $fi->{Digester}->reset();
2757 $fi->{Digester}->addfile(*F);
2758 F->error and confess "$!";
2759 $got = $fi->{Digester}->hexdigest();
2760 return $got eq $fi->{Hash};
2763 if (stat_exists $tf) {
2764 if ($checkhash->()) {
2765 progress f_ "using existing %s", $f;
2769 fail f_ "file %s has hash %s but .dsc demands hash %s".
2770 " (perhaps you should delete this file?)",
2771 $f, $got, $fi->{Hash};
2773 progress f_ "need to fetch correct version of %s", $f;
2774 unlink $tf or die "$tf $!";
2777 printdebug "$tf does not exist, need to fetch\n";
2781 $furl =~ s{/[^/]+$}{};
2783 die "$f ?" unless $f =~ m/^\Q${package}\E_/;
2784 die "$f ?" if $f =~ m#/#;
2785 runcmd_ordryrun_local @curl,qw(-f -o),$tf,'--',"$furl";
2786 return 0 if !act_local();
2789 fail f_ "file %s has hash %s but .dsc demands hash %s".
2790 " (got wrong file from archive!)",
2791 $f, $got, $fi->{Hash};
2796 sub ensure_we_have_orig () {
2797 my @dfi = dsc_files_info();
2798 foreach my $fi (@dfi) {
2799 my $f = $fi->{Filename};
2800 next unless is_orig_file_in_dsc($f, \@dfi);
2801 complete_file_from_dsc($buildproductsdir, $fi)
2806 #---------- git fetch ----------
2808 sub lrfetchrefs () { return "refs/dgit-fetch/".access_basedistro(); }
2809 sub lrfetchref () { return lrfetchrefs.'/'.server_branch($csuite); }
2811 # We fetch some parts of lrfetchrefs/*. Ideally we delete these
2812 # locally fetched refs because they have unhelpful names and clutter
2813 # up gitk etc. So we track whether we have "used up" head ref (ie,
2814 # whether we have made another local ref which refers to this object).
2816 # (If we deleted them unconditionally, then we might end up
2817 # re-fetching the same git objects each time dgit fetch was run.)
2819 # So, each use of lrfetchrefs needs to be accompanied by arrangements
2820 # in git_fetch_us to fetch the refs in question, and possibly a call
2821 # to lrfetchref_used.
2823 our (%lrfetchrefs_f, %lrfetchrefs_d);
2824 # $lrfetchrefs_X{lrfetchrefs."/heads/whatever"} = $objid
2826 sub lrfetchref_used ($) {
2827 my ($fullrefname) = @_;
2828 my $objid = $lrfetchrefs_f{$fullrefname};
2829 $lrfetchrefs_d{$fullrefname} = $objid if defined $objid;
2832 sub git_lrfetch_sane {
2833 my ($url, $supplementary, @specs) = @_;
2834 # Make a 'refs/'.lrfetchrefs.'/*' be just like on server,
2835 # at least as regards @specs. Also leave the results in
2836 # %lrfetchrefs_f, and arrange for lrfetchref_used to be
2837 # able to clean these up.
2839 # With $supplementary==1, @specs must not contain wildcards
2840 # and we add to our previous fetches (non-atomically).
2842 # This is rather miserable:
2843 # When git fetch --prune is passed a fetchspec ending with a *,
2844 # it does a plausible thing. If there is no * then:
2845 # - it matches subpaths too, even if the supplied refspec
2846 # starts refs, and behaves completely madly if the source
2847 # has refs/refs/something. (See, for example, Debian #NNNN.)
2848 # - if there is no matching remote ref, it bombs out the whole
2850 # We want to fetch a fixed ref, and we don't know in advance
2851 # if it exists, so this is not suitable.
2853 # Our workaround is to use git ls-remote. git ls-remote has its
2854 # own qairks. Notably, it has the absurd multi-tail-matching
2855 # behaviour: git ls-remote R refs/foo can report refs/foo AND
2856 # refs/refs/foo etc.
2858 # Also, we want an idempotent snapshot, but we have to make two
2859 # calls to the remote: one to git ls-remote and to git fetch. The
2860 # solution is use git ls-remote to obtain a target state, and
2861 # git fetch to try to generate it. If we don't manage to generate
2862 # the target state, we try again.
2864 printdebug "git_lrfetch_sane suppl=$supplementary specs @specs\n";
2866 my $specre = join '|', map {
2869 my $wildcard = $x =~ s/\\\*$/.*/;
2870 die if $wildcard && $supplementary;
2873 printdebug "git_lrfetch_sane specre=$specre\n";
2874 my $wanted_rref = sub {
2876 return m/^(?:$specre)$/;
2879 my $fetch_iteration = 0;
2882 printdebug "git_lrfetch_sane iteration $fetch_iteration\n";
2883 if (++$fetch_iteration > 10) {
2884 fail __ "too many iterations trying to get sane fetch!";
2887 my @look = map { "refs/$_" } @specs;
2888 my @lcmd = (@git, qw(ls-remote -q --refs), $url, @look);
2892 open GITLS, "-|", @lcmd or confess "$!";
2894 printdebug "=> ", $_;
2895 m/^(\w+)\s+(\S+)\n/ or die "ls-remote $_ ?";
2896 my ($objid,$rrefname) = ($1,$2);
2897 if (!$wanted_rref->($rrefname)) {
2898 print STDERR f_ <<END, "@look", $rrefname;
2899 warning: git ls-remote %s reported %s; this is silly, ignoring it.
2903 $wantr{$rrefname} = $objid;
2906 close GITLS or failedcmd @lcmd;
2908 # OK, now %want is exactly what we want for refs in @specs
2910 !m/\*$/ && !exists $wantr{"refs/$_"} ? () :
2911 "+refs/$_:".lrfetchrefs."/$_";
2914 printdebug "git_lrfetch_sane fspecs @fspecs\n";
2916 my @fcmd = (@git, qw(fetch -p -n -q), $url, @fspecs);
2917 runcmd_ordryrun_local @fcmd if @fspecs;
2919 if (!$supplementary) {
2920 %lrfetchrefs_f = ();
2924 git_for_each_ref(lrfetchrefs, sub {
2925 my ($objid,$objtype,$lrefname,$reftail) = @_;
2926 $lrfetchrefs_f{$lrefname} = $objid;
2927 $objgot{$objid} = 1;
2930 if ($supplementary) {
2934 foreach my $lrefname (sort keys %lrfetchrefs_f) {
2935 my $rrefname = 'refs'.substr($lrefname, length lrfetchrefs);
2936 if (!exists $wantr{$rrefname}) {
2937 if ($wanted_rref->($rrefname)) {
2939 git-fetch @fspecs created $lrefname which git ls-remote @look didn't list.
2942 print STDERR f_ <<END, "@fspecs", $lrefname
2943 warning: git fetch %s created %s; this is silly, deleting it.
2946 runcmd_ordryrun_local @git, qw(update-ref -d), $lrefname;
2947 delete $lrfetchrefs_f{$lrefname};
2951 foreach my $rrefname (sort keys %wantr) {
2952 my $lrefname = lrfetchrefs.substr($rrefname, 4);
2953 my $got = $lrfetchrefs_f{$lrefname} // '<none>';
2954 my $want = $wantr{$rrefname};
2955 next if $got eq $want;
2956 if (!defined $objgot{$want}) {
2957 fail __ <<END unless act_local();
2958 --dry-run specified but we actually wanted the results of git fetch,
2959 so this is not going to work. Try running dgit fetch first,
2960 or using --damp-run instead of --dry-run.
2962 print STDERR f_ <<END, $lrefname, $want;
2963 warning: git ls-remote suggests we want %s
2964 warning: and it should refer to %s
2965 warning: but git fetch didn't fetch that object to any relevant ref.
2966 warning: This may be due to a race with someone updating the server.
2967 warning: Will try again...
2969 next FETCH_ITERATION;
2972 git-fetch @fspecs made $lrefname=$got but want git ls-remote @look says $want
2974 runcmd_ordryrun_local @git, qw(update-ref -m),
2975 "dgit fetch git fetch fixup", $lrefname, $want;
2976 $lrfetchrefs_f{$lrefname} = $want;
2981 if (defined $csuite) {
2982 printdebug "git_lrfetch_sane: tidying any old suite lrfetchrefs\n";
2983 git_for_each_ref("refs/dgit-fetch/$csuite", sub {
2984 my ($objid,$objtype,$lrefname,$reftail) = @_;
2985 next if $lrfetchrefs_f{$lrefname}; # $csuite eq $distro ?
2986 runcmd_ordryrun_local @git, qw(update-ref -d), $lrefname;
2990 printdebug "git_lrfetch_sane: git fetch --no-insane emulation complete\n",
2991 Dumper(\%lrfetchrefs_f);
2994 sub git_fetch_us () {
2995 # Want to fetch only what we are going to use, unless
2996 # deliberately-not-ff, in which case we must fetch everything.
2998 my @specs = deliberately_not_fast_forward ? qw(tags/*) :
2999 map { "tags/$_" } debiantags('*',access_nomdistro);
3000 push @specs, server_branch($csuite);
3001 push @specs, $rewritemap;
3002 push @specs, qw(heads/*) if deliberately_not_fast_forward;
3004 my $url = access_giturl();
3005 git_lrfetch_sane $url, 0, @specs;
3008 my @tagpats = debiantags('*',access_nomdistro);
3010 git_for_each_ref([map { "refs/tags/$_" } @tagpats], sub {
3011 my ($objid,$objtype,$fullrefname,$reftail) = @_;
3012 printdebug "currently $fullrefname=$objid\n";
3013 $here{$fullrefname} = $objid;
3015 git_for_each_ref([map { lrfetchrefs."/tags/".$_ } @tagpats], sub {
3016 my ($objid,$objtype,$fullrefname,$reftail) = @_;
3017 my $lref = "refs".substr($fullrefname, length(lrfetchrefs));
3018 printdebug "offered $lref=$objid\n";
3019 if (!defined $here{$lref}) {
3020 my @upd = (@git, qw(update-ref), $lref, $objid, '');
3021 runcmd_ordryrun_local @upd;
3022 lrfetchref_used $fullrefname;
3023 } elsif ($here{$lref} eq $objid) {
3024 lrfetchref_used $fullrefname;
3026 print STDERR f_ "Not updating %s from %s to %s.\n",
3027 $lref, $here{$lref}, $objid;
3032 #---------- dsc and archive handling ----------
3034 sub mergeinfo_getclogp ($) {
3035 # Ensures thit $mi->{Clogp} exists and returns it
3037 $mi->{Clogp} = commit_getclogp($mi->{Commit});
3040 sub mergeinfo_version ($) {
3041 return getfield( (mergeinfo_getclogp $_[0]), 'Version' );
3044 sub fetch_from_archive_record_1 ($) {
3046 runcmd git_update_ref_cmd "dgit fetch $csuite", 'DGIT_ARCHIVE', $hash;
3047 cmdoutput @git, qw(log -n2), $hash;
3048 # ... gives git a chance to complain if our commit is malformed
3051 sub fetch_from_archive_record_2 ($) {
3053 my @upd_cmd = (git_update_ref_cmd 'dgit fetch', lrref(), $hash);
3057 dryrun_report @upd_cmd;
3061 sub parse_dsc_field_def_dsc_distro () {
3062 $dsc_distro //= cfg qw(dgit.default.old-dsc-distro
3063 dgit.default.distro);
3066 sub parse_dsc_field ($$) {
3067 my ($dsc, $what) = @_;
3069 foreach my $field (@ourdscfield) {
3070 $f = $dsc->{$field};
3075 progress f_ "%s: NO git hash", $what;
3076 parse_dsc_field_def_dsc_distro();
3077 } elsif (($dsc_hash, $dsc_distro, $dsc_hint_tag, $dsc_hint_url)
3078 = $f =~ m/^(\w+)\s+($distro_re)\s+($versiontag_re)\s+(\S+)(?:\s|$)/) {
3079 progress f_ "%s: specified git info (%s)", $what, $dsc_distro;
3080 $dsc_hint_tag = [ $dsc_hint_tag ];
3081 } elsif ($f =~ m/^\w+\s*$/) {
3083 parse_dsc_field_def_dsc_distro();
3084 $dsc_hint_tag = [ debiantags +(getfield $dsc, 'Version'),
3086 progress f_ "%s: specified git hash", $what;
3088 fail f_ "%s: invalid Dgit info", $what;
3092 sub resolve_dsc_field_commit ($$) {
3093 my ($already_distro, $already_mapref) = @_;
3095 return unless defined $dsc_hash;
3098 defined $already_mapref &&
3099 ($already_distro eq $dsc_distro || !$chase_dsc_distro)
3100 ? $already_mapref : undef;
3104 my ($what, @fetch) = @_;
3106 local $idistro = $dsc_distro;
3107 my $lrf = lrfetchrefs;
3109 if (!$chase_dsc_distro) {
3110 progress f_ "not chasing .dsc distro %s: not fetching %s",
3115 progress f_ ".dsc names distro %s: fetching %s", $dsc_distro, $what;
3117 my $url = access_giturl();
3118 if (!defined $url) {
3119 defined $dsc_hint_url or fail f_ <<END, $dsc_distro;
3120 .dsc Dgit metadata is in context of distro %s
3121 for which we have no configured url and .dsc provides no hint
3124 $dsc_hint_url =~ m#^([-+0-9a-zA-Z]+):# ? $1 :
3125 $dsc_hint_url =~ m#^/# ? 'file' : 'bad-syntax';
3126 parse_cfg_bool "dsc-url-proto-ok", 'false',
3127 cfg("dgit.dsc-url-proto-ok.$proto",
3128 "dgit.default.dsc-url-proto-ok")
3129 or fail f_ <<END, $dsc_distro, $proto;
3130 .dsc Dgit metadata is in context of distro %s
3131 for which we have no configured url;
3132 .dsc provides hinted url with protocol %s which is unsafe.
3133 (can be overridden by config - consult documentation)
3135 $url = $dsc_hint_url;
3138 git_lrfetch_sane $url, 1, @fetch;
3143 my $rewrite_enable = do {
3144 local $idistro = $dsc_distro;
3145 access_cfg('rewrite-map-enable', 'RETURN-UNDEF');
3148 if (parse_cfg_bool 'rewrite-map-enable', 'true', $rewrite_enable) {
3149 if (!defined $mapref) {
3150 my $lrf = $do_fetch->((__ "rewrite map"), $rewritemap) or return;
3151 $mapref = $lrf.'/'.$rewritemap;
3153 my $rewritemapdata = git_cat_file $mapref.':map';
3154 if (defined $rewritemapdata
3155 && $rewritemapdata =~ m/^$dsc_hash(?:[ \t](\w+))/m) {
3157 "server's git history rewrite map contains a relevant entry!";
3160 if (defined $dsc_hash) {
3161 progress __ "using rewritten git hash in place of .dsc value";
3163 progress __ "server data says .dsc hash is to be disregarded";
3168 if (!defined git_cat_file $dsc_hash) {
3169 my @tags = map { "tags/".$_ } @$dsc_hint_tag;
3170 my $lrf = $do_fetch->((__ "additional commits"), @tags) &&
3171 defined git_cat_file $dsc_hash
3172 or fail f_ <<END, $dsc_hash;
3173 .dsc Dgit metadata requires commit %s
3174 but we could not obtain that object anywhere.
3176 foreach my $t (@tags) {
3177 my $fullrefname = $lrf.'/'.$t;
3178 # print STDERR "CHK $t $fullrefname ".Dumper(\%lrfetchrefs_f);
3179 next unless $lrfetchrefs_f{$fullrefname};
3180 next unless is_fast_fwd "$fullrefname~0", $dsc_hash;
3181 lrfetchref_used $fullrefname;
3186 sub fetch_from_archive () {
3188 ensure_setup_existing_tree();
3190 # Ensures that lrref() is what is actually in the archive, one way
3191 # or another, according to us - ie this client's
3192 # appropritaely-updated archive view. Also returns the commit id.
3193 # If there is nothing in the archive, leaves lrref alone and
3194 # returns undef. git_fetch_us must have already been called.
3198 parse_dsc_field($dsc, __ 'last upload to archive');
3199 resolve_dsc_field_commit access_basedistro,
3200 lrfetchrefs."/".$rewritemap
3202 progress __ "no version available from the archive";
3205 # If the archive's .dsc has a Dgit field, there are three
3206 # relevant git commitids we need to choose between and/or merge
3208 # 1. $dsc_hash: the Dgit field from the archive
3209 # 2. $lastpush_hash: the suite branch on the dgit git server
3210 # 3. $lastfetch_hash: our local tracking brach for the suite
3212 # These may all be distinct and need not be in any fast forward
3215 # If the dsc was pushed to this suite, then the server suite
3216 # branch will have been updated; but it might have been pushed to
3217 # a different suite and copied by the archive. Conversely a more
3218 # recent version may have been pushed with dgit but not appeared
3219 # in the archive (yet).
3221 # $lastfetch_hash may be awkward because archive imports
3222 # (particularly, imports of Dgit-less .dscs) are performed only as
3223 # needed on individual clients, so different clients may perform a
3224 # different subset of them - and these imports are only made
3225 # public during push. So $lastfetch_hash may represent a set of
3226 # imports different to a subsequent upload by a different dgit
3229 # Our approach is as follows:
3231 # As between $dsc_hash and $lastpush_hash: if $lastpush_hash is a
3232 # descendant of $dsc_hash, then it was pushed by a dgit user who
3233 # had based their work on $dsc_hash, so we should prefer it.
3234 # Otherwise, $dsc_hash was installed into this suite in the
3235 # archive other than by a dgit push, and (necessarily) after the
3236 # last dgit push into that suite (since a dgit push would have
3237 # been descended from the dgit server git branch); thus, in that
3238 # case, we prefer the archive's version (and produce a
3239 # pseudo-merge to overwrite the dgit server git branch).
3241 # (If there is no Dgit field in the archive's .dsc then
3242 # generate_commit_from_dsc uses the version numbers to decide
3243 # whether the suite branch or the archive is newer. If the suite
3244 # branch is newer it ignores the archive's .dsc; otherwise it
3245 # generates an import of the .dsc, and produces a pseudo-merge to
3246 # overwrite the suite branch with the archive contents.)
3248 # The outcome of that part of the algorithm is the `public view',
3249 # and is same for all dgit clients: it does not depend on any
3250 # unpublished history in the local tracking branch.
3252 # As between the public view and the local tracking branch: The
3253 # local tracking branch is only updated by dgit fetch, and
3254 # whenever dgit fetch runs it includes the public view in the
3255 # local tracking branch. Therefore if the public view is not
3256 # descended from the local tracking branch, the local tracking
3257 # branch must contain history which was imported from the archive
3258 # but never pushed; and, its tip is now out of date. So, we make
3259 # a pseudo-merge to overwrite the old imports and stitch the old
3262 # Finally: we do not necessarily reify the public view (as
3263 # described above). This is so that we do not end up stacking two
3264 # pseudo-merges. So what we actually do is figure out the inputs
3265 # to any public view pseudo-merge and put them in @mergeinputs.
3268 # $mergeinputs[]{Commit}
3269 # $mergeinputs[]{Info}
3270 # $mergeinputs[0] is the one whose tree we use
3271 # @mergeinputs is in the order we use in the actual commit)
3274 # $mergeinputs[]{Message} is a commit message to use
3275 # $mergeinputs[]{ReverseParents} if def specifies that parent
3276 # list should be in opposite order
3277 # Such an entry has no Commit or Info. It applies only when found
3278 # in the last entry. (This ugliness is to support making
3279 # identical imports to previous dgit versions.)
3281 my $lastpush_hash = git_get_ref(lrfetchref());
3282 printdebug "previous reference hash=$lastpush_hash\n";
3283 $lastpush_mergeinput = $lastpush_hash && {
3284 Commit => $lastpush_hash,
3285 Info => (__ "dgit suite branch on dgit git server"),
3288 my $lastfetch_hash = git_get_ref(lrref());
3289 printdebug "fetch_from_archive: lastfetch=$lastfetch_hash\n";
3290 my $lastfetch_mergeinput = $lastfetch_hash && {
3291 Commit => $lastfetch_hash,
3292 Info => (__ "dgit client's archive history view"),
3295 my $dsc_mergeinput = $dsc_hash && {
3296 Commit => $dsc_hash,
3297 Info => (__ "Dgit field in .dsc from archive"),
3301 my $del_lrfetchrefs = sub {
3304 printdebug "del_lrfetchrefs...\n";
3305 foreach my $fullrefname (sort keys %lrfetchrefs_d) {
3306 my $objid = $lrfetchrefs_d{$fullrefname};
3307 printdebug "del_lrfetchrefs: $objid $fullrefname\n";
3309 $gur ||= new IO::Handle;
3310 open $gur, "|-", qw(git update-ref --stdin) or confess "$!";
3312 printf $gur "delete %s %s\n", $fullrefname, $objid;
3315 close $gur or failedcmd "git update-ref delete lrfetchrefs";
3319 if (defined $dsc_hash) {
3320 ensure_we_have_orig();
3321 if (!$lastpush_hash || $dsc_hash eq $lastpush_hash) {
3322 @mergeinputs = $dsc_mergeinput
3323 } elsif (is_fast_fwd($dsc_hash,$lastpush_hash)) {
3324 print STDERR f_ <<END, $dsc_hash, $lastpush_hash,
3326 Git commit in archive is behind the last version allegedly pushed/uploaded.
3327 Commit referred to by archive: %s
3328 Last version pushed with dgit: %s
3331 __ $later_warning_msg or confess "$!";
3332 @mergeinputs = ($lastpush_mergeinput);
3334 # Archive has .dsc which is not a descendant of the last dgit
3335 # push. This can happen if the archive moves .dscs about.
3336 # Just follow its lead.
3337 if (is_fast_fwd($lastpush_hash,$dsc_hash)) {
3338 progress __ "archive .dsc names newer git commit";
3339 @mergeinputs = ($dsc_mergeinput);
3341 progress __ "archive .dsc names other git commit, fixing up";
3342 @mergeinputs = ($dsc_mergeinput, $lastpush_mergeinput);
3346 @mergeinputs = generate_commits_from_dsc();
3347 # We have just done an import. Now, our import algorithm might
3348 # have been improved. But even so we do not want to generate
3349 # a new different import of the same package. So if the
3350 # version numbers are the same, just use our existing version.
3351 # If the version numbers are different, the archive has changed
3352 # (perhaps, rewound).
3353 if ($lastfetch_mergeinput &&
3354 !version_compare( (mergeinfo_version $lastfetch_mergeinput),
3355 (mergeinfo_version $mergeinputs[0]) )) {
3356 @mergeinputs = ($lastfetch_mergeinput);
3358 } elsif ($lastpush_hash) {
3359 # only in git, not in the archive yet
3360 @mergeinputs = ($lastpush_mergeinput);
3361 print STDERR f_ <<END,
3363 Package not found in the archive, but has allegedly been pushed using dgit.
3366 __ $later_warning_msg or confess "$!";
3368 printdebug "nothing found!\n";
3369 if (defined $skew_warning_vsn) {
3370 print STDERR f_ <<END, $skew_warning_vsn or confess "$!";
3372 Warning: relevant archive skew detected.
3373 Archive allegedly contains %s
3374 But we were not able to obtain any version from the archive or git.
3378 unshift @end, $del_lrfetchrefs;
3382 if ($lastfetch_hash &&
3384 my $h = $_->{Commit};
3385 $h and is_fast_fwd($lastfetch_hash, $h);
3386 # If true, one of the existing parents of this commit
3387 # is a descendant of the $lastfetch_hash, so we'll
3388 # be ff from that automatically.
3392 push @mergeinputs, $lastfetch_mergeinput;
3395 printdebug "fetch mergeinfos:\n";
3396 foreach my $mi (@mergeinputs) {
3398 printdebug " commit $mi->{Commit} $mi->{Info}\n";
3400 printdebug sprintf " ReverseParents=%d Message=%s",
3401 $mi->{ReverseParents}, $mi->{Message};
3405 my $compat_info= pop @mergeinputs
3406 if $mergeinputs[$#mergeinputs]{Message};
3408 @mergeinputs = grep { defined $_->{Commit} } @mergeinputs;
3411 if (@mergeinputs > 1) {
3413 my $tree_commit = $mergeinputs[0]{Commit};
3415 my $tree = get_tree_of_commit $tree_commit;;
3417 # We use the changelog author of the package in question the
3418 # author of this pseudo-merge. This is (roughly) correct if
3419 # this commit is simply representing aa non-dgit upload.
3420 # (Roughly because it does not record sponsorship - but we
3421 # don't have sponsorship info because that's in the .changes,
3422 # which isn't in the archivw.)
3424 # But, it might be that we are representing archive history
3425 # updates (including in-archive copies). These are not really
3426 # the responsibility of the person who created the .dsc, but
3427 # there is no-one whose name we should better use. (The
3428 # author of the .dsc-named commit is clearly worse.)
3430 my $useclogp = mergeinfo_getclogp $mergeinputs[0];
3431 my $author = clogp_authline $useclogp;
3432 my $cversion = getfield $useclogp, 'Version';
3434 my $mcf = dgit_privdir()."/mergecommit";
3435 open MC, ">", $mcf or die "$mcf $!";
3436 print MC <<END or confess "$!";
3440 my @parents = grep { $_->{Commit} } @mergeinputs;
3441 @parents = reverse @parents if $compat_info->{ReverseParents};
3442 print MC <<END or confess "$!" foreach @parents;
3446 print MC <<END or confess "$!";
3452 if (defined $compat_info->{Message}) {
3453 print MC $compat_info->{Message} or confess "$!";
3455 print MC f_ <<END, $package, $cversion, $csuite or confess "$!";
3456 Record %s (%s) in archive suite %s
3460 my $message_add_info = sub {
3462 my $mversion = mergeinfo_version $mi;
3463 printf MC " %-20s %s\n", $mversion, $mi->{Info}
3467 $message_add_info->($mergeinputs[0]);
3468 print MC __ <<END or confess "$!";
3469 should be treated as descended from
3471 $message_add_info->($_) foreach @mergeinputs[1..$#mergeinputs];
3474 close MC or confess "$!";
3475 $hash = hash_commit $mcf;
3477 $hash = $mergeinputs[0]{Commit};
3479 printdebug "fetch hash=$hash\n";
3482 my ($lasth, $what) = @_;
3483 return unless $lasth;
3484 confess "$lasth $hash $what ?" unless is_fast_fwd($lasth, $hash);
3487 $chkff->($lastpush_hash, __ 'dgit repo server tip (last push)')
3489 $chkff->($lastfetch_hash, __ 'local tracking tip (last fetch)');
3491 fetch_from_archive_record_1($hash);
3493 if (defined $skew_warning_vsn) {
3494 printdebug "SKEW CHECK WANT $skew_warning_vsn\n";
3495 my $gotclogp = commit_getclogp($hash);
3496 my $got_vsn = getfield $gotclogp, 'Version';
3497 printdebug "SKEW CHECK GOT $got_vsn\n";
3498 if (version_compare($got_vsn, $skew_warning_vsn) < 0) {
3499 print STDERR f_ <<END, $skew_warning_vsn, $got_vsn or confess "$!";
3501 Warning: archive skew detected. Using the available version:
3502 Archive allegedly contains %s
3503 We were able to obtain only %s
3509 if ($lastfetch_hash ne $hash) {
3510 fetch_from_archive_record_2($hash);
3513 lrfetchref_used lrfetchref();
3515 check_gitattrs($hash, __ "fetched source tree");
3517 unshift @end, $del_lrfetchrefs;
3521 sub set_local_git_config ($$) {
3523 runcmd @git, qw(config), $k, $v;
3526 sub setup_mergechangelogs (;$) {
3528 return unless $always || access_cfg_bool(1, 'setup-mergechangelogs');
3530 my $driver = 'dpkg-mergechangelogs';
3531 my $cb = "merge.$driver";
3532 confess unless defined $maindir;
3533 my $attrs = "$maindir_gitcommon/info/attributes";
3534 ensuredir "$maindir_gitcommon/info";
3536 open NATTRS, ">", "$attrs.new" or die "$attrs.new $!";
3537 if (!open ATTRS, "<", $attrs) {
3538 $!==ENOENT or die "$attrs: $!";
3542 next if m{^debian/changelog\s};
3543 print NATTRS $_, "\n" or confess "$!";
3545 ATTRS->error and confess "$!";
3548 print NATTRS "debian/changelog merge=$driver\n" or confess "$!";
3551 set_local_git_config "$cb.name", __ 'debian/changelog merge driver';
3552 set_local_git_config "$cb.driver", 'dpkg-mergechangelogs -m %O %A %B %A';
3554 rename "$attrs.new", "$attrs" or die "$attrs: $!";
3557 sub setup_useremail (;$) {
3559 return unless $always || access_cfg_bool(1, 'setup-useremail');
3562 my ($k, $envvar) = @_;
3563 my $v = access_cfg("user-$k", 'RETURN-UNDEF') // $ENV{$envvar};
3564 return unless defined $v;
3565 set_local_git_config "user.$k", $v;
3568 $setup->('email', 'DEBEMAIL');
3569 $setup->('name', 'DEBFULLNAME');
3572 sub ensure_setup_existing_tree () {
3573 my $k = "remote.$remotename.skipdefaultupdate";
3574 my $c = git_get_config $k;
3575 return if defined $c;
3576 set_local_git_config $k, 'true';
3579 sub open_main_gitattrs () {
3580 confess 'internal error no maindir' unless defined $maindir;
3581 my $gai = new IO::File "$maindir_gitcommon/info/attributes"
3583 or die "open $maindir_gitcommon/info/attributes: $!";
3587 our $gitattrs_ourmacro_re = qr{^\[attr\]dgit-defuse-attrs\s};
3589 sub is_gitattrs_setup () {
3592 # 1: gitattributes set up and should be left alone
3594 # 0: there is a dgit-defuse-attrs but it needs fixing
3595 # undef: there is none
3596 my $gai = open_main_gitattrs();
3597 return 0 unless $gai;
3599 next unless m{$gitattrs_ourmacro_re};
3600 return 1 if m{\s-working-tree-encoding\s};
3601 printdebug "is_gitattrs_setup: found old macro\n";
3604 $gai->error and confess "$!";
3605 printdebug "is_gitattrs_setup: found nothing\n";
3609 sub setup_gitattrs (;$) {
3611 return unless $always || access_cfg_bool(1, 'setup-gitattributes');
3613 my $already = is_gitattrs_setup();
3616 [attr]dgit-defuse-attrs already found, and proper, in .git/info/attributes
3617 not doing further gitattributes setup
3621 my $new = "[attr]dgit-defuse-attrs $negate_harmful_gitattrs";
3622 my $af = "$maindir_gitcommon/info/attributes";
3623 ensuredir "$maindir_gitcommon/info";
3625 open GAO, "> $af.new" or confess "$!";
3626 print GAO <<END, __ <<ENDT or confess "$!" unless defined $already;
3630 # ^ see GITATTRIBUTES in dgit(7) and dgit setup-new-tree in dgit(1)
3632 my $gai = open_main_gitattrs();
3635 if (m{$gitattrs_ourmacro_re}) {
3636 die unless defined $already;
3640 print GAO $_, "\n" or confess "$!";
3642 $gai->error and confess "$!";
3644 close GAO or confess "$!";
3645 rename "$af.new", "$af" or fail f_ "install %s: %s", $af, $!;
3648 sub setup_new_tree () {
3649 setup_mergechangelogs();
3654 sub check_gitattrs ($$) {
3655 my ($treeish, $what) = @_;
3657 return if is_gitattrs_setup;
3660 my @cmd = (@git, qw(ls-tree -lrz --), "${treeish}:");
3662 my $gafl = new IO::File;
3663 open $gafl, "-|", @cmd or confess "$!";
3666 s/^\d+\s+\w+\s+\w+\s+(\d+)\t// or die;
3668 next unless m{(?:^|/)\.gitattributes$};
3670 # oh dear, found one
3671 print STDERR f_ <<END, $what;
3672 dgit: warning: %s contains .gitattributes
3673 dgit: .gitattributes not (fully) defused. Recommended: dgit setup-new-tree.
3678 # tree contains no .gitattributes files
3679 $?=0; $!=0; close $gafl or failedcmd @cmd;
3683 sub multisuite_suite_child ($$$) {
3684 my ($tsuite, $mergeinputs, $fn) = @_;
3685 # in child, sets things up, calls $fn->(), and returns undef
3686 # in parent, returns canonical suite name for $tsuite
3687 my $canonsuitefh = IO::File::new_tmpfile;
3688 my $pid = fork // confess "$!";
3692 $us .= " [$isuite]";
3693 $debugprefix .= " ";
3694 progress f_ "fetching %s...", $tsuite;
3695 canonicalise_suite();
3696 print $canonsuitefh $csuite, "\n" or confess "$!";
3697 close $canonsuitefh or confess "$!";
3701 waitpid $pid,0 == $pid or confess "$!";
3702 fail f_ "failed to obtain %s: %s", $tsuite, waitstatusmsg()
3704 seek $canonsuitefh,0,0 or confess "$!";
3705 local $csuite = <$canonsuitefh>;
3706 confess "$!" unless defined $csuite && chomp $csuite;
3708 printdebug "multisuite $tsuite missing\n";
3711 printdebug "multisuite $tsuite ok (canon=$csuite)\n";
3712 push @$mergeinputs, {
3719 sub fork_for_multisuite ($) {
3720 my ($before_fetch_merge) = @_;
3721 # if nothing unusual, just returns ''
3724 # returns 0 to caller in child, to do first of the specified suites
3725 # in child, $csuite is not yet set
3727 # returns 1 to caller in parent, to finish up anything needed after
3728 # in parent, $csuite is set to canonicalised portmanteau
3730 my $org_isuite = $isuite;
3731 my @suites = split /\,/, $isuite;
3732 return '' unless @suites > 1;
3733 printdebug "fork_for_multisuite: @suites\n";
3737 my $cbasesuite = multisuite_suite_child($suites[0], \@mergeinputs,
3739 return 0 unless defined $cbasesuite;
3741 fail f_ "package %s missing in (base suite) %s", $package, $cbasesuite
3742 unless @mergeinputs;
3744 my @csuites = ($cbasesuite);
3746 $before_fetch_merge->();
3748 foreach my $tsuite (@suites[1..$#suites]) {
3749 $tsuite =~ s/^-/$cbasesuite-/;
3750 my $csubsuite = multisuite_suite_child($tsuite, \@mergeinputs,
3757 $csubsuite =~ s/^\Q$cbasesuite\E-/-/;
3758 push @csuites, $csubsuite;
3761 foreach my $mi (@mergeinputs) {
3762 my $ref = git_get_ref $mi->{Ref};
3763 die "$mi->{Ref} ?" unless length $ref;
3764 $mi->{Commit} = $ref;
3767 $csuite = join ",", @csuites;
3769 my $previous = git_get_ref lrref;
3771 unshift @mergeinputs, {
3772 Commit => $previous,
3773 Info => (__ "local combined tracking branch"),
3775 "archive seems to have rewound: local tracking branch is ahead!"),
3779 foreach my $ix (0..$#mergeinputs) {
3780 $mergeinputs[$ix]{Index} = $ix;
3783 @mergeinputs = sort {
3784 -version_compare(mergeinfo_version $a,
3785 mergeinfo_version $b) # highest version first
3787 $a->{Index} <=> $b->{Index}; # earliest in spec first
3793 foreach my $mi (@mergeinputs) {
3794 printdebug "multisuite merge check $mi->{Info}\n";
3795 foreach my $previous (@needed) {
3796 next unless is_fast_fwd $mi->{Commit}, $previous->{Commit};
3797 printdebug "multisuite merge un-needed $previous->{Info}\n";
3801 printdebug "multisuite merge this-needed\n";
3802 $mi->{Character} = '+';
3805 $needed[0]{Character} = '*';
3807 my $output = $needed[0]{Commit};
3810 printdebug "multisuite merge nontrivial\n";
3811 my $tree = cmdoutput qw(git rev-parse), $needed[0]{Commit}.':';
3813 my $commit = "tree $tree\n";
3814 my $msg = f_ "Combine archive branches %s [dgit]\n\n".
3815 "Input branches:\n",
3818 foreach my $mi (sort { $a->{Index} <=> $b->{Index} } @mergeinputs) {
3819 printdebug "multisuite merge include $mi->{Info}\n";
3820 $mi->{Character} //= ' ';
3821 $commit .= "parent $mi->{Commit}\n";
3822 $msg .= sprintf " %s %-25s %s\n",
3824 (mergeinfo_version $mi),
3827 my $authline = clogp_authline mergeinfo_getclogp $needed[0];
3828 $msg .= __ "\nKey\n".
3829 " * marks the highest version branch, which choose to use\n".
3830 " + marks each branch which was not already an ancestor\n\n";
3832 "[dgit multi-suite $csuite]\n";
3834 "author $authline\n".
3835 "committer $authline\n\n";
3836 $output = hash_commit_text $commit.$msg;
3837 printdebug "multisuite merge generated $output\n";
3840 fetch_from_archive_record_1($output);
3841 fetch_from_archive_record_2($output);
3843 progress f_ "calculated combined tracking suite %s", $csuite;
3848 sub clone_set_head () {
3849 open H, "> .git/HEAD" or confess "$!";
3850 print H "ref: ".lref()."\n" or confess "$!";
3851 close H or confess "$!";
3853 sub clone_finish ($) {
3855 runcmd @git, qw(reset --hard), lrref();
3856 runcmd qw(bash -ec), <<'END';
3858 git ls-tree -r --name-only -z HEAD | \
3859 xargs -0r touch -h -r . --
3861 printdone f_ "ready for work in %s", $dstdir;
3865 # in multisuite, returns twice!
3866 # once in parent after first suite fetched,
3867 # and then again in child after everything is finished
3869 badusage __ "dry run makes no sense with clone" unless act_local();
3871 my $multi_fetched = fork_for_multisuite(sub {
3872 printdebug "multi clone before fetch merge\n";
3876 if ($multi_fetched) {
3877 printdebug "multi clone after fetch merge\n";
3879 clone_finish($dstdir);
3882 printdebug "clone main body\n";
3884 mkdir $dstdir or fail f_ "create \`%s': %s", $dstdir, $!;
3888 canonicalise_suite();
3889 my $hasgit = check_for_git();
3891 runcmd @git, qw(init -q);
3896 progress __ "fetching existing git history";
3899 progress __ "starting new git history";
3901 fetch_from_archive() or no_such_package;
3902 my $vcsgiturl = $dsc->{'Vcs-Git'};
3903 if (length $vcsgiturl) {
3904 $vcsgiturl =~ s/\s+-b\s+\S+//g;
3905 runcmd @git, qw(remote add vcs-git), $vcsgiturl;
3907 clone_finish($dstdir);
3911 canonicalise_suite();
3912 if (check_for_git()) {
3915 fetch_from_archive() or no_such_package();
3917 my $vcsgiturl = $dsc && $dsc->{'Vcs-Git'};
3918 if (length $vcsgiturl and
3919 (grep { $csuite eq $_ }
3921 cfg 'dgit.vcs-git.suites')) {
3922 my $current = cfg 'remote.vcs-git.url', 'RETURN-UNDEF';
3923 if (defined $current && $current ne $vcsgiturl) {
3924 print STDERR f_ <<END, $csuite;
3925 FYI: Vcs-Git in %s has different url to your vcs-git remote.
3926 Your vcs-git remote url may be out of date. Use dgit update-vcs-git ?
3930 printdone f_ "fetched into %s", lrref();
3934 my $multi_fetched = fork_for_multisuite(sub { });
3935 fetch_one() unless $multi_fetched; # parent
3936 finish 0 if $multi_fetched eq '0'; # child
3941 runcmd_ordryrun_local @git, qw(merge -m),
3942 (f_ "Merge from %s [dgit]", $csuite),
3944 printdone f_ "fetched to %s and merged into HEAD", lrref();
3947 sub check_not_dirty () {
3948 my @forbid = qw(local-options local-patch-header);
3949 @forbid = map { "debian/source/$_" } @forbid;
3950 foreach my $f (@forbid) {
3951 if (stat_exists $f) {
3952 fail f_ "git tree contains %s", $f;
3956 my @cmd = (@git, qw(status -uall --ignored --porcelain));
3957 push @cmd, qw(debian/source/format debian/source/options);
3960 my $bad = cmdoutput @cmd;
3963 "you have uncommitted changes to critical files, cannot continue:\n").
3967 return if $includedirty;
3969 git_check_unmodified();
3972 sub commit_admin ($) {
3975 runcmd_ordryrun_local @git, qw(commit -m), $m;
3978 sub quiltify_nofix_bail ($$) {
3979 my ($headinfo, $xinfo) = @_;
3980 if ($quilt_mode eq 'nofix') {
3982 "quilt fixup required but quilt mode is \`nofix'\n".
3983 "HEAD commit%s differs from tree implied by debian/patches%s",
3988 sub commit_quilty_patch () {
3989 my $output = cmdoutput @git, qw(status --ignored --porcelain);
3991 foreach my $l (split /\n/, $output) {
3992 next unless $l =~ m/\S/;
3993 if ($l =~ m{^(?:[?!][?!]| [MADRC]) (.pc|debian/patches)}) {
3997 delete $adds{'.pc'}; # if there wasn't one before, don't add it
3999 progress __ "nothing quilty to commit, ok.";
4002 quiltify_nofix_bail "", __ " (wanted to commit patch update)";
4003 my @adds = map { s/[][*?\\]/\\$&/g; $_; } sort keys %adds;
4004 runcmd_ordryrun_local @git, qw(add -f), @adds;
4005 commit_admin +(__ <<ENDT).<<END
4006 Commit Debian 3.0 (quilt) metadata
4009 [dgit ($our_version) quilt-fixup]
4013 sub get_source_format () {
4015 if (open F, "debian/source/options") {
4019 s/\s+$//; # ignore missing final newline
4021 my ($k, $v) = ($`, $'); #');
4022 $v =~ s/^"(.*)"$/$1/;
4028 F->error and confess "$!";
4031 confess "$!" unless $!==&ENOENT;
4034 if (!open F, "debian/source/format") {
4035 confess "$!" unless $!==&ENOENT;
4039 F->error and confess "$!";
4041 return ($_, \%options);
4044 sub madformat_wantfixup ($) {
4046 return 0 unless $format eq '3.0 (quilt)';
4047 our $quilt_mode_warned;
4048 if ($quilt_mode eq 'nocheck') {
4049 progress f_ "Not doing any fixup of \`%s'".
4050 " due to ----no-quilt-fixup or --quilt=nocheck", $format
4051 unless $quilt_mode_warned++;
4054 progress f_ "Format \`%s', need to check/update patch stack", $format
4055 unless $quilt_mode_warned++;
4059 sub maybe_split_brain_save ($$$) {
4060 my ($headref, $dgitview, $msg) = @_;
4061 # => message fragment "$saved" describing disposition of $dgitview
4062 # (used inside parens, in the English texts)
4063 my $save = $internal_object_save{'dgit-view'};
4064 return f_ "commit id %s", $dgitview unless defined $save;
4065 my @cmd = (shell_cmd 'cd "$1"; shift', $maindir,
4067 "dgit --dgit-view-save $msg HEAD=$headref",
4070 return f_ "and left in %s", $save;
4073 # An "infopair" is a tuple [ $thing, $what ]
4074 # (often $thing is a commit hash; $what is a description)
4076 sub infopair_cond_equal ($$) {
4078 $x->[0] eq $y->[0] or fail <<END;
4079 $x->[1] ($x->[0]) not equal to $y->[1] ($y->[0])
4083 sub infopair_lrf_tag_lookup ($$) {
4084 my ($tagnames, $what) = @_;
4085 # $tagname may be an array ref
4086 my @tagnames = ref $tagnames ? @$tagnames : ($tagnames);
4087 printdebug "infopair_lrfetchref_tag_lookup $what @tagnames\n";
4088 foreach my $tagname (@tagnames) {
4089 my $lrefname = lrfetchrefs."/tags/$tagname";
4090 my $tagobj = $lrfetchrefs_f{$lrefname};
4091 next unless defined $tagobj;
4092 printdebug "infopair_lrfetchref_tag_lookup $tagobj $tagname $what\n";
4093 return [ git_rev_parse($tagobj), $what ];
4095 fail @tagnames==1 ? (f_ <<END, $what, "@tagnames")
4096 Wanted tag %s (%s) on dgit server, but not found
4098 : (f_ <<END, $what, "@tagnames");
4099 Wanted tag %s (one of: %s) on dgit server, but not found
4103 sub infopair_cond_ff ($$) {
4104 my ($anc,$desc) = @_;
4105 is_fast_fwd($anc->[0], $desc->[0]) or
4106 fail f_ <<END, $anc->[1], $anc->[0], $desc->[1], $desc->[0];
4107 %s (%s) .. %s (%s) is not fast forward
4111 sub pseudomerge_version_check ($$) {
4112 my ($clogp, $archive_hash) = @_;
4114 my $arch_clogp = commit_getclogp $archive_hash;
4115 my $i_arch_v = [ (getfield $arch_clogp, 'Version'),
4116 __ 'version currently in archive' ];
4117 if (defined $overwrite_version) {
4118 if (length $overwrite_version) {
4119 infopair_cond_equal([ $overwrite_version,
4120 '--overwrite= version' ],
4123 my $v = $i_arch_v->[0];
4125 "Checking package changelog for archive version %s ...", $v;
4128 my @xa = ("-f$v", "-t$v");
4129 my $vclogp = parsechangelog @xa;
4132 [ (getfield $vclogp, $fn),
4133 (f_ "%s field from dpkg-parsechangelog %s",
4136 my $cv = $gf->('Version');
4137 infopair_cond_equal($i_arch_v, $cv);
4138 $cd = $gf->('Distribution');
4142 $@ =~ s/^dgit: //gm;
4144 f_ "Perhaps debian/changelog does not mention %s ?", $v;
4146 fail f_ <<END, $cd->[1], $cd->[0], $v
4148 Your tree seems to based on earlier (not uploaded) %s.
4150 if $cd->[0] =~ m/UNRELEASED/;
4154 printdebug "pseudomerge_version_check i_arch_v @$i_arch_v\n";
4158 sub pseudomerge_hash_commit ($$$$ $$) {
4159 my ($clogp, $dgitview, $archive_hash, $i_arch_v,
4160 $msg_cmd, $msg_msg) = @_;
4161 progress f_ "Declaring that HEAD includes all changes in %s...",
4164 my $tree = cmdoutput qw(git rev-parse), "${dgitview}:";
4165 my $authline = clogp_authline $clogp;
4169 !defined $overwrite_version ? ""
4170 : !length $overwrite_version ? " --overwrite"
4171 : " --overwrite=".$overwrite_version;
4173 # Contributing parent is the first parent - that makes
4174 # git rev-list --first-parent DTRT.
4175 my $pmf = dgit_privdir()."/pseudomerge";
4176 open MC, ">", $pmf or die "$pmf $!";
4177 print MC <<END or confess "$!";
4180 parent $archive_hash
4188 close MC or confess "$!";
4190 return hash_commit($pmf);
4193 sub splitbrain_pseudomerge ($$$$) {
4194 my ($clogp, $maintview, $dgitview, $archive_hash) = @_;
4195 # => $merged_dgitview
4196 printdebug "splitbrain_pseudomerge...\n";
4198 # We: debian/PREVIOUS HEAD($maintview)
4199 # expect: o ----------------- o
4202 # a/d/PREVIOUS $dgitview
4205 # we do: `------------------ o
4209 return $dgitview unless defined $archive_hash;
4210 return $dgitview if deliberately_not_fast_forward();
4212 printdebug "splitbrain_pseudomerge...\n";
4214 my $i_arch_v = pseudomerge_version_check($clogp, $archive_hash);
4216 if (!defined $overwrite_version) {
4217 progress __ "Checking that HEAD includes all changes in archive...";
4220 return $dgitview if is_fast_fwd $archive_hash, $dgitview;
4222 if (defined $overwrite_version) {
4224 my $t_dep14 = debiantag_maintview $i_arch_v->[0], access_nomdistro;
4225 my $i_dep14 = infopair_lrf_tag_lookup($t_dep14,
4226 __ "maintainer view tag");
4227 my $t_dgit = debiantag_new $i_arch_v->[0], access_nomdistro;
4228 my $i_dgit = infopair_lrf_tag_lookup($t_dgit, __ "dgit view tag");
4229 my $i_archive = [ $archive_hash, __ "current archive contents" ];
4231 printdebug "splitbrain_pseudomerge i_archive @$i_archive\n";
4233 infopair_cond_equal($i_dgit, $i_archive);
4234 infopair_cond_ff($i_dep14, $i_dgit);
4235 infopair_cond_ff($i_dep14, [ $maintview, 'HEAD' ]);
4238 $@ =~ s/^\n//; chomp $@;
4239 print STDERR <<END.(__ <<ENDT);
4242 | Not fast forward; maybe --overwrite is needed ? Please see dgit(1).
4247 my $arch_v = $i_arch_v->[0];
4248 my $r = pseudomerge_hash_commit
4249 $clogp, $dgitview, $archive_hash, $i_arch_v,
4250 "dgit --quilt=$quilt_mode",
4251 (defined $overwrite_version
4252 ? f_ "Declare fast forward from %s\n", $arch_v
4253 : f_ "Make fast forward from %s\n", $arch_v);
4255 maybe_split_brain_save $maintview, $r, "pseudomerge";
4257 progress f_ "Made pseudo-merge of %s into dgit view.", $arch_v;
4261 sub plain_overwrite_pseudomerge ($$$) {
4262 my ($clogp, $head, $archive_hash) = @_;
4264 printdebug "plain_overwrite_pseudomerge...";
4266 my $i_arch_v = pseudomerge_version_check($clogp, $archive_hash);
4268 return $head if is_fast_fwd $archive_hash, $head;
4270 my $m = f_ "Declare fast forward from %s", $i_arch_v->[0];
4272 my $r = pseudomerge_hash_commit
4273 $clogp, $head, $archive_hash, $i_arch_v,
4276 runcmd git_update_ref_cmd $m, 'HEAD', $r, $head;
4278 progress f_ "Make pseudo-merge of %s into your HEAD.", $i_arch_v->[0];
4282 sub push_parse_changelog ($) {
4285 my $clogp = Dpkg::Control::Hash->new();
4286 $clogp->load($clogpfn) or die;
4288 my $clogpackage = getfield $clogp, 'Source';
4289 $package //= $clogpackage;
4290 fail f_ "-p specified %s but changelog specified %s",
4291 $package, $clogpackage
4292 unless $package eq $clogpackage;
4293 my $cversion = getfield $clogp, 'Version';
4295 if (!$we_are_initiator) {
4296 # rpush initiator can't do this because it doesn't have $isuite yet
4297 my $tag = debiantag_new($cversion, access_nomdistro);
4298 runcmd @git, qw(check-ref-format), $tag;
4301 my $dscfn = dscfn($cversion);
4303 return ($clogp, $cversion, $dscfn);
4306 sub push_parse_dsc ($$$) {
4307 my ($dscfn,$dscfnwhat, $cversion) = @_;
4308 $dsc = parsecontrol($dscfn,$dscfnwhat);
4309 my $dversion = getfield $dsc, 'Version';
4310 my $dscpackage = getfield $dsc, 'Source';
4311 ($dscpackage eq $package && $dversion eq $cversion) or
4312 fail f_ "%s is for %s %s but debian/changelog is for %s %s",
4313 $dscfn, $dscpackage, $dversion,
4314 $package, $cversion;
4317 sub push_tagwants ($$$$) {
4318 my ($cversion, $dgithead, $maintviewhead, $tfbase) = @_;
4321 TagFn => \&debiantag_new,
4326 if (defined $maintviewhead) {
4328 TagFn => \&debiantag_maintview,
4329 Objid => $maintviewhead,
4330 TfSuffix => '-maintview',
4333 } elsif ($dodep14tag ne 'no') {
4335 TagFn => \&debiantag_maintview,
4337 TfSuffix => '-dgit',
4341 foreach my $tw (@tagwants) {
4342 $tw->{Tag} = $tw->{TagFn}($cversion, access_nomdistro);
4343 $tw->{Tfn} = sub { $tfbase.$tw->{TfSuffix}.$_[0]; };
4345 printdebug 'push_tagwants: ', Dumper(\@_, \@tagwants);
4349 sub push_mktags ($$ $$ $) {
4351 $changesfile,$changesfilewhat,
4354 die unless $tagwants->[0]{View} eq 'dgit';
4356 my $declaredistro = access_nomdistro();
4357 my $reader_giturl = do { local $access_forpush=0; access_giturl(); };
4358 $dsc->{$ourdscfield[0]} = join " ",
4359 $tagwants->[0]{Objid}, $declaredistro, $tagwants->[0]{Tag},
4361 $dsc->save("$dscfn.tmp") or confess "$!";
4363 my $changes = parsecontrol($changesfile,$changesfilewhat);
4364 foreach my $field (qw(Source Distribution Version)) {
4365 $changes->{$field} eq $clogp->{$field} or
4366 fail f_ "changes field %s \`%s' does not match changelog \`%s'",
4367 $field, $changes->{$field}, $clogp->{$field};
4370 my $cversion = getfield $clogp, 'Version';
4371 my $clogsuite = getfield $clogp, 'Distribution';
4372 my $format = getfield $dsc, 'Format';
4374 # We make the git tag by hand because (a) that makes it easier
4375 # to control the "tagger" (b) we can do remote signing
4376 my $authline = clogp_authline $clogp;
4380 my $tfn = $tw->{Tfn};
4381 my $head = $tw->{Objid};
4382 my $tag = $tw->{Tag};
4384 open TO, '>', $tfn->('.tmp') or confess "$!";
4385 print TO <<END or confess "$!";
4393 my @dtxinfo = @deliberatelies;
4394 unshift @dtxinfo, "--quilt=$quilt_mode" if madformat($format);
4395 unshift @dtxinfo, do_split_brain() ? "split" : "no-split"
4396 # rpush protocol 5 and earlier don't tell us
4397 unless $we_are_initiator && $protovsn < 6;
4398 my $dtxinfo = join(" ", "",@dtxinfo);
4399 my $tag_metadata = <<END;
4400 [dgit distro=$declaredistro$dtxinfo]
4402 foreach my $ref (sort keys %previously) {
4403 $tag_metadata .= <<END or confess "$!";
4404 [dgit previously:$ref=$previously{$ref}]
4408 if ($tw->{View} eq 'dgit') {
4409 print TO sprintf <<ENDT, $package, $cversion, $clogsuite, $csuite
4410 %s release %s for %s (%s) [dgit]
4413 } elsif ($tw->{View} eq 'maint') {
4414 print TO sprintf <<END, $package, $cversion, $clogsuite, $csuite;
4415 %s release %s for %s (%s)
4419 (maintainer view tag generated by dgit --quilt=%s)
4424 confess Dumper($tw)."?";
4426 print TO "\n", $tag_metadata;
4428 close TO or confess "$!";
4430 my $tagobjfn = $tfn->('.tmp');
4432 if (!defined $keyid) {
4433 $keyid = access_cfg('keyid','RETURN-UNDEF');
4435 if (!defined $keyid) {
4436 $keyid = getfield $clogp, 'Maintainer';
4438 unlink $tfn->('.tmp.asc') or $!==&ENOENT or confess "$!";
4439 my @sign_cmd = (@gpg, qw(--detach-sign --armor));
4440 push @sign_cmd, qw(-u),$keyid if defined $keyid;
4441 push @sign_cmd, $tfn->('.tmp');
4442 runcmd_ordryrun @sign_cmd;
4444 $tagobjfn = $tfn->('.signed.tmp');
4445 runcmd shell_cmd "exec >$tagobjfn", qw(cat --),
4446 $tfn->('.tmp'), $tfn->('.tmp.asc');
4452 my @r = map { $mktag->($_); } @$tagwants;
4456 sub sign_changes ($) {
4457 my ($changesfile) = @_;
4459 my @debsign_cmd = @debsign;
4460 push @debsign_cmd, "-k$keyid" if defined $keyid;
4461 push @debsign_cmd, "-p$gpg[0]" if $gpg[0] ne 'gpg';
4462 push @debsign_cmd, $changesfile;
4463 runcmd_ordryrun @debsign_cmd;
4468 printdebug "actually entering push\n";
4470 supplementary_message(__ <<'END');
4471 Push failed, while checking state of the archive.
4472 You can retry the push, after fixing the problem, if you like.
4474 if (check_for_git()) {
4477 my $archive_hash = fetch_from_archive();
4478 if (!$archive_hash) {
4480 fail __ "package appears to be new in this suite;".
4481 " if this is intentional, use --new";
4484 supplementary_message(__ <<'END');
4485 Push failed, while preparing your push.
4486 You can retry the push, after fixing the problem, if you like.
4491 access_giturl(); # check that success is vaguely likely
4492 rpush_handle_protovsn_bothends() if $we_are_initiator;
4494 my $clogpfn = dgit_privdir()."/changelog.822.tmp";
4495 runcmd shell_cmd "exec >$clogpfn", qw(dpkg-parsechangelog);
4497 responder_send_file('parsed-changelog', $clogpfn);
4499 my ($clogp, $cversion, $dscfn) =
4500 push_parse_changelog("$clogpfn");
4502 my $dscpath = "$buildproductsdir/$dscfn";
4503 stat_exists $dscpath or
4504 fail f_ "looked for .dsc %s, but %s; maybe you forgot to build",
4507 responder_send_file('dsc', $dscpath);
4509 push_parse_dsc($dscpath, $dscfn, $cversion);
4511 my $format = getfield $dsc, 'Format';
4513 my $symref = git_get_symref();
4514 my $actualhead = git_rev_parse('HEAD');
4516 if (branch_is_gdr_unstitched_ff($symref, $actualhead, $archive_hash)) {
4517 if (quiltmode_splitting()) {
4518 my ($ffq_prev, $gdrlast) = branch_gdr_info($symref, $actualhead);
4519 fail f_ <<END, $ffq_prev, $quilt_mode;
4520 Branch is managed by git-debrebase (%s
4521 exists), but quilt mode (%s) implies a split view.
4522 Pass the right --quilt option or adjust your git config.
4523 Or, maybe, run git-debrebase forget-was-ever-debrebase.
4526 runcmd_ordryrun_local @git_debrebase, 'stitch';
4527 $actualhead = git_rev_parse('HEAD');
4530 my $dgithead = $actualhead;
4531 my $maintviewhead = undef;
4533 my $upstreamversion = upstreamversion $clogp->{Version};
4535 if (madformat_wantfixup($format)) {
4536 # user might have not used dgit build, so maybe do this now:
4537 if (do_split_brain()) {
4538 changedir $playground;
4540 ($dgithead, $cachekey) =
4541 quilt_check_splitbrain_cache($actualhead, $upstreamversion);
4542 $dgithead or fail f_
4543 "--quilt=%s but no cached dgit view:
4544 perhaps HEAD changed since dgit build[-source] ?",
4547 if (!do_split_brain()) {
4548 # In split brain mode, do not attempt to incorporate dirty
4549 # stuff from the user's working tree. That would be mad.
4550 commit_quilty_patch();
4553 if (do_split_brain()) {
4554 $made_split_brain = 1;
4555 $dgithead = splitbrain_pseudomerge($clogp,
4556 $actualhead, $dgithead,
4558 $maintviewhead = $actualhead;
4560 prep_ud(); # so _only_subdir() works, below
4563 if (defined $overwrite_version && !defined $maintviewhead
4565 $dgithead = plain_overwrite_pseudomerge($clogp,
4573 if ($archive_hash) {
4574 if (is_fast_fwd($archive_hash, $dgithead)) {
4576 } elsif (deliberately_not_fast_forward) {
4579 fail __ "dgit push: HEAD is not a descendant".
4580 " of the archive's version.\n".
4581 "To overwrite the archive's contents,".
4582 " pass --overwrite[=VERSION].\n".
4583 "To rewind history, if permitted by the archive,".
4584 " use --deliberately-not-fast-forward.";
4588 confess unless !!$made_split_brain == do_split_brain();
4590 changedir $playground;
4591 progress f_ "checking that %s corresponds to HEAD", $dscfn;
4592 runcmd qw(dpkg-source -x --),
4593 $dscpath =~ m#^/# ? $dscpath : "$maindir/$dscpath";
4594 my ($tree,$dir) = mktree_in_ud_from_only_subdir("source package");
4595 check_for_vendor_patches() if madformat($dsc->{format});
4597 my @diffcmd = (@git, qw(diff --quiet), $tree, $dgithead);
4598 debugcmd "+",@diffcmd;
4600 my $r = system @diffcmd;
4603 my $referent = $made_split_brain ? $dgithead : 'HEAD';
4604 my $diffs = cmdoutput @git, qw(diff --stat), $tree, $dgithead;
4607 my $raw = cmdoutput @git,
4608 qw(diff --no-renames -z -r --raw), $tree, $dgithead;
4610 foreach (split /\0/, $raw) {
4611 if (defined $changed) {
4612 push @mode_changes, "$changed: $_\n" if $changed;
4615 } elsif (m/^:0+ 0+ /) {
4617 } elsif (m/^:(?:10*)?(\d+) (?:10*)?(\d+) /) {
4618 $changed = "Mode change from $1 to $2"
4623 if (@mode_changes) {
4624 fail +(f_ <<ENDT, $dscfn).<<END
4625 HEAD specifies a different tree to %s:
4629 .(join '', @mode_changes)
4630 .(f_ <<ENDT, $tree, $referent);
4631 There is a problem with your source tree (see dgit(7) for some hints).
4632 To see a full diff, run git diff %s %s
4636 fail +(f_ <<ENDT, $dscfn).<<END.(f_ <<ENDT, $tree, $referent);
4637 HEAD specifies a different tree to %s:
4641 Perhaps you forgot to build. Or perhaps there is a problem with your
4642 source tree (see dgit(7) for some hints). To see a full diff, run
4649 if (!$changesfile) {
4650 my $pat = changespat $cversion;
4651 my @cs = glob "$buildproductsdir/$pat";
4652 fail f_ "failed to find unique changes file".
4653 " (looked for %s in %s);".
4654 " perhaps you need to use dgit -C",
4655 $pat, $buildproductsdir
4657 ($changesfile) = @cs;
4659 $changesfile = "$buildproductsdir/$changesfile";
4662 # Check that changes and .dsc agree enough
4663 $changesfile =~ m{[^/]*$};
4664 my $changes = parsecontrol($changesfile,$&);
4665 files_compare_inputs($dsc, $changes)
4666 unless forceing [qw(dsc-changes-mismatch)];
4668 # Check whether this is a source only upload
4669 my $hasdebs = $changes->{Files} =~ m{\.deb$}m;
4670 my $sourceonlypolicy = access_cfg 'source-only-uploads';
4671 if ($sourceonlypolicy eq 'ok') {
4672 } elsif ($sourceonlypolicy eq 'always') {
4673 forceable_fail [qw(uploading-binaries)],
4674 __ "uploading binaries, although distro policy is source only"
4676 } elsif ($sourceonlypolicy eq 'never') {
4677 forceable_fail [qw(uploading-source-only)],
4678 __ "source-only upload, although distro policy requires .debs"
4680 } elsif ($sourceonlypolicy eq 'not-wholly-new') {
4681 forceable_fail [qw(uploading-source-only)],
4682 f_ "source-only upload, even though package is entirely NEW\n".
4683 "(this is contrary to policy in %s)",
4687 && !(archive_query('package_not_wholly_new', $package) // 1);
4689 badcfg f_ "unknown source-only-uploads policy \`%s'",
4693 # Perhaps adjust .dsc to contain right set of origs
4694 changes_update_origs_from_dsc($dsc, $changes, $upstreamversion,
4696 unless forceing [qw(changes-origs-exactly)];
4698 # Checks complete, we're going to try and go ahead:
4700 responder_send_file('changes',$changesfile);
4701 responder_send_command("param head $dgithead");
4702 responder_send_command("param csuite $csuite");
4703 responder_send_command("param isuite $isuite");
4704 responder_send_command("param tagformat new"); # needed in $protovsn==4
4705 responder_send_command("param splitbrain $do_split_brain");
4706 if (defined $maintviewhead) {
4707 responder_send_command("param maint-view $maintviewhead");
4710 # Perhaps send buildinfo(s) for signing
4711 my $changes_files = getfield $changes, 'Files';
4712 my @buildinfos = ($changes_files =~ m/ .* (\S+\.buildinfo)$/mg);
4713 foreach my $bi (@buildinfos) {
4714 responder_send_command("param buildinfo-filename $bi");
4715 responder_send_file('buildinfo', "$buildproductsdir/$bi");
4718 if (deliberately_not_fast_forward) {
4719 git_for_each_ref(lrfetchrefs, sub {
4720 my ($objid,$objtype,$lrfetchrefname,$reftail) = @_;
4721 my $rrefname= substr($lrfetchrefname, length(lrfetchrefs) + 1);
4722 responder_send_command("previously $rrefname=$objid");
4723 $previously{$rrefname} = $objid;
4727 my @tagwants = push_tagwants($cversion, $dgithead, $maintviewhead,
4728 dgit_privdir()."/tag");
4731 supplementary_message(__ <<'END');
4732 Push failed, while signing the tag.
4733 You can retry the push, after fixing the problem, if you like.
4735 # If we manage to sign but fail to record it anywhere, it's fine.
4736 if ($we_are_responder) {
4737 @tagobjfns = map { $_->{Tfn}('.signed-tmp') } @tagwants;
4738 responder_receive_files('signed-tag', @tagobjfns);
4740 @tagobjfns = push_mktags($clogp,$dscpath,
4741 $changesfile,$changesfile,
4744 supplementary_message(__ <<'END');
4745 Push failed, *after* signing the tag.
4746 If you want to try again, you should use a new version number.
4749 pairwise { $a->{TagObjFn} = $b } @tagwants, @tagobjfns;
4751 foreach my $tw (@tagwants) {
4752 my $tag = $tw->{Tag};
4753 my $tagobjfn = $tw->{TagObjFn};
4755 cmdoutput @git, qw(hash-object -w -t tag), $tagobjfn;
4756 runcmd_ordryrun @git, qw(verify-tag), $tag_obj_hash;
4757 runcmd_ordryrun_local
4758 @git, qw(update-ref), "refs/tags/$tag", $tag_obj_hash;
4761 supplementary_message(__ <<'END');
4762 Push failed, while updating the remote git repository - see messages above.
4763 If you want to try again, you should use a new version number.
4765 if (!check_for_git()) {
4766 create_remote_git_repo();
4769 my @pushrefs = $forceflag.$dgithead.":".rrref();
4770 foreach my $tw (@tagwants) {
4771 push @pushrefs, $forceflag."refs/tags/$tw->{Tag}";
4774 runcmd_ordryrun @git,
4775 qw(-c push.followTags=false push), access_giturl(), @pushrefs;
4776 runcmd_ordryrun git_update_ref_cmd 'dgit push', lrref(), $dgithead;
4778 supplementary_message(__ <<'END');
4779 Push failed, while obtaining signatures on the .changes and .dsc.
4780 If it was just that the signature failed, you may try again by using
4781 debsign by hand to sign the changes file (see the command dgit tried,
4782 above), and then dput that changes file to complete the upload.
4783 If you need to change the package, you must use a new version number.
4785 if ($we_are_responder) {
4786 my $dryrunsuffix = act_local() ? "" : ".tmp";
4787 my @rfiles = ($dscpath, $changesfile);
4788 push @rfiles, map { "$buildproductsdir/$_" } @buildinfos;
4789 responder_receive_files('signed-dsc-changes',
4790 map { "$_$dryrunsuffix" } @rfiles);
4793 rename "$dscpath.tmp",$dscpath or die "$dscfn $!";
4795 progress f_ "[new .dsc left in %s.tmp]", $dscpath;
4797 sign_changes $changesfile;
4800 supplementary_message(f_ <<END, $changesfile);
4801 Push failed, while uploading package(s) to the archive server.
4802 You can retry the upload of exactly these same files with dput of:
4804 If that .changes file is broken, you will need to use a new version
4805 number for your next attempt at the upload.
4807 my $host = access_cfg('upload-host','RETURN-UNDEF');
4808 my @hostarg = defined($host) ? ($host,) : ();
4809 runcmd_ordryrun @dput, @hostarg, $changesfile;
4810 printdone f_ "pushed and uploaded %s", $cversion;
4812 supplementary_message('');
4813 responder_send_command("complete");
4817 not_necessarily_a_tree();
4822 badusage __ "-p is not allowed with clone; specify as argument instead"
4823 if defined $package;
4826 } elsif (@ARGV==2 && $ARGV[1] =~ m#^\w#) {
4827 ($package,$isuite) = @ARGV;
4828 } elsif (@ARGV==2 && $ARGV[1] =~ m#^[./]#) {
4829 ($package,$dstdir) = @ARGV;
4830 } elsif (@ARGV==3) {
4831 ($package,$isuite,$dstdir) = @ARGV;
4833 badusage __ "incorrect arguments to dgit clone";
4837 $dstdir ||= "$package";
4838 if (stat_exists $dstdir) {
4839 fail f_ "%s already exists", $dstdir;
4843 if ($rmonerror && !$dryrun_level) {
4844 $cwd_remove= getcwd();
4846 return unless defined $cwd_remove;
4847 if (!chdir "$cwd_remove") {
4848 return if $!==&ENOENT;
4849 confess "chdir $cwd_remove: $!";
4851 printdebug "clone rmonerror removing $dstdir\n";
4853 rmtree($dstdir) or fail f_ "remove %s: %s\n", $dstdir, $!;
4854 } elsif (grep { $! == $_ }
4855 (ENOENT, ENOTDIR, EACCES, EPERM, ELOOP)) {
4857 print STDERR f_ "check whether to remove %s: %s\n",
4864 $cwd_remove = undef;
4867 sub branchsuite () {
4868 my $branch = git_get_symref();
4869 if (defined $branch && $branch =~ m#$lbranch_re#o) {
4876 sub package_from_d_control () {
4877 if (!defined $package) {
4878 my $sourcep = parsecontrol('debian/control','debian/control');
4879 $package = getfield $sourcep, 'Source';
4883 sub fetchpullargs () {
4884 package_from_d_control();
4886 $isuite = branchsuite();
4888 my $clogp = parsechangelog();
4889 my $clogsuite = getfield $clogp, 'Distribution';
4890 $isuite= $clogsuite if $clogsuite ne 'UNRELEASED';
4892 } elsif (@ARGV==1) {
4895 badusage __ "incorrect arguments to dgit fetch or dgit pull";
4909 determine_whether_split_brain get_source_format();
4910 if (do_split_brain()) {
4911 my ($format, $fopts) = get_source_format();
4912 madformat($format) and fail f_ <<END, $quilt_mode
4913 dgit pull not yet supported in split view mode (including with view-splitting quilt modes)
4921 package_from_d_control();
4922 @ARGV==1 or badusage __ "dgit checkout needs a suite argument";
4926 foreach my $canon (qw(0 1)) {
4931 canonicalise_suite();
4933 if (length git_get_ref lref()) {
4934 # local branch already exists, yay
4937 if (!length git_get_ref lrref()) {
4945 runcmd (@git, qw(update-ref), lref(), lrref(), '');
4948 local $ENV{GIT_REFLOG_ACTION} = git_reflog_action_msg
4949 "dgit checkout $isuite";
4950 runcmd (@git, qw(checkout), lbranch());
4953 sub cmd_update_vcs_git () {
4955 if (@ARGV==0 || $ARGV[0] =~ m/^-/) {
4956 ($specsuite,) = split /\;/, cfg 'dgit.vcs-git.suites';
4958 ($specsuite) = (@ARGV);
4963 if ($ARGV[0] eq '-') {
4965 } elsif ($ARGV[0] eq '-') {
4970 package_from_d_control();
4972 if ($specsuite eq '.') {
4973 $ctrl = parsecontrol 'debian/control', 'debian/control';
4975 $isuite = $specsuite;
4979 my $url = getfield $ctrl, 'Vcs-Git';
4982 my $orgurl = cfg 'remote.vcs-git.url', 'RETURN-UNDEF';
4983 if (!defined $orgurl) {
4984 print STDERR f_ "setting up vcs-git: %s\n", $url;
4985 @cmd = (@git, qw(remote add vcs-git), $url);
4986 } elsif ($orgurl eq $url) {
4987 print STDERR f_ "vcs git already configured: %s\n", $url;
4989 print STDERR f_ "changing vcs-git url to: %s\n", $url;
4990 @cmd = (@git, qw(remote set-url vcs-git), $url);
4992 runcmd_ordryrun_local @cmd;
4994 print f_ "fetching (%s)\n", "@ARGV";
4995 runcmd_ordryrun_local @git, qw(fetch vcs-git), @ARGV;
5001 build_or_push_prep_early();
5003 build_or_push_prep_modes();
5007 } elsif (@ARGV==1) {
5008 ($specsuite) = (@ARGV);
5010 badusage f_ "incorrect arguments to dgit %s", $subcommand;
5013 local ($package) = $existing_package; # this is a hack
5014 canonicalise_suite();
5016 canonicalise_suite();
5018 if (defined $specsuite &&
5019 $specsuite ne $isuite &&
5020 $specsuite ne $csuite) {
5021 fail f_ "dgit %s: changelog specifies %s (%s)".
5022 " but command line specifies %s",
5023 $subcommand, $isuite, $csuite, $specsuite;
5032 #---------- remote commands' implementation ----------
5034 sub pre_remote_push_build_host {
5035 my ($nrargs) = shift @ARGV;
5036 my (@rargs) = @ARGV[0..$nrargs-1];
5037 @ARGV = @ARGV[$nrargs..$#ARGV];
5039 my ($dir,$vsnwant) = @rargs;
5040 # vsnwant is a comma-separated list; we report which we have
5041 # chosen in our ready response (so other end can tell if they
5044 $we_are_responder = 1;
5045 $us .= " (build host)";
5047 open PI, "<&STDIN" or confess "$!";
5048 open STDIN, "/dev/null" or confess "$!";
5049 open PO, ">&STDOUT" or confess "$!";
5051 open STDOUT, ">&STDERR" or confess "$!";
5055 ($protovsn) = grep {
5056 $vsnwant =~ m{^(?:.*,)?$_(?:,.*)?$}
5057 } @rpushprotovsn_support;
5059 fail f_ "build host has dgit rpush protocol versions %s".
5060 " but invocation host has %s",
5061 (join ",", @rpushprotovsn_support), $vsnwant
5062 unless defined $protovsn;
5066 sub cmd_remote_push_build_host {
5067 responder_send_command("dgit-remote-push-ready $protovsn");
5071 sub pre_remote_push_responder { pre_remote_push_build_host(); }
5072 sub cmd_remote_push_responder { cmd_remote_push_build_host(); }
5073 # ... for compatibility with proto vsn.1 dgit (just so that user gets
5074 # a good error message)
5076 sub rpush_handle_protovsn_bothends () {
5083 my $report = i_child_report();
5084 if (defined $report) {
5085 printdebug "($report)\n";
5086 } elsif ($i_child_pid) {
5087 printdebug "(killing build host child $i_child_pid)\n";
5088 kill 15, $i_child_pid;
5090 if (defined $i_tmp && !defined $initiator_tempdir) {
5092 eval { rmtree $i_tmp; };
5097 return unless forkcheck_mainprocess();
5102 my ($base,$selector,@args) = @_;
5103 $selector =~ s/\-/_/g;
5104 { no strict qw(refs); &{"${base}_${selector}"}(@args); }
5108 not_necessarily_a_tree();
5113 if ($host =~ m/^((?:[^][]|\[[^][]*\])*)\:/) {
5121 push @rargs, join ",", @rpushprotovsn_support;
5124 push @rdgit, @ropts;
5125 push @rdgit, qw(remote-push-build-host), (scalar @rargs), @rargs;
5127 my @cmd = (@ssh, $host, shellquote @rdgit);
5130 $we_are_initiator=1;
5132 if (defined $initiator_tempdir) {
5133 rmtree $initiator_tempdir;
5134 mkdir $initiator_tempdir, 0700
5135 or fail f_ "create %s: %s", $initiator_tempdir, $!;
5136 $i_tmp = $initiator_tempdir;
5140 $i_child_pid = open2(\*RO, \*RI, @cmd);
5142 ($protovsn) = initiator_expect { m/^dgit-remote-push-ready (\S+)/ };
5143 die "$protovsn ?" unless grep { $_ eq $protovsn } @rpushprotovsn_support;
5146 my ($icmd,$iargs) = initiator_expect {
5147 m/^(\S+)(?: (.*))?$/;
5150 i_method "i_resp", $icmd, $iargs;
5154 sub i_resp_progress ($) {
5156 my $msg = protocol_read_bytes \*RO, $rhs;
5160 sub i_resp_supplementary_message ($) {
5162 $supplementary_message = protocol_read_bytes \*RO, $rhs;
5165 sub i_resp_complete {
5166 my $pid = $i_child_pid;
5167 $i_child_pid = undef; # prevents killing some other process with same pid
5168 printdebug "waiting for build host child $pid...\n";
5169 my $got = waitpid $pid, 0;
5170 confess "$!" unless $got == $pid;
5171 fail f_ "build host child failed: %s", waitstatusmsg() if $?;
5174 printdebug __ "all done\n";
5178 sub i_resp_file ($) {
5180 my $localname = i_method "i_localname", $keyword;
5181 my $localpath = "$i_tmp/$localname";
5182 stat_exists $localpath and
5183 badproto \*RO, f_ "file %s (%s) twice", $keyword, $localpath;
5184 protocol_receive_file \*RO, $localpath;
5185 i_method "i_file", $keyword;
5190 sub i_resp_param ($) {
5191 $_[0] =~ m/^(\S+) (.*)$/ or badproto \*RO, __ "bad param spec";
5195 sub i_resp_previously ($) {
5196 $_[0] =~ m#^(refs/tags/\S+)=(\w+)$#
5197 or badproto \*RO, __ "bad previously spec";
5198 my $r = system qw(git check-ref-format), $1;
5199 confess "bad previously ref spec ($r)" if $r;
5200 $previously{$1} = $2;
5204 our ($i_clogp, $i_version, $i_dscfn, $i_changesfn, @i_buildinfos);
5206 sub i_resp_want ($) {
5208 die "$keyword ?" if $i_wanted{$keyword}++;
5210 defined $i_param{'csuite'} or badproto \*RO, "premature desire, no csuite";
5211 $isuite = $i_param{'isuite'} // $i_param{'csuite'};
5212 die unless $isuite =~ m/^$suite_re$/;
5214 if (!defined $dsc) {
5216 rpush_handle_protovsn_bothends();
5217 push_parse_dsc $i_dscfn, 'remote dsc', $i_version;
5218 if ($protovsn >= 6) {
5219 determine_whether_split_brain getfield $dsc, 'Format';
5220 $do_split_brain eq ($i_param{'splitbrain'} // '<unsent>')
5222 "split brain mismatch, $do_split_brain != $i_param{'split_brain'}";
5223 printdebug "rpush split brain $do_split_brain\n";
5227 my @localpaths = i_method "i_want", $keyword;
5228 printdebug "[[ $keyword @localpaths\n";
5229 foreach my $localpath (@localpaths) {
5230 protocol_send_file \*RI, $localpath;
5232 print RI "files-end\n" or confess "$!";
5235 sub i_localname_parsed_changelog {
5236 return "remote-changelog.822";
5238 sub i_file_parsed_changelog {
5239 ($i_clogp, $i_version, $i_dscfn) =
5240 push_parse_changelog "$i_tmp/remote-changelog.822";
5241 die if $i_dscfn =~ m#/|^\W#;
5244 sub i_localname_dsc {
5245 defined $i_dscfn or badproto \*RO, "dsc (before parsed-changelog)";
5250 sub i_localname_buildinfo ($) {
5251 my $bi = $i_param{'buildinfo-filename'};
5252 defined $bi or badproto \*RO, "buildinfo before filename";
5253 defined $i_changesfn or badproto \*RO, "buildinfo before changes";
5254 $bi =~ m{^\Q$package\E_[!-.0-~]*\.buildinfo$}s
5255 or badproto \*RO, "improper buildinfo filename";
5258 sub i_file_buildinfo {
5259 my $bi = $i_param{'buildinfo-filename'};
5260 my $bd = parsecontrol "$i_tmp/$bi", $bi;
5261 my $ch = parsecontrol "$i_tmp/$i_changesfn", 'changes';
5262 if (!forceing [qw(buildinfo-changes-mismatch)]) {
5263 files_compare_inputs($bd, $ch);
5264 (getfield $bd, $_) eq (getfield $ch, $_) or
5265 fail f_ "buildinfo mismatch in field %s", $_
5266 foreach qw(Source Version);
5267 !defined $bd->{$_} or
5268 fail f_ "buildinfo contains forbidden field %s", $_
5269 foreach qw(Changes Changed-by Distribution);
5271 push @i_buildinfos, $bi;
5272 delete $i_param{'buildinfo-filename'};
5275 sub i_localname_changes {
5276 defined $i_dscfn or badproto \*RO, "dsc (before parsed-changelog)";
5277 $i_changesfn = $i_dscfn;
5278 $i_changesfn =~ s/\.dsc$/_dgit.changes/ or die;
5279 return $i_changesfn;
5281 sub i_file_changes { }
5283 sub i_want_signed_tag {
5284 printdebug Dumper(\%i_param, $i_dscfn);
5285 defined $i_param{'head'} && defined $i_dscfn && defined $i_clogp
5286 && defined $i_param{'csuite'}
5287 or badproto \*RO, "premature desire for signed-tag";
5288 my $head = $i_param{'head'};
5289 die if $head =~ m/[^0-9a-f]/ || $head !~ m/^../;
5291 my $maintview = $i_param{'maint-view'};
5292 die if defined $maintview && $maintview =~ m/[^0-9a-f]/;
5294 if ($protovsn == 4) {
5295 my $p = $i_param{'tagformat'} // '<undef>';
5297 or badproto \*RO, "tag format mismatch: $p vs. new";
5300 die unless $i_param{'csuite'} =~ m/^$suite_re$/;
5302 defined $dsc or badproto \*RO, "dsc (before parsed-changelog)";
5304 my @tagwants = push_tagwants $i_version, $head, $maintview, "tag";
5307 push_mktags $i_clogp, $i_dscfn,
5308 $i_changesfn, (__ 'remote changes file'),
5312 sub i_want_signed_dsc_changes {
5313 rename "$i_dscfn.tmp","$i_dscfn" or die "$i_dscfn $!";
5314 sign_changes $i_changesfn;
5315 return ($i_dscfn, $i_changesfn, @i_buildinfos);
5318 #---------- building etc. ----------
5324 #----- `3.0 (quilt)' handling -----
5326 our $fakeeditorenv = 'DGIT_FAKE_EDITOR_QUILT';
5328 sub quiltify_dpkg_commit ($$$;$) {
5329 my ($patchname,$author,$msg, $xinfo) = @_;
5332 mkpath '.git/dgit'; # we are in playtree
5333 my $descfn = ".git/dgit/quilt-description.tmp";
5334 open O, '>', $descfn or confess "$descfn: $!";
5335 $msg =~ s/\n+/\n\n/;
5336 print O <<END or confess "$!";
5338 ${xinfo}Subject: $msg
5342 close O or confess "$!";
5345 local $ENV{'EDITOR'} = cmdoutput qw(realpath --), $0;
5346 local $ENV{'VISUAL'} = $ENV{'EDITOR'};
5347 local $ENV{$fakeeditorenv} = cmdoutput qw(realpath --), $descfn;
5348 runcmd @dpkgsource, qw(--commit --include-removal .), $patchname;
5352 sub quiltify_trees_differ ($$;$$$) {
5353 my ($x,$y,$finegrained,$ignorenamesr,$unrepres) = @_;
5354 # returns true iff the two tree objects differ other than in debian/
5355 # with $finegrained,
5356 # returns bitmask 01 - differ in upstream files except .gitignore
5357 # 02 - differ in .gitignore
5358 # if $ignorenamesr is defined, $ingorenamesr->{$fn}
5359 # is set for each modified .gitignore filename $fn
5360 # if $unrepres is defined, array ref to which is appeneded
5361 # a list of unrepresentable changes (removals of upstream files
5364 my @cmd = (@git, qw(diff-tree -z --no-renames));
5365 push @cmd, qw(--name-only) unless $unrepres;
5366 push @cmd, qw(-r) if $finegrained || $unrepres;
5368 my $diffs= cmdoutput @cmd;
5371 foreach my $f (split /\0/, $diffs) {
5372 if ($unrepres && !@lmodes) {
5373 @lmodes = $f =~ m/^\:(\w+) (\w+) \w+ \w+ / or die "$_ ?";
5376 my ($oldmode,$newmode) = @lmodes;
5379 next if $f =~ m#^debian(?:/.*)?$#s;
5383 die __ "not a plain file or symlink\n"
5384 unless $newmode =~ m/^(?:10|12)\d{4}$/ ||
5385 $oldmode =~ m/^(?:10|12)\d{4}$/;
5386 if ($oldmode =~ m/[^0]/ &&
5387 $newmode =~ m/[^0]/) {
5388 # both old and new files exist
5389 die __ "mode or type changed\n" if $oldmode ne $newmode;
5390 die __ "modified symlink\n" unless $newmode =~ m/^10/;
5391 } elsif ($oldmode =~ m/[^0]/) {
5393 die __ "deletion of symlink\n"
5394 unless $oldmode =~ m/^10/;
5397 die __ "creation with non-default mode\n"
5398 unless $newmode =~ m/^100644$/ or
5399 $newmode =~ m/^120000$/;
5403 local $/="\n"; chomp $@;
5404 push @$unrepres, [ $f, "$@ ($oldmode->$newmode)" ];
5408 my $isignore = $f =~ m#^(?:.*/)?.gitignore$#s;
5409 $r |= $isignore ? 02 : 01;
5410 $ignorenamesr->{$f}=1 if $ignorenamesr && $isignore;
5412 printdebug "quiltify_trees_differ $x $y => $r\n";
5416 sub quiltify_tree_sentinelfiles ($) {
5417 # lists the `sentinel' files present in the tree
5419 my $r = cmdoutput @git, qw(ls-tree --name-only), $x,
5420 qw(-- debian/rules debian/control);
5425 sub quiltify_splitting ($$$$$$$) {
5426 my ($clogp, $unapplied, $headref, $oldtiptree, $diffbits,
5427 $editedignores, $cachekey) = @_;
5428 my $gitignore_special = 1;
5429 if ($quilt_mode !~ m/gbp|dpm|baredebian/) {
5430 # treat .gitignore just like any other upstream file
5431 $diffbits = { %$diffbits };
5432 $_ = !!$_ foreach values %$diffbits;
5433 $gitignore_special = 0;
5435 # We would like any commits we generate to be reproducible
5436 my @authline = clogp_authline($clogp);
5437 local $ENV{GIT_COMMITTER_NAME} = $authline[0];
5438 local $ENV{GIT_COMMITTER_EMAIL} = $authline[1];
5439 local $ENV{GIT_COMMITTER_DATE} = $authline[2];
5440 local $ENV{GIT_AUTHOR_NAME} = $authline[0];
5441 local $ENV{GIT_AUTHOR_EMAIL} = $authline[1];
5442 local $ENV{GIT_AUTHOR_DATE} = $authline[2];
5444 confess unless do_split_brain();
5446 my $fulldiffhint = sub {
5448 my $cmd = "git diff $x $y -- :/ ':!debian'";
5449 $cmd .= " ':!/.gitignore' ':!*/.gitignore'" if $gitignore_special;
5450 return f_ "\nFor full diff showing the problem(s), type:\n %s\n",
5454 if ($quilt_mode =~ m/gbp|unapplied|baredebian/ &&
5455 ($diffbits->{O2H} & 01)) {
5457 "--quilt=%s specified, implying patches-unapplied git tree\n".
5458 " but git tree differs from orig in upstream files.",
5460 $msg .= $fulldiffhint->($unapplied, 'HEAD');
5461 if (!stat_exists "debian/patches" and $quilt_mode !~ m/baredebian/) {
5463 "\n ... debian/patches is missing; perhaps this is a patch queue branch?";
5467 if ($quilt_mode =~ m/dpm/ &&
5468 ($diffbits->{H2A} & 01)) {
5469 fail +(f_ <<END, $quilt_mode). $fulldiffhint->($oldtiptree,'HEAD');
5470 --quilt=%s specified, implying patches-applied git tree
5471 but git tree differs from result of applying debian/patches to upstream
5474 if ($quilt_mode =~ m/baredebian/) {
5475 # We need to construct a merge which has upstream files from
5476 # upstream and debian/ files from HEAD.
5478 read_tree_upstream $quilt_upstream_commitish, 1, $headref;
5479 my $version = getfield $clogp, 'Version';
5480 my $upsversion = upstreamversion $version;
5481 my $merge = make_commit
5482 [ $headref, $quilt_upstream_commitish ],
5483 [ +(f_ <<ENDT, $upsversion), $quilt_upstream_commitish_message, <<ENDU ];
5484 Combine debian/ with upstream source for %s
5486 [dgit ($our_version) baredebian-merge $version $quilt_upstream_commitish_used]
5488 runcmd @git, qw(reset -q --hard), $merge;
5490 if ($quilt_mode =~ m/gbp|unapplied|baredebian/ &&
5491 ($diffbits->{O2A} & 01)) { # some patches
5492 progress __ "dgit view: creating patches-applied version using gbp pq";
5493 runcmd shell_cmd 'exec >/dev/null', gbp_pq, qw(import);
5494 # gbp pq import creates a fresh branch; push back to dgit-view
5495 runcmd @git, qw(update-ref refs/heads/dgit-view HEAD);
5496 runcmd @git, qw(checkout -q dgit-view);
5498 if ($quilt_mode =~ m/gbp|dpm/ &&
5499 ($diffbits->{O2A} & 02)) {
5500 fail f_ <<END, $quilt_mode;
5501 --quilt=%s specified, implying that HEAD is for use with a
5502 tool which does not create patches for changes to upstream
5503 .gitignores: but, such patches exist in debian/patches.
5506 if (($diffbits->{O2H} & 02) && # user has modified .gitignore
5507 !($diffbits->{O2A} & 02)) { # patches do not change .gitignore
5509 "dgit view: creating patch to represent .gitignore changes";
5510 ensuredir "debian/patches";
5511 my $gipatch = "debian/patches/auto-gitignore";
5512 open GIPATCH, ">>", "$gipatch" or confess "$gipatch: $!";
5513 stat GIPATCH or confess "$gipatch: $!";
5514 fail f_ "%s already exists; but want to create it".
5515 " to record .gitignore changes",
5518 print GIPATCH +(__ <<END).<<ENDU or die "$gipatch: $!";
5519 Subject: Update .gitignore from Debian packaging branch
5521 The Debian packaging git branch contains these updates to the upstream
5522 .gitignore file(s). This patch is autogenerated, to provide these
5523 updates to users of the official Debian archive view of the package.
5526 [dgit ($our_version) update-gitignore]
5529 close GIPATCH or die "$gipatch: $!";
5530 runcmd shell_cmd "exec >>$gipatch", @git, qw(diff),
5531 $unapplied, $headref, "--", sort keys %$editedignores;
5532 open SERIES, "+>>", "debian/patches/series" or confess "$!";
5533 defined seek SERIES, -1, 2 or $!==EINVAL or confess "$!";
5535 defined read SERIES, $newline, 1 or confess "$!";
5536 print SERIES "\n" or confess "$!" unless $newline eq "\n";
5537 print SERIES "auto-gitignore\n" or confess "$!";
5538 close SERIES or die $!;
5539 runcmd @git, qw(add -f -- debian/patches/series), $gipatch;
5540 commit_admin +(__ <<END).<<ENDU
5541 Commit patch to update .gitignore
5544 [dgit ($our_version) update-gitignore-quilt-fixup]
5549 sub quiltify ($$$$) {
5550 my ($clogp,$target,$oldtiptree,$failsuggestion) = @_;
5552 # Quilt patchification algorithm
5554 # We search backwards through the history of the main tree's HEAD
5555 # (T) looking for a start commit S whose tree object is identical
5556 # to to the patch tip tree (ie the tree corresponding to the
5557 # current dpkg-committed patch series). For these purposes
5558 # `identical' disregards anything in debian/ - this wrinkle is
5559 # necessary because dpkg-source treates debian/ specially.
5561 # We can only traverse edges where at most one of the ancestors'
5562 # trees differs (in changes outside in debian/). And we cannot
5563 # handle edges which change .pc/ or debian/patches. To avoid
5564 # going down a rathole we avoid traversing edges which introduce
5565 # debian/rules or debian/control. And we set a limit on the
5566 # number of edges we are willing to look at.
5568 # If we succeed, we walk forwards again. For each traversed edge
5569 # PC (with P parent, C child) (starting with P=S and ending with
5570 # C=T) to we do this:
5572 # - dpkg-source --commit with a patch name and message derived from C
5573 # After traversing PT, we git commit the changes which
5574 # should be contained within debian/patches.
5576 # The search for the path S..T is breadth-first. We maintain a
5577 # todo list containing search nodes. A search node identifies a
5578 # commit, and looks something like this:
5580 # Commit => $git_commit_id,
5581 # Child => $c, # or undef if P=T
5582 # Whynot => $reason_edge_PC_unsuitable, # in @nots only
5583 # Nontrivial => true iff $p..$c has relevant changes
5590 my %considered; # saves being exponential on some weird graphs
5592 my $t_sentinels = quiltify_tree_sentinelfiles $target;
5595 my ($search,$whynot) = @_;
5596 printdebug " search NOT $search->{Commit} $whynot\n";
5597 $search->{Whynot} = $whynot;
5598 push @nots, $search;
5599 no warnings qw(exiting);
5608 my $c = shift @todo;
5609 next if $considered{$c->{Commit}}++;
5611 $not->($c, __ "maximum search space exceeded") if --$max_work <= 0;
5613 printdebug "quiltify investigate $c->{Commit}\n";
5616 if (!quiltify_trees_differ $c->{Commit}, $oldtiptree) {
5617 printdebug " search finished hooray!\n";
5622 quiltify_nofix_bail " $c->{Commit}", " (tree object $oldtiptree)";
5623 if ($quilt_mode eq 'smash') {
5624 printdebug " search quitting smash\n";
5628 my $c_sentinels = quiltify_tree_sentinelfiles $c->{Commit};
5629 $not->($c, f_ "has %s not %s", $c_sentinels, $t_sentinels)
5630 if $c_sentinels ne $t_sentinels;
5632 my $commitdata = cmdoutput @git, qw(cat-file commit), $c->{Commit};
5633 $commitdata =~ m/\n\n/;
5635 my @parents = ($commitdata =~ m/^parent (\w+)$/gm);
5636 @parents = map { { Commit => $_, Child => $c } } @parents;
5638 $not->($c, __ "root commit") if !@parents;
5640 foreach my $p (@parents) {
5641 $p->{Nontrivial}= quiltify_trees_differ $p->{Commit},$c->{Commit};
5643 my $ndiffers = grep { $_->{Nontrivial} } @parents;
5644 $not->($c, f_ "merge (%s nontrivial parents)", $ndiffers)
5647 foreach my $p (@parents) {
5648 printdebug "considering C=$c->{Commit} P=$p->{Commit}\n";
5650 my @cmd= (@git, qw(diff-tree -r --name-only),
5651 $p->{Commit},$c->{Commit},
5652 qw(-- debian/patches .pc debian/source/format));
5653 my $patchstackchange = cmdoutput @cmd;
5654 if (length $patchstackchange) {
5655 $patchstackchange =~ s/\n/,/g;
5656 $not->($p, f_ "changed %s", $patchstackchange);
5659 printdebug " search queue P=$p->{Commit} ",
5660 ($p->{Nontrivial} ? "NT" : "triv"),"\n";
5666 printdebug "quiltify want to smash\n";
5669 my $x = $_[0]{Commit};
5670 $x =~ s/(.*?[0-9a-z]{8})[0-9a-z]*$/$1/;
5673 if ($quilt_mode eq 'linear') {
5675 "\n%s: error: quilt fixup cannot be linear. Stopped at:\n",
5677 my $all_gdr = !!@nots;
5678 foreach my $notp (@nots) {
5679 my $c = $notp->{Child};
5680 my $cprange = $abbrev->($notp);
5681 $cprange .= "..".$abbrev->($c) if $c;
5682 print STDERR f_ "%s: %s: %s\n",
5683 $us, $cprange, $notp->{Whynot};
5684 $all_gdr &&= $notp->{Child} &&
5685 (git_cat_file $notp->{Child}{Commit}, 'commit')
5686 =~ m{^\[git-debrebase(?! split[: ]).*\]$}m;
5690 [ grep { $_->[0] ne 'quilt-mode' } @$failsuggestion ]
5692 print STDERR "$us: $_->[1]\n" foreach @$failsuggestion;
5694 "quilt history linearisation failed. Search \`quilt fixup' in dgit(7).\n";
5695 } elsif ($quilt_mode eq 'smash') {
5696 } elsif ($quilt_mode eq 'auto') {
5697 progress __ "quilt fixup cannot be linear, smashing...";
5699 confess "$quilt_mode ?";
5702 my $time = $ENV{'GIT_COMMITTER_DATE'} || time;
5703 $time =~ s/\s.*//; # trim timezone from GIT_COMMITTER_DATE
5705 my $msg = cmdoutput @git, qw(log), "-n$ncommits";
5707 quiltify_dpkg_commit "auto-$version-$target-$time",
5708 (getfield $clogp, 'Maintainer'),
5709 (f_ "Automatically generated patch (%s)\n".
5710 "Last (up to) %s git changes, FYI:\n\n",
5711 $clogp->{Version}, $ncommits).
5716 progress __ "quiltify linearisation planning successful, executing...";
5718 for (my $p = $sref_S;
5719 my $c = $p->{Child};
5721 printdebug "quiltify traverse $p->{Commit}..$c->{Commit}\n";
5722 next unless $p->{Nontrivial};
5724 my $cc = $c->{Commit};
5726 my $commitdata = cmdoutput @git, qw(cat-file commit), $cc;
5727 $commitdata =~ m/\n\n/ or die "$c ?";
5730 $commitdata =~ m/^author (.*) \d+ [-+0-9]+$/m or die "$cc ?";
5733 my $commitdate = cmdoutput
5734 @git, qw(log -n1 --pretty=format:%aD), $cc;
5736 $msg =~ s/^(.*)\n*/$1\n/ or die "$cc $msg ?";
5738 my $strip_nls = sub { $msg =~ s/\n+$//; $msg .= "\n"; };
5745 my $gbp_check_suitable = sub {
5750 die __ "contains unexpected slashes\n" if m{//} || m{/$};
5751 die __ "contains leading punctuation\n" if m{^\W} || m{/\W};
5752 die __ "contains bad character(s)\n" if m{[^-a-z0-9_.+=~/]}i;
5753 die __ "is series file\n" if m{$series_filename_re}o;
5754 die __ "too long\n" if length > 200;
5756 return $_ unless $@;
5758 "quiltifying commit %s: ignoring/dropping Gbp-Pq %s: %s",
5763 if ($msg =~ s/^ (?: gbp(?:-pq)? : \s* name \s+ |
5765 (\S+) \s* \n //ixm) {
5766 $patchname = $gbp_check_suitable->($1, 'Name');
5768 if ($msg =~ s/^ (?: gbp(?:-pq)? : \s* topic \s+ |
5770 (\S+) \s* \n //ixm) {
5771 $patchdir = $gbp_check_suitable->($1, 'Topic');
5776 if (!defined $patchname) {
5777 $patchname = $title;
5778 $patchname =~ s/[.:]$//;
5781 my $converter = new Text::Iconv qw(UTF-8 ASCII//TRANSLIT);
5782 my $translitname = $converter->convert($patchname);
5783 die unless defined $translitname;
5784 $patchname = $translitname;
5787 +(f_ "dgit: patch title transliteration error: %s", $@)
5789 $patchname =~ y/ A-Z/-a-z/;
5790 $patchname =~ y/-a-z0-9_.+=~//cd;
5791 $patchname =~ s/^\W/x-$&/;
5792 $patchname = substr($patchname,0,40);
5793 $patchname .= ".patch";
5795 if (!defined $patchdir) {
5798 if (length $patchdir) {
5799 $patchname = "$patchdir/$patchname";
5801 if ($patchname =~ m{^(.*)/}) {
5802 mkpath "debian/patches/$1";
5807 stat "debian/patches/$patchname$index";
5809 $!==ENOENT or confess "$patchname$index $!";
5811 runcmd @git, qw(checkout -q), $cc;
5813 # We use the tip's changelog so that dpkg-source doesn't
5814 # produce complaining messages from dpkg-parsechangelog. None
5815 # of the information dpkg-source gets from the changelog is
5816 # actually relevant - it gets put into the original message
5817 # which dpkg-source provides our stunt editor, and then
5819 runcmd @git, qw(checkout -q), $target, qw(debian/changelog);
5821 quiltify_dpkg_commit "$patchname$index", $author, $msg,
5822 "Date: $commitdate\n".
5823 "X-Dgit-Generated: $clogp->{Version} $cc\n";
5825 runcmd @git, qw(checkout -q), $cc, qw(debian/changelog);
5829 sub build_maybe_quilt_fixup () {
5830 my ($format,$fopts) = get_source_format;
5831 return unless madformat_wantfixup $format;
5834 check_for_vendor_patches();
5836 my $clogp = parsechangelog();
5837 my $headref = git_rev_parse('HEAD');
5838 my $symref = git_get_symref();
5839 my $upstreamversion = upstreamversion $version;
5842 changedir $playground;
5844 my $splitbrain_cachekey;
5846 if (do_split_brain()) {
5848 ($cachehit, $splitbrain_cachekey) =
5849 quilt_check_splitbrain_cache($headref, $upstreamversion);
5856 unpack_playtree_need_cd_work($headref);
5857 if (do_split_brain()) {
5858 runcmd @git, qw(checkout -q -b dgit-view);
5859 # so long as work is not deleted, its current branch will
5860 # remain dgit-view, rather than master, so subsequent calls to
5861 # unpack_playtree_need_cd_work
5862 # will DTRT, resetting dgit-view.
5863 confess if $made_split_brain;
5864 $made_split_brain = 1;
5868 if ($fopts->{'single-debian-patch'}) {
5870 "quilt mode %s does not make sense (or is not supported) with single-debian-patch",
5872 if quiltmode_splitting();
5873 quilt_fixup_singlepatch($clogp, $headref, $upstreamversion);
5875 quilt_fixup_multipatch($clogp, $headref, $upstreamversion,
5876 $splitbrain_cachekey);
5879 if (do_split_brain()) {
5880 my $dgitview = git_rev_parse 'HEAD';
5883 reflog_cache_insert "refs/$splitbraincache",
5884 $splitbrain_cachekey, $dgitview;
5886 changedir "$playground/work";
5888 my $saved = maybe_split_brain_save $headref, $dgitview, __ "converted";
5889 progress f_ "dgit view: created (%s)", $saved;
5893 runcmd_ordryrun_local
5894 @git, qw(pull --ff-only -q), "$playground/work", qw(master);
5897 sub build_check_quilt_splitbrain () {
5898 build_maybe_quilt_fixup();
5901 sub unpack_playtree_need_cd_work ($) {
5904 # prep_ud() must have been called already.
5905 if (!chdir "work") {
5906 # Check in the filesystem because sometimes we run prep_ud
5907 # in between multiple calls to unpack_playtree_need_cd_work.
5908 confess "$!" unless $!==ENOENT;
5909 mkdir "work" or confess "$!";
5911 mktree_in_ud_here();
5913 runcmd @git, qw(reset -q --hard), $headref;
5916 sub unpack_playtree_linkorigs ($$) {
5917 my ($upstreamversion, $fn) = @_;
5918 # calls $fn->($leafname);
5920 my $bpd_abs = bpd_abs();
5922 dotdot_bpd_transfer_origs $bpd_abs, $upstreamversion, sub { 1 };
5924 opendir QFD, $bpd_abs or fail "buildproductsdir: $bpd_abs: $!";
5925 while ($!=0, defined(my $leaf = readdir QFD)) {
5926 my $f = bpd_abs()."/".$leaf;
5928 local ($debuglevel) = $debuglevel-1;
5929 printdebug "QF linkorigs bpd $leaf, $f ?\n";
5931 next unless is_orig_file_of_vsn $leaf, $upstreamversion;
5932 printdebug "QF linkorigs $leaf, $f Y\n";
5933 link_ltarget $f, $leaf or die "$leaf $!";
5936 die "$buildproductsdir: $!" if $!;
5940 sub quilt_fixup_delete_pc () {
5941 runcmd @git, qw(rm -rqf .pc);
5942 commit_admin +(__ <<END).<<ENDU
5943 Commit removal of .pc (quilt series tracking data)
5946 [dgit ($our_version) upgrade quilt-remove-pc]
5950 sub quilt_fixup_singlepatch ($$$) {
5951 my ($clogp, $headref, $upstreamversion) = @_;
5953 progress __ "starting quiltify (single-debian-patch)";
5955 # dpkg-source --commit generates new patches even if
5956 # single-debian-patch is in debian/source/options. In order to
5957 # get it to generate debian/patches/debian-changes, it is
5958 # necessary to build the source package.
5960 unpack_playtree_linkorigs($upstreamversion, sub { });
5961 unpack_playtree_need_cd_work($headref);
5963 rmtree("debian/patches");
5965 runcmd @dpkgsource, qw(-b .);
5967 runcmd @dpkgsource, qw(-x), (srcfn $version, ".dsc");
5968 rename srcfn("$upstreamversion", "/debian/patches"),
5969 "work/debian/patches"
5971 or confess "install d/patches: $!";
5974 commit_quilty_patch();
5977 sub quilt_need_fake_dsc ($) {
5978 # cwd should be playground
5979 my ($upstreamversion) = @_;
5981 return if stat_exists "fake.dsc";
5982 # ^ OK to test this as a sentinel because if we created it
5983 # we must either have done the rest too, or crashed.
5985 my $fakeversion="$upstreamversion-~~DGITFAKE";
5987 my $fakedsc=new IO::File 'fake.dsc', '>' or confess "$!";
5988 print $fakedsc <<END or confess "$!";
5991 Version: $fakeversion
5995 my $dscaddfile=sub {
5998 my $md = new Digest::MD5;
6000 my $fh = new IO::File $leaf, '<' or die "$leaf $!";
6001 stat $fh or confess "$!";
6005 print $fakedsc " ".$md->hexdigest." $size $leaf\n" or confess "$!";
6008 unpack_playtree_linkorigs($upstreamversion, $dscaddfile);
6010 my @files=qw(debian/source/format debian/rules
6011 debian/control debian/changelog);
6012 foreach my $maybe (qw(debian/patches debian/source/options
6013 debian/tests/control)) {
6014 next unless stat_exists "$maindir/$maybe";
6015 push @files, $maybe;
6018 my $debtar= srcfn $fakeversion,'.debian.tar.gz';
6019 runcmd qw(env GZIP=-1n tar -zcf), "./$debtar", qw(-C), $maindir, @files;
6021 $dscaddfile->($debtar);
6022 close $fakedsc or confess "$!";
6025 sub quilt_fakedsc2unapplied ($$) {
6026 my ($headref, $upstreamversion) = @_;
6027 # must be run in the playground
6028 # quilt_need_fake_dsc must have been called
6030 quilt_need_fake_dsc($upstreamversion);
6032 'exec dpkg-source --no-check --skip-patches -x fake.dsc >/dev/null';
6034 my $fakexdir= $package.'-'.(stripepoch $upstreamversion);
6035 rename $fakexdir, "fake" or die "$fakexdir $!";
6039 remove_stray_gits(__ "source package");
6040 mktree_in_ud_here();
6044 rmtree 'debian'; # git checkout commitish paths does not delete!
6045 runcmd @git, qw(checkout -f), $headref, qw(-- debian);
6046 my $unapplied=git_add_write_tree();
6047 printdebug "fake orig tree object $unapplied\n";
6051 sub quilt_check_splitbrain_cache ($$) {
6052 my ($headref, $upstreamversion) = @_;
6053 # Called only if we are in (potentially) split brain mode.
6054 # Called in playground.
6055 # Computes the cache key and looks in the cache.
6056 # Returns ($dgit_view_commitid, $cachekey) or (undef, $cachekey)
6058 quilt_need_fake_dsc($upstreamversion);
6060 my $splitbrain_cachekey;
6063 "dgit: split brain (separate dgit view) may be needed (--quilt=%s).",
6065 # we look in the reflog of dgit-intern/quilt-cache
6066 # we look for an entry whose message is the key for the cache lookup
6067 my @cachekey = (qw(dgit), $our_version);
6068 push @cachekey, $upstreamversion;
6069 push @cachekey, $quilt_mode;
6070 push @cachekey, $headref;
6071 push @cachekey, $quilt_upstream_commitish // '-';
6073 push @cachekey, hashfile('fake.dsc');
6075 my $srcshash = Digest::SHA->new(256);
6076 my %sfs = ( %INC, '$0(dgit)' => $0 );
6077 foreach my $sfk (sort keys %sfs) {
6078 next unless $sfk =~ m/^\$0\b/ || $sfk =~ m{^Debian/Dgit\b};
6079 $srcshash->add($sfk," ");
6080 $srcshash->add(hashfile($sfs{$sfk}));
6081 $srcshash->add("\n");
6083 push @cachekey, $srcshash->hexdigest();
6084 $splitbrain_cachekey = "@cachekey";
6086 printdebug "splitbrain cachekey $splitbrain_cachekey\n";
6088 my $cachehit = reflog_cache_lookup
6089 "refs/$splitbraincache", $splitbrain_cachekey;
6092 unpack_playtree_need_cd_work($headref);
6093 my $saved = maybe_split_brain_save $headref, $cachehit, "cache-hit";
6094 if ($cachehit ne $headref) {
6095 progress f_ "dgit view: found cached (%s)", $saved;
6096 runcmd @git, qw(checkout -q -b dgit-view), $cachehit;
6097 $made_split_brain = 1;
6098 return ($cachehit, $splitbrain_cachekey);
6100 progress __ "dgit view: found cached, no changes required";
6101 return ($headref, $splitbrain_cachekey);
6104 printdebug "splitbrain cache miss\n";
6105 return (undef, $splitbrain_cachekey);
6108 sub baredebian_origtarballs_scan ($$$) {
6109 my ($fakedfi, $upstreamversion, $dir) = @_;
6110 if (!opendir OD, $dir) {
6111 return if $! == ENOENT;
6112 fail "opendir $dir (origs): $!";
6115 while ($!=0, defined(my $leaf = readdir OD)) {
6117 local ($debuglevel) = $debuglevel-1;
6118 printdebug "BDOS $dir $leaf ?\n";
6120 next unless is_orig_file_of_vsn $leaf, $upstreamversion;
6121 next if grep { $_->{Filename} eq $leaf } @$fakedfi;
6124 Path => "$dir/$leaf",
6128 die "$dir; $!" if $!;
6132 sub quilt_fixup_multipatch ($$$) {
6133 my ($clogp, $headref, $upstreamversion, $splitbrain_cachekey) = @_;
6135 progress f_ "examining quilt state (multiple patches, %s mode)",
6139 # - honour any existing .pc in case it has any strangeness
6140 # - determine the git commit corresponding to the tip of
6141 # the patch stack (if there is one)
6142 # - if there is such a git commit, convert each subsequent
6143 # git commit into a quilt patch with dpkg-source --commit
6144 # - otherwise convert all the differences in the tree into
6145 # a single git commit
6149 # Our git tree doesn't necessarily contain .pc. (Some versions of
6150 # dgit would include the .pc in the git tree.) If there isn't
6151 # one, we need to generate one by unpacking the patches that we
6154 # We first look for a .pc in the git tree. If there is one, we
6155 # will use it. (This is not the normal case.)
6157 # Otherwise need to regenerate .pc so that dpkg-source --commit
6158 # can work. We do this as follows:
6159 # 1. Collect all relevant .orig from parent directory
6160 # 2. Generate a debian.tar.gz out of
6161 # debian/{patches,rules,source/format,source/options}
6162 # 3. Generate a fake .dsc containing just these fields:
6163 # Format Source Version Files
6164 # 4. Extract the fake .dsc
6165 # Now the fake .dsc has a .pc directory.
6166 # (In fact we do this in every case, because in future we will
6167 # want to search for a good base commit for generating patches.)
6169 # Then we can actually do the dpkg-source --commit
6170 # 1. Make a new working tree with the same object
6171 # store as our main tree and check out the main
6173 # 2. Copy .pc from the fake's extraction, if necessary
6174 # 3. Run dpkg-source --commit
6175 # 4. If the result has changes to debian/, then
6176 # - git add them them
6177 # - git add .pc if we had a .pc in-tree
6179 # 5. If we had a .pc in-tree, delete it, and git commit
6180 # 6. Back in the main tree, fast forward to the new HEAD
6182 # Another situation we may have to cope with is gbp-style
6183 # patches-unapplied trees.
6185 # We would want to detect these, so we know to escape into
6186 # quilt_fixup_gbp. However, this is in general not possible.
6187 # Consider a package with a one patch which the dgit user reverts
6188 # (with git revert or the moral equivalent).
6190 # That is indistinguishable in contents from a patches-unapplied
6191 # tree. And looking at the history to distinguish them is not
6192 # useful because the user might have made a confusing-looking git
6193 # history structure (which ought to produce an error if dgit can't
6194 # cope, not a silent reintroduction of an unwanted patch).
6196 # So gbp users will have to pass an option. But we can usually
6197 # detect their failure to do so: if the tree is not a clean
6198 # patches-applied tree, quilt linearisation fails, but the tree
6199 # _is_ a clean patches-unapplied tree, we can suggest that maybe
6200 # they want --quilt=unapplied.
6202 # To help detect this, when we are extracting the fake dsc, we
6203 # first extract it with --skip-patches, and then apply the patches
6204 # afterwards with dpkg-source --before-build. That lets us save a
6205 # tree object corresponding to .origs.
6207 if ($quilt_mode eq 'linear'
6208 && branch_is_gdr($headref)) {
6209 # This is much faster. It also makes patches that gdr
6210 # likes better for future updates without laundering.
6212 # However, it can fail in some casses where we would
6213 # succeed: if there are existing patches, which correspond
6214 # to a prefix of the branch, but are not in gbp/gdr
6215 # format, gdr will fail (exiting status 7), but we might
6216 # be able to figure out where to start linearising. That
6217 # will be slower so hopefully there's not much to do.
6219 unpack_playtree_need_cd_work $headref;
6221 my @cmd = (@git_debrebase,
6222 qw(--noop-ok -funclean-mixed -funclean-ordering
6223 make-patches --quiet-would-amend));
6224 # We tolerate soe snags that gdr wouldn't, by default.
6230 and not ($? == 7*256 or
6231 $? == -1 && $!==ENOENT);
6235 $headref = git_rev_parse('HEAD');
6240 my $unapplied=quilt_fakedsc2unapplied($headref, $upstreamversion);
6244 my @bbcmd = (qw(sh -ec), 'exec dpkg-source --before-build . >/dev/null');
6246 if (system @bbcmd) {
6247 failedcmd @bbcmd if $? < 0;
6249 failed to apply your git tree's patch stack (from debian/patches/) to
6250 the corresponding upstream tarball(s). Your source tree and .orig
6251 are probably too inconsistent. dgit can only fix up certain kinds of
6252 anomaly (depending on the quilt mode). Please see --quilt= in dgit(1).
6258 unpack_playtree_need_cd_work($headref);
6261 if (stat_exists ".pc") {
6263 progress __ "Tree already contains .pc - will use it then delete it.";
6266 rename '../fake/.pc','.pc' or confess "$!";
6269 changedir '../fake';
6271 my $oldtiptree=git_add_write_tree();
6272 printdebug "fake o+d/p tree object $unapplied\n";
6273 changedir '../work';
6276 # We calculate some guesswork now about what kind of tree this might
6277 # be. This is mostly for error reporting.
6279 my $tentries = cmdoutput @git, qw(ls-tree --name-only -z), $headref;
6280 my $onlydebian = $tentries eq "debian\0";
6282 my $uheadref = $headref;
6283 my $uhead_whatshort = 'HEAD';
6285 if ($quilt_mode =~ m/baredebian\+tarball/) {
6286 # We need to make a tarball import. Yuk.
6287 # We want to do this here so that we have a $uheadref value
6290 baredebian_origtarballs_scan \@fakedfi, $upstreamversion, bpd_abs();
6291 baredebian_origtarballs_scan \@fakedfi, $upstreamversion,
6292 "$maindir/.." unless $buildproductsdir eq '..';
6295 my @tartrees = import_tarball_tartrees $upstreamversion, \@fakedfi;
6297 fail __ "baredebian quilt fixup: could not find any origs"
6301 my ($authline, $r1authline, $clogp,) =
6302 import_tarball_commits \@tartrees, $upstreamversion;
6304 if (@tartrees == 1) {
6305 $uheadref = $tartrees[0]{Commit};
6306 # TRANSLATORS: this translation must fit in the ASCII art
6307 # quilt differences display. The untranslated display
6308 # says %9.9s, so with that display it must be at most 9
6310 $uhead_whatshort = __ 'tarball';
6312 # on .dsc import we do not make a separate commit, but
6313 # here we need to do so
6314 rm_subdir_cached '.';
6316 foreach my $ti (@tartrees) {
6317 my $c = $ti->{Commit};
6318 if ($ti->{OrigPart} eq 'orig') {
6319 runcmd qw(git read-tree), $c;
6320 } elsif ($ti->{OrigPart} =~ m/orig-/) {
6321 read_tree_subdir $', $c;
6323 confess "$ti->OrigPart} ?"
6325 $parents .= "parent $c\n";
6327 my $tree = git_write_tree();
6328 my $mbody = f_ 'Combine orig tarballs for %s %s',
6329 $package, $upstreamversion;
6330 $uheadref = hash_commit_text <<END;
6332 ${parents}author $r1authline
6333 committer $r1authline
6337 [dgit import tarballs combine $package $upstreamversion]
6339 # TRANSLATORS: this translation must fit in the ASCII art
6340 # quilt differences display. The untranslated display
6341 # says %9.9s, so with that display it must be at most 9
6342 # characters. This fragmentt is referring to multiple
6343 # orig tarballs in a source package.
6344 $uhead_whatshort = __ 'tarballs';
6346 runcmd @git, qw(reset -q);
6348 $quilt_upstream_commitish = $uheadref;
6349 $quilt_upstream_commitish_used = '*orig*';
6350 $quilt_upstream_commitish_message = '';
6352 if ($quilt_mode =~ m/baredebian$/) {
6353 $uheadref = $quilt_upstream_commitish;
6354 # TRANSLATORS: this translation must fit in the ASCII art
6355 # quilt differences display. The untranslated display
6356 # says %9.9s, so with that display it must be at most 9
6358 $uhead_whatshort = __ 'upstream';
6365 # O = orig, without patches applied
6366 # A = "applied", ie orig with H's debian/patches applied
6367 O2H => quiltify_trees_differ($unapplied,$uheadref, 1,
6368 \%editedignores, \@unrepres),
6369 H2A => quiltify_trees_differ($uheadref, $oldtiptree,1),
6370 O2A => quiltify_trees_differ($unapplied,$oldtiptree,1),
6374 foreach my $bits (qw(01 02)) {
6375 foreach my $v (qw(O2H O2A H2A)) {
6376 push @dl, ($diffbits->{$v} & $bits) ? '##' : '==';
6379 printdebug "differences \@dl @dl.\n";
6382 "%s: base trees orig=%.20s o+d/p=%.20s",
6383 $us, $unapplied, $oldtiptree;
6384 # TRANSLATORS: Try to keep this ascii-art layout right. The 0s in
6385 # %9.00009s will be ignored and are there to make the format the
6386 # same length (9 characters) as the output it generates. If you
6387 # change the value 9, your translations of "upstream" and
6388 # 'tarball' must fit into the new length, and you should change
6389 # the number of 0s. Do not reduce it below 4 as HEAD has to fit
6392 "%s: quilt differences: src: %s orig %s gitignores: %s orig %s\n".
6393 "%s: quilt differences: %9.00009s %s o+d/p %9.00009s %s o+d/p",
6394 $us, $dl[0], $dl[1], $dl[3], $dl[4],
6395 $us, $uhead_whatshort, $dl[2], $uhead_whatshort, $dl[5];
6397 if (@unrepres && $quilt_mode !~ m/baredebian/) {
6398 # With baredebian, even if the upstream commitish has this
6399 # problem, we don't want to print this message, as nothing
6400 # is going to try to make a patch out of it anyway.
6401 print STDERR f_ "dgit: cannot represent change: %s: %s\n",
6404 forceable_fail [qw(unrepresentable)], __ <<END;
6405 HEAD has changes to .orig[s] which are not representable by `3.0 (quilt)'
6411 push @failsuggestion, [ 'onlydebian', __
6412 "This has only a debian/ directory; you probably want --quilt=bare debian." ]
6413 unless $quilt_mode =~ m/baredebian/;
6414 } elsif (!($diffbits->{O2H} & $diffbits->{O2A})) {
6415 push @failsuggestion, [ 'unapplied', __
6416 "This might be a patches-unapplied branch." ];
6417 } elsif (!($diffbits->{H2A} & $diffbits->{O2A})) {
6418 push @failsuggestion, [ 'applied', __
6419 "This might be a patches-applied branch." ];
6421 push @failsuggestion, [ 'quilt-mode', __
6422 "Maybe you need one of --[quilt=]gbp --[quilt=]dpm --quilt=unapplied ?" ];
6424 push @failsuggestion, [ 'gitattrs', __
6425 "Warning: Tree has .gitattributes. See GITATTRIBUTES in dgit(7)." ]
6426 if stat_exists '.gitattributes';
6428 push @failsuggestion, [ 'origs', __
6429 "Maybe orig tarball(s) are not identical to git representation?" ]
6430 unless $onlydebian && $quilt_mode !~ m/baredebian/;
6431 # ^ in that case, we didn't really look properly
6433 if (quiltmode_splitting()) {
6434 quiltify_splitting($clogp, $unapplied, $headref, $oldtiptree,
6435 $diffbits, \%editedignores,
6436 $splitbrain_cachekey);
6440 progress f_ "starting quiltify (multiple patches, %s mode)", $quilt_mode;
6441 quiltify($clogp,$headref,$oldtiptree,\@failsuggestion);
6442 runcmd @git, qw(checkout -q), (qw(master dgit-view)[do_split_brain()]);
6444 if (!open P, '>>', ".pc/applied-patches") {
6445 $!==&ENOENT or confess "$!";
6450 commit_quilty_patch();
6452 if ($mustdeletepc) {
6453 quilt_fixup_delete_pc();
6457 sub quilt_fixup_editor () {
6458 my $descfn = $ENV{$fakeeditorenv};
6459 my $editing = $ARGV[$#ARGV];
6460 open I1, '<', $descfn or confess "$descfn: $!";
6461 open I2, '<', $editing or confess "$editing: $!";
6462 unlink $editing or confess "$editing: $!";
6463 open O, '>', $editing or confess "$editing: $!";
6464 while (<I1>) { print O or confess "$!"; } I1->error and confess "$!";
6467 $copying ||= m/^\-\-\- /;
6468 next unless $copying;
6469 print O or confess "$!";
6471 I2->error and confess "$!";
6476 sub maybe_apply_patches_dirtily () {
6477 return unless $quilt_mode =~ m/gbp|unapplied|baredebian/;
6478 print STDERR __ <<END or confess "$!";
6480 dgit: Building, or cleaning with rules target, in patches-unapplied tree.
6481 dgit: Have to apply the patches - making the tree dirty.
6482 dgit: (Consider specifying --clean=git and (or) using dgit sbuild.)
6485 $patches_applied_dirtily = 01;
6486 $patches_applied_dirtily |= 02 unless stat_exists '.pc';
6487 runcmd qw(dpkg-source --before-build .);
6490 sub maybe_unapply_patches_again () {
6491 progress __ "dgit: Unapplying patches again to tidy up the tree."
6492 if $patches_applied_dirtily;
6493 runcmd qw(dpkg-source --after-build .)
6494 if $patches_applied_dirtily & 01;
6496 if $patches_applied_dirtily & 02;
6497 $patches_applied_dirtily = 0;
6500 #----- other building -----
6502 sub clean_tree_check_git ($$$) {
6503 my ($honour_ignores, $message, $ignmessage) = @_;
6504 my @cmd = (@git, qw(clean -dn));
6505 push @cmd, qw(-x) unless $honour_ignores;
6506 my $leftovers = cmdoutput @cmd;
6507 if (length $leftovers) {
6508 print STDERR $leftovers, "\n" or confess "$!";
6509 $message .= $ignmessage if $honour_ignores;
6514 sub clean_tree_check_git_wd ($) {
6516 return if $cleanmode =~ m{no-check};
6517 return if $patches_applied_dirtily; # yuk
6518 clean_tree_check_git +($cleanmode !~ m{all-check}),
6519 $message, "\n".__ <<END;
6520 If this is just missing .gitignore entries, use a different clean
6521 mode, eg --clean=dpkg-source,no-check (-wdn/-wddn) to ignore them
6522 or --clean=git (-wg/-wgf) to use \`git clean' instead.
6526 sub clean_tree_check () {
6527 # This function needs to not care about modified but tracked files.
6528 # That was done by check_not_dirty, and by now we may have run
6529 # the rules clean target which might modify tracked files (!)
6530 if ($cleanmode =~ m{^check}) {
6531 clean_tree_check_git +($cleanmode =~ m{ignores}), __
6532 "tree contains uncommitted files and --clean=check specified", '';
6533 } elsif ($cleanmode =~ m{^dpkg-source}) {
6534 clean_tree_check_git_wd __
6535 "tree contains uncommitted files (NB dgit didn't run rules clean)";
6536 } elsif ($cleanmode =~ m{^git}) {
6537 clean_tree_check_git 1, __
6538 "tree contains uncommited, untracked, unignored files\n".
6539 "You can use --clean=git[-ff],always (-wga/-wgfa) to delete them.", '';
6540 } elsif ($cleanmode eq 'none') {
6542 confess "$cleanmode ?";
6547 # We always clean the tree ourselves, rather than leave it to the
6548 # builder (dpkg-source, or soemthing which calls dpkg-source).
6549 if ($quilt_mode =~ m/baredebian/ and $cleanmode =~ m/git/) {
6550 fail f_ <<END, $quilt_mode, $cleanmode;
6551 quilt mode %s (generally needs untracked upstream files)
6552 contradicts clean mode %s (which would delete them)
6554 # This is not 100% true: dgit build-source and push-source
6555 # (for example) could operate just fine with no upstream
6556 # source in the working tree. But it doesn't seem likely that
6557 # the user wants dgit to proactively delete such things.
6558 # -wn, for example, would produce identical output without
6559 # deleting anything from the working tree.
6561 if ($cleanmode =~ m{^dpkg-source}) {
6562 my @cmd = @dpkgbuildpackage;
6563 push @cmd, qw(-d) if $cleanmode =~ m{^dpkg-source-d};
6564 push @cmd, qw(-T clean);
6565 maybe_apply_patches_dirtily();
6566 runcmd_ordryrun_local @cmd;
6567 clean_tree_check_git_wd __
6568 "tree contains uncommitted files (after running rules clean)";
6569 } elsif ($cleanmode =~ m{^git(?!-)}) {
6570 runcmd_ordryrun_local @git, qw(clean -xdf);
6571 } elsif ($cleanmode =~ m{^git-ff}) {
6572 runcmd_ordryrun_local @git, qw(clean -xdff);
6573 } elsif ($cleanmode =~ m{^check}) {
6575 } elsif ($cleanmode eq 'none') {
6577 confess "$cleanmode ?";
6582 badusage __ "clean takes no additional arguments" if @ARGV;
6585 maybe_unapply_patches_again();
6588 # return values from massage_dbp_args are one or both of these flags
6589 sub WANTSRC_SOURCE () { 01; } # caller should build source (separately)
6590 sub WANTSRC_BUILDER () { 02; } # caller should run dpkg-buildpackage
6592 sub build_or_push_prep_early () {
6593 our $build_or_push_prep_early_done //= 0;
6594 return if $build_or_push_prep_early_done++;
6595 my $clogp = parsechangelog();
6596 $isuite = getfield $clogp, 'Distribution';
6597 my $gotpackage = getfield $clogp, 'Source';
6598 $version = getfield $clogp, 'Version';
6599 $package //= $gotpackage;
6600 if ($package ne $gotpackage) {
6601 fail f_ "-p specified package %s, but changelog says %s",
6602 $package, $gotpackage;
6604 $dscfn = dscfn($version);
6607 sub build_or_push_prep_modes () {
6608 my ($format) = get_source_format();
6609 determine_whether_split_brain($format);
6611 fail __ "dgit: --include-dirty is not supported with split view".
6612 " (including with view-splitting quilt modes)"
6613 if do_split_brain() && $includedirty;
6615 if (madformat_wantfixup $format and $quilt_mode =~ m/baredebian$/) {
6616 ($quilt_upstream_commitish, $quilt_upstream_commitish_used,
6617 $quilt_upstream_commitish_message)
6618 = resolve_upstream_version
6619 $quilt_upstream_commitish, upstreamversion $version;
6620 progress f_ "dgit: --quilt=%s, %s", $quilt_mode,
6621 $quilt_upstream_commitish_message;
6622 } elsif (defined $quilt_upstream_commitish) {
6624 "dgit: --upstream-commitish only makes sense with --quilt=baredebian"
6628 sub build_prep_early () {
6629 build_or_push_prep_early();
6631 build_or_push_prep_modes();
6635 sub build_prep ($) {
6639 if (!building_source_in_playtree() || ($wantsrc & WANTSRC_BUILDER)
6640 # Clean the tree because we're going to use the contents of
6641 # $maindir. (We trying to include dirty changes in the source
6642 # package, or we are running the builder in $maindir.)
6643 || $cleanmode =~ m{always}) {
6644 # Or because the user asked us to.
6647 # We don't actually need to do anything in $maindir, but we
6648 # should do some kind of cleanliness check because (i) the
6649 # user may have forgotten a `git add', and (ii) if the user
6650 # said -wc we should still do the check.
6653 build_check_quilt_splitbrain();
6655 my $pat = changespat $version;
6656 foreach my $f (glob "$buildproductsdir/$pat") {
6659 fail f_ "remove old changes file %s: %s", $f, $!;
6661 progress f_ "would remove %s", $f;
6667 sub changesopts_initial () {
6668 my @opts =@changesopts[1..$#changesopts];
6671 sub changesopts_version () {
6672 if (!defined $changes_since_version) {
6675 @vsns = archive_query('archive_query');
6676 my @quirk = access_quirk();
6677 if ($quirk[0] eq 'backports') {
6678 local $isuite = $quirk[2];
6680 canonicalise_suite();
6681 push @vsns, archive_query('archive_query');
6687 "archive query failed (queried because --since-version not specified)";
6690 @vsns = map { $_->[0] } @vsns;
6691 @vsns = sort { -version_compare($a, $b) } @vsns;
6692 $changes_since_version = $vsns[0];
6693 progress f_ "changelog will contain changes since %s", $vsns[0];
6695 $changes_since_version = '_';
6696 progress __ "package seems new, not specifying -v<version>";
6699 if ($changes_since_version ne '_') {
6700 return ("-v$changes_since_version");
6706 sub changesopts () {
6707 return (changesopts_initial(), changesopts_version());
6710 sub massage_dbp_args ($;$) {
6711 my ($cmd,$xargs) = @_;
6712 # Since we split the source build out so we can do strange things
6713 # to it, massage the arguments to dpkg-buildpackage so that the
6714 # main build doessn't build source (or add an argument to stop it
6715 # building source by default).
6716 debugcmd '#massaging#', @$cmd if $debuglevel>1;
6717 # -nc has the side effect of specifying -b if nothing else specified
6718 # and some combinations of -S, -b, et al, are errors, rather than
6719 # later simply overriding earlie. So we need to:
6720 # - search the command line for these options
6721 # - pick the last one
6722 # - perhaps add our own as a default
6723 # - perhaps adjust it to the corresponding non-source-building version
6725 foreach my $l ($cmd, $xargs) {
6727 @$l = grep { !(m/^-[SgGFABb]$|^--build=/s and $dmode=$_) } @$l;
6730 #print STDERR "MASS1 ",Dumper($cmd, $xargs, $dmode);
6731 my $r = WANTSRC_BUILDER;
6732 printdebug "massage split $dmode.\n";
6733 if ($dmode =~ s/^--build=//) {
6735 my @d = split /,/, $dmode;
6736 $r |= WANTSRC_SOURCE if grep { s/^full$/binary/ } @d;
6737 $r |= WANTSRC_SOURCE if grep { s/^source$// } @d;
6738 $r |= WANTSRC_BUILDER if grep { m/./ } @d;
6739 fail __ "Wanted to build nothing!" unless $r;
6740 $dmode = '--build='. join ',', grep m/./, @d;
6743 $dmode =~ m/[S]/ ? WANTSRC_SOURCE :
6744 $dmode =~ y/gGF/ABb/ ? WANTSRC_SOURCE | WANTSRC_BUILDER :
6745 $dmode =~ m/[ABb]/ ? WANTSRC_BUILDER :
6748 printdebug "massage done $r $dmode.\n";
6750 #print STDERR "MASS2 ",Dumper($cmd, $xargs, $r);
6756 my $wasdir = must_getcwd();
6757 changedir $buildproductsdir;
6762 # this sub must run with CWD=$buildproductsdir (eg in in_bpd)
6763 sub postbuild_mergechanges ($) {
6764 my ($msg_if_onlyone) = @_;
6765 # If there is only one .changes file, fail with $msg_if_onlyone,
6766 # or if that is undef, be a no-op.
6767 # Returns the changes file to report to the user.
6768 my $pat = changespat $version;
6769 my @changesfiles = grep { !m/_multi\.changes/ } glob $pat;
6770 @changesfiles = sort {
6771 ($b =~ m/_source\.changes$/ <=> $a =~ m/_source\.changes$/)
6775 if (@changesfiles==1) {
6776 fail +(f_ <<END, "@changesfiles").$msg_if_onlyone
6777 only one changes file from build (%s)
6779 if defined $msg_if_onlyone;
6780 $result = $changesfiles[0];
6781 } elsif (@changesfiles==2) {
6782 my $binchanges = parsecontrol($changesfiles[1], "binary changes file");
6783 foreach my $l (split /\n/, getfield $binchanges, 'Files') {
6784 fail f_ "%s found in binaries changes file %s", $l, $binchanges
6787 runcmd_ordryrun_local @mergechanges, @changesfiles;
6788 my $multichanges = changespat $version,'multi';
6790 stat_exists $multichanges or fail f_
6791 "%s unexpectedly not created by build", $multichanges;
6792 foreach my $cf (glob $pat) {
6793 next if $cf eq $multichanges;
6794 rename "$cf", "$cf.inmulti" or fail f_
6795 "install new changes %s\{,.inmulti}: %s", $cf, $!;
6798 $result = $multichanges;
6800 fail f_ "wrong number of different changes files (%s)",
6803 printdone f_ "build successful, results in %s\n", $result
6807 sub midbuild_checkchanges () {
6808 my $pat = changespat $version;
6809 return if $rmchanges;
6810 my @unwanted = map { s#.*/##; $_; } glob "$bpd_glob/$pat";
6812 $_ ne changespat $version,'source' and
6813 $_ ne changespat $version,'multi'
6815 fail +(f_ <<END, $pat, "@unwanted")
6816 changes files other than source matching %s already present; building would result in ambiguity about the intended results.
6817 Suggest you delete %s.
6822 sub midbuild_checkchanges_vanilla ($) {
6824 midbuild_checkchanges() if $wantsrc == (WANTSRC_SOURCE|WANTSRC_BUILDER);
6827 sub postbuild_mergechanges_vanilla ($) {
6829 if ($wantsrc == (WANTSRC_SOURCE|WANTSRC_BUILDER)) {
6831 postbuild_mergechanges(undef);
6834 printdone __ "build successful\n";
6840 $buildproductsdir eq '..' or print STDERR +(f_ <<END, $us, $us);
6841 %s: warning: build-products-dir set, but not supported by dpkg-buildpackage
6842 %s: warning: build-products-dir will be ignored; files will go to ..
6844 $buildproductsdir = '..';
6845 my @dbp = (@dpkgbuildpackage, qw(-us -uc), changesopts_initial(), @ARGV);
6846 my $wantsrc = massage_dbp_args \@dbp;
6847 build_prep($wantsrc);
6848 if ($wantsrc & WANTSRC_SOURCE) {
6850 midbuild_checkchanges_vanilla $wantsrc;
6852 if ($wantsrc & WANTSRC_BUILDER) {
6853 push @dbp, changesopts_version();
6854 maybe_apply_patches_dirtily();
6855 runcmd_ordryrun_local @dbp;
6857 maybe_unapply_patches_again();
6858 postbuild_mergechanges_vanilla $wantsrc;
6862 $quilt_mode //= 'gbp';
6868 # gbp can make .origs out of thin air. In my tests it does this
6869 # even for a 1.0 format package, with no origs present. So I
6870 # guess it keys off just the version number. We don't know
6871 # exactly what .origs ought to exist, but let's assume that we
6872 # should run gbp if: the version has an upstream part and the main
6874 my $upstreamversion = upstreamversion $version;
6875 my $origfnpat = srcfn $upstreamversion, '.orig.tar.*';
6876 my $gbp_make_orig = $version =~ m/-/ && !(() = glob "$bpd_glob/$origfnpat");
6878 if ($gbp_make_orig) {
6880 $cleanmode = 'none'; # don't do it again
6883 my @dbp = @dpkgbuildpackage;
6885 my $wantsrc = massage_dbp_args \@dbp, \@ARGV;
6887 if (!length $gbp_build[0]) {
6888 if (length executable_on_path('git-buildpackage')) {
6889 $gbp_build[0] = qw(git-buildpackage);
6891 $gbp_build[0] = 'gbp buildpackage';
6894 my @cmd = opts_opt_multi_cmd [], @gbp_build;
6896 push @cmd, (qw(-us -uc --git-no-sign-tags),
6897 "--git-builder=".(shellquote @dbp));
6899 if ($gbp_make_orig) {
6900 my $priv = dgit_privdir();
6901 my $ok = "$priv/origs-gen-ok";
6902 unlink $ok or $!==&ENOENT or confess "$!";
6903 my @origs_cmd = @cmd;
6904 push @origs_cmd, qw(--git-cleaner=true);
6905 push @origs_cmd, "--git-prebuild=".
6906 "touch ".(shellquote $ok)." ".(shellquote "$priv/no-such-dir/ok");
6907 push @origs_cmd, @ARGV;
6909 debugcmd @origs_cmd;
6911 do { local $!; stat_exists $ok; }
6912 or failedcmd @origs_cmd;
6914 dryrun_report @origs_cmd;
6918 build_prep($wantsrc);
6919 if ($wantsrc & WANTSRC_SOURCE) {
6921 midbuild_checkchanges_vanilla $wantsrc;
6923 push @cmd, '--git-cleaner=true';
6925 maybe_unapply_patches_again();
6926 if ($wantsrc & WANTSRC_BUILDER) {
6927 push @cmd, changesopts();
6928 runcmd_ordryrun_local @cmd, @ARGV;
6930 postbuild_mergechanges_vanilla $wantsrc;
6932 sub cmd_git_build { cmd_gbp_build(); } # compatibility with <= 1.0
6934 sub building_source_in_playtree {
6935 # If $includedirty, we have to build the source package from the
6936 # working tree, not a playtree, so that uncommitted changes are
6937 # included (copying or hardlinking them into the playtree could
6940 # Note that if we are building a source package in split brain
6941 # mode we do not support including uncommitted changes, because
6942 # that makes quilt fixup too hard. I.e. ($made_split_brain && (dgit is
6943 # building a source package)) => !$includedirty
6944 return !$includedirty;
6948 $sourcechanges = changespat $version,'source';
6950 unlink "$buildproductsdir/$sourcechanges" or $!==ENOENT
6951 or fail f_ "remove %s: %s", $sourcechanges, $!;
6953 # confess unless !!$made_split_brain == do_split_brain();
6955 my @cmd = (@dpkgsource, qw(-b --));
6957 if (building_source_in_playtree()) {
6959 my $headref = git_rev_parse('HEAD');
6960 # If we are in split brain, there is already a playtree with
6961 # the thing we should package into a .dsc (thanks to quilt
6962 # fixup). If not, make a playtree
6963 prep_ud() unless $made_split_brain;
6964 changedir $playground;
6965 unless ($made_split_brain) {
6966 my $upstreamversion = upstreamversion $version;
6967 unpack_playtree_linkorigs($upstreamversion, sub { });
6968 unpack_playtree_need_cd_work($headref);
6972 $leafdir = basename $maindir;
6974 if ($buildproductsdir ne '..') {
6975 # Well, we are going to run dpkg-source -b which consumes
6976 # origs from .. and generates output there. To make this
6977 # work when the bpd is not .. , we would have to (i) link
6978 # origs from bpd to .. , (ii) check for files that
6979 # dpkg-source -b would/might overwrite, and afterwards
6980 # (iii) move all the outputs back to the bpd (iv) except
6981 # for the origs which should be deleted from .. if they
6982 # weren't there beforehand. And if there is an error and
6983 # we don't run to completion we would necessarily leave a
6984 # mess. This is too much. The real way to fix this
6985 # is for dpkg-source to have bpd support.
6986 confess unless $includedirty;
6988 "--include-dirty not supported with --build-products-dir, sorry";
6993 runcmd_ordryrun_local @cmd, $leafdir;
6996 runcmd_ordryrun_local qw(sh -ec),
6997 'exec >../$1; shift; exec "$@"','x', $sourcechanges,
6998 @dpkggenchanges, qw(-S), changesopts();
7001 printdebug "moving $dscfn, $sourcechanges, etc. to ".bpd_abs()."\n";
7002 $dsc = parsecontrol($dscfn, "source package");
7006 printdebug " renaming ($why) $l\n";
7007 rename_link_xf 0, "$l", bpd_abs()."/$l"
7008 or fail f_ "put in place new built file (%s): %s", $l, $@;
7010 foreach my $l (split /\n/, getfield $dsc, 'Files') {
7011 $l =~ m/\S+$/ or next;
7014 $mv->('dsc', $dscfn);
7015 $mv->('changes', $sourcechanges);
7020 sub cmd_build_source {
7021 badusage __ "build-source takes no additional arguments" if @ARGV;
7022 build_prep(WANTSRC_SOURCE);
7024 maybe_unapply_patches_again();
7025 printdone f_ "source built, results in %s and %s",
7026 $dscfn, $sourcechanges;
7029 sub cmd_push_source {
7032 "dgit push-source: --include-dirty/--ignore-dirty does not make".
7033 "sense with push-source!"
7035 build_check_quilt_splitbrain();
7037 my $changes = parsecontrol("$buildproductsdir/$changesfile",
7038 __ "source changes file");
7039 unless (test_source_only_changes($changes)) {
7040 fail __ "user-specified changes file is not source-only";
7043 # Building a source package is very fast, so just do it
7045 confess "er, patches are applied dirtily but shouldn't be.."
7046 if $patches_applied_dirtily;
7047 $changesfile = $sourcechanges;
7052 sub binary_builder {
7053 my ($bbuilder, $pbmc_msg, @args) = @_;
7054 build_prep(WANTSRC_SOURCE);
7056 midbuild_checkchanges();
7059 stat_exists $dscfn or fail f_
7060 "%s (in build products dir): %s", $dscfn, $!;
7061 stat_exists $sourcechanges or fail f_
7062 "%s (in build products dir): %s", $sourcechanges, $!;
7064 runcmd_ordryrun_local @$bbuilder, @args;
7066 maybe_unapply_patches_again();
7068 postbuild_mergechanges($pbmc_msg);
7074 binary_builder(\@sbuild, (__ <<END), qw(-d), $isuite, @ARGV, $dscfn);
7075 perhaps you need to pass -A ? (sbuild's default is to build only
7076 arch-specific binaries; dgit 1.4 used to override that.)
7081 my ($pbuilder) = @_;
7083 # @ARGV is allowed to contain only things that should be passed to
7084 # pbuilder under debbuildopts; just massage those
7085 my $wantsrc = massage_dbp_args \@ARGV;
7087 "you asked for a builder but your debbuildopts didn't ask for".
7088 " any binaries -- is this really what you meant?"
7089 unless $wantsrc & WANTSRC_BUILDER;
7091 "we must build a .dsc to pass to the builder but your debbuiltopts".
7092 " forbids the building of a source package; cannot continue"
7093 unless $wantsrc & WANTSRC_SOURCE;
7094 # We do not want to include the verb "build" in @pbuilder because
7095 # the user can customise @pbuilder and they shouldn't be required
7096 # to include "build" in their customised value. However, if the
7097 # user passes any additional args to pbuilder using the dgit
7098 # option --pbuilder:foo, such args need to come after the "build"
7099 # verb. opts_opt_multi_cmd does all of that.
7100 binary_builder([opts_opt_multi_cmd ["build"], @$pbuilder], undef,
7101 qw(--debbuildopts), "@ARGV", qw(--distribution), $isuite,
7106 pbuilder(\@pbuilder);
7109 sub cmd_cowbuilder {
7110 pbuilder(\@cowbuilder);
7113 sub cmd_quilt_fixup {
7114 badusage "incorrect arguments to dgit quilt-fixup" if @ARGV;
7117 build_maybe_quilt_fixup();
7120 sub cmd_print_unapplied_treeish {
7121 badusage __ "incorrect arguments to dgit print-unapplied-treeish"
7123 my $headref = git_rev_parse('HEAD');
7124 my $clogp = commit_getclogp $headref;
7125 $package = getfield $clogp, 'Source';
7126 $version = getfield $clogp, 'Version';
7127 $isuite = getfield $clogp, 'Distribution';
7128 $csuite = $isuite; # we want this to be offline!
7132 changedir $playground;
7133 my $uv = upstreamversion $version;
7134 my $u = quilt_fakedsc2unapplied($headref, $uv);
7135 print $u, "\n" or confess "$!";
7138 sub import_dsc_result {
7139 my ($dstref, $newhash, $what_log, $what_msg) = @_;
7140 my @cmd = (git_update_ref_cmd $what_log, $dstref, $newhash);
7142 check_gitattrs($newhash, __ "source tree");
7144 progress f_ "dgit: import-dsc: %s", $what_msg;
7147 sub cmd_import_dsc {
7151 last unless $ARGV[0] =~ m/^-/;
7154 if (m/^--require-valid-signature$/) {
7157 badusage f_ "unknown dgit import-dsc sub-option \`%s'", $_;
7161 badusage __ "usage: dgit import-dsc .../PATH/TO/.DSC BRANCH"
7163 my ($dscfn, $dstbranch) = @ARGV;
7165 badusage __ "dry run makes no sense with import-dsc"
7168 my $force = $dstbranch =~ s/^\+// ? +1 :
7169 $dstbranch =~ s/^\.\.// ? -1 :
7171 my $info = $force ? " $&" : '';
7172 $info = "$dscfn$info";
7174 my $specbranch = $dstbranch;
7175 $dstbranch = "refs/heads/$dstbranch" unless $dstbranch =~ m#^refs/#;
7176 $dstbranch = cmdoutput @git, qw(check-ref-format --normalize), $dstbranch;
7178 my @symcmd = (@git, qw(symbolic-ref -q HEAD));
7179 my $chead = cmdoutput_errok @symcmd;
7180 defined $chead or $?==256 or failedcmd @symcmd;
7182 fail f_ "%s is checked out - will not update it", $dstbranch
7183 if defined $chead and $chead eq $dstbranch;
7185 my $oldhash = git_get_ref $dstbranch;
7187 open D, "<", $dscfn or fail f_ "open import .dsc (%s): %s", $dscfn, $!;
7188 $dscdata = do { local $/ = undef; <D>; };
7189 D->error and fail f_ "read %s: %s", $dscfn, $!;
7192 # we don't normally need this so import it here
7193 use Dpkg::Source::Package;
7194 my $dp = new Dpkg::Source::Package filename => $dscfn,
7195 require_valid_signature => $needsig;
7197 local $SIG{__WARN__} = sub {
7199 return unless $needsig;
7200 fail __ "import-dsc signature check failed";
7202 if (!$dp->is_signed()) {
7203 warn f_ "%s: warning: importing unsigned .dsc\n", $us;
7205 my $r = $dp->check_signature();
7206 confess "->check_signature => $r" if $needsig && $r;
7212 $package = getfield $dsc, 'Source';
7214 parse_dsc_field($dsc, __ "Dgit metadata in .dsc")
7215 unless forceing [qw(import-dsc-with-dgit-field)];
7216 parse_dsc_field_def_dsc_distro();
7218 $isuite = 'DGIT-IMPORT-DSC';
7219 $idistro //= $dsc_distro;
7223 if (defined $dsc_hash) {
7225 "dgit: import-dsc of .dsc with Dgit field, using git hash";
7226 resolve_dsc_field_commit undef, undef;
7228 if (defined $dsc_hash) {
7229 my @cmd = (qw(sh -ec),
7230 "echo $dsc_hash | git cat-file --batch-check");
7231 my $objgot = cmdoutput @cmd;
7232 if ($objgot =~ m#^\w+ missing\b#) {
7233 fail f_ <<END, $dsc_hash
7234 .dsc contains Dgit field referring to object %s
7235 Your git tree does not have that object. Try `git fetch' from a
7236 plausible server (browse.dgit.d.o? salsa?), and try the import-dsc again.
7239 if ($oldhash && !is_fast_fwd $oldhash, $dsc_hash) {
7241 progress __ "Not fast forward, forced update.";
7243 fail f_ "Not fast forward to %s", $dsc_hash;
7246 import_dsc_result $dstbranch, $dsc_hash,
7247 "dgit import-dsc (Dgit): $info",
7248 f_ "updated git ref %s", $dstbranch;
7252 fail f_ <<END, $dstbranch, $specbranch, $specbranch
7253 Branch %s already exists
7254 Specify ..%s for a pseudo-merge, binding in existing history
7255 Specify +%s to overwrite, discarding existing history
7257 if $oldhash && !$force;
7259 my @dfi = dsc_files_info();
7260 foreach my $fi (@dfi) {
7261 my $f = $fi->{Filename};
7262 # We transfer all the pieces of the dsc to the bpd, not just
7263 # origs. This is by analogy with dgit fetch, which wants to
7264 # keep them somewhere to avoid downloading them again.
7265 # We make symlinks, though. If the user wants copies, then
7266 # they can copy the parts of the dsc to the bpd using dcmd,
7268 my $here = "$buildproductsdir/$f";
7273 fail f_ "lstat %s works but stat gives %s !", $here, $!;
7275 fail f_ "stat %s: %s", $here, $! unless $! == ENOENT;
7276 printdebug "not in bpd, $f ...\n";
7277 # $f does not exist in bpd, we need to transfer it
7279 $there =~ s{[^/]+$}{$f} or confess "$there ?";
7280 # $there is file we want, relative to user's cwd, or abs
7281 printdebug "not in bpd, $f, test $there ...\n";
7282 stat $there or fail f_
7283 "import %s requires %s, but: %s", $dscfn, $there, $!;
7284 if ($there =~ m#^(?:\./+)?\.\./+#) {
7285 # $there is relative to user's cwd
7286 my $there_from_parent = $';
7287 if ($buildproductsdir !~ m{^/}) {
7288 # abs2rel, despite its name, can take two relative paths
7289 $there = File::Spec->abs2rel($there,$buildproductsdir);
7290 # now $there is relative to bpd, great
7291 printdebug "not in bpd, $f, abs2rel, $there ...\n";
7293 $there = (dirname $maindir)."/$there_from_parent";
7294 # now $there is absoute
7295 printdebug "not in bpd, $f, rel2rel, $there ...\n";
7297 } elsif ($there =~ m#^/#) {
7298 # $there is absolute already
7299 printdebug "not in bpd, $f, abs, $there ...\n";
7302 "cannot import %s which seems to be inside working tree!",
7305 symlink $there, $here or fail f_
7306 "symlink %s to %s: %s", $there, $here, $!;
7307 progress f_ "made symlink %s -> %s", $here, $there;
7308 # print STDERR Dumper($fi);
7310 my @mergeinputs = generate_commits_from_dsc();
7311 die unless @mergeinputs == 1;
7313 my $newhash = $mergeinputs[0]{Commit};
7318 "Import, forced update - synthetic orphan git history.";
7319 } elsif ($force < 0) {
7320 progress __ "Import, merging.";
7321 my $tree = cmdoutput @git, qw(rev-parse), "$newhash:";
7322 my $version = getfield $dsc, 'Version';
7323 my $clogp = commit_getclogp $newhash;
7324 my $authline = clogp_authline $clogp;
7325 $newhash = hash_commit_text <<ENDU
7333 .(f_ <<END, $package, $version, $dstbranch);
7334 Merge %s (%s) import into %s
7337 die; # caught earlier
7341 import_dsc_result $dstbranch, $newhash,
7342 "dgit import-dsc: $info",
7343 f_ "results are in git ref %s", $dstbranch;
7346 sub pre_archive_api_query () {
7347 not_necessarily_a_tree();
7349 sub cmd_archive_api_query {
7350 badusage __ "need only 1 subpath argument" unless @ARGV==1;
7351 my ($subpath) = @ARGV;
7352 local $isuite = 'DGIT-API-QUERY-CMD';
7353 my $json = api_query_raw $subpath;
7354 print $json or die "$!";
7357 sub repos_server_url () {
7358 $package = '_dgit-repos-server';
7359 local $access_forpush = 1;
7360 local $isuite = 'DGIT-REPOS-SERVER';
7361 my $url = access_giturl();
7364 sub pre_clone_dgit_repos_server () {
7365 not_necessarily_a_tree();
7367 sub cmd_clone_dgit_repos_server {
7368 badusage __ "need destination argument" unless @ARGV==1;
7369 my ($destdir) = @ARGV;
7370 my $url = repos_server_url();
7371 my @cmd = (@git, qw(clone), $url, $destdir);
7373 exec @cmd or fail f_ "exec git clone: %s\n", $!;
7376 sub pre_print_dgit_repos_server_source_url () {
7377 not_necessarily_a_tree();
7379 sub cmd_print_dgit_repos_server_source_url {
7381 "no arguments allowed to dgit print-dgit-repos-server-source-url"
7383 my $url = repos_server_url();
7384 print $url, "\n" or confess "$!";
7387 sub pre_print_dpkg_source_ignores {
7388 not_necessarily_a_tree();
7390 sub cmd_print_dpkg_source_ignores {
7392 "no arguments allowed to dgit print-dpkg-source-ignores"
7394 print "@dpkg_source_ignores\n" or confess "$!";
7397 sub cmd_setup_mergechangelogs {
7398 badusage __ "no arguments allowed to dgit setup-mergechangelogs"
7400 local $isuite = 'DGIT-SETUP-TREE';
7401 setup_mergechangelogs(1);
7404 sub cmd_setup_useremail {
7405 badusage __ "no arguments allowed to dgit setup-useremail" if @ARGV;
7406 local $isuite = 'DGIT-SETUP-TREE';
7410 sub cmd_setup_gitattributes {
7411 badusage __ "no arguments allowed to dgit setup-useremail" if @ARGV;
7412 local $isuite = 'DGIT-SETUP-TREE';
7416 sub cmd_setup_new_tree {
7417 badusage __ "no arguments allowed to dgit setup-tree" if @ARGV;
7418 local $isuite = 'DGIT-SETUP-TREE';
7422 #---------- argument parsing and main program ----------
7425 print "dgit version $our_version\n" or confess "$!";
7429 our (%valopts_long, %valopts_short);
7430 our (%funcopts_long);
7432 our (@modeopt_cfgs);
7434 sub defvalopt ($$$$) {
7435 my ($long,$short,$val_re,$how) = @_;
7436 my $oi = { Long => $long, Short => $short, Re => $val_re, How => $how };
7437 $valopts_long{$long} = $oi;
7438 $valopts_short{$short} = $oi;
7439 # $how subref should:
7440 # do whatever assignemnt or thing it likes with $_[0]
7441 # if the option should not be passed on to remote, @rvalopts=()
7442 # or $how can be a scalar ref, meaning simply assign the value
7445 defvalopt '--since-version', '-v', '[^_]+|_', \$changes_since_version;
7446 defvalopt '--distro', '-d', '.+', \$idistro;
7447 defvalopt '', '-k', '.+', \$keyid;
7448 defvalopt '--existing-package','', '.*', \$existing_package;
7449 defvalopt '--build-products-dir','','.*', \$buildproductsdir;
7450 defvalopt '--clean', '', $cleanmode_re, \$cleanmode;
7451 defvalopt '--package', '-p', $package_re, \$package;
7452 defvalopt '--quilt', '', $quilt_modes_re, \$quilt_mode;
7454 defvalopt '', '-C', '.+', sub {
7455 ($changesfile) = (@_);
7456 if ($changesfile =~ s#^(.*)/##) {
7457 $buildproductsdir = $1;
7461 defvalopt '--initiator-tempdir','','.*', sub {
7462 ($initiator_tempdir) = (@_);
7463 $initiator_tempdir =~ m#^/# or
7464 badusage __ "--initiator-tempdir must be used specify an".
7465 " absolute, not relative, directory."
7468 sub defoptmodes ($@) {
7469 my ($varref, $cfgkey, $default, %optmap) = @_;
7471 while (my ($opt,$val) = each %optmap) {
7472 $funcopts_long{$opt} = sub { $$varref = $val; };
7473 $permit{$val} = $val;
7475 push @modeopt_cfgs, {
7478 Default => $default,
7483 defoptmodes \$dodep14tag, qw( dep14tag want
7486 --always-dep14tag always );
7491 if (defined $ENV{'DGIT_SSH'}) {
7492 @ssh = string_to_ssh $ENV{'DGIT_SSH'};
7493 } elsif (defined $ENV{'GIT_SSH'}) {
7494 @ssh = ($ENV{'GIT_SSH'});
7502 if (!defined $val) {
7503 badusage f_ "%s needs a value", $what unless @ARGV;
7505 push @rvalopts, $val;
7507 badusage f_ "bad value \`%s' for %s", $val, $what unless
7508 $val =~ m/^$oi->{Re}$(?!\n)/s;
7509 my $how = $oi->{How};
7510 if (ref($how) eq 'SCALAR') {
7515 push @ropts, @rvalopts;
7519 last unless $ARGV[0] =~ m/^-/;
7523 if (m/^--dry-run$/) {
7526 } elsif (m/^--damp-run$/) {
7529 } elsif (m/^--no-sign$/) {
7532 } elsif (m/^--help$/) {
7534 } elsif (m/^--version$/) {
7536 } elsif (m/^--new$/) {
7539 } elsif (m/^--([-0-9a-z]+)=(.+)/s &&
7540 ($om = $opts_opt_map{$1}) &&
7544 } elsif (m/^--([-0-9a-z]+):(.*)/s &&
7545 !$opts_opt_cmdonly{$1} &&
7546 ($om = $opts_opt_map{$1})) {
7549 } elsif (m/^--([-0-9a-z]+)\!:(.*)/s &&
7550 !$opts_opt_cmdonly{$1} &&
7551 ($om = $opts_opt_map{$1})) {
7553 my $cmd = shift @$om;
7554 @$om = ($cmd, grep { $_ ne $2 } @$om);
7555 } elsif (m/^--($quilt_options_re)$/s) {
7556 push @ropts, "--quilt=$1";
7558 } elsif (m/^--(?:ignore|include)-dirty$/s) {
7561 } elsif (m/^--no-quilt-fixup$/s) {
7563 $quilt_mode = 'nocheck';
7564 } elsif (m/^--no-rm-on-error$/s) {
7567 } elsif (m/^--no-chase-dsc-distro$/s) {
7569 $chase_dsc_distro = 0;
7570 } elsif (m/^--overwrite$/s) {
7572 $overwrite_version = '';
7573 } elsif (m/^--split-(?:view|brain)$/s) {
7575 $splitview_mode = 'always';
7576 } elsif (m/^--split-(?:view|brain)=($splitview_modes_re)$/s) {
7578 $splitview_mode = $1;
7579 } elsif (m/^--overwrite=(.+)$/s) {
7581 $overwrite_version = $1;
7582 } elsif (m/^--delayed=(\d+)$/s) {
7585 } elsif (m/^--upstream-commitish=(.+)$/s) {
7587 $quilt_upstream_commitish = $1;
7588 } elsif (m/^--save-(dgit-view)=(.+)$/s ||
7589 m/^--(dgit-view)-save=(.+)$/s
7591 my ($k,$v) = ($1,$2);
7593 $v =~ s#^(?!refs/)#refs/heads/#;
7594 $internal_object_save{$k} = $v;
7595 } elsif (m/^--(no-)?rm-old-changes$/s) {
7598 } elsif (m/^--deliberately-($deliberately_re)$/s) {
7600 push @deliberatelies, $&;
7601 } elsif (m/^--force-(.*)/ && defined $forceopts{$1}) {
7605 } elsif (m/^--force-/) {
7607 f_ "%s: warning: ignoring unknown force option %s\n",
7610 } elsif (m/^--for-push$/s) {
7612 $access_forpush = 1;
7613 } elsif (m/^--config-lookup-explode=(.+)$/s) {
7614 # undocumented, for testing
7616 $gitcfgs{cmdline}{$1} = 'CONFIG-LOOKUP-EXPLODE';
7617 # ^ it's supposed to be an array ref
7618 } elsif (m/^(--[-0-9a-z]+)(=|$)/ && ($oi = $valopts_long{$1})) {
7619 $val = $2 ? $' : undef; #';
7620 $valopt->($oi->{Long});
7621 } elsif ($funcopts_long{$_}) {
7623 $funcopts_long{$_}();
7625 badusage f_ "unknown long option \`%s'", $_;
7632 } elsif (s/^-L/-/) {
7635 } elsif (s/^-h/-/) {
7637 } elsif (s/^-D/-/) {
7641 } elsif (s/^-N/-/) {
7646 push @changesopts, $_;
7648 } elsif (s/^-wn$//s) {
7650 $cleanmode = 'none';
7651 } elsif (s/^-wg(f?)(a?)$//s) {
7654 $cleanmode .= '-ff' if $1;
7655 $cleanmode .= ',always' if $2;
7656 } elsif (s/^-wd(d?)([na]?)$//s) {
7658 $cleanmode = 'dpkg-source';
7659 $cleanmode .= '-d' if $1;
7660 $cleanmode .= ',no-check' if $2 eq 'n';
7661 $cleanmode .= ',all-check' if $2 eq 'a';
7662 } elsif (s/^-wc$//s) {
7664 $cleanmode = 'check';
7665 } elsif (s/^-wci$//s) {
7667 $cleanmode = 'check,ignores';
7668 } elsif (s/^-c([^=]*)\=(.*)$//s) {
7669 push @git, '-c', $&;
7670 $gitcfgs{cmdline}{$1} = [ $2 ];
7671 } elsif (s/^-c([^=]+)$//s) {
7672 push @git, '-c', $&;
7673 $gitcfgs{cmdline}{$1} = [ 'true' ];
7674 } elsif (m/^-[a-zA-Z]/ && ($oi = $valopts_short{$&})) {
7676 $val = undef unless length $val;
7677 $valopt->($oi->{Short});
7680 badusage f_ "unknown short option \`%s'", $_;
7687 sub check_env_sanity () {
7688 my $blocked = new POSIX::SigSet;
7689 sigprocmask SIG_UNBLOCK, $blocked, $blocked or confess "$!";
7692 foreach my $name (qw(PIPE CHLD)) {
7693 my $signame = "SIG$name";
7694 my $signum = eval "POSIX::$signame" // die;
7695 die f_ "%s is set to something other than SIG_DFL\n",
7697 if defined $SIG{$name} and $SIG{$name} ne 'DEFAULT';
7698 $blocked->ismember($signum) and
7699 die f_ "%s is blocked\n", $signame;
7705 On entry to dgit, %s
7706 This is a bug produced by something in your execution environment.
7712 sub parseopts_late_defaults () {
7713 $isuite //= cfg("dgit-distro.$idistro.default-suite", 'RETURN-UNDEF')
7714 if defined $idistro;
7715 $isuite //= cfg('dgit.default.default-suite');
7717 foreach my $k (keys %opts_opt_map) {
7718 my $om = $opts_opt_map{$k};
7720 my $v = access_cfg("cmd-$k", 'RETURN-UNDEF');
7722 badcfg f_ "cannot set command for %s", $k
7723 unless length $om->[0];
7727 foreach my $c (access_cfg_cfgs("opts-$k")) {
7729 map { $_ ? @$_ : () }
7730 map { $gitcfgs{$_}{$c} }
7731 reverse @gitcfgsources;
7732 printdebug "CL $c ", (join " ", map { shellquote } @vl),
7733 "\n" if $debuglevel >= 4;
7735 badcfg f_ "cannot configure options for %s", $k
7736 if $opts_opt_cmdonly{$k};
7737 my $insertpos = $opts_cfg_insertpos{$k};
7738 @$om = ( @$om[0..$insertpos-1],
7740 @$om[$insertpos..$#$om] );
7744 if (!defined $rmchanges) {
7745 local $access_forpush;
7746 $rmchanges = access_cfg_bool(0, 'rm-old-changes');
7749 if (!defined $quilt_mode) {
7750 local $access_forpush;
7751 $quilt_mode = cfg('dgit.force.quilt-mode', 'RETURN-UNDEF')
7752 // access_cfg('quilt-mode', 'RETURN-UNDEF')
7754 $quilt_mode =~ m/^($quilt_modes_re)$/
7755 or badcfg f_ "unknown quilt-mode \`%s'", $quilt_mode;
7758 $quilt_mode =~ s/^(baredebian)\+git$/$1/;
7760 foreach my $moc (@modeopt_cfgs) {
7761 local $access_forpush;
7762 my $vr = $moc->{Var};
7763 next if defined $$vr;
7764 $$vr = access_cfg($moc->{Key}, 'RETURN-UNDEF') // $moc->{Default};
7765 my $v = $moc->{Vals}{$$vr};
7766 badcfg f_ "unknown %s setting \`%s'", $moc->{Key}, $$vr
7772 local $access_forpush;
7773 default_from_access_cfg(\$cleanmode, 'clean-mode', 'dpkg-source',
7777 $buildproductsdir //= access_cfg('build-products-dir', 'RETURN-UNDEF');
7778 $buildproductsdir //= '..';
7779 $bpd_glob = $buildproductsdir;
7780 $bpd_glob =~ s#[][\\{}*?~]#\\$&#g;
7783 setlocale(LC_MESSAGES, "");
7786 if ($ENV{$fakeeditorenv}) {
7788 quilt_fixup_editor();
7794 print STDERR __ "DRY RUN ONLY\n" if $dryrun_level > 1;
7795 print STDERR __ "DAMP RUN - WILL MAKE LOCAL (UNSIGNED) CHANGES\n"
7796 if $dryrun_level == 1;
7798 print STDERR __ $helpmsg or confess "$!";
7801 $cmd = $subcommand = shift @ARGV;
7804 my $pre_fn = ${*::}{"pre_$cmd"};
7805 $pre_fn->() if $pre_fn;
7807 if ($invoked_in_git_tree) {
7808 changedir_git_toplevel();
7813 my $fn = ${*::}{"cmd_$cmd"};
7814 $fn or badusage f_ "unknown operation %s", $cmd;