3 # Integration between git and Debian-style archives
5 # Copyright (C)2013-2018 Ian Jackson
6 # Copyright (C)2017-2018 Sean Whitton
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 END { $? = $Debian::Dgit::ExitStatus::desired // -1; };
22 use Debian::Dgit::ExitStatus;
23 use Debian::Dgit::I18n;
27 use Debian::Dgit qw(:DEFAULT :playground);
33 use Dpkg::Control::Hash;
35 use File::Temp qw(tempdir);
38 use Dpkg::Compression;
39 use Dpkg::Compression::Process;
45 use List::MoreUtils qw(pairwise);
46 use Text::Glob qw(match_glob);
47 use Fcntl qw(:DEFAULT :flock);
52 our $our_version = 'UNRELEASED'; ###substituted###
53 our $absurdity = undef; ###substituted###
55 our @rpushprotovsn_support = qw(4 3 2); # 4 is new tag format
66 our $dryrun_level = 0;
68 our $buildproductsdir;
71 our $includedirty = 0;
75 our $existing_package = 'dpkg';
77 our $changes_since_version;
79 our $overwrite_version; # undef: not specified; '': check changelog
81 our $quilt_modes_re = 'linear|smash|auto|nofix|nocheck|gbp|dpm|unapplied';
83 our %internal_object_save;
84 our $we_are_responder;
85 our $we_are_initiator;
86 our $initiator_tempdir;
87 our $patches_applied_dirtily = 00;
91 our $chase_dsc_distro=1;
93 our %forceopts = map { $_=>0 }
94 qw(unrepresentable unsupported-source-format
95 dsc-changes-mismatch changes-origs-exactly
96 uploading-binaries uploading-source-only
97 import-gitapply-absurd
98 import-gitapply-no-absurd
99 import-dsc-with-dgit-field);
101 our %format_ok = map { $_=>1 } ("1.0","3.0 (native)","3.0 (quilt)");
103 our $suite_re = '[-+.0-9a-z]+';
104 our $cleanmode_re = qr{(?: dpkg-source (?: -d )? (?: ,no-check | ,all-check )?
105 | (?: git | git-ff ) (?: ,always )?
106 | check (?: ,ignores )?
110 our $git_authline_re = '^([^<>]+) \<(\S+)\> (\d+ [-+]\d+)$';
111 our $splitbraincache = 'dgit-intern/quilt-cache';
112 our $rewritemap = 'dgit-rewrite/map';
114 our @dpkg_source_ignores = qw(-i(?:^|/)\.git(?:/|$) -I.git);
116 our (@git) = qw(git);
117 our (@dget) = qw(dget);
118 our (@curl) = (qw(curl --proto-redir), '-all,http,https', qw(-L));
119 our (@dput) = qw(dput);
120 our (@debsign) = qw(debsign);
121 our (@gpg) = qw(gpg);
122 our (@sbuild) = (qw(sbuild --no-source));
124 our (@dgit) = qw(dgit);
125 our (@git_debrebase) = qw(git-debrebase);
126 our (@aptget) = qw(apt-get);
127 our (@aptcache) = qw(apt-cache);
128 our (@dpkgbuildpackage) = (qw(dpkg-buildpackage), @dpkg_source_ignores);
129 our (@dpkgsource) = (qw(dpkg-source), @dpkg_source_ignores);
130 our (@dpkggenchanges) = qw(dpkg-genchanges);
131 our (@mergechanges) = qw(mergechanges -f);
132 our (@gbp_build) = ('');
133 our (@gbp_pq) = ('gbp pq');
134 our (@changesopts) = ('');
135 our (@pbuilder) = ("sudo -E pbuilder","--no-source-only-changes");
136 our (@cowbuilder) = ("sudo -E cowbuilder","--no-source-only-changes");
138 our %opts_opt_map = ('dget' => \@dget, # accept for compatibility
141 'debsign' => \@debsign,
143 'sbuild' => \@sbuild,
147 'git-debrebase' => \@git_debrebase,
148 'apt-get' => \@aptget,
149 'apt-cache' => \@aptcache,
150 'dpkg-source' => \@dpkgsource,
151 'dpkg-buildpackage' => \@dpkgbuildpackage,
152 'dpkg-genchanges' => \@dpkggenchanges,
153 'gbp-build' => \@gbp_build,
154 'gbp-pq' => \@gbp_pq,
155 'ch' => \@changesopts,
156 'mergechanges' => \@mergechanges,
157 'pbuilder' => \@pbuilder,
158 'cowbuilder' => \@cowbuilder);
160 our %opts_opt_cmdonly = ('gpg' => 1, 'git' => 1);
161 our %opts_cfg_insertpos = map {
163 scalar @{ $opts_opt_map{$_} }
164 } keys %opts_opt_map;
166 sub parseopts_late_defaults();
167 sub setup_gitattrs(;$);
168 sub check_gitattrs($$);
175 our $supplementary_message = '';
176 our $split_brain = 0;
180 return unless forkcheck_mainprocess();
181 print STDERR "! $_\n" foreach $supplementary_message =~ m/^.+$/mg;
184 our $remotename = 'dgit';
185 our @ourdscfield = qw(Dgit Vcs-Dgit-Master);
189 if (!defined $absurdity) {
191 $absurdity =~ s{/[^/]+$}{/absurd} or die;
195 my ($v,$distro) = @_;
196 return $tagformatfn->($v, $distro);
199 sub madformat ($) { $_[0] eq '3.0 (quilt)' }
201 sub lbranch () { return "$branchprefix/$csuite"; }
202 my $lbranch_re = '^refs/heads/'.$branchprefix.'/([^/.]+)$';
203 sub lref () { return "refs/heads/".lbranch(); }
204 sub lrref () { return "refs/remotes/$remotename/".server_branch($csuite); }
205 sub rrref () { return server_ref($csuite); }
208 my ($vsn, $sfx) = @_;
209 return &source_file_leafname($package, $vsn, $sfx);
211 sub is_orig_file_of_vsn ($$) {
212 my ($f, $upstreamvsn) = @_;
213 return is_orig_file_of_p_v($f, $package, $upstreamvsn);
218 return srcfn($vsn,".dsc");
221 sub changespat ($;$) {
222 my ($vsn, $arch) = @_;
223 return "${package}_".(stripepoch $vsn)."_".($arch//'*').".changes";
232 return unless forkcheck_mainprocess();
233 foreach my $f (@end) {
235 print STDERR "$us: cleanup: $@" if length $@;
240 print STDERR f_ "%s: invalid configuration: %s\n", $us, "@_";
244 sub forceable_fail ($$) {
245 my ($forceoptsl, $msg) = @_;
246 fail $msg unless grep { $forceopts{$_} } @$forceoptsl;
247 print STDERR +(__ "warning: overriding problem due to --force:\n"). $msg;
251 my ($forceoptsl) = @_;
252 my @got = grep { $forceopts{$_} } @$forceoptsl;
253 return 0 unless @got;
255 "warning: skipping checks or functionality due to --force-%s\n",
259 sub no_such_package () {
260 print STDERR f_ "%s: package %s does not exist in suite %s\n",
261 $us, $package, $isuite;
265 sub deliberately ($) {
267 return !!grep { $_ eq "--deliberately-$enquiry" } @deliberatelies;
270 sub deliberately_not_fast_forward () {
271 foreach (qw(not-fast-forward fresh-repo)) {
272 return 1 if deliberately($_) || deliberately("TEST-dgit-only-$_");
276 sub quiltmode_splitbrain () {
277 $quilt_mode =~ m/gbp|dpm|unapplied/;
280 sub opts_opt_multi_cmd {
283 push @cmd, split /\s+/, shift @_;
290 return opts_opt_multi_cmd [], @gbp_pq;
293 sub dgit_privdir () {
294 our $dgit_privdir_made //= ensure_a_playground 'dgit';
298 my $r = $buildproductsdir;
299 $r = "$maindir/$r" unless $r =~ m{^/};
303 sub get_tree_of_commit ($) {
304 my ($commitish) = @_;
305 my $cdata = cmdoutput @git, qw(cat-file commit), $commitish;
306 $cdata =~ m/\n\n/; $cdata = $`;
307 $cdata =~ m/^tree (\w+)$/m or confess "cdata $cdata ?";
311 sub branch_gdr_info ($$) {
312 my ($symref, $head) = @_;
313 my ($status, $msg, $current, $ffq_prev, $gdrlast) =
314 gdr_ffq_prev_branchinfo($symref);
315 return () unless $status eq 'branch';
316 $ffq_prev = git_get_ref $ffq_prev;
317 $gdrlast = git_get_ref $gdrlast;
318 $gdrlast &&= is_fast_fwd $gdrlast, $head;
319 return ($ffq_prev, $gdrlast);
322 sub branch_is_gdr_unstitched_ff ($$$) {
323 my ($symref, $head, $ancestor) = @_;
324 my ($ffq_prev, $gdrlast) = branch_gdr_info($symref, $head);
325 return 0 unless $ffq_prev;
326 return 0 unless !defined $ancestor or is_fast_fwd $ancestor, $ffq_prev;
330 sub branch_is_gdr ($) {
332 # This is quite like git-debrebase's keycommits.
333 # We have our own implementation because:
334 # - our algorighm can do fewer tests so is faster
335 # - it saves testing to see if gdr is installed
337 # NB we use this jsut for deciding whether to run gdr make-patches
338 # Before reusing this algorithm for somthing else, its
339 # suitability should be reconsidered.
342 local $Debian::Dgit::debugcmd_when_debuglevel = 3;
343 printdebug "branch_is_gdr $head...\n";
344 my $get_patches = sub {
345 my $t = git_cat_file "$_[0]:debian/patches", [qw(missing tree)];
348 my $tip_patches = $get_patches->($head);
351 my $cdata = git_cat_file $walk, 'commit';
352 my ($hdrs,$msg) = $cdata =~ m{\n\n} ? ($`,$') : ($cdata,'');
353 if ($msg =~ m{^\[git-debrebase\ (
354 anchor | changelog | make-patches |
355 merged-breakwater | pseudomerge
357 # no need to analyse this - it's sufficient
358 # (gdr classifications: Anchor, MergedBreakwaters)
359 # (made by gdr: Pseudomerge, Changelog)
360 printdebug "branch_is_gdr $walk gdr $1 YES\n";
363 my @parents = ($hdrs =~ m/^parent (\w+)$/gm);
365 my $walk_tree = get_tree_of_commit $walk;
366 foreach my $p (@parents) {
367 my $p_tree = get_tree_of_commit $p;
368 if ($p_tree eq $walk_tree) { # pseudomerge contriburor
369 # (gdr classification: Pseudomerge; not made by gdr)
370 printdebug "branch_is_gdr $walk unmarked pseudomerge\n"
376 # some other non-gdr merge
377 # (gdr classification: VanillaMerge, DgitImportUnpatched, ?)
378 printdebug "branch_is_gdr $walk ?-2-merge NO\n";
382 # (gdr classification: ?)
383 printdebug "branch_is_gdr $walk ?-octopus NO\n";
387 printdebug "branch_is_gdr $walk origin\n";
390 if ($get_patches->($walk) ne $tip_patches) {
391 # Our parent added, removed, or edited patches, and wasn't
392 # a gdr make-patches commit. gdr make-patches probably
393 # won't do that well, then.
394 # (gdr classification of parent: AddPatches or ?)
395 printdebug "branch_is_gdr $walk ?-patches NO\n";
398 if ($tip_patches eq '' and
399 !defined git_cat_file "$walk:debian") {
400 # (gdr classification of parent: BreakwaterStart
401 printdebug "branch_is_gdr $walk unmarked BreakwaterStart YES\n";
404 # (gdr classification: Upstream Packaging Mixed Changelog)
405 printdebug "branch_is_gdr $walk plain\n"
411 #---------- remote protocol support, common ----------
413 # remote push initiator/responder protocol:
414 # $ dgit remote-push-build-host <n-rargs> <rargs>... <push-args>...
415 # where <rargs> is <push-host-dir> <supported-proto-vsn>,... ...
416 # < dgit-remote-push-ready <actual-proto-vsn>
423 # > supplementary-message NBYTES # $protovsn >= 3
428 # > file parsed-changelog
429 # [indicates that output of dpkg-parsechangelog follows]
430 # > data-block NBYTES
431 # > [NBYTES bytes of data (no newline)]
432 # [maybe some more blocks]
441 # > param head DGIT-VIEW-HEAD
442 # > param csuite SUITE
443 # > param tagformat old|new
444 # > param maint-view MAINT-VIEW-HEAD
446 # > param buildinfo-filename P_V_X.buildinfo # zero or more times
447 # > file buildinfo # for buildinfos to sign
449 # > previously REFNAME=OBJNAME # if --deliberately-not-fast-forward
450 # # goes into tag, for replay prevention
453 # [indicates that signed tag is wanted]
454 # < data-block NBYTES
455 # < [NBYTES bytes of data (no newline)]
456 # [maybe some more blocks]
460 # > want signed-dsc-changes
461 # < data-block NBYTES [transfer of signed dsc]
463 # < data-block NBYTES [transfer of signed changes]
465 # < data-block NBYTES [transfer of each signed buildinfo
466 # [etc] same number and order as "file buildinfo"]
474 sub i_child_report () {
475 # Sees if our child has died, and reap it if so. Returns a string
476 # describing how it died if it failed, or undef otherwise.
477 return undef unless $i_child_pid;
478 my $got = waitpid $i_child_pid, WNOHANG;
479 return undef if $got <= 0;
480 die unless $got == $i_child_pid;
481 $i_child_pid = undef;
482 return undef unless $?;
483 return f_ "build host child %s", waitstatusmsg();
488 fail f_ "connection lost: %s", $! if $fh->error;
489 fail f_ "protocol violation; %s not expected", $m;
492 sub badproto_badread ($$) {
494 fail f_ "connection lost: %s", $! if $!;
495 my $report = i_child_report();
496 fail $report if defined $report;
497 badproto $fh, f_ "eof (reading %s)", $wh;
500 sub protocol_expect (&$) {
501 my ($match, $fh) = @_;
504 defined && chomp or badproto_badread $fh, __ "protocol message";
512 badproto $fh, f_ "\`%s'", $_;
515 sub protocol_send_file ($$) {
516 my ($fh, $ourfn) = @_;
517 open PF, "<", $ourfn or die "$ourfn: $!";
520 my $got = read PF, $d, 65536;
521 die "$ourfn: $!" unless defined $got;
523 print $fh "data-block ".length($d)."\n" or confess $!;
524 print $fh $d or confess $!;
526 PF->error and die "$ourfn $!";
527 print $fh "data-end\n" or confess $!;
531 sub protocol_read_bytes ($$) {
532 my ($fh, $nbytes) = @_;
533 $nbytes =~ m/^[1-9]\d{0,5}$|^0$/ or badproto \*RO, __ "bad byte count";
535 my $got = read $fh, $d, $nbytes;
536 $got==$nbytes or badproto_badread $fh, __ "data block";
540 sub protocol_receive_file ($$) {
541 my ($fh, $ourfn) = @_;
542 printdebug "() $ourfn\n";
543 open PF, ">", $ourfn or die "$ourfn: $!";
545 my ($y,$l) = protocol_expect {
546 m/^data-block (.*)$/ ? (1,$1) :
547 m/^data-end$/ ? (0,) :
551 my $d = protocol_read_bytes $fh, $l;
552 print PF $d or confess $!;
554 close PF or confess $!;
557 #---------- remote protocol support, responder ----------
559 sub responder_send_command ($) {
561 return unless $we_are_responder;
562 # called even without $we_are_responder
563 printdebug ">> $command\n";
564 print PO $command, "\n" or confess $!;
567 sub responder_send_file ($$) {
568 my ($keyword, $ourfn) = @_;
569 return unless $we_are_responder;
570 printdebug "]] $keyword $ourfn\n";
571 responder_send_command "file $keyword";
572 protocol_send_file \*PO, $ourfn;
575 sub responder_receive_files ($@) {
576 my ($keyword, @ourfns) = @_;
577 die unless $we_are_responder;
578 printdebug "[[ $keyword @ourfns\n";
579 responder_send_command "want $keyword";
580 foreach my $fn (@ourfns) {
581 protocol_receive_file \*PI, $fn;
584 protocol_expect { m/^files-end$/ } \*PI;
587 #---------- remote protocol support, initiator ----------
589 sub initiator_expect (&) {
591 protocol_expect { &$match } \*RO;
594 #---------- end remote code ----------
597 if ($we_are_responder) {
599 responder_send_command "progress ".length($m) or confess $!;
600 print PO $m or confess $!;
610 $ua = LWP::UserAgent->new();
614 progress "downloading $what...";
615 my $r = $ua->get(@_) or confess $!;
616 return undef if $r->code == 404;
617 $r->is_success or fail f_ "failed to fetch %s: %s",
618 $what, $r->status_line;
619 return $r->decoded_content(charset => 'none');
622 our ($dscdata,$dscurl,$dsc,$dsc_checked,$skew_warning_vsn);
624 sub act_local () { return $dryrun_level <= 1; }
625 sub act_scary () { return !$dryrun_level; }
628 if (!$dryrun_level) {
629 progress f_ "%s ok: %s", $us, "@_";
631 progress f_ "would be ok: %s (but dry run only)", "@_";
636 printcmd(\*STDERR,$debugprefix."#",@_);
639 sub runcmd_ordryrun {
647 sub runcmd_ordryrun_local {
655 our $helpmsg = i_ <<END;
657 dgit [dgit-opts] clone [dgit-opts] package [suite] [./dir|/dir]
658 dgit [dgit-opts] fetch|pull [dgit-opts] [suite]
659 dgit [dgit-opts] build [dpkg-buildpackage-opts]
660 dgit [dgit-opts] sbuild [sbuild-opts]
661 dgit [dgit-opts] pbuilder|cowbuilder [debbuildopts]
662 dgit [dgit-opts] push [dgit-opts] [suite]
663 dgit [dgit-opts] push-source [dgit-opts] [suite]
664 dgit [dgit-opts] rpush build-host:build-dir ...
665 important dgit options:
666 -k<keyid> sign tag and package with <keyid> instead of default
667 --dry-run -n do not change anything, but go through the motions
668 --damp-run -L like --dry-run but make local changes, without signing
669 --new -N allow introducing a new package
670 --debug -D increase debug level
671 -c<name>=<value> set git config option (used directly by dgit too)
674 our $later_warning_msg = i_ <<END;
675 Perhaps the upload is stuck in incoming. Using the version from git.
679 print STDERR f_ "%s: %s\n%s", $us, "@_", __ $helpmsg or confess $!;
684 @ARGV or badusage __ "too few arguments";
685 return scalar shift @ARGV;
689 not_necessarily_a_tree();
692 print __ $helpmsg or confess $!;
696 our $td = $ENV{DGIT_TEST_DUMMY_DIR} || "DGIT_TEST_DUMMY_DIR-unset";
698 our %defcfg = ('dgit.default.distro' => 'debian',
699 'dgit.default.default-suite' => 'unstable',
700 'dgit.default.old-dsc-distro' => 'debian',
701 'dgit-suite.*-security.distro' => 'debian-security',
702 'dgit.default.username' => '',
703 'dgit.default.archive-query-default-component' => 'main',
704 'dgit.default.ssh' => 'ssh',
705 'dgit.default.archive-query' => 'madison:',
706 'dgit.default.sshpsql-dbname' => 'service=projectb',
707 'dgit.default.aptget-components' => 'main',
708 'dgit.default.dgit-tag-format' => 'new,old,maint',
709 'dgit.default.source-only-uploads' => 'ok',
710 'dgit.dsc-url-proto-ok.http' => 'true',
711 'dgit.dsc-url-proto-ok.https' => 'true',
712 'dgit.dsc-url-proto-ok.git' => 'true',
713 'dgit.vcs-git.suites', => 'sid', # ;-separated
714 'dgit.default.dsc-url-proto-ok' => 'false',
715 # old means "repo server accepts pushes with old dgit tags"
716 # new means "repo server accepts pushes with new dgit tags"
717 # maint means "repo server accepts split brain pushes"
718 # hist means "repo server may have old pushes without new tag"
719 # ("hist" is implied by "old")
720 'dgit-distro.debian.archive-query' => 'ftpmasterapi:',
721 'dgit-distro.debian.git-check' => 'url',
722 'dgit-distro.debian.git-check-suffix' => '/info/refs',
723 'dgit-distro.debian.new-private-pushers' => 't',
724 'dgit-distro.debian.source-only-uploads' => 'not-wholly-new',
725 'dgit-distro.debian/push.git-url' => '',
726 'dgit-distro.debian/push.git-host' => 'push.dgit.debian.org',
727 'dgit-distro.debian/push.git-user-force' => 'dgit',
728 'dgit-distro.debian/push.git-proto' => 'git+ssh://',
729 'dgit-distro.debian/push.git-path' => '/dgit/debian/repos',
730 'dgit-distro.debian/push.git-create' => 'true',
731 'dgit-distro.debian/push.git-check' => 'ssh-cmd',
732 'dgit-distro.debian.archive-query-url', 'https://api.ftp-master.debian.org/',
733 # 'dgit-distro.debian.archive-query-tls-key',
734 # '/etc/ssl/certs/%HOST%.pem:/etc/dgit/%HOST%.pem',
735 # ^ this does not work because curl is broken nowadays
736 # Fixing #790093 properly will involve providing providing the key
737 # in some pacagke and maybe updating these paths.
739 # 'dgit-distro.debian.archive-query-tls-curl-args',
740 # '--ca-path=/etc/ssl/ca-debian',
741 # ^ this is a workaround but works (only) on DSA-administered machines
742 'dgit-distro.debian.git-url' => 'https://git.dgit.debian.org',
743 'dgit-distro.debian.git-url-suffix' => '',
744 'dgit-distro.debian.upload-host' => 'ftp-master', # for dput
745 'dgit-distro.debian.mirror' => 'http://ftp.debian.org/debian/',
746 'dgit-distro.debian-security.archive-query' => 'aptget:',
747 'dgit-distro.debian-security.mirror' => 'http://security.debian.org/debian-security/',
748 'dgit-distro.debian-security.aptget-suite-map' => 's#-security$#/updates#',
749 'dgit-distro.debian-security.aptget-suite-rmap' => 's#$#-security#',
750 'dgit-distro.debian-security.nominal-distro' => 'debian',
751 'dgit-distro.debian.backports-quirk' => '(squeeze)-backports*',
752 'dgit-distro.debian-backports.mirror' => 'http://backports.debian.org/debian-backports/',
753 'dgit-distro.ubuntu.git-check' => 'false',
754 'dgit-distro.ubuntu.mirror' => 'http://archive.ubuntu.com/ubuntu',
755 'dgit-distro.test-dummy.ssh' => "$td/ssh",
756 'dgit-distro.test-dummy.username' => "alice",
757 'dgit-distro.test-dummy.git-check' => "ssh-cmd",
758 'dgit-distro.test-dummy.git-create' => "ssh-cmd",
759 'dgit-distro.test-dummy.git-url' => "$td/git",
760 'dgit-distro.test-dummy.git-host' => "git",
761 'dgit-distro.test-dummy.git-path' => "$td/git",
762 'dgit-distro.test-dummy.archive-query' => "dummycatapi:",
763 'dgit-distro.test-dummy.archive-query-url' => "file://$td/aq/",
764 'dgit-distro.test-dummy.mirror' => "file://$td/mirror/",
765 'dgit-distro.test-dummy.upload-host' => 'test-dummy',
769 our @gitcfgsources = qw(cmdline local global system);
770 our $invoked_in_git_tree = 1;
772 sub git_slurp_config () {
773 # This algoritm is a bit subtle, but this is needed so that for
774 # options which we want to be single-valued, we allow the
775 # different config sources to override properly. See #835858.
776 foreach my $src (@gitcfgsources) {
777 next if $src eq 'cmdline';
778 # we do this ourselves since git doesn't handle it
780 $gitcfgs{$src} = git_slurp_config_src $src;
784 sub git_get_config ($) {
786 foreach my $src (@gitcfgsources) {
787 my $l = $gitcfgs{$src}{$c};
788 confess "internal error ($l $c)" if $l && !ref $l;
789 printdebug"C $c ".(defined $l ?
790 join " ", map { messagequote "'$_'" } @$l :
795 f_ "multiple values for %s (in %s git config)", $c, $src
797 $l->[0] =~ m/\n/ and badcfg f_
798 "value for config option %s (in %s git config) contains newline(s)!",
807 return undef if $c =~ /RETURN-UNDEF/;
808 printdebug "C? $c\n" if $debuglevel >= 5;
809 my $v = git_get_config($c);
810 return $v if defined $v;
811 my $dv = $defcfg{$c};
813 printdebug "CD $c $dv\n" if $debuglevel >= 4;
818 "need value for one of: %s\n".
819 "%s: distro or suite appears not to be (properly) supported",
823 sub not_necessarily_a_tree () {
824 # needs to be called from pre_*
825 @gitcfgsources = grep { $_ ne 'local' } @gitcfgsources;
826 $invoked_in_git_tree = 0;
829 sub access_basedistro__noalias () {
830 if (defined $idistro) {
833 my $def = cfg("dgit-suite.$isuite.distro", 'RETURN-UNDEF');
834 return $def if defined $def;
835 foreach my $src (@gitcfgsources, 'internal') {
836 my $kl = $src eq 'internal' ? \%defcfg : $gitcfgs{$src};
838 foreach my $k (keys %$kl) {
839 next unless $k =~ m#^dgit-suite\.(.*)\.distro$#;
841 next unless match_glob $dpat, $isuite;
845 return cfg("dgit.default.distro");
849 sub access_basedistro () {
850 my $noalias = access_basedistro__noalias();
851 my $canon = cfg("dgit-distro.$noalias.alias-canon",'RETURN-UNDEF');
852 return $canon // $noalias;
855 sub access_nomdistro () {
856 my $base = access_basedistro();
857 my $r = cfg("dgit-distro.$base.nominal-distro",'RETURN-UNDEF') // $base;
858 $r =~ m/^$distro_re$/ or badcfg
859 f_ "bad syntax for (nominal) distro \`%s' (does not match %s)",
860 $r, "/^$distro_re$/";
864 sub access_quirk () {
865 # returns (quirk name, distro to use instead or undef, quirk-specific info)
866 my $basedistro = access_basedistro();
867 my $backports_quirk = cfg("dgit-distro.$basedistro.backports-quirk",
869 if (defined $backports_quirk) {
870 my $re = $backports_quirk;
871 $re =~ s/[^-0-9a-z_\%*()]/\\$&/ig;
873 $re =~ s/\%/([-0-9a-z_]+)/
874 or $re =~ m/[()]/ or badcfg __ "backports-quirk needs \% or ( )";
875 if ($isuite =~ m/^$re$/) {
876 return ('backports',"$basedistro-backports",$1);
879 return ('none',undef);
884 sub parse_cfg_bool ($$$) {
885 my ($what,$def,$v) = @_;
888 $v =~ m/^[ty1]/ ? 1 :
889 $v =~ m/^[fn0]/ ? 0 :
890 badcfg f_ "%s needs t (true, y, 1) or f (false, n, 0) not \`%s'",
894 sub access_forpush_config () {
895 my $d = access_basedistro();
899 parse_cfg_bool('new-private-pushers', 0,
900 cfg("dgit-distro.$d.new-private-pushers",
903 my $v = cfg("dgit-distro.$d.readonly", 'RETURN-UNDEF');
906 $v =~ m/^[ty1]/ ? 0 : # force readonly, forpush = 0
907 $v =~ m/^[fn0]/ ? 1 : # force nonreadonly, forpush = 1
908 $v =~ m/^[a]/ ? '' : # auto, forpush = ''
910 "readonly needs t (true, y, 1) or f (false, n, 0) or a (auto)";
913 sub access_forpush () {
914 $access_forpush //= access_forpush_config();
915 return $access_forpush;
919 confess +(__ 'internal error').' '.Dumper($access_forpush)," ?" if
920 defined $access_forpush and !$access_forpush;
921 badcfg __ "pushing but distro is configured readonly"
922 if access_forpush_config() eq '0';
924 $supplementary_message = __ <<'END' unless $we_are_responder;
925 Push failed, before we got started.
926 You can retry the push, after fixing the problem, if you like.
928 parseopts_late_defaults();
932 parseopts_late_defaults();
935 sub supplementary_message ($) {
937 if (!$we_are_responder) {
938 $supplementary_message = $msg;
940 } elsif ($protovsn >= 3) {
941 responder_send_command "supplementary-message ".length($msg)
943 print PO $msg or confess $!;
947 sub access_distros () {
948 # Returns list of distros to try, in order
951 # 0. `instead of' distro name(s) we have been pointed to
952 # 1. the access_quirk distro, if any
953 # 2a. the user's specified distro, or failing that } basedistro
954 # 2b. the distro calculated from the suite }
955 my @l = access_basedistro();
957 my (undef,$quirkdistro) = access_quirk();
958 unshift @l, $quirkdistro;
959 unshift @l, $instead_distro;
960 @l = grep { defined } @l;
962 push @l, access_nomdistro();
964 if (access_forpush()) {
965 @l = map { ("$_/push", $_) } @l;
970 sub access_cfg_cfgs (@) {
973 # The nesting of these loops determines the search order. We put
974 # the key loop on the outside so that we search all the distros
975 # for each key, before going on to the next key. That means that
976 # if access_cfg is called with a more specific, and then a less
977 # specific, key, an earlier distro can override the less specific
978 # without necessarily overriding any more specific keys. (If the
979 # distro wants to override the more specific keys it can simply do
980 # so; whereas if we did the loop the other way around, it would be
981 # impossible to for an earlier distro to override a less specific
982 # key but not the more specific ones without restating the unknown
983 # values of the more specific keys.
986 # We have to deal with RETURN-UNDEF specially, so that we don't
987 # terminate the search prematurely.
989 if (m/RETURN-UNDEF/) { push @rundef, $_; last; }
992 foreach my $d (access_distros()) {
993 push @cfgs, map { "dgit-distro.$d.$_" } @realkeys;
995 push @cfgs, map { "dgit.default.$_" } @realkeys;
1000 sub access_cfg (@) {
1002 my (@cfgs) = access_cfg_cfgs(@keys);
1003 my $value = cfg(@cfgs);
1007 sub access_cfg_bool ($$) {
1008 my ($def, @keys) = @_;
1009 parse_cfg_bool($keys[0], $def, access_cfg(@keys, 'RETURN-UNDEF'));
1012 sub string_to_ssh ($) {
1014 if ($spec =~ m/\s/) {
1015 return qw(sh -ec), 'exec '.$spec.' "$@"', 'x';
1021 sub access_cfg_ssh () {
1022 my $gitssh = access_cfg('ssh', 'RETURN-UNDEF');
1023 if (!defined $gitssh) {
1026 return string_to_ssh $gitssh;
1030 sub access_runeinfo ($) {
1032 return ": dgit ".access_basedistro()." $info ;";
1035 sub access_someuserhost ($) {
1037 my $user = access_cfg("$some-user-force", 'RETURN-UNDEF');
1038 defined($user) && length($user) or
1039 $user = access_cfg("$some-user",'username');
1040 my $host = access_cfg("$some-host");
1041 return length($user) ? "$user\@$host" : $host;
1044 sub access_gituserhost () {
1045 return access_someuserhost('git');
1048 sub access_giturl (;$) {
1049 my ($optional) = @_;
1050 my $url = access_cfg('git-url','RETURN-UNDEF');
1053 my $proto = access_cfg('git-proto', 'RETURN-UNDEF');
1054 return undef unless defined $proto;
1057 access_gituserhost().
1058 access_cfg('git-path');
1060 $suffix = access_cfg('git-url-suffix','RETURN-UNDEF');
1063 return "$url/$package$suffix";
1066 sub commit_getclogp ($) {
1067 # Returns the parsed changelog hashref for a particular commit
1069 our %commit_getclogp_memo;
1070 my $memo = $commit_getclogp_memo{$objid};
1071 return $memo if $memo;
1073 my $mclog = dgit_privdir()."clog";
1074 runcmd shell_cmd "exec >$mclog", @git, qw(cat-file blob),
1075 "$objid:debian/changelog";
1076 $commit_getclogp_memo{$objid} = parsechangelog("-l$mclog");
1079 sub parse_dscdata () {
1080 my $dscfh = new IO::File \$dscdata, '<' or confess $!;
1081 printdebug Dumper($dscdata) if $debuglevel>1;
1082 $dsc = parsecontrolfh($dscfh,$dscurl,1);
1083 printdebug Dumper($dsc) if $debuglevel>1;
1088 sub archive_query ($;@) {
1089 my ($method) = shift @_;
1090 fail __ "this operation does not support multiple comma-separated suites"
1092 my $query = access_cfg('archive-query','RETURN-UNDEF');
1093 $query =~ s/^(\w+):// or badcfg "invalid archive-query method \`$query'";
1096 { no strict qw(refs); &{"${method}_${proto}"}($proto,$data,@_); }
1099 sub archive_query_prepend_mirror {
1100 my $m = access_cfg('mirror');
1101 return map { [ $_->[0], $m.$_->[1], @$_[2..$#$_] ] } @_;
1104 sub pool_dsc_subpath ($$) {
1105 my ($vsn,$component) = @_; # $package is implict arg
1106 my $prefix = substr($package, 0, $package =~ m/^l/ ? 4 : 1);
1107 return "/pool/$component/$prefix/$package/".dscfn($vsn);
1110 sub cfg_apply_map ($$$) {
1111 my ($varref, $what, $mapspec) = @_;
1112 return unless $mapspec;
1114 printdebug "config $what EVAL{ $mapspec; }\n";
1116 eval "package Dgit::Config; $mapspec;";
1121 #---------- `ftpmasterapi' archive query method (nascent) ----------
1123 sub archive_api_query_cmd ($) {
1125 my @cmd = (@curl, qw(-sS));
1126 my $url = access_cfg('archive-query-url');
1127 if ($url =~ m#^https://([-.0-9a-z]+)/#) {
1129 my $keys = access_cfg('archive-query-tls-key','RETURN-UNDEF') //'';
1130 foreach my $key (split /\:/, $keys) {
1131 $key =~ s/\%HOST\%/$host/g;
1133 fail "for $url: stat $key: $!" unless $!==ENOENT;
1136 fail f_ "config requested specific TLS key but do not know".
1137 " how to get curl to use exactly that EE key (%s)",
1139 # push @cmd, "--cacert", $key, "--capath", "/dev/enoent";
1140 # # Sadly the above line does not work because of changes
1141 # # to gnutls. The real fix for #790093 may involve
1142 # # new curl options.
1145 # Fixing #790093 properly will involve providing a value
1146 # for this on clients.
1147 my $kargs = access_cfg('archive-query-tls-curl-ca-args','RETURN-UNDEF');
1148 push @cmd, split / /, $kargs if defined $kargs;
1150 push @cmd, $url.$subpath;
1154 sub api_query ($$;$) {
1156 my ($data, $subpath, $ok404) = @_;
1157 badcfg __ "ftpmasterapi archive query method takes no data part"
1159 my @cmd = archive_api_query_cmd($subpath);
1160 my $url = $cmd[$#cmd];
1161 push @cmd, qw(-w %{http_code});
1162 my $json = cmdoutput @cmd;
1163 unless ($json =~ s/\d+\d+\d$//) {
1164 failedcmd_report_cmd undef, @cmd;
1165 fail __ "curl failed to print 3-digit HTTP code";
1168 return undef if $code eq '404' && $ok404;
1169 fail f_ "fetch of %s gave HTTP code %s", $url, $code
1170 unless $url =~ m#^file://# or $code =~ m/^2/;
1171 return decode_json($json);
1174 sub canonicalise_suite_ftpmasterapi {
1175 my ($proto,$data) = @_;
1176 my $suites = api_query($data, 'suites');
1178 foreach my $entry (@$suites) {
1180 my $v = $entry->{$_};
1181 defined $v && $v eq $isuite;
1182 } qw(codename name);
1183 push @matched, $entry;
1185 fail f_ "unknown suite %s, maybe -d would help", $isuite
1189 @matched==1 or die f_ "multiple matches for suite %s\n", $isuite;
1190 $cn = "$matched[0]{codename}";
1191 defined $cn or die f_ "suite %s info has no codename\n", $isuite;
1192 $cn =~ m/^$suite_re$/
1193 or die f_ "suite %s maps to bad codename\n", $isuite;
1195 die +(__ "bad ftpmaster api response: ")."$@\n".Dumper(\@matched)
1200 sub archive_query_ftpmasterapi {
1201 my ($proto,$data) = @_;
1202 my $info = api_query($data, "dsc_in_suite/$isuite/$package");
1204 my $digester = Digest::SHA->new(256);
1205 foreach my $entry (@$info) {
1207 my $vsn = "$entry->{version}";
1208 my ($ok,$msg) = version_check $vsn;
1209 die f_ "bad version: %s\n", $msg unless $ok;
1210 my $component = "$entry->{component}";
1211 $component =~ m/^$component_re$/ or die __ "bad component";
1212 my $filename = "$entry->{filename}";
1213 $filename && $filename !~ m#[^-+:._~0-9a-zA-Z/]|^[/.]|/[/.]#
1214 or die __ "bad filename";
1215 my $sha256sum = "$entry->{sha256sum}";
1216 $sha256sum =~ m/^[0-9a-f]+$/ or die __ "bad sha256sum";
1217 push @rows, [ $vsn, "/pool/$component/$filename",
1218 $digester, $sha256sum ];
1220 die +(__ "bad ftpmaster api response: ")."$@\n".Dumper($entry)
1223 @rows = sort { -version_compare($a->[0],$b->[0]) } @rows;
1224 return archive_query_prepend_mirror @rows;
1227 sub file_in_archive_ftpmasterapi {
1228 my ($proto,$data,$filename) = @_;
1229 my $pat = $filename;
1232 $pat =~ s#[^-+_.0-9a-z/]# sprintf '%%%02x', ord $& #ge;
1233 my $info = api_query($data, "file_in_archive/$pat", 1);
1236 sub package_not_wholly_new_ftpmasterapi {
1237 my ($proto,$data,$pkg) = @_;
1238 my $info = api_query($data,"madison?package=${pkg}&f=json");
1242 #---------- `aptget' archive query method ----------
1245 our $aptget_releasefile;
1246 our $aptget_configpath;
1248 sub aptget_aptget () { return @aptget, qw(-c), $aptget_configpath; }
1249 sub aptget_aptcache () { return @aptcache, qw(-c), $aptget_configpath; }
1251 sub aptget_cache_clean {
1252 runcmd_ordryrun_local qw(sh -ec),
1253 'cd "$1"; find -atime +30 -type f -print0 | xargs -0r rm --',
1257 sub aptget_lock_acquire () {
1258 my $lockfile = "$aptget_base/lock";
1259 open APTGET_LOCK, '>', $lockfile or confess "open $lockfile: $!";
1260 flock APTGET_LOCK, LOCK_EX or confess "lock $lockfile: $!";
1263 sub aptget_prep ($) {
1265 return if defined $aptget_base;
1267 badcfg __ "aptget archive query method takes no data part"
1270 my $cache = $ENV{XDG_CACHE_DIR} // "$ENV{HOME}/.cache";
1273 ensuredir "$cache/dgit";
1275 access_cfg('aptget-cachekey','RETURN-UNDEF')
1276 // access_nomdistro();
1278 $aptget_base = "$cache/dgit/aptget";
1279 ensuredir $aptget_base;
1281 my $quoted_base = $aptget_base;
1282 confess "$quoted_base contains bad chars, cannot continue"
1283 if $quoted_base =~ m/["\\]/; # apt.conf(5) says no escaping :-/
1285 ensuredir $aptget_base;
1287 aptget_lock_acquire();
1289 aptget_cache_clean();
1291 $aptget_configpath = "$aptget_base/apt.conf#$cachekey";
1292 my $sourceslist = "source.list#$cachekey";
1294 my $aptsuites = $isuite;
1295 cfg_apply_map(\$aptsuites, 'suite map',
1296 access_cfg('aptget-suite-map', 'RETURN-UNDEF'));
1298 open SRCS, ">", "$aptget_base/$sourceslist" or confess $!;
1299 printf SRCS "deb-src %s %s %s\n",
1300 access_cfg('mirror'),
1302 access_cfg('aptget-components')
1305 ensuredir "$aptget_base/cache";
1306 ensuredir "$aptget_base/lists";
1308 open CONF, ">", $aptget_configpath or confess $!;
1310 Debug::NoLocking "true";
1311 APT::Get::List-Cleanup "false";
1312 #clear APT::Update::Post-Invoke-Success;
1313 Dir::Etc::SourceList "$quoted_base/$sourceslist";
1314 Dir::State::Lists "$quoted_base/lists";
1315 Dir::Etc::preferences "$quoted_base/preferences";
1316 Dir::Cache::srcpkgcache "$quoted_base/cache/srcs#$cachekey";
1317 Dir::Cache::pkgcache "$quoted_base/cache/pkgs#$cachekey";
1320 foreach my $key (qw(
1323 Dir::Cache::Archives
1324 Dir::Etc::SourceParts
1325 Dir::Etc::preferencesparts
1327 ensuredir "$aptget_base/$key";
1328 print CONF "$key \"$quoted_base/$key\";\n" or confess $!;
1331 my $oldatime = (time // confess $!) - 1;
1332 foreach my $oldlist (<$aptget_base/lists/*Release>) {
1333 next unless stat_exists $oldlist;
1334 my ($mtime) = (stat _)[9];
1335 utime $oldatime, $mtime, $oldlist or die "$oldlist $!";
1338 runcmd_ordryrun_local aptget_aptget(), qw(update);
1341 foreach my $oldlist (<$aptget_base/lists/*Release>) {
1342 next unless stat_exists $oldlist;
1343 my ($atime) = (stat _)[8];
1344 next if $atime == $oldatime;
1345 push @releasefiles, $oldlist;
1347 my @inreleasefiles = grep { m#/InRelease$# } @releasefiles;
1348 @releasefiles = @inreleasefiles if @inreleasefiles;
1349 if (!@releasefiles) {
1350 fail f_ <<END, $isuite, $cache;
1351 apt seemed to not to update dgit's cached Release files for %s.
1353 is on a filesystem mounted `noatime'; if so, please use `relatime'.)
1356 confess "apt updated too many Release files (@releasefiles), erk"
1357 unless @releasefiles == 1;
1359 ($aptget_releasefile) = @releasefiles;
1362 sub canonicalise_suite_aptget {
1363 my ($proto,$data) = @_;
1366 my $release = parsecontrol $aptget_releasefile, "Release file", 1;
1368 foreach my $name (qw(Codename Suite)) {
1369 my $val = $release->{$name};
1371 printdebug "release file $name: $val\n";
1372 $val =~ m/^$suite_re$/o or fail f_
1373 "Release file (%s) specifies intolerable %s",
1374 $aptget_releasefile, $name;
1375 cfg_apply_map(\$val, 'suite rmap',
1376 access_cfg('aptget-suite-rmap', 'RETURN-UNDEF'));
1383 sub archive_query_aptget {
1384 my ($proto,$data) = @_;
1387 ensuredir "$aptget_base/source";
1388 foreach my $old (<$aptget_base/source/*.dsc>) {
1389 unlink $old or die "$old: $!";
1392 my $showsrc = cmdoutput aptget_aptcache(), qw(showsrc), $package;
1393 return () unless $showsrc =~ m/^package:\s*\Q$package\E\s*$/mi;
1394 # avoids apt-get source failing with ambiguous error code
1396 runcmd_ordryrun_local
1397 shell_cmd 'cd "$1"/source; shift', $aptget_base,
1398 aptget_aptget(), qw(--download-only --only-source source), $package;
1400 my @dscs = <$aptget_base/source/*.dsc>;
1401 fail __ "apt-get source did not produce a .dsc" unless @dscs;
1402 fail f_ "apt-get source produced several .dscs (%s)", "@dscs"
1405 my $pre_dsc = parsecontrol $dscs[0], $dscs[0], 1;
1408 my $uri = "file://". uri_escape $dscs[0];
1409 $uri =~ s{\%2f}{/}gi;
1410 return [ (getfield $pre_dsc, 'Version'), $uri ];
1413 sub file_in_archive_aptget () { return undef; }
1414 sub package_not_wholly_new_aptget () { return undef; }
1416 #---------- `dummyapicat' archive query method ----------
1417 # (untranslated, because this is for testing purposes etc.)
1419 sub archive_query_dummycatapi { archive_query_ftpmasterapi @_; }
1420 sub canonicalise_suite_dummycatapi { canonicalise_suite_ftpmasterapi @_; }
1422 sub dummycatapi_run_in_mirror ($@) {
1423 # runs $fn with FIA open onto rune
1424 my ($rune, $argl, $fn) = @_;
1426 my $mirror = access_cfg('mirror');
1427 $mirror =~ s#^file://#/# or die "$mirror ?";
1428 my @cmd = (qw(sh -ec), 'cd "$1"; shift'."\n".$rune,
1429 qw(x), $mirror, @$argl);
1430 debugcmd "-|", @cmd;
1431 open FIA, "-|", @cmd or confess $!;
1433 close FIA or ($!==0 && $?==141) or die failedcmd @cmd;
1437 sub file_in_archive_dummycatapi ($$$) {
1438 my ($proto,$data,$filename) = @_;
1440 dummycatapi_run_in_mirror '
1441 find -name "$1" -print0 |
1443 ', [$filename], sub {
1446 printdebug "| $_\n";
1447 m/^(\w+) (\S+)$/ or die "$_ ?";
1448 push @out, { sha256sum => $1, filename => $2 };
1454 sub package_not_wholly_new_dummycatapi {
1455 my ($proto,$data,$pkg) = @_;
1456 dummycatapi_run_in_mirror "
1457 find -name ${pkg}_*.dsc
1464 #---------- `madison' archive query method ----------
1466 sub archive_query_madison {
1467 return archive_query_prepend_mirror
1468 map { [ @$_[0..1] ] } madison_get_parse(@_);
1471 sub madison_get_parse {
1472 my ($proto,$data) = @_;
1473 die unless $proto eq 'madison';
1474 if (!length $data) {
1475 $data= access_cfg('madison-distro','RETURN-UNDEF');
1476 $data //= access_basedistro();
1478 $rmad{$proto,$data,$package} ||= cmdoutput
1479 qw(rmadison -asource),"-s$isuite","-u$data",$package;
1480 my $rmad = $rmad{$proto,$data,$package};
1483 foreach my $l (split /\n/, $rmad) {
1484 $l =~ m{^ \s*( [^ \t|]+ )\s* \|
1485 \s*( [^ \t|]+ )\s* \|
1486 \s*( [^ \t|/]+ )(?:/([^ \t|/]+))? \s* \|
1487 \s*( [^ \t|]+ )\s* }x or die "$rmad ?";
1488 $1 eq $package or die "$rmad $package ?";
1495 $component = access_cfg('archive-query-default-component');
1497 $5 eq 'source' or die "$rmad ?";
1498 push @out, [$vsn,pool_dsc_subpath($vsn,$component),$newsuite];
1500 return sort { -version_compare($a->[0],$b->[0]); } @out;
1503 sub canonicalise_suite_madison {
1504 # madison canonicalises for us
1505 my @r = madison_get_parse(@_);
1507 "unable to canonicalise suite using package %s".
1508 " which does not appear to exist in suite %s;".
1509 " --existing-package may help",
1514 sub file_in_archive_madison { return undef; }
1515 sub package_not_wholly_new_madison { return undef; }
1517 #---------- `sshpsql' archive query method ----------
1518 # (untranslated, because this is obsolete)
1521 my ($data,$runeinfo,$sql) = @_;
1522 if (!length $data) {
1523 $data= access_someuserhost('sshpsql').':'.
1524 access_cfg('sshpsql-dbname');
1526 $data =~ m/:/ or badcfg "invalid sshpsql method string \`$data'";
1527 my ($userhost,$dbname) = ($`,$'); #';
1529 my @cmd = (access_cfg_ssh, $userhost,
1530 access_runeinfo("ssh-psql $runeinfo").
1531 " export LC_MESSAGES=C; export LC_CTYPE=C;".
1532 " ".shellquote qw(psql -A), $dbname, qw(-c), $sql);
1534 open P, "-|", @cmd or confess $!;
1537 printdebug(">|$_|\n");
1540 $!=0; $?=0; close P or failedcmd @cmd;
1542 my $nrows = pop @rows;
1543 $nrows =~ s/^\((\d+) rows?\)$/$1/ or die "$nrows ?";
1544 @rows == $nrows+1 or die "$nrows ".(scalar @rows)." ?";
1545 @rows = map { [ split /\|/, $_ ] } @rows;
1546 my $ncols = scalar @{ shift @rows };
1547 die if grep { scalar @$_ != $ncols } @rows;
1551 sub sql_injection_check {
1552 foreach (@_) { die "$_ $& ?" if m{[^-+=:_.,/0-9a-zA-Z]}; }
1555 sub archive_query_sshpsql ($$) {
1556 my ($proto,$data) = @_;
1557 sql_injection_check $isuite, $package;
1558 my @rows = sshpsql($data, "archive-query $isuite $package", <<END);
1559 SELECT source.version, component.name, files.filename, files.sha256sum
1561 JOIN src_associations ON source.id = src_associations.source
1562 JOIN suite ON suite.id = src_associations.suite
1563 JOIN dsc_files ON dsc_files.source = source.id
1564 JOIN files_archive_map ON files_archive_map.file_id = dsc_files.file
1565 JOIN component ON component.id = files_archive_map.component_id
1566 JOIN files ON files.id = dsc_files.file
1567 WHERE ( suite.suite_name='$isuite' OR suite.codename='$isuite' )
1568 AND source.source='$package'
1569 AND files.filename LIKE '%.dsc';
1571 @rows = sort { -version_compare($a->[0],$b->[0]) } @rows;
1572 my $digester = Digest::SHA->new(256);
1574 my ($vsn,$component,$filename,$sha256sum) = @$_;
1575 [ $vsn, "/pool/$component/$filename",$digester,$sha256sum ];
1577 return archive_query_prepend_mirror @rows;
1580 sub canonicalise_suite_sshpsql ($$) {
1581 my ($proto,$data) = @_;
1582 sql_injection_check $isuite;
1583 my @rows = sshpsql($data, "canonicalise-suite $isuite", <<END);
1584 SELECT suite.codename
1585 FROM suite where suite_name='$isuite' or codename='$isuite';
1587 @rows = map { $_->[0] } @rows;
1588 fail "unknown suite $isuite" unless @rows;
1589 die "ambiguous $isuite: @rows ?" if @rows>1;
1593 sub file_in_archive_sshpsql ($$$) { return undef; }
1594 sub package_not_wholly_new_sshpsql ($$$) { return undef; }
1596 #---------- `dummycat' archive query method ----------
1597 # (untranslated, because this is for testing purposes etc.)
1599 sub canonicalise_suite_dummycat ($$) {
1600 my ($proto,$data) = @_;
1601 my $dpath = "$data/suite.$isuite";
1602 if (!open C, "<", $dpath) {
1603 $!==ENOENT or die "$dpath: $!";
1604 printdebug "dummycat canonicalise_suite $isuite $dpath ENOENT\n";
1608 chomp or die "$dpath: $!";
1610 printdebug "dummycat canonicalise_suite $isuite $dpath = $_\n";
1614 sub archive_query_dummycat ($$) {
1615 my ($proto,$data) = @_;
1616 canonicalise_suite();
1617 my $dpath = "$data/package.$csuite.$package";
1618 if (!open C, "<", $dpath) {
1619 $!==ENOENT or die "$dpath: $!";
1620 printdebug "dummycat query $csuite $package $dpath ENOENT\n";
1628 printdebug "dummycat query $csuite $package $dpath | $_\n";
1629 my @row = split /\s+/, $_;
1630 @row==2 or die "$dpath: $_ ?";
1633 C->error and die "$dpath: $!";
1635 return archive_query_prepend_mirror
1636 sort { -version_compare($a->[0],$b->[0]); } @rows;
1639 sub file_in_archive_dummycat () { return undef; }
1640 sub package_not_wholly_new_dummycat () { return undef; }
1642 #---------- tag format handling ----------
1643 # (untranslated, because everything should be new tag format by now)
1645 sub access_cfg_tagformats () {
1646 split /\,/, access_cfg('dgit-tag-format');
1649 sub access_cfg_tagformats_can_splitbrain () {
1650 my %y = map { $_ => 1 } access_cfg_tagformats;
1651 foreach my $needtf (qw(new maint)) {
1652 next if $y{$needtf};
1658 sub need_tagformat ($$) {
1659 my ($fmt, $why) = @_;
1660 fail "need to use tag format $fmt ($why) but also need".
1661 " to use tag format $tagformat_want->[0] ($tagformat_want->[1])".
1662 " - no way to proceed"
1663 if $tagformat_want && $tagformat_want->[0] ne $fmt;
1664 $tagformat_want = [$fmt, $why, $tagformat_want->[2] // 0];
1667 sub select_tagformat () {
1669 return if $tagformatfn && !$tagformat_want;
1670 die 'bug' if $tagformatfn && $tagformat_want;
1671 # ... $tagformat_want assigned after previous select_tagformat
1673 my (@supported) = grep { $_ =~ m/^(?:old|new)$/ } access_cfg_tagformats();
1674 printdebug "select_tagformat supported @supported\n";
1676 $tagformat_want //= [ $supported[0], "distro access configuration", 0 ];
1677 printdebug "select_tagformat specified @$tagformat_want\n";
1679 my ($fmt,$why,$override) = @$tagformat_want;
1681 fail "target distro supports tag formats @supported".
1682 " but have to use $fmt ($why)"
1684 or grep { $_ eq $fmt } @supported;
1686 $tagformat_want = undef;
1688 $tagformatfn = ${*::}{"debiantag_$fmt"};
1690 fail "trying to use unknown tag format \`$fmt' ($why) !"
1691 unless $tagformatfn;
1694 #---------- archive query entrypoints and rest of program ----------
1696 sub canonicalise_suite () {
1697 return if defined $csuite;
1698 fail f_ "cannot operate on %s suite", $isuite if $isuite eq 'UNRELEASED';
1699 $csuite = archive_query('canonicalise_suite');
1700 if ($isuite ne $csuite) {
1701 progress f_ "canonical suite name for %s is %s", $isuite, $csuite;
1703 progress f_ "canonical suite name is %s", $csuite;
1707 sub get_archive_dsc () {
1708 canonicalise_suite();
1709 my @vsns = archive_query('archive_query');
1710 foreach my $vinfo (@vsns) {
1711 my ($vsn,$vsn_dscurl,$digester,$digest) = @$vinfo;
1712 $dscurl = $vsn_dscurl;
1713 $dscdata = url_get($dscurl);
1715 $skew_warning_vsn = $vsn if !defined $skew_warning_vsn;
1720 $digester->add($dscdata);
1721 my $got = $digester->hexdigest();
1723 fail f_ "%s has hash %s but archive told us to expect %s",
1724 $dscurl, $got, $digest;
1727 my $fmt = getfield $dsc, 'Format';
1728 $format_ok{$fmt} or forceable_fail [qw(unsupported-source-format)],
1729 f_ "unsupported source format %s, sorry", $fmt;
1731 $dsc_checked = !!$digester;
1732 printdebug "get_archive_dsc: Version ".(getfield $dsc, 'Version')."\n";
1736 printdebug "get_archive_dsc: nothing in archive, returning undef\n";
1739 sub check_for_git ();
1740 sub check_for_git () {
1742 my $how = access_cfg('git-check');
1743 if ($how eq 'ssh-cmd') {
1745 (access_cfg_ssh, access_gituserhost(),
1746 access_runeinfo("git-check $package").
1747 " set -e; cd ".access_cfg('git-path').";".
1748 " if test -d $package.git; then echo 1; else echo 0; fi");
1749 my $r= cmdoutput @cmd;
1750 if (defined $r and $r =~ m/^divert (\w+)$/) {
1752 my ($usedistro,) = access_distros();
1753 # NB that if we are pushing, $usedistro will be $distro/push
1754 $instead_distro= cfg("dgit-distro.$usedistro.diverts.$divert");
1755 $instead_distro =~ s{^/}{ access_basedistro()."/" }e;
1756 progress f_ "diverting to %s (using config for %s)",
1757 $divert, $instead_distro;
1758 return check_for_git();
1760 failedcmd @cmd unless defined $r and $r =~ m/^[01]$/;
1762 } elsif ($how eq 'url') {
1763 my $prefix = access_cfg('git-check-url','git-url');
1764 my $suffix = access_cfg('git-check-suffix','git-suffix',
1765 'RETURN-UNDEF') // '.git';
1766 my $url = "$prefix/$package$suffix";
1767 my @cmd = (@curl, qw(-sS -I), $url);
1768 my $result = cmdoutput @cmd;
1769 $result =~ s/^\S+ 200 .*\n\r?\n//;
1770 # curl -sS -I with https_proxy prints
1771 # HTTP/1.0 200 Connection established
1772 $result =~ m/^\S+ (404|200) /s or
1773 fail +(__ "unexpected results from git check query - ").
1774 Dumper($prefix, $result);
1776 if ($code eq '404') {
1778 } elsif ($code eq '200') {
1783 } elsif ($how eq 'true') {
1785 } elsif ($how eq 'false') {
1788 badcfg f_ "unknown git-check \`%s'", $how;
1792 sub create_remote_git_repo () {
1793 my $how = access_cfg('git-create');
1794 if ($how eq 'ssh-cmd') {
1796 (access_cfg_ssh, access_gituserhost(),
1797 access_runeinfo("git-create $package").
1798 "set -e; cd ".access_cfg('git-path').";".
1799 " cp -a _template $package.git");
1800 } elsif ($how eq 'true') {
1803 badcfg f_ "unknown git-create \`%s'", $how;
1807 our ($dsc_hash,$lastpush_mergeinput);
1808 our ($dsc_distro, $dsc_hint_tag, $dsc_hint_url);
1812 dgit_privdir(); # ensures that $dgit_privdir_made is based on $maindir
1813 $playground = fresh_playground 'dgit/unpack';
1816 sub mktree_in_ud_here () {
1817 playtree_setup $gitcfgs{local};
1820 sub git_write_tree () {
1821 my $tree = cmdoutput @git, qw(write-tree);
1822 $tree =~ m/^\w+$/ or die "$tree ?";
1826 sub git_add_write_tree () {
1827 runcmd @git, qw(add -Af .);
1828 return git_write_tree();
1831 sub remove_stray_gits ($) {
1833 my @gitscmd = qw(find -name .git -prune -print0);
1834 debugcmd "|",@gitscmd;
1835 open GITS, "-|", @gitscmd or confess $!;
1840 print STDERR f_ "%s: warning: removing from %s: %s\n",
1841 $us, $what, (messagequote $_);
1845 $!=0; $?=0; close GITS or failedcmd @gitscmd;
1848 sub mktree_in_ud_from_only_subdir ($;$) {
1849 my ($what,$raw) = @_;
1850 # changes into the subdir
1853 confess "expected one subdir but found @dirs ?" unless @dirs==1;
1854 $dirs[0] =~ m#^([^/]+)/\.$# or die;
1858 remove_stray_gits($what);
1859 mktree_in_ud_here();
1861 my ($format, $fopts) = get_source_format();
1862 if (madformat($format)) {
1867 my $tree=git_add_write_tree();
1868 return ($tree,$dir);
1871 our @files_csum_info_fields =
1872 (['Checksums-Sha256','Digest::SHA', 'new(256)', 'sha256sum'],
1873 ['Checksums-Sha1', 'Digest::SHA', 'new(1)', 'sha1sum'],
1874 ['Files', 'Digest::MD5', 'new()', 'md5sum']);
1876 sub dsc_files_info () {
1877 foreach my $csumi (@files_csum_info_fields) {
1878 my ($fname, $module, $method) = @$csumi;
1879 my $field = $dsc->{$fname};
1880 next unless defined $field;
1881 eval "use $module; 1;" or die $@;
1883 foreach (split /\n/, $field) {
1885 m/^(\w+) (\d+) (\S+)$/ or
1886 fail f_ "could not parse .dsc %s line \`%s'", $fname, $_;
1887 my $digester = eval "$module"."->$method;" or die $@;
1892 Digester => $digester,
1897 fail f_ "missing any supported Checksums-* or Files field in %s",
1898 $dsc->get_option('name');
1902 map { $_->{Filename} } dsc_files_info();
1905 sub files_compare_inputs (@) {
1910 my $showinputs = sub {
1911 return join "; ", map { $_->get_option('name') } @$inputs;
1914 foreach my $in (@$inputs) {
1916 my $in_name = $in->get_option('name');
1918 printdebug "files_compare_inputs $in_name\n";
1920 foreach my $csumi (@files_csum_info_fields) {
1921 my ($fname) = @$csumi;
1922 printdebug "files_compare_inputs $in_name $fname\n";
1924 my $field = $in->{$fname};
1925 next unless defined $field;
1928 foreach (split /\n/, $field) {
1931 my ($info, $f) = m/^(\w+ \d+) (?:\S+ \S+ )?(\S+)$/ or
1932 fail "could not parse $in_name $fname line \`$_'";
1934 printdebug "files_compare_inputs $in_name $fname $f\n";
1938 my $re = \ $record{$f}{$fname};
1940 $fchecked{$f}{$in_name} = 1;
1943 "hash or size of %s varies in %s fields (between: %s)",
1944 $f, $fname, $showinputs->();
1949 @files = sort @files;
1950 $expected_files //= \@files;
1951 "@$expected_files" eq "@files" or
1952 fail f_ "file list in %s varies between hash fields!",
1956 fail f_ "%s has no files list field(s)", $in_name;
1958 printdebug "files_compare_inputs ".Dumper(\%fchecked, \%record)
1961 grep { keys %$_ == @$inputs-1 } values %fchecked
1962 or fail f_ "no file appears in all file lists (looked in: %s)",
1966 sub is_orig_file_in_dsc ($$) {
1967 my ($f, $dsc_files_info) = @_;
1968 return 0 if @$dsc_files_info <= 1;
1969 # One file means no origs, and the filename doesn't have a "what
1970 # part of dsc" component. (Consider versions ending `.orig'.)
1971 return 0 unless $f =~ m/\.$orig_f_tail_re$/o;
1975 # This function determines whether a .changes file is source-only from
1976 # the point of view of dak. Thus, it permits *_source.buildinfo
1979 # It does not, however, permit any other buildinfo files. After a
1980 # source-only upload, the buildds will try to upload files like
1981 # foo_1.2.3_amd64.buildinfo. If the package maintainer included files
1982 # named like this in their (otherwise) source-only upload, the uploads
1983 # of the buildd can be rejected by dak. Fixing the resultant
1984 # situation can require manual intervention. So we block such
1985 # .buildinfo files when the user tells us to perform a source-only
1986 # upload (such as when using the push-source subcommand with the -C
1987 # option, which calls this function).
1989 # Note, though, that when dgit is told to prepare a source-only
1990 # upload, such as when subcommands like build-source and push-source
1991 # without -C are used, dgit has a more restrictive notion of
1992 # source-only .changes than dak: such uploads will never include
1993 # *_source.buildinfo files. This is because there is no use for such
1994 # files when using a tool like dgit to produce the source package, as
1995 # dgit ensures the source is identical to git HEAD.
1996 sub test_source_only_changes ($) {
1998 foreach my $l (split /\n/, getfield $changes, 'Files') {
1999 $l =~ m/\S+$/ or next;
2000 # \.tar\.[a-z0-9]+ covers orig.tar and the tarballs in native packages
2001 unless ($& =~ m/(?:\.dsc|\.diff\.gz|\.tar\.[a-z0-9]+|_source\.buildinfo)$/) {
2002 print f_ "purportedly source-only changes polluted by %s\n", $&;
2009 sub changes_update_origs_from_dsc ($$$$) {
2010 my ($dsc, $changes, $upstreamvsn, $changesfile) = @_;
2012 printdebug "checking origs needed ($upstreamvsn)...\n";
2013 $_ = getfield $changes, 'Files';
2014 m/^\w+ \d+ (\S+ \S+) \S+$/m or
2015 fail __ "cannot find section/priority from .changes Files field";
2016 my $placementinfo = $1;
2018 printdebug "checking origs needed placement '$placementinfo'...\n";
2019 foreach my $l (split /\n/, getfield $dsc, 'Files') {
2020 $l =~ m/\S+$/ or next;
2022 printdebug "origs $file | $l\n";
2023 next unless is_orig_file_of_vsn $file, $upstreamvsn;
2024 printdebug "origs $file is_orig\n";
2025 my $have = archive_query('file_in_archive', $file);
2026 if (!defined $have) {
2027 print STDERR __ <<END;
2028 archive does not support .orig check; hope you used --ch:--sa/-sd if needed
2034 printdebug "origs $file \$#\$have=$#$have\n";
2035 foreach my $h (@$have) {
2038 foreach my $csumi (@files_csum_info_fields) {
2039 my ($fname, $module, $method, $archivefield) = @$csumi;
2040 next unless defined $h->{$archivefield};
2041 $_ = $dsc->{$fname};
2042 next unless defined;
2043 m/^(\w+) .* \Q$file\E$/m or
2044 fail f_ ".dsc %s missing entry for %s", $fname, $file;
2045 if ($h->{$archivefield} eq $1) {
2049 "%s: %s (archive) != %s (local .dsc)",
2050 $archivefield, $h->{$archivefield}, $1;
2053 confess "$file ".Dumper($h)." ?!" if $same && @differ;
2057 f_ "archive %s: %s", $h->{filename}, join "; ", @differ
2060 printdebug "origs $file f.same=$found_same".
2061 " #f._differ=$#found_differ\n";
2062 if (@found_differ && !$found_same) {
2064 (f_ "archive contains %s with different checksum", $file),
2067 # Now we edit the changes file to add or remove it
2068 foreach my $csumi (@files_csum_info_fields) {
2069 my ($fname, $module, $method, $archivefield) = @$csumi;
2070 next unless defined $changes->{$fname};
2072 # in archive, delete from .changes if it's there
2073 $changed{$file} = "removed" if
2074 $changes->{$fname} =~ s/\n.* \Q$file\E$(?:)$//m;
2075 } elsif ($changes->{$fname} =~ m/^.* \Q$file\E$(?:)$/m) {
2076 # not in archive, but it's here in the .changes
2078 my $dsc_data = getfield $dsc, $fname;
2079 $dsc_data =~ m/^(.* \Q$file\E$)$/m or die "$dsc_data $file ?";
2081 $extra =~ s/ \d+ /$&$placementinfo /
2082 or confess "$fname $extra >$dsc_data< ?"
2083 if $fname eq 'Files';
2084 $changes->{$fname} .= "\n". $extra;
2085 $changed{$file} = "added";
2090 foreach my $file (keys %changed) {
2092 "edited .changes for archive .orig contents: %s %s",
2093 $changed{$file}, $file;
2095 my $chtmp = "$changesfile.tmp";
2096 $changes->save($chtmp);
2098 rename $chtmp,$changesfile or die "$changesfile $!";
2100 progress f_ "[new .changes left in %s]", $changesfile;
2103 progress f_ "%s already has appropriate .orig(s) (if any)",
2108 sub make_commit ($) {
2110 return cmdoutput @git, qw(hash-object -w -t commit), $file;
2113 sub clogp_authline ($) {
2115 my $author = getfield $clogp, 'Maintainer';
2116 if ($author =~ m/^[^"\@]+\,/) {
2117 # single entry Maintainer field with unquoted comma
2118 $author = ($& =~ y/,//rd).$'; # strip the comma
2120 # git wants a single author; any remaining commas in $author
2121 # are by now preceded by @ (or "). It seems safer to punt on
2122 # "..." for now rather than attempting to dequote or something.
2123 $author =~ s#,.*##ms unless $author =~ m/"/;
2124 my $date = cmdoutput qw(date), '+%s %z', qw(-d), getfield($clogp,'Date');
2125 my $authline = "$author $date";
2126 $authline =~ m/$git_authline_re/o or
2127 fail f_ "unexpected commit author line format \`%s'".
2128 " (was generated from changelog Maintainer field)",
2130 return ($1,$2,$3) if wantarray;
2134 sub vendor_patches_distro ($$) {
2135 my ($checkdistro, $what) = @_;
2136 return unless defined $checkdistro;
2138 my $series = "debian/patches/\L$checkdistro\E.series";
2139 printdebug "checking for vendor-specific $series ($what)\n";
2141 if (!open SERIES, "<", $series) {
2142 confess "$series $!" unless $!==ENOENT;
2149 print STDERR __ <<END;
2151 Unfortunately, this source package uses a feature of dpkg-source where
2152 the same source package unpacks to different source code on different
2153 distros. dgit cannot safely operate on such packages on affected
2154 distros, because the meaning of source packages is not stable.
2156 Please ask the distro/maintainer to remove the distro-specific series
2157 files and use a different technique (if necessary, uploading actually
2158 different packages, if different distros are supposed to have
2162 fail f_ "Found active distro-specific series file for".
2163 " %s (%s): %s, cannot continue",
2164 $checkdistro, $what, $series;
2166 die "$series $!" if SERIES->error;
2170 sub check_for_vendor_patches () {
2171 # This dpkg-source feature doesn't seem to be documented anywhere!
2172 # But it can be found in the changelog (reformatted):
2174 # commit 4fa01b70df1dc4458daee306cfa1f987b69da58c
2175 # Author: Raphael Hertzog <hertzog@debian.org>
2176 # Date: Sun Oct 3 09:36:48 2010 +0200
2178 # dpkg-source: correctly create .pc/.quilt_series with alternate
2181 # If you have debian/patches/ubuntu.series and you were
2182 # unpacking the source package on ubuntu, quilt was still
2183 # directed to debian/patches/series instead of
2184 # debian/patches/ubuntu.series.
2186 # debian/changelog | 3 +++
2187 # scripts/Dpkg/Source/Package/V3/quilt.pm | 4 +++-
2188 # 2 files changed, 6 insertions(+), 1 deletion(-)
2191 vendor_patches_distro($ENV{DEB_VENDOR}, "DEB_VENDOR");
2192 vendor_patches_distro(Dpkg::Vendor::get_current_vendor(),
2193 __ "Dpkg::Vendor \`current vendor'");
2194 vendor_patches_distro(access_basedistro(),
2195 __ "(base) distro being accessed");
2196 vendor_patches_distro(access_nomdistro(),
2197 __ "(nominal) distro being accessed");
2200 sub check_bpd_exists () {
2201 stat $buildproductsdir
2202 or fail f_ "build-products-dir %s is not accessible: %s\n",
2203 $buildproductsdir, $!;
2206 sub generate_commits_from_dsc () {
2207 # See big comment in fetch_from_archive, below.
2208 # See also README.dsc-import.
2210 changedir $playground;
2212 my $bpd_abs = bpd_abs();
2213 my @dfi = dsc_files_info();
2215 foreach my $fi (@dfi) {
2216 my $f = $fi->{Filename};
2217 die "$f ?" if $f =~ m#/|^\.|\.dsc$|\.tmp$#;
2218 my $upper_f = "$bpd_abs/$f";
2220 printdebug "considering reusing $f: ";
2222 if (link_ltarget "$upper_f,fetch", $f) {
2223 printdebug "linked (using ...,fetch).\n";
2224 } elsif ((printdebug "($!) "),
2226 fail f_ "accessing %s: %s", "$buildproductsdir/$f,fetch", $!;
2227 } elsif (link_ltarget $upper_f, $f) {
2228 printdebug "linked.\n";
2229 } elsif ((printdebug "($!) "),
2231 fail f_ "accessing %s: %s", "$buildproductsdir/$f", $!;
2233 printdebug "absent.\n";
2237 complete_file_from_dsc('.', $fi, \$refetched)
2240 printdebug "considering saving $f: ";
2242 if (rename_link_xf 1, $f, $upper_f) {
2243 printdebug "linked.\n";
2244 } elsif ((printdebug "($@) "),
2246 fail f_ "saving %s: %s", "$buildproductsdir/$f", $@;
2247 } elsif (!$refetched) {
2248 printdebug "no need.\n";
2249 } elsif (rename_link_xf 1, $f, "$upper_f,fetch") {
2250 printdebug "linked (using ...,fetch).\n";
2251 } elsif ((printdebug "($@) "),
2253 fail f_ "saving %s: %s", "$buildproductsdir/$f,fetch", $@;
2255 printdebug "cannot.\n";
2259 # We unpack and record the orig tarballs first, so that we only
2260 # need disk space for one private copy of the unpacked source.
2261 # But we can't make them into commits until we have the metadata
2262 # from the debian/changelog, so we record the tree objects now and
2263 # make them into commits later.
2265 my $upstreamv = upstreamversion $dsc->{version};
2266 my $orig_f_base = srcfn $upstreamv, '';
2268 foreach my $fi (@dfi) {
2269 # We actually import, and record as a commit, every tarball
2270 # (unless there is only one file, in which case there seems
2273 my $f = $fi->{Filename};
2274 printdebug "import considering $f ";
2275 (printdebug "only one dfi\n"), next if @dfi == 1;
2276 (printdebug "not tar\n"), next unless $f =~ m/\.tar(\.\w+)?$/;
2277 (printdebug "signature\n"), next if $f =~ m/$orig_f_sig_re$/o;
2281 $f =~ m/^\Q$orig_f_base\E\.([^._]+)?\.tar(?:\.\w+)?$/;
2283 printdebug "Y ", (join ' ', map { $_//"(none)" }
2284 $compr_ext, $orig_f_part
2287 my $input = new IO::File $f, '<' or die "$f $!";
2291 if (defined $compr_ext) {
2293 Dpkg::Compression::compression_guess_from_filename $f;
2294 fail "Dpkg::Compression cannot handle file $f in source package"
2295 if defined $compr_ext && !defined $cname;
2297 new Dpkg::Compression::Process compression => $cname;
2298 @compr_cmd = $compr_proc->get_uncompress_cmdline();
2299 my $compr_fh = new IO::Handle;
2300 my $compr_pid = open $compr_fh, "-|" // confess $!;
2302 open STDIN, "<&", $input or confess $!;
2304 die "dgit (child): exec $compr_cmd[0]: $!\n";
2309 rmtree "_unpack-tar";
2310 mkdir "_unpack-tar" or confess $!;
2311 my @tarcmd = qw(tar -x -f -
2312 --no-same-owner --no-same-permissions
2313 --no-acls --no-xattrs --no-selinux);
2314 my $tar_pid = fork // confess $!;
2316 chdir "_unpack-tar" or confess $!;
2317 open STDIN, "<&", $input or confess $!;
2319 die f_ "dgit (child): exec %s: %s", $tarcmd[0], $!;
2321 $!=0; (waitpid $tar_pid, 0) == $tar_pid or confess $!;
2322 !$? or failedcmd @tarcmd;
2325 (@compr_cmd ? ($?==SIGPIPE || failedcmd @compr_cmd)
2327 # finally, we have the results in "tarball", but maybe
2328 # with the wrong permissions
2330 runcmd qw(chmod -R +rwX _unpack-tar);
2331 changedir "_unpack-tar";
2332 remove_stray_gits($f);
2333 mktree_in_ud_here();
2335 my ($tree) = git_add_write_tree();
2336 my $tentries = cmdoutput @git, qw(ls-tree -z), $tree;
2337 if ($tentries =~ m/^\d+ tree (\w+)\t[^\000]+\000$/s) {
2339 printdebug "one subtree $1\n";
2341 printdebug "multiple subtrees\n";
2344 rmtree "_unpack-tar";
2346 my $ent = [ $f, $tree ];
2348 Orig => !!$orig_f_part,
2349 Sort => (!$orig_f_part ? 2 :
2350 $orig_f_part =~ m/-/g ? 1 :
2358 # put any without "_" first (spec is not clear whether files
2359 # are always in the usual order). Tarballs without "_" are
2360 # the main orig or the debian tarball.
2361 $a->{Sort} <=> $b->{Sort} or
2365 my $any_orig = grep { $_->{Orig} } @tartrees;
2367 my $dscfn = "$package.dsc";
2369 my $treeimporthow = 'package';
2371 open D, ">", $dscfn or die "$dscfn: $!";
2372 print D $dscdata or die "$dscfn: $!";
2373 close D or die "$dscfn: $!";
2374 my @cmd = qw(dpkg-source);
2375 push @cmd, '--no-check' if $dsc_checked;
2376 if (madformat $dsc->{format}) {
2377 push @cmd, '--skip-patches';
2378 $treeimporthow = 'unpatched';
2380 push @cmd, qw(-x --), $dscfn;
2383 my ($tree,$dir) = mktree_in_ud_from_only_subdir(__ "source package");
2384 if (madformat $dsc->{format}) {
2385 check_for_vendor_patches();
2389 if (madformat $dsc->{format}) {
2390 my @pcmd = qw(dpkg-source --before-build .);
2391 runcmd shell_cmd 'exec >/dev/null', @pcmd;
2393 $dappliedtree = git_add_write_tree();
2396 my @clogcmd = qw(dpkg-parsechangelog --format rfc822 --all);
2400 printdebug "import clog search...\n";
2401 parsechangelog_loop \@clogcmd, (__ "package changelog"), sub {
2402 my ($thisstanza, $desc) = @_;
2403 no warnings qw(exiting);
2405 $clogp //= $thisstanza;
2407 printdebug "import clog $thisstanza->{version} $desc...\n";
2409 last if !$any_orig; # we don't need $r1clogp
2411 # We look for the first (most recent) changelog entry whose
2412 # version number is lower than the upstream version of this
2413 # package. Then the last (least recent) previous changelog
2414 # entry is treated as the one which introduced this upstream
2415 # version and used for the synthetic commits for the upstream
2418 # One might think that a more sophisticated algorithm would be
2419 # necessary. But: we do not want to scan the whole changelog
2420 # file. Stopping when we see an earlier version, which
2421 # necessarily then is an earlier upstream version, is the only
2422 # realistic way to do that. Then, either the earliest
2423 # changelog entry we have seen so far is indeed the earliest
2424 # upload of this upstream version; or there are only changelog
2425 # entries relating to later upstream versions (which is not
2426 # possible unless the changelog and .dsc disagree about the
2427 # version). Then it remains to choose between the physically
2428 # last entry in the file, and the one with the lowest version
2429 # number. If these are not the same, we guess that the
2430 # versions were created in a non-monotonic order rather than
2431 # that the changelog entries have been misordered.
2433 printdebug "import clog $thisstanza->{version} vs $upstreamv...\n";
2435 last if version_compare($thisstanza->{version}, $upstreamv) < 0;
2436 $r1clogp = $thisstanza;
2438 printdebug "import clog $r1clogp->{version} becomes r1\n";
2441 $clogp or fail __ "package changelog has no entries!";
2443 my $authline = clogp_authline $clogp;
2444 my $changes = getfield $clogp, 'Changes';
2445 $changes =~ s/^\n//; # Changes: \n
2446 my $cversion = getfield $clogp, 'Version';
2449 $r1clogp //= $clogp; # maybe there's only one entry;
2450 my $r1authline = clogp_authline $r1clogp;
2451 # Strictly, r1authline might now be wrong if it's going to be
2452 # unused because !$any_orig. Whatever.
2454 printdebug "import tartrees authline $authline\n";
2455 printdebug "import tartrees r1authline $r1authline\n";
2457 foreach my $tt (@tartrees) {
2458 printdebug "import tartree $tt->{F} $tt->{Tree}\n";
2460 my $mbody = f_ "Import %s", $tt->{F};
2461 $tt->{Commit} = make_commit_text($tt->{Orig} ? <<END_O : <<END_T);
2464 committer $r1authline
2468 [dgit import orig $tt->{F}]
2476 [dgit import tarball $package $cversion $tt->{F}]
2481 printdebug "import main commit\n";
2483 open C, ">../commit.tmp" or confess $!;
2484 print C <<END or confess $!;
2487 print C <<END or confess $! foreach @tartrees;
2490 print C <<END or confess $!;
2496 [dgit import $treeimporthow $package $cversion]
2499 close C or confess $!;
2500 my $rawimport_hash = make_commit qw(../commit.tmp);
2502 if (madformat $dsc->{format}) {
2503 printdebug "import apply patches...\n";
2505 # regularise the state of the working tree so that
2506 # the checkout of $rawimport_hash works nicely.
2507 my $dappliedcommit = make_commit_text(<<END);
2514 runcmd @git, qw(checkout -q -b dapplied), $dappliedcommit;
2516 runcmd @git, qw(checkout -q -b unpa), $rawimport_hash;
2518 # We need the answers to be reproducible
2519 my @authline = clogp_authline($clogp);
2520 local $ENV{GIT_COMMITTER_NAME} = $authline[0];
2521 local $ENV{GIT_COMMITTER_EMAIL} = $authline[1];
2522 local $ENV{GIT_COMMITTER_DATE} = $authline[2];
2523 local $ENV{GIT_AUTHOR_NAME} = $authline[0];
2524 local $ENV{GIT_AUTHOR_EMAIL} = $authline[1];
2525 local $ENV{GIT_AUTHOR_DATE} = $authline[2];
2527 my $path = $ENV{PATH} or die;
2529 # we use ../../gbp-pq-output, which (given that we are in
2530 # $playground/PLAYTREE, and $playground is .git/dgit/unpack,
2533 foreach my $use_absurd (qw(0 1)) {
2534 runcmd @git, qw(checkout -q unpa);
2535 runcmd @git, qw(update-ref -d refs/heads/patch-queue/unpa);
2536 local $ENV{PATH} = $path;
2539 progress "warning: $@";
2540 $path = "$absurdity:$path";
2541 progress f_ "%s: trying slow absurd-git-apply...", $us;
2542 rename "../../gbp-pq-output","../../gbp-pq-output.0"
2547 die "forbid absurd git-apply\n" if $use_absurd
2548 && forceing [qw(import-gitapply-no-absurd)];
2549 die "only absurd git-apply!\n" if !$use_absurd
2550 && forceing [qw(import-gitapply-absurd)];
2552 local $ENV{DGIT_ABSURD_DEBUG} = $debuglevel if $use_absurd;
2553 local $ENV{PATH} = $path if $use_absurd;
2555 my @showcmd = (gbp_pq, qw(import));
2556 my @realcmd = shell_cmd
2557 'exec >/dev/null 2>>../../gbp-pq-output', @showcmd;
2558 debugcmd "+",@realcmd;
2559 if (system @realcmd) {
2560 die f_ "%s failed: %s\n",
2561 +(shellquote @showcmd),
2562 failedcmd_waitstatus();
2565 my $gapplied = git_rev_parse('HEAD');
2566 my $gappliedtree = cmdoutput @git, qw(rev-parse HEAD:);
2567 $gappliedtree eq $dappliedtree or
2568 fail f_ <<END, $gapplied, $gappliedtree, $dappliedtree;
2569 gbp-pq import and dpkg-source disagree!
2570 gbp-pq import gave commit %s
2571 gbp-pq import gave tree %s
2572 dpkg-source --before-build gave tree %s
2574 $rawimport_hash = $gapplied;
2579 { local $@; eval { runcmd qw(cat ../../gbp-pq-output); }; }
2584 progress f_ "synthesised git commit from .dsc %s", $cversion;
2586 my $rawimport_mergeinput = {
2587 Commit => $rawimport_hash,
2588 Info => __ "Import of source package",
2590 my @output = ($rawimport_mergeinput);
2592 if ($lastpush_mergeinput) {
2593 my $oldclogp = mergeinfo_getclogp($lastpush_mergeinput);
2594 my $oversion = getfield $oldclogp, 'Version';
2596 version_compare($oversion, $cversion);
2598 @output = ($rawimport_mergeinput, $lastpush_mergeinput,
2599 { ReverseParents => 1,
2600 Message => (f_ <<END, $package, $cversion, $csuite) });
2601 Record %s (%s) in archive suite %s
2603 } elsif ($vcmp > 0) {
2604 print STDERR f_ <<END, $cversion, $oversion,
2606 Version actually in archive: %s (older)
2607 Last version pushed with dgit: %s (newer or same)
2610 __ $later_warning_msg or confess $!;
2611 @output = $lastpush_mergeinput;
2613 # Same version. Use what's in the server git branch,
2614 # discarding our own import. (This could happen if the
2615 # server automatically imports all packages into git.)
2616 @output = $lastpush_mergeinput;
2624 sub complete_file_from_dsc ($$;$) {
2625 our ($dstdir, $fi, $refetched) = @_;
2626 # Ensures that we have, in $dstdir, the file $fi, with the correct
2627 # contents. (Downloading it from alongside $dscurl if necessary.)
2628 # If $refetched is defined, can overwrite "$dstdir/$fi->{Filename}"
2629 # and will set $$refetched=1 if it did so (or tried to).
2631 my $f = $fi->{Filename};
2632 my $tf = "$dstdir/$f";
2636 my $checkhash = sub {
2637 open F, "<", "$tf" or die "$tf: $!";
2638 $fi->{Digester}->reset();
2639 $fi->{Digester}->addfile(*F);
2640 F->error and confess $!;
2641 $got = $fi->{Digester}->hexdigest();
2642 return $got eq $fi->{Hash};
2645 if (stat_exists $tf) {
2646 if ($checkhash->()) {
2647 progress f_ "using existing %s", $f;
2651 fail f_ "file %s has hash %s but .dsc demands hash %s".
2652 " (perhaps you should delete this file?)",
2653 $f, $got, $fi->{Hash};
2655 progress f_ "need to fetch correct version of %s", $f;
2656 unlink $tf or die "$tf $!";
2659 printdebug "$tf does not exist, need to fetch\n";
2663 $furl =~ s{/[^/]+$}{};
2665 die "$f ?" unless $f =~ m/^\Q${package}\E_/;
2666 die "$f ?" if $f =~ m#/#;
2667 runcmd_ordryrun_local @curl,qw(-f -o),$tf,'--',"$furl";
2668 return 0 if !act_local();
2671 fail f_ "file %s has hash %s but .dsc demands hash %s".
2672 " (got wrong file from archive!)",
2673 $f, $got, $fi->{Hash};
2678 sub ensure_we_have_orig () {
2679 my @dfi = dsc_files_info();
2680 foreach my $fi (@dfi) {
2681 my $f = $fi->{Filename};
2682 next unless is_orig_file_in_dsc($f, \@dfi);
2683 complete_file_from_dsc($buildproductsdir, $fi)
2688 #---------- git fetch ----------
2690 sub lrfetchrefs () { return "refs/dgit-fetch/".access_basedistro(); }
2691 sub lrfetchref () { return lrfetchrefs.'/'.server_branch($csuite); }
2693 # We fetch some parts of lrfetchrefs/*. Ideally we delete these
2694 # locally fetched refs because they have unhelpful names and clutter
2695 # up gitk etc. So we track whether we have "used up" head ref (ie,
2696 # whether we have made another local ref which refers to this object).
2698 # (If we deleted them unconditionally, then we might end up
2699 # re-fetching the same git objects each time dgit fetch was run.)
2701 # So, each use of lrfetchrefs needs to be accompanied by arrangements
2702 # in git_fetch_us to fetch the refs in question, and possibly a call
2703 # to lrfetchref_used.
2705 our (%lrfetchrefs_f, %lrfetchrefs_d);
2706 # $lrfetchrefs_X{lrfetchrefs."/heads/whatever"} = $objid
2708 sub lrfetchref_used ($) {
2709 my ($fullrefname) = @_;
2710 my $objid = $lrfetchrefs_f{$fullrefname};
2711 $lrfetchrefs_d{$fullrefname} = $objid if defined $objid;
2714 sub git_lrfetch_sane {
2715 my ($url, $supplementary, @specs) = @_;
2716 # Make a 'refs/'.lrfetchrefs.'/*' be just like on server,
2717 # at least as regards @specs. Also leave the results in
2718 # %lrfetchrefs_f, and arrange for lrfetchref_used to be
2719 # able to clean these up.
2721 # With $supplementary==1, @specs must not contain wildcards
2722 # and we add to our previous fetches (non-atomically).
2724 # This is rather miserable:
2725 # When git fetch --prune is passed a fetchspec ending with a *,
2726 # it does a plausible thing. If there is no * then:
2727 # - it matches subpaths too, even if the supplied refspec
2728 # starts refs, and behaves completely madly if the source
2729 # has refs/refs/something. (See, for example, Debian #NNNN.)
2730 # - if there is no matching remote ref, it bombs out the whole
2732 # We want to fetch a fixed ref, and we don't know in advance
2733 # if it exists, so this is not suitable.
2735 # Our workaround is to use git ls-remote. git ls-remote has its
2736 # own qairks. Notably, it has the absurd multi-tail-matching
2737 # behaviour: git ls-remote R refs/foo can report refs/foo AND
2738 # refs/refs/foo etc.
2740 # Also, we want an idempotent snapshot, but we have to make two
2741 # calls to the remote: one to git ls-remote and to git fetch. The
2742 # solution is use git ls-remote to obtain a target state, and
2743 # git fetch to try to generate it. If we don't manage to generate
2744 # the target state, we try again.
2746 printdebug "git_lrfetch_sane suppl=$supplementary specs @specs\n";
2748 my $specre = join '|', map {
2751 my $wildcard = $x =~ s/\\\*$/.*/;
2752 die if $wildcard && $supplementary;
2755 printdebug "git_lrfetch_sane specre=$specre\n";
2756 my $wanted_rref = sub {
2758 return m/^(?:$specre)$/;
2761 my $fetch_iteration = 0;
2764 printdebug "git_lrfetch_sane iteration $fetch_iteration\n";
2765 if (++$fetch_iteration > 10) {
2766 fail __ "too many iterations trying to get sane fetch!";
2769 my @look = map { "refs/$_" } @specs;
2770 my @lcmd = (@git, qw(ls-remote -q --refs), $url, @look);
2774 open GITLS, "-|", @lcmd or confess $!;
2776 printdebug "=> ", $_;
2777 m/^(\w+)\s+(\S+)\n/ or die "ls-remote $_ ?";
2778 my ($objid,$rrefname) = ($1,$2);
2779 if (!$wanted_rref->($rrefname)) {
2780 print STDERR f_ <<END, "@look", $rrefname;
2781 warning: git ls-remote %s reported %s; this is silly, ignoring it.
2785 $wantr{$rrefname} = $objid;
2788 close GITLS or failedcmd @lcmd;
2790 # OK, now %want is exactly what we want for refs in @specs
2792 !m/\*$/ && !exists $wantr{"refs/$_"} ? () :
2793 "+refs/$_:".lrfetchrefs."/$_";
2796 printdebug "git_lrfetch_sane fspecs @fspecs\n";
2798 my @fcmd = (@git, qw(fetch -p -n -q), $url, @fspecs);
2799 runcmd_ordryrun_local @fcmd if @fspecs;
2801 if (!$supplementary) {
2802 %lrfetchrefs_f = ();
2806 git_for_each_ref(lrfetchrefs, sub {
2807 my ($objid,$objtype,$lrefname,$reftail) = @_;
2808 $lrfetchrefs_f{$lrefname} = $objid;
2809 $objgot{$objid} = 1;
2812 if ($supplementary) {
2816 foreach my $lrefname (sort keys %lrfetchrefs_f) {
2817 my $rrefname = 'refs'.substr($lrefname, length lrfetchrefs);
2818 if (!exists $wantr{$rrefname}) {
2819 if ($wanted_rref->($rrefname)) {
2821 git-fetch @fspecs created $lrefname which git ls-remote @look didn't list.
2824 print STDERR f_ <<END, "@fspecs", $lrefname
2825 warning: git fetch %s created %s; this is silly, deleting it.
2828 runcmd_ordryrun_local @git, qw(update-ref -d), $lrefname;
2829 delete $lrfetchrefs_f{$lrefname};
2833 foreach my $rrefname (sort keys %wantr) {
2834 my $lrefname = lrfetchrefs.substr($rrefname, 4);
2835 my $got = $lrfetchrefs_f{$lrefname} // '<none>';
2836 my $want = $wantr{$rrefname};
2837 next if $got eq $want;
2838 if (!defined $objgot{$want}) {
2839 fail __ <<END unless act_local();
2840 --dry-run specified but we actually wanted the results of git fetch,
2841 so this is not going to work. Try running dgit fetch first,
2842 or using --damp-run instead of --dry-run.
2844 print STDERR f_ <<END, $lrefname, $want;
2845 warning: git ls-remote suggests we want %s
2846 warning: and it should refer to %s
2847 warning: but git fetch didn't fetch that object to any relevant ref.
2848 warning: This may be due to a race with someone updating the server.
2849 warning: Will try again...
2851 next FETCH_ITERATION;
2854 git-fetch @fspecs made $lrefname=$got but want git ls-remote @look says $want
2856 runcmd_ordryrun_local @git, qw(update-ref -m),
2857 "dgit fetch git fetch fixup", $lrefname, $want;
2858 $lrfetchrefs_f{$lrefname} = $want;
2863 if (defined $csuite) {
2864 printdebug "git_lrfetch_sane: tidying any old suite lrfetchrefs\n";
2865 git_for_each_ref("refs/dgit-fetch/$csuite", sub {
2866 my ($objid,$objtype,$lrefname,$reftail) = @_;
2867 next if $lrfetchrefs_f{$lrefname}; # $csuite eq $distro ?
2868 runcmd_ordryrun_local @git, qw(update-ref -d), $lrefname;
2872 printdebug "git_lrfetch_sane: git fetch --no-insane emulation complete\n",
2873 Dumper(\%lrfetchrefs_f);
2876 sub git_fetch_us () {
2877 # Want to fetch only what we are going to use, unless
2878 # deliberately-not-ff, in which case we must fetch everything.
2880 my @specs = deliberately_not_fast_forward ? qw(tags/*) :
2882 (quiltmode_splitbrain
2883 ? (map { $_->('*',access_nomdistro) }
2884 \&debiantag_new, \&debiantag_maintview)
2885 : debiantags('*',access_nomdistro));
2886 push @specs, server_branch($csuite);
2887 push @specs, $rewritemap;
2888 push @specs, qw(heads/*) if deliberately_not_fast_forward;
2890 my $url = access_giturl();
2891 git_lrfetch_sane $url, 0, @specs;
2894 my @tagpats = debiantags('*',access_nomdistro);
2896 git_for_each_ref([map { "refs/tags/$_" } @tagpats], sub {
2897 my ($objid,$objtype,$fullrefname,$reftail) = @_;
2898 printdebug "currently $fullrefname=$objid\n";
2899 $here{$fullrefname} = $objid;
2901 git_for_each_ref([map { lrfetchrefs."/tags/".$_ } @tagpats], sub {
2902 my ($objid,$objtype,$fullrefname,$reftail) = @_;
2903 my $lref = "refs".substr($fullrefname, length(lrfetchrefs));
2904 printdebug "offered $lref=$objid\n";
2905 if (!defined $here{$lref}) {
2906 my @upd = (@git, qw(update-ref), $lref, $objid, '');
2907 runcmd_ordryrun_local @upd;
2908 lrfetchref_used $fullrefname;
2909 } elsif ($here{$lref} eq $objid) {
2910 lrfetchref_used $fullrefname;
2912 print STDERR f_ "Not updating %s from %s to %s.\n",
2913 $lref, $here{$lref}, $objid;
2918 #---------- dsc and archive handling ----------
2920 sub mergeinfo_getclogp ($) {
2921 # Ensures thit $mi->{Clogp} exists and returns it
2923 $mi->{Clogp} = commit_getclogp($mi->{Commit});
2926 sub mergeinfo_version ($) {
2927 return getfield( (mergeinfo_getclogp $_[0]), 'Version' );
2930 sub fetch_from_archive_record_1 ($) {
2932 runcmd git_update_ref_cmd "dgit fetch $csuite", 'DGIT_ARCHIVE', $hash;
2933 cmdoutput @git, qw(log -n2), $hash;
2934 # ... gives git a chance to complain if our commit is malformed
2937 sub fetch_from_archive_record_2 ($) {
2939 my @upd_cmd = (git_update_ref_cmd 'dgit fetch', lrref(), $hash);
2943 dryrun_report @upd_cmd;
2947 sub parse_dsc_field_def_dsc_distro () {
2948 $dsc_distro //= cfg qw(dgit.default.old-dsc-distro
2949 dgit.default.distro);
2952 sub parse_dsc_field ($$) {
2953 my ($dsc, $what) = @_;
2955 foreach my $field (@ourdscfield) {
2956 $f = $dsc->{$field};
2961 progress f_ "%s: NO git hash", $what;
2962 parse_dsc_field_def_dsc_distro();
2963 } elsif (($dsc_hash, $dsc_distro, $dsc_hint_tag, $dsc_hint_url)
2964 = $f =~ m/^(\w+)\s+($distro_re)\s+($versiontag_re)\s+(\S+)(?:\s|$)/) {
2965 progress f_ "%s: specified git info (%s)", $what, $dsc_distro;
2966 $dsc_hint_tag = [ $dsc_hint_tag ];
2967 } elsif ($f =~ m/^\w+\s*$/) {
2969 parse_dsc_field_def_dsc_distro();
2970 $dsc_hint_tag = [ debiantags +(getfield $dsc, 'Version'),
2972 progress f_ "%s: specified git hash", $what;
2974 fail f_ "%s: invalid Dgit info", $what;
2978 sub resolve_dsc_field_commit ($$) {
2979 my ($already_distro, $already_mapref) = @_;
2981 return unless defined $dsc_hash;
2984 defined $already_mapref &&
2985 ($already_distro eq $dsc_distro || !$chase_dsc_distro)
2986 ? $already_mapref : undef;
2990 my ($what, @fetch) = @_;
2992 local $idistro = $dsc_distro;
2993 my $lrf = lrfetchrefs;
2995 if (!$chase_dsc_distro) {
2996 progress f_ "not chasing .dsc distro %s: not fetching %s",
3001 progress f_ ".dsc names distro %s: fetching %s", $dsc_distro, $what;
3003 my $url = access_giturl();
3004 if (!defined $url) {
3005 defined $dsc_hint_url or fail f_ <<END, $dsc_distro;
3006 .dsc Dgit metadata is in context of distro %s
3007 for which we have no configured url and .dsc provides no hint
3010 $dsc_hint_url =~ m#^([-+0-9a-zA-Z]+):# ? $1 :
3011 $dsc_hint_url =~ m#^/# ? 'file' : 'bad-syntax';
3012 parse_cfg_bool "dsc-url-proto-ok", 'false',
3013 cfg("dgit.dsc-url-proto-ok.$proto",
3014 "dgit.default.dsc-url-proto-ok")
3015 or fail f_ <<END, $dsc_distro, $proto;
3016 .dsc Dgit metadata is in context of distro %s
3017 for which we have no configured url;
3018 .dsc provides hinted url with protocol %s which is unsafe.
3019 (can be overridden by config - consult documentation)
3021 $url = $dsc_hint_url;
3024 git_lrfetch_sane $url, 1, @fetch;
3029 my $rewrite_enable = do {
3030 local $idistro = $dsc_distro;
3031 access_cfg('rewrite-map-enable', 'RETURN-UNDEF');
3034 if (parse_cfg_bool 'rewrite-map-enable', 'true', $rewrite_enable) {
3035 if (!defined $mapref) {
3036 my $lrf = $do_fetch->((__ "rewrite map"), $rewritemap) or return;
3037 $mapref = $lrf.'/'.$rewritemap;
3039 my $rewritemapdata = git_cat_file $mapref.':map';
3040 if (defined $rewritemapdata
3041 && $rewritemapdata =~ m/^$dsc_hash(?:[ \t](\w+))/m) {
3043 "server's git history rewrite map contains a relevant entry!";
3046 if (defined $dsc_hash) {
3047 progress __ "using rewritten git hash in place of .dsc value";
3049 progress __ "server data says .dsc hash is to be disregarded";
3054 if (!defined git_cat_file $dsc_hash) {
3055 my @tags = map { "tags/".$_ } @$dsc_hint_tag;
3056 my $lrf = $do_fetch->((__ "additional commits"), @tags) &&
3057 defined git_cat_file $dsc_hash
3058 or fail f_ <<END, $dsc_hash;
3059 .dsc Dgit metadata requires commit %s
3060 but we could not obtain that object anywhere.
3062 foreach my $t (@tags) {
3063 my $fullrefname = $lrf.'/'.$t;
3064 # print STDERR "CHK $t $fullrefname ".Dumper(\%lrfetchrefs_f);
3065 next unless $lrfetchrefs_f{$fullrefname};
3066 next unless is_fast_fwd "$fullrefname~0", $dsc_hash;
3067 lrfetchref_used $fullrefname;
3072 sub fetch_from_archive () {
3074 ensure_setup_existing_tree();
3076 # Ensures that lrref() is what is actually in the archive, one way
3077 # or another, according to us - ie this client's
3078 # appropritaely-updated archive view. Also returns the commit id.
3079 # If there is nothing in the archive, leaves lrref alone and
3080 # returns undef. git_fetch_us must have already been called.
3084 parse_dsc_field($dsc, __ 'last upload to archive');
3085 resolve_dsc_field_commit access_basedistro,
3086 lrfetchrefs."/".$rewritemap
3088 progress __ "no version available from the archive";
3091 # If the archive's .dsc has a Dgit field, there are three
3092 # relevant git commitids we need to choose between and/or merge
3094 # 1. $dsc_hash: the Dgit field from the archive
3095 # 2. $lastpush_hash: the suite branch on the dgit git server
3096 # 3. $lastfetch_hash: our local tracking brach for the suite
3098 # These may all be distinct and need not be in any fast forward
3101 # If the dsc was pushed to this suite, then the server suite
3102 # branch will have been updated; but it might have been pushed to
3103 # a different suite and copied by the archive. Conversely a more
3104 # recent version may have been pushed with dgit but not appeared
3105 # in the archive (yet).
3107 # $lastfetch_hash may be awkward because archive imports
3108 # (particularly, imports of Dgit-less .dscs) are performed only as
3109 # needed on individual clients, so different clients may perform a
3110 # different subset of them - and these imports are only made
3111 # public during push. So $lastfetch_hash may represent a set of
3112 # imports different to a subsequent upload by a different dgit
3115 # Our approach is as follows:
3117 # As between $dsc_hash and $lastpush_hash: if $lastpush_hash is a
3118 # descendant of $dsc_hash, then it was pushed by a dgit user who
3119 # had based their work on $dsc_hash, so we should prefer it.
3120 # Otherwise, $dsc_hash was installed into this suite in the
3121 # archive other than by a dgit push, and (necessarily) after the
3122 # last dgit push into that suite (since a dgit push would have
3123 # been descended from the dgit server git branch); thus, in that
3124 # case, we prefer the archive's version (and produce a
3125 # pseudo-merge to overwrite the dgit server git branch).
3127 # (If there is no Dgit field in the archive's .dsc then
3128 # generate_commit_from_dsc uses the version numbers to decide
3129 # whether the suite branch or the archive is newer. If the suite
3130 # branch is newer it ignores the archive's .dsc; otherwise it
3131 # generates an import of the .dsc, and produces a pseudo-merge to
3132 # overwrite the suite branch with the archive contents.)
3134 # The outcome of that part of the algorithm is the `public view',
3135 # and is same for all dgit clients: it does not depend on any
3136 # unpublished history in the local tracking branch.
3138 # As between the public view and the local tracking branch: The
3139 # local tracking branch is only updated by dgit fetch, and
3140 # whenever dgit fetch runs it includes the public view in the
3141 # local tracking branch. Therefore if the public view is not
3142 # descended from the local tracking branch, the local tracking
3143 # branch must contain history which was imported from the archive
3144 # but never pushed; and, its tip is now out of date. So, we make
3145 # a pseudo-merge to overwrite the old imports and stitch the old
3148 # Finally: we do not necessarily reify the public view (as
3149 # described above). This is so that we do not end up stacking two
3150 # pseudo-merges. So what we actually do is figure out the inputs
3151 # to any public view pseudo-merge and put them in @mergeinputs.
3154 # $mergeinputs[]{Commit}
3155 # $mergeinputs[]{Info}
3156 # $mergeinputs[0] is the one whose tree we use
3157 # @mergeinputs is in the order we use in the actual commit)
3160 # $mergeinputs[]{Message} is a commit message to use
3161 # $mergeinputs[]{ReverseParents} if def specifies that parent
3162 # list should be in opposite order
3163 # Such an entry has no Commit or Info. It applies only when found
3164 # in the last entry. (This ugliness is to support making
3165 # identical imports to previous dgit versions.)
3167 my $lastpush_hash = git_get_ref(lrfetchref());
3168 printdebug "previous reference hash=$lastpush_hash\n";
3169 $lastpush_mergeinput = $lastpush_hash && {
3170 Commit => $lastpush_hash,
3171 Info => (__ "dgit suite branch on dgit git server"),
3174 my $lastfetch_hash = git_get_ref(lrref());
3175 printdebug "fetch_from_archive: lastfetch=$lastfetch_hash\n";
3176 my $lastfetch_mergeinput = $lastfetch_hash && {
3177 Commit => $lastfetch_hash,
3178 Info => (__ "dgit client's archive history view"),
3181 my $dsc_mergeinput = $dsc_hash && {
3182 Commit => $dsc_hash,
3183 Info => (__ "Dgit field in .dsc from archive"),
3187 my $del_lrfetchrefs = sub {
3190 printdebug "del_lrfetchrefs...\n";
3191 foreach my $fullrefname (sort keys %lrfetchrefs_d) {
3192 my $objid = $lrfetchrefs_d{$fullrefname};
3193 printdebug "del_lrfetchrefs: $objid $fullrefname\n";
3195 $gur ||= new IO::Handle;
3196 open $gur, "|-", qw(git update-ref --stdin) or confess $!;
3198 printf $gur "delete %s %s\n", $fullrefname, $objid;
3201 close $gur or failedcmd "git update-ref delete lrfetchrefs";
3205 if (defined $dsc_hash) {
3206 ensure_we_have_orig();
3207 if (!$lastpush_hash || $dsc_hash eq $lastpush_hash) {
3208 @mergeinputs = $dsc_mergeinput
3209 } elsif (is_fast_fwd($dsc_hash,$lastpush_hash)) {
3210 print STDERR f_ <<END, $dsc_hash, $lastpush_hash,
3212 Git commit in archive is behind the last version allegedly pushed/uploaded.
3213 Commit referred to by archive: %s
3214 Last version pushed with dgit: %s
3217 __ $later_warning_msg or confess $!;
3218 @mergeinputs = ($lastpush_mergeinput);
3220 # Archive has .dsc which is not a descendant of the last dgit
3221 # push. This can happen if the archive moves .dscs about.
3222 # Just follow its lead.
3223 if (is_fast_fwd($lastpush_hash,$dsc_hash)) {
3224 progress __ "archive .dsc names newer git commit";
3225 @mergeinputs = ($dsc_mergeinput);
3227 progress __ "archive .dsc names other git commit, fixing up";
3228 @mergeinputs = ($dsc_mergeinput, $lastpush_mergeinput);
3232 @mergeinputs = generate_commits_from_dsc();
3233 # We have just done an import. Now, our import algorithm might
3234 # have been improved. But even so we do not want to generate
3235 # a new different import of the same package. So if the
3236 # version numbers are the same, just use our existing version.
3237 # If the version numbers are different, the archive has changed
3238 # (perhaps, rewound).
3239 if ($lastfetch_mergeinput &&
3240 !version_compare( (mergeinfo_version $lastfetch_mergeinput),
3241 (mergeinfo_version $mergeinputs[0]) )) {
3242 @mergeinputs = ($lastfetch_mergeinput);
3244 } elsif ($lastpush_hash) {
3245 # only in git, not in the archive yet
3246 @mergeinputs = ($lastpush_mergeinput);
3247 print STDERR f_ <<END,
3249 Package not found in the archive, but has allegedly been pushed using dgit.
3252 __ $later_warning_msg or confess $!;
3254 printdebug "nothing found!\n";
3255 if (defined $skew_warning_vsn) {
3256 print STDERR f_ <<END, $skew_warning_vsn or confess $!;
3258 Warning: relevant archive skew detected.
3259 Archive allegedly contains %s
3260 But we were not able to obtain any version from the archive or git.
3264 unshift @end, $del_lrfetchrefs;
3268 if ($lastfetch_hash &&
3270 my $h = $_->{Commit};
3271 $h and is_fast_fwd($lastfetch_hash, $h);
3272 # If true, one of the existing parents of this commit
3273 # is a descendant of the $lastfetch_hash, so we'll
3274 # be ff from that automatically.
3278 push @mergeinputs, $lastfetch_mergeinput;
3281 printdebug "fetch mergeinfos:\n";
3282 foreach my $mi (@mergeinputs) {
3284 printdebug " commit $mi->{Commit} $mi->{Info}\n";
3286 printdebug sprintf " ReverseParents=%d Message=%s",
3287 $mi->{ReverseParents}, $mi->{Message};
3291 my $compat_info= pop @mergeinputs
3292 if $mergeinputs[$#mergeinputs]{Message};
3294 @mergeinputs = grep { defined $_->{Commit} } @mergeinputs;
3297 if (@mergeinputs > 1) {
3299 my $tree_commit = $mergeinputs[0]{Commit};
3301 my $tree = get_tree_of_commit $tree_commit;;
3303 # We use the changelog author of the package in question the
3304 # author of this pseudo-merge. This is (roughly) correct if
3305 # this commit is simply representing aa non-dgit upload.
3306 # (Roughly because it does not record sponsorship - but we
3307 # don't have sponsorship info because that's in the .changes,
3308 # which isn't in the archivw.)
3310 # But, it might be that we are representing archive history
3311 # updates (including in-archive copies). These are not really
3312 # the responsibility of the person who created the .dsc, but
3313 # there is no-one whose name we should better use. (The
3314 # author of the .dsc-named commit is clearly worse.)
3316 my $useclogp = mergeinfo_getclogp $mergeinputs[0];
3317 my $author = clogp_authline $useclogp;
3318 my $cversion = getfield $useclogp, 'Version';
3320 my $mcf = dgit_privdir()."/mergecommit";
3321 open MC, ">", $mcf or die "$mcf $!";
3322 print MC <<END or confess $!;
3326 my @parents = grep { $_->{Commit} } @mergeinputs;
3327 @parents = reverse @parents if $compat_info->{ReverseParents};
3328 print MC <<END or confess $! foreach @parents;
3332 print MC <<END or confess $!;
3338 if (defined $compat_info->{Message}) {
3339 print MC $compat_info->{Message} or confess $!;
3341 print MC f_ <<END, $package, $cversion, $csuite or confess $!;
3342 Record %s (%s) in archive suite %s
3346 my $message_add_info = sub {
3348 my $mversion = mergeinfo_version $mi;
3349 printf MC " %-20s %s\n", $mversion, $mi->{Info}
3353 $message_add_info->($mergeinputs[0]);
3354 print MC __ <<END or confess $!;
3355 should be treated as descended from
3357 $message_add_info->($_) foreach @mergeinputs[1..$#mergeinputs];
3360 close MC or confess $!;
3361 $hash = make_commit $mcf;
3363 $hash = $mergeinputs[0]{Commit};
3365 printdebug "fetch hash=$hash\n";
3368 my ($lasth, $what) = @_;
3369 return unless $lasth;
3370 confess "$lasth $hash $what ?" unless is_fast_fwd($lasth, $hash);
3373 $chkff->($lastpush_hash, __ 'dgit repo server tip (last push)')
3375 $chkff->($lastfetch_hash, __ 'local tracking tip (last fetch)');
3377 fetch_from_archive_record_1($hash);
3379 if (defined $skew_warning_vsn) {
3380 printdebug "SKEW CHECK WANT $skew_warning_vsn\n";
3381 my $gotclogp = commit_getclogp($hash);
3382 my $got_vsn = getfield $gotclogp, 'Version';
3383 printdebug "SKEW CHECK GOT $got_vsn\n";
3384 if (version_compare($got_vsn, $skew_warning_vsn) < 0) {
3385 print STDERR f_ <<END, $skew_warning_vsn, $got_vsn or confess $!;
3387 Warning: archive skew detected. Using the available version:
3388 Archive allegedly contains %s
3389 We were able to obtain only %s
3395 if ($lastfetch_hash ne $hash) {
3396 fetch_from_archive_record_2($hash);
3399 lrfetchref_used lrfetchref();
3401 check_gitattrs($hash, __ "fetched source tree");
3403 unshift @end, $del_lrfetchrefs;
3407 sub set_local_git_config ($$) {
3409 runcmd @git, qw(config), $k, $v;
3412 sub setup_mergechangelogs (;$) {
3414 return unless $always || access_cfg_bool(1, 'setup-mergechangelogs');
3416 my $driver = 'dpkg-mergechangelogs';
3417 my $cb = "merge.$driver";
3418 confess unless defined $maindir;
3419 my $attrs = "$maindir_gitcommon/info/attributes";
3420 ensuredir "$maindir_gitcommon/info";
3422 open NATTRS, ">", "$attrs.new" or die "$attrs.new $!";
3423 if (!open ATTRS, "<", $attrs) {
3424 $!==ENOENT or die "$attrs: $!";
3428 next if m{^debian/changelog\s};
3429 print NATTRS $_, "\n" or confess $!;
3431 ATTRS->error and confess $!;
3434 print NATTRS "debian/changelog merge=$driver\n" or confess $!;
3437 set_local_git_config "$cb.name", __ 'debian/changelog merge driver';
3438 set_local_git_config "$cb.driver", 'dpkg-mergechangelogs -m %O %A %B %A';
3440 rename "$attrs.new", "$attrs" or die "$attrs: $!";
3443 sub setup_useremail (;$) {
3445 return unless $always || access_cfg_bool(1, 'setup-useremail');
3448 my ($k, $envvar) = @_;
3449 my $v = access_cfg("user-$k", 'RETURN-UNDEF') // $ENV{$envvar};
3450 return unless defined $v;
3451 set_local_git_config "user.$k", $v;
3454 $setup->('email', 'DEBEMAIL');
3455 $setup->('name', 'DEBFULLNAME');
3458 sub ensure_setup_existing_tree () {
3459 my $k = "remote.$remotename.skipdefaultupdate";
3460 my $c = git_get_config $k;
3461 return if defined $c;
3462 set_local_git_config $k, 'true';
3465 sub open_main_gitattrs () {
3466 confess 'internal error no maindir' unless defined $maindir;
3467 my $gai = new IO::File "$maindir_gitcommon/info/attributes"
3469 or die "open $maindir_gitcommon/info/attributes: $!";
3473 our $gitattrs_ourmacro_re = qr{^\[attr\]dgit-defuse-attrs\s};
3475 sub is_gitattrs_setup () {
3478 # 1: gitattributes set up and should be left alone
3480 # 0: there is a dgit-defuse-attrs but it needs fixing
3481 # undef: there is none
3482 my $gai = open_main_gitattrs();
3483 return 0 unless $gai;
3485 next unless m{$gitattrs_ourmacro_re};
3486 return 1 if m{\s-working-tree-encoding\s};
3487 printdebug "is_gitattrs_setup: found old macro\n";
3490 $gai->error and confess $!;
3491 printdebug "is_gitattrs_setup: found nothing\n";
3495 sub setup_gitattrs (;$) {
3497 return unless $always || access_cfg_bool(1, 'setup-gitattributes');
3499 my $already = is_gitattrs_setup();
3502 [attr]dgit-defuse-attrs already found, and proper, in .git/info/attributes
3503 not doing further gitattributes setup
3507 my $new = "[attr]dgit-defuse-attrs $negate_harmful_gitattrs";
3508 my $af = "$maindir_gitcommon/info/attributes";
3509 ensuredir "$maindir_gitcommon/info";
3511 open GAO, "> $af.new" or confess $!;
3512 print GAO <<END, __ <<ENDT or confess $! unless defined $already;
3516 # ^ see GITATTRIBUTES in dgit(7) and dgit setup-new-tree in dgit(1)
3518 my $gai = open_main_gitattrs();
3521 if (m{$gitattrs_ourmacro_re}) {
3522 die unless defined $already;
3526 print GAO $_, "\n" or confess $!;
3528 $gai->error and confess $!;
3530 close GAO or confess $!;
3531 rename "$af.new", "$af" or fail f_ "install %s: %s", $af, $!;
3534 sub setup_new_tree () {
3535 setup_mergechangelogs();
3540 sub check_gitattrs ($$) {
3541 my ($treeish, $what) = @_;
3543 return if is_gitattrs_setup;
3546 my @cmd = (@git, qw(ls-tree -lrz --), "${treeish}:");
3548 my $gafl = new IO::File;
3549 open $gafl, "-|", @cmd or confess $!;
3552 s/^\d+\s+\w+\s+\w+\s+(\d+)\t// or die;
3554 next unless m{(?:^|/)\.gitattributes$};
3556 # oh dear, found one
3557 print STDERR f_ <<END, $what;
3558 dgit: warning: %s contains .gitattributes
3559 dgit: .gitattributes not (fully) defused. Recommended: dgit setup-new-tree.
3564 # tree contains no .gitattributes files
3565 $?=0; $!=0; close $gafl or failedcmd @cmd;
3569 sub multisuite_suite_child ($$$) {
3570 my ($tsuite, $mergeinputs, $fn) = @_;
3571 # in child, sets things up, calls $fn->(), and returns undef
3572 # in parent, returns canonical suite name for $tsuite
3573 my $canonsuitefh = IO::File::new_tmpfile;
3574 my $pid = fork // confess $!;
3578 $us .= " [$isuite]";
3579 $debugprefix .= " ";
3580 progress f_ "fetching %s...", $tsuite;
3581 canonicalise_suite();
3582 print $canonsuitefh $csuite, "\n" or confess $!;
3583 close $canonsuitefh or confess $!;
3587 waitpid $pid,0 == $pid or confess $!;
3588 fail f_ "failed to obtain %s: %s", $tsuite, waitstatusmsg()
3590 seek $canonsuitefh,0,0 or confess $!;
3591 local $csuite = <$canonsuitefh>;
3592 confess $! unless defined $csuite && chomp $csuite;
3594 printdebug "multisuite $tsuite missing\n";
3597 printdebug "multisuite $tsuite ok (canon=$csuite)\n";
3598 push @$mergeinputs, {
3605 sub fork_for_multisuite ($) {
3606 my ($before_fetch_merge) = @_;
3607 # if nothing unusual, just returns ''
3610 # returns 0 to caller in child, to do first of the specified suites
3611 # in child, $csuite is not yet set
3613 # returns 1 to caller in parent, to finish up anything needed after
3614 # in parent, $csuite is set to canonicalised portmanteau
3616 my $org_isuite = $isuite;
3617 my @suites = split /\,/, $isuite;
3618 return '' unless @suites > 1;
3619 printdebug "fork_for_multisuite: @suites\n";
3623 my $cbasesuite = multisuite_suite_child($suites[0], \@mergeinputs,
3625 return 0 unless defined $cbasesuite;
3627 fail f_ "package %s missing in (base suite) %s", $package, $cbasesuite
3628 unless @mergeinputs;
3630 my @csuites = ($cbasesuite);
3632 $before_fetch_merge->();
3634 foreach my $tsuite (@suites[1..$#suites]) {
3635 $tsuite =~ s/^-/$cbasesuite-/;
3636 my $csubsuite = multisuite_suite_child($tsuite, \@mergeinputs,
3643 $csubsuite =~ s/^\Q$cbasesuite\E-/-/;
3644 push @csuites, $csubsuite;
3647 foreach my $mi (@mergeinputs) {
3648 my $ref = git_get_ref $mi->{Ref};
3649 die "$mi->{Ref} ?" unless length $ref;
3650 $mi->{Commit} = $ref;
3653 $csuite = join ",", @csuites;
3655 my $previous = git_get_ref lrref;
3657 unshift @mergeinputs, {
3658 Commit => $previous,
3659 Info => (__ "local combined tracking branch"),
3661 "archive seems to have rewound: local tracking branch is ahead!"),
3665 foreach my $ix (0..$#mergeinputs) {
3666 $mergeinputs[$ix]{Index} = $ix;
3669 @mergeinputs = sort {
3670 -version_compare(mergeinfo_version $a,
3671 mergeinfo_version $b) # highest version first
3673 $a->{Index} <=> $b->{Index}; # earliest in spec first
3679 foreach my $mi (@mergeinputs) {
3680 printdebug "multisuite merge check $mi->{Info}\n";
3681 foreach my $previous (@needed) {
3682 next unless is_fast_fwd $mi->{Commit}, $previous->{Commit};
3683 printdebug "multisuite merge un-needed $previous->{Info}\n";
3687 printdebug "multisuite merge this-needed\n";
3688 $mi->{Character} = '+';
3691 $needed[0]{Character} = '*';
3693 my $output = $needed[0]{Commit};
3696 printdebug "multisuite merge nontrivial\n";
3697 my $tree = cmdoutput qw(git rev-parse), $needed[0]{Commit}.':';
3699 my $commit = "tree $tree\n";
3700 my $msg = f_ "Combine archive branches %s [dgit]\n\n".
3701 "Input branches:\n",
3704 foreach my $mi (sort { $a->{Index} <=> $b->{Index} } @mergeinputs) {
3705 printdebug "multisuite merge include $mi->{Info}\n";
3706 $mi->{Character} //= ' ';
3707 $commit .= "parent $mi->{Commit}\n";
3708 $msg .= sprintf " %s %-25s %s\n",
3710 (mergeinfo_version $mi),
3713 my $authline = clogp_authline mergeinfo_getclogp $needed[0];
3714 $msg .= __ "\nKey\n".
3715 " * marks the highest version branch, which choose to use\n".
3716 " + marks each branch which was not already an ancestor\n\n";
3718 "[dgit multi-suite $csuite]\n";
3720 "author $authline\n".
3721 "committer $authline\n\n";
3722 $output = make_commit_text $commit.$msg;
3723 printdebug "multisuite merge generated $output\n";
3726 fetch_from_archive_record_1($output);
3727 fetch_from_archive_record_2($output);
3729 progress f_ "calculated combined tracking suite %s", $csuite;
3734 sub clone_set_head () {
3735 open H, "> .git/HEAD" or confess $!;
3736 print H "ref: ".lref()."\n" or confess $!;
3737 close H or confess $!;
3739 sub clone_finish ($) {
3741 runcmd @git, qw(reset --hard), lrref();
3742 runcmd qw(bash -ec), <<'END';
3744 git ls-tree -r --name-only -z HEAD | \
3745 xargs -0r touch -h -r . --
3747 printdone f_ "ready for work in %s", $dstdir;
3751 # in multisuite, returns twice!
3752 # once in parent after first suite fetched,
3753 # and then again in child after everything is finished
3755 badusage __ "dry run makes no sense with clone" unless act_local();
3757 my $multi_fetched = fork_for_multisuite(sub {
3758 printdebug "multi clone before fetch merge\n";
3762 if ($multi_fetched) {
3763 printdebug "multi clone after fetch merge\n";
3765 clone_finish($dstdir);
3768 printdebug "clone main body\n";
3770 mkdir $dstdir or fail f_ "create \`%s': %s", $dstdir, $!;
3774 canonicalise_suite();
3775 my $hasgit = check_for_git();
3777 runcmd @git, qw(init -q);
3781 my $giturl = access_giturl(1);
3782 if (defined $giturl) {
3783 runcmd @git, qw(remote add), 'origin', $giturl;
3786 progress __ "fetching existing git history";
3788 runcmd_ordryrun_local @git, qw(fetch origin);
3790 progress __ "starting new git history";
3792 fetch_from_archive() or no_such_package;
3793 my $vcsgiturl = $dsc->{'Vcs-Git'};
3794 if (length $vcsgiturl) {
3795 $vcsgiturl =~ s/\s+-b\s+\S+//g;
3796 runcmd @git, qw(remote add vcs-git), $vcsgiturl;
3798 clone_finish($dstdir);
3802 canonicalise_suite();
3803 if (check_for_git()) {
3806 fetch_from_archive() or no_such_package();
3808 my $vcsgiturl = $dsc && $dsc->{'Vcs-Git'};
3809 if (length $vcsgiturl and
3810 (grep { $csuite eq $_ }
3812 cfg 'dgit.vcs-git.suites')) {
3813 my $current = cfg 'remote.vcs-git.url', 'RETURN-UNDEF';
3814 if (defined $current && $current ne $vcsgiturl) {
3815 print STDERR f_ <<END, $csuite;
3816 FYI: Vcs-Git in %s has different url to your vcs-git remote.
3817 Your vcs-git remote url may be out of date. Use dgit update-vcs-git ?
3821 printdone f_ "fetched into %s", lrref();
3825 my $multi_fetched = fork_for_multisuite(sub { });
3826 fetch_one() unless $multi_fetched; # parent
3827 finish 0 if $multi_fetched eq '0'; # child
3832 runcmd_ordryrun_local @git, qw(merge -m),
3833 (f_ "Merge from %s [dgit]", $csuite),
3835 printdone f_ "fetched to %s and merged into HEAD", lrref();
3838 sub check_not_dirty () {
3839 my @forbid = qw(local-options local-patch-header);
3840 @forbid = map { "debian/source/$_" } @forbid;
3841 foreach my $f (@forbid) {
3842 if (stat_exists $f) {
3843 fail f_ "git tree contains %s", $f;
3847 my @cmd = (@git, qw(status -uall --ignored --porcelain));
3848 push @cmd, qw(debian/source/format debian/source/options);
3851 my $bad = cmdoutput @cmd;
3854 "you have uncommitted changes to critical files, cannot continue:\n").
3858 return if $includedirty;
3860 git_check_unmodified();
3863 sub commit_admin ($) {
3866 runcmd_ordryrun_local @git, qw(commit -m), $m;
3869 sub quiltify_nofix_bail ($$) {
3870 my ($headinfo, $xinfo) = @_;
3871 if ($quilt_mode eq 'nofix') {
3873 "quilt fixup required but quilt mode is \`nofix'\n".
3874 "HEAD commit%s differs from tree implied by debian/patches%s",
3879 sub commit_quilty_patch () {
3880 my $output = cmdoutput @git, qw(status --ignored --porcelain);
3882 foreach my $l (split /\n/, $output) {
3883 next unless $l =~ m/\S/;
3884 if ($l =~ m{^(?:[?!][?!]| [MADRC]) (.pc|debian/patches)}) {
3888 delete $adds{'.pc'}; # if there wasn't one before, don't add it
3890 progress __ "nothing quilty to commit, ok.";
3893 quiltify_nofix_bail "", __ " (wanted to commit patch update)";
3894 my @adds = map { s/[][*?\\]/\\$&/g; $_; } sort keys %adds;
3895 runcmd_ordryrun_local @git, qw(add -f), @adds;
3896 commit_admin +(__ <<ENDT).<<END
3897 Commit Debian 3.0 (quilt) metadata
3900 [dgit ($our_version) quilt-fixup]
3904 sub get_source_format () {
3906 if (open F, "debian/source/options") {
3910 s/\s+$//; # ignore missing final newline
3912 my ($k, $v) = ($`, $'); #');
3913 $v =~ s/^"(.*)"$/$1/;
3919 F->error and confess $!;
3922 confess $! unless $!==&ENOENT;
3925 if (!open F, "debian/source/format") {
3926 confess $! unless $!==&ENOENT;
3930 F->error and confess $!;
3932 return ($_, \%options);
3935 sub madformat_wantfixup ($) {
3937 return 0 unless $format eq '3.0 (quilt)';
3938 our $quilt_mode_warned;
3939 if ($quilt_mode eq 'nocheck') {
3940 progress f_ "Not doing any fixup of \`%s'".
3941 " due to ----no-quilt-fixup or --quilt=nocheck", $format
3942 unless $quilt_mode_warned++;
3945 progress f_ "Format \`%s', need to check/update patch stack", $format
3946 unless $quilt_mode_warned++;
3950 sub maybe_split_brain_save ($$$) {
3951 my ($headref, $dgitview, $msg) = @_;
3952 # => message fragment "$saved" describing disposition of $dgitview
3953 # (used inside parens, in the English texts)
3954 my $save = $internal_object_save{'dgit-view'};
3955 return f_ "commit id %s", $dgitview unless defined $save;
3956 my @cmd = (shell_cmd 'cd "$1"; shift', $maindir,
3958 "dgit --dgit-view-save $msg HEAD=$headref",
3961 return f_ "and left in %s", $save;
3964 # An "infopair" is a tuple [ $thing, $what ]
3965 # (often $thing is a commit hash; $what is a description)
3967 sub infopair_cond_equal ($$) {
3969 $x->[0] eq $y->[0] or fail <<END;
3970 $x->[1] ($x->[0]) not equal to $y->[1] ($y->[0])
3974 sub infopair_lrf_tag_lookup ($$) {
3975 my ($tagnames, $what) = @_;
3976 # $tagname may be an array ref
3977 my @tagnames = ref $tagnames ? @$tagnames : ($tagnames);
3978 printdebug "infopair_lrfetchref_tag_lookup $what @tagnames\n";
3979 foreach my $tagname (@tagnames) {
3980 my $lrefname = lrfetchrefs."/tags/$tagname";
3981 my $tagobj = $lrfetchrefs_f{$lrefname};
3982 next unless defined $tagobj;
3983 printdebug "infopair_lrfetchref_tag_lookup $tagobj $tagname $what\n";
3984 return [ git_rev_parse($tagobj), $what ];
3986 fail @tagnames==1 ? (f_ <<END, $what, "@tagnames")
3987 Wanted tag %s (%s) on dgit server, but not found
3989 : (f_ <<END, $what, "@tagnames");
3990 Wanted tag %s (one of: %s) on dgit server, but not found
3994 sub infopair_cond_ff ($$) {
3995 my ($anc,$desc) = @_;
3996 is_fast_fwd($anc->[0], $desc->[0]) or
3997 fail f_ <<END, $anc->[1], $anc->[0], $desc->[1], $desc->[0];
3998 %s (%s) .. %s (%s) is not fast forward
4002 sub pseudomerge_version_check ($$) {
4003 my ($clogp, $archive_hash) = @_;
4005 my $arch_clogp = commit_getclogp $archive_hash;
4006 my $i_arch_v = [ (getfield $arch_clogp, 'Version'),
4007 __ 'version currently in archive' ];
4008 if (defined $overwrite_version) {
4009 if (length $overwrite_version) {
4010 infopair_cond_equal([ $overwrite_version,
4011 '--overwrite= version' ],
4014 my $v = $i_arch_v->[0];
4016 "Checking package changelog for archive version %s ...", $v;
4019 my @xa = ("-f$v", "-t$v");
4020 my $vclogp = parsechangelog @xa;
4023 [ (getfield $vclogp, $fn),
4024 (f_ "%s field from dpkg-parsechangelog %s",
4027 my $cv = $gf->('Version');
4028 infopair_cond_equal($i_arch_v, $cv);
4029 $cd = $gf->('Distribution');
4032 $@ =~ s/^dgit: //gm;
4034 f_ "Perhaps debian/changelog does not mention %s ?", $v;
4036 fail f_ <<END, $cd->[1], $cd->[0], $v
4038 Your tree seems to based on earlier (not uploaded) %s.
4040 if $cd->[0] =~ m/UNRELEASED/;
4044 printdebug "pseudomerge_version_check i_arch_v @$i_arch_v\n";
4048 sub pseudomerge_make_commit ($$$$ $$) {
4049 my ($clogp, $dgitview, $archive_hash, $i_arch_v,
4050 $msg_cmd, $msg_msg) = @_;
4051 progress f_ "Declaring that HEAD includes all changes in %s...",
4054 my $tree = cmdoutput qw(git rev-parse), "${dgitview}:";
4055 my $authline = clogp_authline $clogp;
4059 !defined $overwrite_version ? ""
4060 : !length $overwrite_version ? " --overwrite"
4061 : " --overwrite=".$overwrite_version;
4063 # Contributing parent is the first parent - that makes
4064 # git rev-list --first-parent DTRT.
4065 my $pmf = dgit_privdir()."/pseudomerge";
4066 open MC, ">", $pmf or die "$pmf $!";
4067 print MC <<END or confess $!;
4070 parent $archive_hash
4078 close MC or confess $!;
4080 return make_commit($pmf);
4083 sub splitbrain_pseudomerge ($$$$) {
4084 my ($clogp, $maintview, $dgitview, $archive_hash) = @_;
4085 # => $merged_dgitview
4086 printdebug "splitbrain_pseudomerge...\n";
4088 # We: debian/PREVIOUS HEAD($maintview)
4089 # expect: o ----------------- o
4092 # a/d/PREVIOUS $dgitview
4095 # we do: `------------------ o
4099 return $dgitview unless defined $archive_hash;
4100 return $dgitview if deliberately_not_fast_forward();
4102 printdebug "splitbrain_pseudomerge...\n";
4104 my $i_arch_v = pseudomerge_version_check($clogp, $archive_hash);
4106 if (!defined $overwrite_version) {
4107 progress __ "Checking that HEAD includes all changes in archive...";
4110 return $dgitview if is_fast_fwd $archive_hash, $dgitview;
4112 if (defined $overwrite_version) {
4114 my $t_dep14 = debiantag_maintview $i_arch_v->[0], access_nomdistro;
4115 my $i_dep14 = infopair_lrf_tag_lookup($t_dep14,
4116 __ "maintainer view tag");
4117 my $t_dgit = debiantag_new $i_arch_v->[0], access_nomdistro;
4118 my $i_dgit = infopair_lrf_tag_lookup($t_dgit, __ "dgit view tag");
4119 my $i_archive = [ $archive_hash, __ "current archive contents" ];
4121 printdebug "splitbrain_pseudomerge i_archive @$i_archive\n";
4123 infopair_cond_equal($i_dgit, $i_archive);
4124 infopair_cond_ff($i_dep14, $i_dgit);
4125 infopair_cond_ff($i_dep14, [ $maintview, 'HEAD' ]);
4128 $@ =~ s/^\n//; chomp $@;
4129 print STDERR <<END.(__ <<ENDT);
4132 | Not fast forward; maybe --overwrite is needed ? Please see dgit(1).
4137 my $arch_v = $i_arch_v->[0];
4138 my $r = pseudomerge_make_commit
4139 $clogp, $dgitview, $archive_hash, $i_arch_v,
4140 "dgit --quilt=$quilt_mode",
4141 (defined $overwrite_version
4142 ? f_ "Declare fast forward from %s\n", $arch_v
4143 : f_ "Make fast forward from %s\n", $arch_v);
4145 maybe_split_brain_save $maintview, $r, "pseudomerge";
4147 progress f_ "Made pseudo-merge of %s into dgit view.", $arch_v;
4151 sub plain_overwrite_pseudomerge ($$$) {
4152 my ($clogp, $head, $archive_hash) = @_;
4154 printdebug "plain_overwrite_pseudomerge...";
4156 my $i_arch_v = pseudomerge_version_check($clogp, $archive_hash);
4158 return $head if is_fast_fwd $archive_hash, $head;
4160 my $m = f_ "Declare fast forward from %s", $i_arch_v->[0];
4162 my $r = pseudomerge_make_commit
4163 $clogp, $head, $archive_hash, $i_arch_v,
4166 runcmd git_update_ref_cmd $m, 'HEAD', $r, $head;
4168 progress f_ "Make pseudo-merge of %s into your HEAD.", $i_arch_v->[0];
4172 sub push_parse_changelog ($) {
4175 my $clogp = Dpkg::Control::Hash->new();
4176 $clogp->load($clogpfn) or die;
4178 my $clogpackage = getfield $clogp, 'Source';
4179 $package //= $clogpackage;
4180 fail f_ "-p specified %s but changelog specified %s",
4181 $package, $clogpackage
4182 unless $package eq $clogpackage;
4183 my $cversion = getfield $clogp, 'Version';
4185 if (!$we_are_initiator) {
4186 # rpush initiator can't do this because it doesn't have $isuite yet
4187 my $tag = debiantag($cversion, access_nomdistro);
4188 runcmd @git, qw(check-ref-format), $tag;
4191 my $dscfn = dscfn($cversion);
4193 return ($clogp, $cversion, $dscfn);
4196 sub push_parse_dsc ($$$) {
4197 my ($dscfn,$dscfnwhat, $cversion) = @_;
4198 $dsc = parsecontrol($dscfn,$dscfnwhat);
4199 my $dversion = getfield $dsc, 'Version';
4200 my $dscpackage = getfield $dsc, 'Source';
4201 ($dscpackage eq $package && $dversion eq $cversion) or
4202 fail f_ "%s is for %s %s but debian/changelog is for %s %s",
4203 $dscfn, $dscpackage, $dversion,
4204 $package, $cversion;
4207 sub push_tagwants ($$$$) {
4208 my ($cversion, $dgithead, $maintviewhead, $tfbase) = @_;
4211 TagFn => \&debiantag,
4216 if (defined $maintviewhead) {
4218 TagFn => \&debiantag_maintview,
4219 Objid => $maintviewhead,
4220 TfSuffix => '-maintview',
4223 } elsif ($dodep14tag eq 'no' ? 0
4224 : $dodep14tag eq 'want' ? access_cfg_tagformats_can_splitbrain
4225 : $dodep14tag eq 'always'
4226 ? (access_cfg_tagformats_can_splitbrain or fail <<END)
4227 --dep14tag-always (or equivalent in config) means server must support
4228 both "new" and "maint" tag formats, but config says it doesn't.
4230 : die "$dodep14tag ?") {
4232 TagFn => \&debiantag_maintview,
4234 TfSuffix => '-dgit',
4238 foreach my $tw (@tagwants) {
4239 $tw->{Tag} = $tw->{TagFn}($cversion, access_nomdistro);
4240 $tw->{Tfn} = sub { $tfbase.$tw->{TfSuffix}.$_[0]; };
4242 printdebug 'push_tagwants: ', Dumper(\@_, \@tagwants);
4246 sub push_mktags ($$ $$ $) {
4248 $changesfile,$changesfilewhat,
4251 die unless $tagwants->[0]{View} eq 'dgit';
4253 my $declaredistro = access_nomdistro();
4254 my $reader_giturl = do { local $access_forpush=0; access_giturl(); };
4255 $dsc->{$ourdscfield[0]} = join " ",
4256 $tagwants->[0]{Objid}, $declaredistro, $tagwants->[0]{Tag},
4258 $dsc->save("$dscfn.tmp") or confess $!;
4260 my $changes = parsecontrol($changesfile,$changesfilewhat);
4261 foreach my $field (qw(Source Distribution Version)) {
4262 $changes->{$field} eq $clogp->{$field} or
4263 fail f_ "changes field %s \`%s' does not match changelog \`%s'",
4264 $field, $changes->{$field}, $clogp->{$field};
4267 my $cversion = getfield $clogp, 'Version';
4268 my $clogsuite = getfield $clogp, 'Distribution';
4270 # We make the git tag by hand because (a) that makes it easier
4271 # to control the "tagger" (b) we can do remote signing
4272 my $authline = clogp_authline $clogp;
4273 my $delibs = join(" ", "",@deliberatelies);
4277 my $tfn = $tw->{Tfn};
4278 my $head = $tw->{Objid};
4279 my $tag = $tw->{Tag};
4281 open TO, '>', $tfn->('.tmp') or confess $!;
4282 print TO <<END or confess $!;
4289 if ($tw->{View} eq 'dgit') {
4290 print TO f_ <<ENDT, $package, $cversion, $clogsuite, $csuite
4291 %s release %s for %s (%s) [dgit]
4294 print TO <<END or confess $!;
4295 [dgit distro=$declaredistro$delibs]
4297 foreach my $ref (sort keys %previously) {
4298 print TO <<END or confess $!;
4299 [dgit previously:$ref=$previously{$ref}]
4302 } elsif ($tw->{View} eq 'maint') {
4303 print TO f_ <<END, $package, $cversion, $clogsuite, $csuite,
4304 %s release %s for %s (%s)
4305 (maintainer view tag generated by dgit --quilt=%s)
4310 confess Dumper($tw)."?";
4313 close TO or confess $!;
4315 my $tagobjfn = $tfn->('.tmp');
4317 if (!defined $keyid) {
4318 $keyid = access_cfg('keyid','RETURN-UNDEF');
4320 if (!defined $keyid) {
4321 $keyid = getfield $clogp, 'Maintainer';
4323 unlink $tfn->('.tmp.asc') or $!==&ENOENT or confess $!;
4324 my @sign_cmd = (@gpg, qw(--detach-sign --armor));
4325 push @sign_cmd, qw(-u),$keyid if defined $keyid;
4326 push @sign_cmd, $tfn->('.tmp');
4327 runcmd_ordryrun @sign_cmd;
4329 $tagobjfn = $tfn->('.signed.tmp');
4330 runcmd shell_cmd "exec >$tagobjfn", qw(cat --),
4331 $tfn->('.tmp'), $tfn->('.tmp.asc');
4337 my @r = map { $mktag->($_); } @$tagwants;
4341 sub sign_changes ($) {
4342 my ($changesfile) = @_;
4344 my @debsign_cmd = @debsign;
4345 push @debsign_cmd, "-k$keyid" if defined $keyid;
4346 push @debsign_cmd, "-p$gpg[0]" if $gpg[0] ne 'gpg';
4347 push @debsign_cmd, $changesfile;
4348 runcmd_ordryrun @debsign_cmd;
4353 printdebug "actually entering push\n";
4355 supplementary_message(__ <<'END');
4356 Push failed, while checking state of the archive.
4357 You can retry the push, after fixing the problem, if you like.
4359 if (check_for_git()) {
4362 my $archive_hash = fetch_from_archive();
4363 if (!$archive_hash) {
4365 fail __ "package appears to be new in this suite;".
4366 " if this is intentional, use --new";
4369 supplementary_message(__ <<'END');
4370 Push failed, while preparing your push.
4371 You can retry the push, after fixing the problem, if you like.
4374 need_tagformat 'new', "quilt mode $quilt_mode"
4375 if quiltmode_splitbrain;
4379 access_giturl(); # check that success is vaguely likely
4380 rpush_handle_protovsn_bothends() if $we_are_initiator;
4383 my $clogpfn = dgit_privdir()."/changelog.822.tmp";
4384 runcmd shell_cmd "exec >$clogpfn", qw(dpkg-parsechangelog);
4386 responder_send_file('parsed-changelog', $clogpfn);
4388 my ($clogp, $cversion, $dscfn) =
4389 push_parse_changelog("$clogpfn");
4391 my $dscpath = "$buildproductsdir/$dscfn";
4392 stat_exists $dscpath or
4393 fail f_ "looked for .dsc %s, but %s; maybe you forgot to build",
4396 responder_send_file('dsc', $dscpath);
4398 push_parse_dsc($dscpath, $dscfn, $cversion);
4400 my $format = getfield $dsc, 'Format';
4401 printdebug "format $format\n";
4403 my $symref = git_get_symref();
4404 my $actualhead = git_rev_parse('HEAD');
4406 if (branch_is_gdr_unstitched_ff($symref, $actualhead, $archive_hash)) {
4407 if (quiltmode_splitbrain()) {
4408 my ($ffq_prev, $gdrlast) = branch_gdr_info($symref, $actualhead);
4409 fail f_ <<END, $ffq_prev, $quilt_mode;
4410 Branch is managed by git-debrebase (%s
4411 exists), but quilt mode (%s) implies a split view.
4412 Pass the right --quilt option or adjust your git config.
4413 Or, maybe, run git-debrebase forget-was-ever-debrebase.
4416 runcmd_ordryrun_local @git_debrebase, 'stitch';
4417 $actualhead = git_rev_parse('HEAD');
4420 my $dgithead = $actualhead;
4421 my $maintviewhead = undef;
4423 my $upstreamversion = upstreamversion $clogp->{Version};
4425 if (madformat_wantfixup($format)) {
4426 # user might have not used dgit build, so maybe do this now:
4427 if (quiltmode_splitbrain()) {
4428 changedir $playground;
4429 quilt_make_fake_dsc($upstreamversion);
4431 ($dgithead, $cachekey) =
4432 quilt_check_splitbrain_cache($actualhead, $upstreamversion);
4433 $dgithead or fail f_
4434 "--quilt=%s but no cached dgit view:
4435 perhaps HEAD changed since dgit build[-source] ?",
4438 $dgithead = splitbrain_pseudomerge($clogp,
4439 $actualhead, $dgithead,
4441 $maintviewhead = $actualhead;
4443 prep_ud(); # so _only_subdir() works, below
4445 commit_quilty_patch();
4449 if (defined $overwrite_version && !defined $maintviewhead
4451 $dgithead = plain_overwrite_pseudomerge($clogp,
4459 if ($archive_hash) {
4460 if (is_fast_fwd($archive_hash, $dgithead)) {
4462 } elsif (deliberately_not_fast_forward) {
4465 fail __ "dgit push: HEAD is not a descendant".
4466 " of the archive's version.\n".
4467 "To overwrite the archive's contents,".
4468 " pass --overwrite[=VERSION].\n".
4469 "To rewind history, if permitted by the archive,".
4470 " use --deliberately-not-fast-forward.";
4474 changedir $playground;
4475 progress f_ "checking that %s corresponds to HEAD", $dscfn;
4476 runcmd qw(dpkg-source -x --),
4477 $dscpath =~ m#^/# ? $dscpath : "$maindir/$dscpath";
4478 my ($tree,$dir) = mktree_in_ud_from_only_subdir("source package");
4479 check_for_vendor_patches() if madformat($dsc->{format});
4481 my @diffcmd = (@git, qw(diff --quiet), $tree, $dgithead);
4482 debugcmd "+",@diffcmd;
4484 my $r = system @diffcmd;
4487 my $referent = $split_brain ? $dgithead : 'HEAD';
4488 my $diffs = cmdoutput @git, qw(diff --stat), $tree, $dgithead;
4491 my $raw = cmdoutput @git,
4492 qw(diff --no-renames -z -r --raw), $tree, $dgithead;
4494 foreach (split /\0/, $raw) {
4495 if (defined $changed) {
4496 push @mode_changes, "$changed: $_\n" if $changed;
4499 } elsif (m/^:0+ 0+ /) {
4501 } elsif (m/^:(?:10*)?(\d+) (?:10*)?(\d+) /) {
4502 $changed = "Mode change from $1 to $2"
4507 if (@mode_changes) {
4508 fail +(f_ <<ENDT, $dscfn).<<END
4509 HEAD specifies a different tree to %s:
4513 .(join '', @mode_changes)
4514 .(f_ <<ENDT, $tree, $referent);
4515 There is a problem with your source tree (see dgit(7) for some hints).
4516 To see a full diff, run git diff %s %s
4520 fail +(f_ <<ENDT, $dscfn).<<END.(f_ <<ENDT, $tree, $referent);
4521 HEAD specifies a different tree to %s:
4525 Perhaps you forgot to build. Or perhaps there is a problem with your
4526 source tree (see dgit(7) for some hints). To see a full diff, run
4533 if (!$changesfile) {
4534 my $pat = changespat $cversion;
4535 my @cs = glob "$buildproductsdir/$pat";
4536 fail f_ "failed to find unique changes file".
4537 " (looked for %s in %s);".
4538 " perhaps you need to use dgit -C",
4539 $pat, $buildproductsdir
4541 ($changesfile) = @cs;
4543 $changesfile = "$buildproductsdir/$changesfile";
4546 # Check that changes and .dsc agree enough
4547 $changesfile =~ m{[^/]*$};
4548 my $changes = parsecontrol($changesfile,$&);
4549 files_compare_inputs($dsc, $changes)
4550 unless forceing [qw(dsc-changes-mismatch)];
4552 # Check whether this is a source only upload
4553 my $hasdebs = $changes->{Files} =~ m{\.deb$}m;
4554 my $sourceonlypolicy = access_cfg 'source-only-uploads';
4555 if ($sourceonlypolicy eq 'ok') {
4556 } elsif ($sourceonlypolicy eq 'always') {
4557 forceable_fail [qw(uploading-binaries)],
4558 __ "uploading binaries, although distro policy is source only"
4560 } elsif ($sourceonlypolicy eq 'never') {
4561 forceable_fail [qw(uploading-source-only)],
4562 __ "source-only upload, although distro policy requires .debs"
4564 } elsif ($sourceonlypolicy eq 'not-wholly-new') {
4565 forceable_fail [qw(uploading-source-only)],
4566 f_ "source-only upload, even though package is entirely NEW\n".
4567 "(this is contrary to policy in %s)",
4571 && !(archive_query('package_not_wholly_new', $package) // 1);
4573 badcfg f_ "unknown source-only-uploads policy \`%s'",
4577 # Perhaps adjust .dsc to contain right set of origs
4578 changes_update_origs_from_dsc($dsc, $changes, $upstreamversion,
4580 unless forceing [qw(changes-origs-exactly)];
4582 # Checks complete, we're going to try and go ahead:
4584 responder_send_file('changes',$changesfile);
4585 responder_send_command("param head $dgithead");
4586 responder_send_command("param csuite $csuite");
4587 responder_send_command("param isuite $isuite");
4588 responder_send_command("param tagformat $tagformat");
4589 if (defined $maintviewhead) {
4590 confess "internal error (protovsn=$protovsn)"
4591 if defined $protovsn and $protovsn < 4;
4592 responder_send_command("param maint-view $maintviewhead");
4595 # Perhaps send buildinfo(s) for signing
4596 my $changes_files = getfield $changes, 'Files';
4597 my @buildinfos = ($changes_files =~ m/ .* (\S+\.buildinfo)$/mg);
4598 foreach my $bi (@buildinfos) {
4599 responder_send_command("param buildinfo-filename $bi");
4600 responder_send_file('buildinfo', "$buildproductsdir/$bi");
4603 if (deliberately_not_fast_forward) {
4604 git_for_each_ref(lrfetchrefs, sub {
4605 my ($objid,$objtype,$lrfetchrefname,$reftail) = @_;
4606 my $rrefname= substr($lrfetchrefname, length(lrfetchrefs) + 1);
4607 responder_send_command("previously $rrefname=$objid");
4608 $previously{$rrefname} = $objid;
4612 my @tagwants = push_tagwants($cversion, $dgithead, $maintviewhead,
4613 dgit_privdir()."/tag");
4616 supplementary_message(__ <<'END');
4617 Push failed, while signing the tag.
4618 You can retry the push, after fixing the problem, if you like.
4620 # If we manage to sign but fail to record it anywhere, it's fine.
4621 if ($we_are_responder) {
4622 @tagobjfns = map { $_->{Tfn}('.signed-tmp') } @tagwants;
4623 responder_receive_files('signed-tag', @tagobjfns);
4625 @tagobjfns = push_mktags($clogp,$dscpath,
4626 $changesfile,$changesfile,
4629 supplementary_message(__ <<'END');
4630 Push failed, *after* signing the tag.
4631 If you want to try again, you should use a new version number.
4634 pairwise { $a->{TagObjFn} = $b } @tagwants, @tagobjfns;
4636 foreach my $tw (@tagwants) {
4637 my $tag = $tw->{Tag};
4638 my $tagobjfn = $tw->{TagObjFn};
4640 cmdoutput @git, qw(hash-object -w -t tag), $tagobjfn;
4641 runcmd_ordryrun @git, qw(verify-tag), $tag_obj_hash;
4642 runcmd_ordryrun_local
4643 @git, qw(update-ref), "refs/tags/$tag", $tag_obj_hash;
4646 supplementary_message(__ <<'END');
4647 Push failed, while updating the remote git repository - see messages above.
4648 If you want to try again, you should use a new version number.
4650 if (!check_for_git()) {
4651 create_remote_git_repo();
4654 my @pushrefs = $forceflag.$dgithead.":".rrref();
4655 foreach my $tw (@tagwants) {
4656 push @pushrefs, $forceflag."refs/tags/$tw->{Tag}";
4659 runcmd_ordryrun @git,
4660 qw(-c push.followTags=false push), access_giturl(), @pushrefs;
4661 runcmd_ordryrun git_update_ref_cmd 'dgit push', lrref(), $dgithead;
4663 supplementary_message(__ <<'END');
4664 Push failed, while obtaining signatures on the .changes and .dsc.
4665 If it was just that the signature failed, you may try again by using
4666 debsign by hand to sign the changes file (see the command dgit tried,
4667 above), and then dput that changes file to complete the upload.
4668 If you need to change the package, you must use a new version number.
4670 if ($we_are_responder) {
4671 my $dryrunsuffix = act_local() ? "" : ".tmp";
4672 my @rfiles = ($dscpath, $changesfile);
4673 push @rfiles, map { "$buildproductsdir/$_" } @buildinfos;
4674 responder_receive_files('signed-dsc-changes',
4675 map { "$_$dryrunsuffix" } @rfiles);
4678 rename "$dscpath.tmp",$dscpath or die "$dscfn $!";
4680 progress f_ "[new .dsc left in %s.tmp]", $dscpath;
4682 sign_changes $changesfile;
4685 supplementary_message(f_ <<END, $changesfile);
4686 Push failed, while uploading package(s) to the archive server.
4687 You can retry the upload of exactly these same files with dput of:
4689 If that .changes file is broken, you will need to use a new version
4690 number for your next attempt at the upload.
4692 my $host = access_cfg('upload-host','RETURN-UNDEF');
4693 my @hostarg = defined($host) ? ($host,) : ();
4694 runcmd_ordryrun @dput, @hostarg, $changesfile;
4695 printdone f_ "pushed and uploaded %s", $cversion;
4697 supplementary_message('');
4698 responder_send_command("complete");
4702 not_necessarily_a_tree();
4707 badusage __ "-p is not allowed with clone; specify as argument instead"
4708 if defined $package;
4711 } elsif (@ARGV==2 && $ARGV[1] =~ m#^\w#) {
4712 ($package,$isuite) = @ARGV;
4713 } elsif (@ARGV==2 && $ARGV[1] =~ m#^[./]#) {
4714 ($package,$dstdir) = @ARGV;
4715 } elsif (@ARGV==3) {
4716 ($package,$isuite,$dstdir) = @ARGV;
4718 badusage __ "incorrect arguments to dgit clone";
4722 $dstdir ||= "$package";
4723 if (stat_exists $dstdir) {
4724 fail f_ "%s already exists", $dstdir;
4728 if ($rmonerror && !$dryrun_level) {
4729 $cwd_remove= getcwd();
4731 return unless defined $cwd_remove;
4732 if (!chdir "$cwd_remove") {
4733 return if $!==&ENOENT;
4734 confess "chdir $cwd_remove: $!";
4736 printdebug "clone rmonerror removing $dstdir\n";
4738 rmtree($dstdir) or fail f_ "remove %s: %s\n", $dstdir, $!;
4739 } elsif (grep { $! == $_ }
4740 (ENOENT, ENOTDIR, EACCES, EPERM, ELOOP)) {
4742 print STDERR f_ "check whether to remove %s: %s\n",
4749 $cwd_remove = undef;
4752 sub branchsuite () {
4753 my $branch = git_get_symref();
4754 if (defined $branch && $branch =~ m#$lbranch_re#o) {
4761 sub package_from_d_control () {
4762 if (!defined $package) {
4763 my $sourcep = parsecontrol('debian/control','debian/control');
4764 $package = getfield $sourcep, 'Source';
4768 sub fetchpullargs () {
4769 package_from_d_control();
4771 $isuite = branchsuite();
4773 my $clogp = parsechangelog();
4774 my $clogsuite = getfield $clogp, 'Distribution';
4775 $isuite= $clogsuite if $clogsuite ne 'UNRELEASED';
4777 } elsif (@ARGV==1) {
4780 badusage __ "incorrect arguments to dgit fetch or dgit pull";
4794 if (quiltmode_splitbrain()) {
4795 my ($format, $fopts) = get_source_format();
4796 madformat($format) and fail f_ <<END, $quilt_mode
4797 dgit pull not yet supported in split view mode (--quilt=%s)
4805 package_from_d_control();
4806 @ARGV==1 or badusage __ "dgit checkout needs a suite argument";
4810 foreach my $canon (qw(0 1)) {
4815 canonicalise_suite();
4817 if (length git_get_ref lref()) {
4818 # local branch already exists, yay
4821 if (!length git_get_ref lrref()) {
4829 runcmd (@git, qw(update-ref), lref(), lrref(), '');
4832 local $ENV{GIT_REFLOG_ACTION} = git_reflog_action_msg
4833 "dgit checkout $isuite";
4834 runcmd (@git, qw(checkout), lbranch());
4837 sub cmd_update_vcs_git () {
4839 if (@ARGV==0 || $ARGV[0] =~ m/^-/) {
4840 ($specsuite,) = split /\;/, cfg 'dgit.vcs-git.suites';
4842 ($specsuite) = (@ARGV);
4847 if ($ARGV[0] eq '-') {
4849 } elsif ($ARGV[0] eq '-') {
4854 package_from_d_control();
4856 if ($specsuite eq '.') {
4857 $ctrl = parsecontrol 'debian/control', 'debian/control';
4859 $isuite = $specsuite;
4863 my $url = getfield $ctrl, 'Vcs-Git';
4866 my $orgurl = cfg 'remote.vcs-git.url', 'RETURN-UNDEF';
4867 if (!defined $orgurl) {
4868 print STDERR f_ "setting up vcs-git: %s\n", $url;
4869 @cmd = (@git, qw(remote add vcs-git), $url);
4870 } elsif ($orgurl eq $url) {
4871 print STDERR f_ "vcs git already configured: %s\n", $url;
4873 print STDERR f_ "changing vcs-git url to: %s\n", $url;
4874 @cmd = (@git, qw(remote set-url vcs-git), $url);
4876 runcmd_ordryrun_local @cmd;
4878 print f_ "fetching (%s)\n", "@ARGV";
4879 runcmd_ordryrun_local @git, qw(fetch vcs-git), @ARGV;
4885 build_or_push_prep_early();
4890 } elsif (@ARGV==1) {
4891 ($specsuite) = (@ARGV);
4893 badusage f_ "incorrect arguments to dgit %s", $subcommand;
4896 local ($package) = $existing_package; # this is a hack
4897 canonicalise_suite();
4899 canonicalise_suite();
4901 if (defined $specsuite &&
4902 $specsuite ne $isuite &&
4903 $specsuite ne $csuite) {
4904 fail f_ "dgit %s: changelog specifies %s (%s)".
4905 " but command line specifies %s",
4906 $subcommand, $isuite, $csuite, $specsuite;
4915 #---------- remote commands' implementation ----------
4917 sub pre_remote_push_build_host {
4918 my ($nrargs) = shift @ARGV;
4919 my (@rargs) = @ARGV[0..$nrargs-1];
4920 @ARGV = @ARGV[$nrargs..$#ARGV];
4922 my ($dir,$vsnwant) = @rargs;
4923 # vsnwant is a comma-separated list; we report which we have
4924 # chosen in our ready response (so other end can tell if they
4927 $we_are_responder = 1;
4928 $us .= " (build host)";
4930 open PI, "<&STDIN" or confess $!;
4931 open STDIN, "/dev/null" or confess $!;
4932 open PO, ">&STDOUT" or confess $!;
4934 open STDOUT, ">&STDERR" or confess $!;
4938 ($protovsn) = grep {
4939 $vsnwant =~ m{^(?:.*,)?$_(?:,.*)?$}
4940 } @rpushprotovsn_support;
4942 fail f_ "build host has dgit rpush protocol versions %s".
4943 " but invocation host has %s",
4944 (join ",", @rpushprotovsn_support), $vsnwant
4945 unless defined $protovsn;
4949 sub cmd_remote_push_build_host {
4950 responder_send_command("dgit-remote-push-ready $protovsn");
4954 sub pre_remote_push_responder { pre_remote_push_build_host(); }
4955 sub cmd_remote_push_responder { cmd_remote_push_build_host(); }
4956 # ... for compatibility with proto vsn.1 dgit (just so that user gets
4957 # a good error message)
4959 sub rpush_handle_protovsn_bothends () {
4960 if ($protovsn < 4) {
4961 need_tagformat 'old', "rpush negotiated protocol $protovsn";
4970 my $report = i_child_report();
4971 if (defined $report) {
4972 printdebug "($report)\n";
4973 } elsif ($i_child_pid) {
4974 printdebug "(killing build host child $i_child_pid)\n";
4975 kill 15, $i_child_pid;
4977 if (defined $i_tmp && !defined $initiator_tempdir) {
4979 eval { rmtree $i_tmp; };
4984 return unless forkcheck_mainprocess();
4989 my ($base,$selector,@args) = @_;
4990 $selector =~ s/\-/_/g;
4991 { no strict qw(refs); &{"${base}_${selector}"}(@args); }
4995 not_necessarily_a_tree();
5000 if ($host =~ m/^((?:[^][]|\[[^][]*\])*)\:/) {
5008 push @rargs, join ",", @rpushprotovsn_support;
5011 push @rdgit, @ropts;
5012 push @rdgit, qw(remote-push-build-host), (scalar @rargs), @rargs;
5014 my @cmd = (@ssh, $host, shellquote @rdgit);
5017 $we_are_initiator=1;
5019 if (defined $initiator_tempdir) {
5020 rmtree $initiator_tempdir;
5021 mkdir $initiator_tempdir, 0700
5022 or fail f_ "create %s: %s", $initiator_tempdir, $!;
5023 $i_tmp = $initiator_tempdir;
5027 $i_child_pid = open2(\*RO, \*RI, @cmd);
5029 ($protovsn) = initiator_expect { m/^dgit-remote-push-ready (\S+)/ };
5030 die "$protovsn ?" unless grep { $_ eq $protovsn } @rpushprotovsn_support;
5031 $supplementary_message = '' unless $protovsn >= 3;
5034 my ($icmd,$iargs) = initiator_expect {
5035 m/^(\S+)(?: (.*))?$/;
5038 i_method "i_resp", $icmd, $iargs;
5042 sub i_resp_progress ($) {
5044 my $msg = protocol_read_bytes \*RO, $rhs;
5048 sub i_resp_supplementary_message ($) {
5050 $supplementary_message = protocol_read_bytes \*RO, $rhs;
5053 sub i_resp_complete {
5054 my $pid = $i_child_pid;
5055 $i_child_pid = undef; # prevents killing some other process with same pid
5056 printdebug "waiting for build host child $pid...\n";
5057 my $got = waitpid $pid, 0;
5058 confess $! unless $got == $pid;
5059 fail f_ "build host child failed: %s", waitstatusmsg() if $?;
5062 printdebug __ "all done\n";
5066 sub i_resp_file ($) {
5068 my $localname = i_method "i_localname", $keyword;
5069 my $localpath = "$i_tmp/$localname";
5070 stat_exists $localpath and
5071 badproto \*RO, f_ "file %s (%s) twice", $keyword, $localpath;
5072 protocol_receive_file \*RO, $localpath;
5073 i_method "i_file", $keyword;
5078 sub i_resp_param ($) {
5079 $_[0] =~ m/^(\S+) (.*)$/ or badproto \*RO, __ "bad param spec";
5083 sub i_resp_previously ($) {
5084 $_[0] =~ m#^(refs/tags/\S+)=(\w+)$#
5085 or badproto \*RO, __ "bad previously spec";
5086 my $r = system qw(git check-ref-format), $1;
5087 confess "bad previously ref spec ($r)" if $r;
5088 $previously{$1} = $2;
5093 sub i_resp_want ($) {
5095 die "$keyword ?" if $i_wanted{$keyword}++;
5097 defined $i_param{'csuite'} or badproto \*RO, "premature desire, no csuite";
5098 $isuite = $i_param{'isuite'} // $i_param{'csuite'};
5099 die unless $isuite =~ m/^$suite_re$/;
5102 rpush_handle_protovsn_bothends();
5104 fail f_ "rpush negotiated protocol version %s".
5105 " which does not support quilt mode %s",
5106 $protovsn, $quilt_mode
5107 if quiltmode_splitbrain;
5109 my @localpaths = i_method "i_want", $keyword;
5110 printdebug "[[ $keyword @localpaths\n";
5111 foreach my $localpath (@localpaths) {
5112 protocol_send_file \*RI, $localpath;
5114 print RI "files-end\n" or confess $!;
5117 our ($i_clogp, $i_version, $i_dscfn, $i_changesfn, @i_buildinfos);
5119 sub i_localname_parsed_changelog {
5120 return "remote-changelog.822";
5122 sub i_file_parsed_changelog {
5123 ($i_clogp, $i_version, $i_dscfn) =
5124 push_parse_changelog "$i_tmp/remote-changelog.822";
5125 die if $i_dscfn =~ m#/|^\W#;
5128 sub i_localname_dsc {
5129 defined $i_dscfn or badproto \*RO, "dsc (before parsed-changelog)";
5134 sub i_localname_buildinfo ($) {
5135 my $bi = $i_param{'buildinfo-filename'};
5136 defined $bi or badproto \*RO, "buildinfo before filename";
5137 defined $i_changesfn or badproto \*RO, "buildinfo before changes";
5138 $bi =~ m{^\Q$package\E_[!-.0-~]*\.buildinfo$}s
5139 or badproto \*RO, "improper buildinfo filename";
5142 sub i_file_buildinfo {
5143 my $bi = $i_param{'buildinfo-filename'};
5144 my $bd = parsecontrol "$i_tmp/$bi", $bi;
5145 my $ch = parsecontrol "$i_tmp/$i_changesfn", 'changes';
5146 if (!forceing [qw(buildinfo-changes-mismatch)]) {
5147 files_compare_inputs($bd, $ch);
5148 (getfield $bd, $_) eq (getfield $ch, $_) or
5149 fail f_ "buildinfo mismatch in field %s", $_
5150 foreach qw(Source Version);
5151 !defined $bd->{$_} or
5152 fail f_ "buildinfo contains forbidden field %s", $_
5153 foreach qw(Changes Changed-by Distribution);
5155 push @i_buildinfos, $bi;
5156 delete $i_param{'buildinfo-filename'};
5159 sub i_localname_changes {
5160 defined $i_dscfn or badproto \*RO, "dsc (before parsed-changelog)";
5161 $i_changesfn = $i_dscfn;
5162 $i_changesfn =~ s/\.dsc$/_dgit.changes/ or die;
5163 return $i_changesfn;
5165 sub i_file_changes { }
5167 sub i_want_signed_tag {
5168 printdebug Dumper(\%i_param, $i_dscfn);
5169 defined $i_param{'head'} && defined $i_dscfn && defined $i_clogp
5170 && defined $i_param{'csuite'}
5171 or badproto \*RO, "premature desire for signed-tag";
5172 my $head = $i_param{'head'};
5173 die if $head =~ m/[^0-9a-f]/ || $head !~ m/^../;
5175 my $maintview = $i_param{'maint-view'};
5176 die if defined $maintview && $maintview =~ m/[^0-9a-f]/;
5179 if ($protovsn >= 4) {
5180 my $p = $i_param{'tagformat'} // '<undef>';
5182 or badproto \*RO, "tag format mismatch: $p vs. $tagformat";
5185 die unless $i_param{'csuite'} =~ m/^$suite_re$/;
5187 push_parse_dsc $i_dscfn, 'remote dsc', $i_version;
5189 my @tagwants = push_tagwants $i_version, $head, $maintview, "tag";
5192 push_mktags $i_clogp, $i_dscfn,
5193 $i_changesfn, (__ 'remote changes file'),
5197 sub i_want_signed_dsc_changes {
5198 rename "$i_dscfn.tmp","$i_dscfn" or die "$i_dscfn $!";
5199 sign_changes $i_changesfn;
5200 return ($i_dscfn, $i_changesfn, @i_buildinfos);
5203 #---------- building etc. ----------
5209 #----- `3.0 (quilt)' handling -----
5211 our $fakeeditorenv = 'DGIT_FAKE_EDITOR_QUILT';
5213 sub quiltify_dpkg_commit ($$$;$) {
5214 my ($patchname,$author,$msg, $xinfo) = @_;
5217 mkpath '.git/dgit'; # we are in playtree
5218 my $descfn = ".git/dgit/quilt-description.tmp";
5219 open O, '>', $descfn or confess "$descfn: $!";
5220 $msg =~ s/\n+/\n\n/;
5221 print O <<END or confess $!;
5223 ${xinfo}Subject: $msg
5227 close O or confess $!;
5230 local $ENV{'EDITOR'} = cmdoutput qw(realpath --), $0;
5231 local $ENV{'VISUAL'} = $ENV{'EDITOR'};
5232 local $ENV{$fakeeditorenv} = cmdoutput qw(realpath --), $descfn;
5233 runcmd @dpkgsource, qw(--commit --include-removal .), $patchname;
5237 sub quiltify_trees_differ ($$;$$$) {
5238 my ($x,$y,$finegrained,$ignorenamesr,$unrepres) = @_;
5239 # returns true iff the two tree objects differ other than in debian/
5240 # with $finegrained,
5241 # returns bitmask 01 - differ in upstream files except .gitignore
5242 # 02 - differ in .gitignore
5243 # if $ignorenamesr is defined, $ingorenamesr->{$fn}
5244 # is set for each modified .gitignore filename $fn
5245 # if $unrepres is defined, array ref to which is appeneded
5246 # a list of unrepresentable changes (removals of upstream files
5249 my @cmd = (@git, qw(diff-tree -z --no-renames));
5250 push @cmd, qw(--name-only) unless $unrepres;
5251 push @cmd, qw(-r) if $finegrained || $unrepres;
5253 my $diffs= cmdoutput @cmd;
5256 foreach my $f (split /\0/, $diffs) {
5257 if ($unrepres && !@lmodes) {
5258 @lmodes = $f =~ m/^\:(\w+) (\w+) \w+ \w+ / or die "$_ ?";
5261 my ($oldmode,$newmode) = @lmodes;
5264 next if $f =~ m#^debian(?:/.*)?$#s;
5268 die __ "not a plain file or symlink\n"
5269 unless $newmode =~ m/^(?:10|12)\d{4}$/ ||
5270 $oldmode =~ m/^(?:10|12)\d{4}$/;
5271 if ($oldmode =~ m/[^0]/ &&
5272 $newmode =~ m/[^0]/) {
5273 # both old and new files exist
5274 die __ "mode or type changed\n" if $oldmode ne $newmode;
5275 die __ "modified symlink\n" unless $newmode =~ m/^10/;
5276 } elsif ($oldmode =~ m/[^0]/) {
5278 die __ "deletion of symlink\n"
5279 unless $oldmode =~ m/^10/;
5282 die __ "creation with non-default mode\n"
5283 unless $newmode =~ m/^100644$/ or
5284 $newmode =~ m/^120000$/;
5288 local $/="\n"; chomp $@;
5289 push @$unrepres, [ $f, "$@ ($oldmode->$newmode)" ];
5293 my $isignore = $f =~ m#^(?:.*/)?.gitignore$#s;
5294 $r |= $isignore ? 02 : 01;
5295 $ignorenamesr->{$f}=1 if $ignorenamesr && $isignore;
5297 printdebug "quiltify_trees_differ $x $y => $r\n";
5301 sub quiltify_tree_sentinelfiles ($) {
5302 # lists the `sentinel' files present in the tree
5304 my $r = cmdoutput @git, qw(ls-tree --name-only), $x,
5305 qw(-- debian/rules debian/control);
5310 sub quiltify_splitbrain_needed () {
5311 if (!$split_brain) {
5312 progress __ "dgit view: changes are required...";
5313 runcmd @git, qw(checkout -q -b dgit-view);
5318 sub quiltify_splitbrain ($$$$$$$) {
5319 my ($clogp, $unapplied, $headref, $oldtiptree, $diffbits,
5320 $editedignores, $cachekey) = @_;
5321 my $gitignore_special = 1;
5322 if ($quilt_mode !~ m/gbp|dpm/) {
5323 # treat .gitignore just like any other upstream file
5324 $diffbits = { %$diffbits };
5325 $_ = !!$_ foreach values %$diffbits;
5326 $gitignore_special = 0;
5328 # We would like any commits we generate to be reproducible
5329 my @authline = clogp_authline($clogp);
5330 local $ENV{GIT_COMMITTER_NAME} = $authline[0];
5331 local $ENV{GIT_COMMITTER_EMAIL} = $authline[1];
5332 local $ENV{GIT_COMMITTER_DATE} = $authline[2];
5333 local $ENV{GIT_AUTHOR_NAME} = $authline[0];
5334 local $ENV{GIT_AUTHOR_EMAIL} = $authline[1];
5335 local $ENV{GIT_AUTHOR_DATE} = $authline[2];
5337 my $fulldiffhint = sub {
5339 my $cmd = "git diff $x $y -- :/ ':!debian'";
5340 $cmd .= " ':!/.gitignore' ':!*/.gitignore'" if $gitignore_special;
5341 return f_ "\nFor full diff showing the problem(s), type:\n %s\n",
5345 if ($quilt_mode =~ m/gbp|unapplied/ &&
5346 ($diffbits->{O2H} & 01)) {
5348 "--quilt=%s specified, implying patches-unapplied git tree\n".
5349 " but git tree differs from orig in upstream files.",
5351 $msg .= $fulldiffhint->($unapplied, 'HEAD');
5352 if (!stat_exists "debian/patches") {
5354 "\n ... debian/patches is missing; perhaps this is a patch queue branch?";
5358 if ($quilt_mode =~ m/dpm/ &&
5359 ($diffbits->{H2A} & 01)) {
5360 fail +(f_ <<END, $quilt_mode). $fulldiffhint->($oldtiptree,'HEAD');
5361 --quilt=%s specified, implying patches-applied git tree
5362 but git tree differs from result of applying debian/patches to upstream
5365 if ($quilt_mode =~ m/gbp|unapplied/ &&
5366 ($diffbits->{O2A} & 01)) { # some patches
5367 quiltify_splitbrain_needed();
5368 progress __ "dgit view: creating patches-applied version using gbp pq";
5369 runcmd shell_cmd 'exec >/dev/null', gbp_pq, qw(import);
5370 # gbp pq import creates a fresh branch; push back to dgit-view
5371 runcmd @git, qw(update-ref refs/heads/dgit-view HEAD);
5372 runcmd @git, qw(checkout -q dgit-view);
5374 if ($quilt_mode =~ m/gbp|dpm/ &&
5375 ($diffbits->{O2A} & 02)) {
5376 fail f_ <<END, $quilt_mode;
5377 --quilt=%s specified, implying that HEAD is for use with a
5378 tool which does not create patches for changes to upstream
5379 .gitignores: but, such patches exist in debian/patches.
5382 if (($diffbits->{O2H} & 02) && # user has modified .gitignore
5383 !($diffbits->{O2A} & 02)) { # patches do not change .gitignore
5384 quiltify_splitbrain_needed();
5386 "dgit view: creating patch to represent .gitignore changes";
5387 ensuredir "debian/patches";
5388 my $gipatch = "debian/patches/auto-gitignore";
5389 open GIPATCH, ">>", "$gipatch" or confess "$gipatch: $!";
5390 stat GIPATCH or confess "$gipatch: $!";
5391 fail f_ "%s already exists; but want to create it".
5392 " to record .gitignore changes",
5395 print GIPATCH +(__ <<END).<<ENDU or die "$gipatch: $!";
5396 Subject: Update .gitignore from Debian packaging branch
5398 The Debian packaging git branch contains these updates to the upstream
5399 .gitignore file(s). This patch is autogenerated, to provide these
5400 updates to users of the official Debian archive view of the package.
5403 [dgit ($our_version) update-gitignore]
5406 close GIPATCH or die "$gipatch: $!";
5407 runcmd shell_cmd "exec >>$gipatch", @git, qw(diff),
5408 $unapplied, $headref, "--", sort keys %$editedignores;
5409 open SERIES, "+>>", "debian/patches/series" or confess $!;
5410 defined seek SERIES, -1, 2 or $!==EINVAL or confess $!;
5412 defined read SERIES, $newline, 1 or confess $!;
5413 print SERIES "\n" or confess $! unless $newline eq "\n";
5414 print SERIES "auto-gitignore\n" or confess $!;
5415 close SERIES or die $!;
5416 runcmd @git, qw(add -f -- debian/patches/series), $gipatch;
5417 commit_admin +(__ <<END).<<ENDU
5418 Commit patch to update .gitignore
5421 [dgit ($our_version) update-gitignore-quilt-fixup]
5425 my $dgitview = git_rev_parse 'HEAD';
5428 reflog_cache_insert "refs/$splitbraincache", $cachekey, $dgitview;
5430 changedir "$playground/work";
5432 my $saved = maybe_split_brain_save $headref, $dgitview, __ "converted";
5433 progress f_ "dgit view: created (%s)", $saved;
5436 sub quiltify ($$$$) {
5437 my ($clogp,$target,$oldtiptree,$failsuggestion) = @_;
5439 # Quilt patchification algorithm
5441 # We search backwards through the history of the main tree's HEAD
5442 # (T) looking for a start commit S whose tree object is identical
5443 # to to the patch tip tree (ie the tree corresponding to the
5444 # current dpkg-committed patch series). For these purposes
5445 # `identical' disregards anything in debian/ - this wrinkle is
5446 # necessary because dpkg-source treates debian/ specially.
5448 # We can only traverse edges where at most one of the ancestors'
5449 # trees differs (in changes outside in debian/). And we cannot
5450 # handle edges which change .pc/ or debian/patches. To avoid
5451 # going down a rathole we avoid traversing edges which introduce
5452 # debian/rules or debian/control. And we set a limit on the
5453 # number of edges we are willing to look at.
5455 # If we succeed, we walk forwards again. For each traversed edge
5456 # PC (with P parent, C child) (starting with P=S and ending with
5457 # C=T) to we do this:
5459 # - dpkg-source --commit with a patch name and message derived from C
5460 # After traversing PT, we git commit the changes which
5461 # should be contained within debian/patches.
5463 # The search for the path S..T is breadth-first. We maintain a
5464 # todo list containing search nodes. A search node identifies a
5465 # commit, and looks something like this:
5467 # Commit => $git_commit_id,
5468 # Child => $c, # or undef if P=T
5469 # Whynot => $reason_edge_PC_unsuitable, # in @nots only
5470 # Nontrivial => true iff $p..$c has relevant changes
5477 my %considered; # saves being exponential on some weird graphs
5479 my $t_sentinels = quiltify_tree_sentinelfiles $target;
5482 my ($search,$whynot) = @_;
5483 printdebug " search NOT $search->{Commit} $whynot\n";
5484 $search->{Whynot} = $whynot;
5485 push @nots, $search;
5486 no warnings qw(exiting);
5495 my $c = shift @todo;
5496 next if $considered{$c->{Commit}}++;
5498 $not->($c, __ "maximum search space exceeded") if --$max_work <= 0;
5500 printdebug "quiltify investigate $c->{Commit}\n";
5503 if (!quiltify_trees_differ $c->{Commit}, $oldtiptree) {
5504 printdebug " search finished hooray!\n";
5509 quiltify_nofix_bail " $c->{Commit}", " (tree object $oldtiptree)";
5510 if ($quilt_mode eq 'smash') {
5511 printdebug " search quitting smash\n";
5515 my $c_sentinels = quiltify_tree_sentinelfiles $c->{Commit};
5516 $not->($c, f_ "has %s not %s", $c_sentinels, $t_sentinels)
5517 if $c_sentinels ne $t_sentinels;
5519 my $commitdata = cmdoutput @git, qw(cat-file commit), $c->{Commit};
5520 $commitdata =~ m/\n\n/;
5522 my @parents = ($commitdata =~ m/^parent (\w+)$/gm);
5523 @parents = map { { Commit => $_, Child => $c } } @parents;
5525 $not->($c, __ "root commit") if !@parents;
5527 foreach my $p (@parents) {
5528 $p->{Nontrivial}= quiltify_trees_differ $p->{Commit},$c->{Commit};
5530 my $ndiffers = grep { $_->{Nontrivial} } @parents;
5531 $not->($c, f_ "merge (%s nontrivial parents)", $ndiffers)
5534 foreach my $p (@parents) {
5535 printdebug "considering C=$c->{Commit} P=$p->{Commit}\n";
5537 my @cmd= (@git, qw(diff-tree -r --name-only),
5538 $p->{Commit},$c->{Commit},
5539 qw(-- debian/patches .pc debian/source/format));
5540 my $patchstackchange = cmdoutput @cmd;
5541 if (length $patchstackchange) {
5542 $patchstackchange =~ s/\n/,/g;
5543 $not->($p, f_ "changed %s", $patchstackchange);
5546 printdebug " search queue P=$p->{Commit} ",
5547 ($p->{Nontrivial} ? "NT" : "triv"),"\n";
5553 printdebug "quiltify want to smash\n";
5556 my $x = $_[0]{Commit};
5557 $x =~ s/(.*?[0-9a-z]{8})[0-9a-z]*$/$1/;
5560 if ($quilt_mode eq 'linear') {
5562 "\n%s: error: quilt fixup cannot be linear. Stopped at:\n",
5564 my $all_gdr = !!@nots;
5565 foreach my $notp (@nots) {
5566 my $c = $notp->{Child};
5567 my $cprange = $abbrev->($notp);
5568 $cprange .= "..".$abbrev->($c) if $c;
5569 print STDERR f_ "%s: %s: %s\n",
5570 $us, $cprange, $notp->{Whynot};
5571 $all_gdr &&= $notp->{Child} &&
5572 (git_cat_file $notp->{Child}{Commit}, 'commit')
5573 =~ m{^\[git-debrebase(?! split[: ]).*\]$}m;
5577 [ grep { $_->[0] ne 'quilt-mode' } @$failsuggestion ]
5579 print STDERR "$us: $_->[1]\n" foreach @$failsuggestion;
5581 "quilt history linearisation failed. Search \`quilt fixup' in dgit(7).\n";
5582 } elsif ($quilt_mode eq 'smash') {
5583 } elsif ($quilt_mode eq 'auto') {
5584 progress __ "quilt fixup cannot be linear, smashing...";
5586 confess "$quilt_mode ?";
5589 my $time = $ENV{'GIT_COMMITTER_DATE'} || time;
5590 $time =~ s/\s.*//; # trim timezone from GIT_COMMITTER_DATE
5592 my $msg = cmdoutput @git, qw(log), "-n$ncommits";
5594 quiltify_dpkg_commit "auto-$version-$target-$time",
5595 (getfield $clogp, 'Maintainer'),
5596 (f_ "Automatically generated patch (%s)\n".
5597 "Last (up to) %s git changes, FYI:\n\n",
5598 $clogp->{Version}, $ncommits).
5603 progress __ "quiltify linearisation planning successful, executing...";
5605 for (my $p = $sref_S;
5606 my $c = $p->{Child};
5608 printdebug "quiltify traverse $p->{Commit}..$c->{Commit}\n";
5609 next unless $p->{Nontrivial};
5611 my $cc = $c->{Commit};
5613 my $commitdata = cmdoutput @git, qw(cat-file commit), $cc;
5614 $commitdata =~ m/\n\n/ or die "$c ?";
5617 $commitdata =~ m/^author (.*) \d+ [-+0-9]+$/m or die "$cc ?";
5620 my $commitdate = cmdoutput
5621 @git, qw(log -n1 --pretty=format:%aD), $cc;
5623 $msg =~ s/^(.*)\n*/$1\n/ or die "$cc $msg ?";
5625 my $strip_nls = sub { $msg =~ s/\n+$//; $msg .= "\n"; };
5632 my $gbp_check_suitable = sub {
5637 die __ "contains unexpected slashes\n" if m{//} || m{/$};
5638 die __ "contains leading punctuation\n" if m{^\W} || m{/\W};
5639 die __ "contains bad character(s)\n" if m{[^-a-z0-9_.+=~/]}i;
5640 die __ "is series file\n" if m{$series_filename_re}o;
5641 die __ "too long\n" if length > 200;
5643 return $_ unless $@;
5645 "quiltifying commit %s: ignoring/dropping Gbp-Pq %s: %s",
5650 if ($msg =~ s/^ (?: gbp(?:-pq)? : \s* name \s+ |
5652 (\S+) \s* \n //ixm) {
5653 $patchname = $gbp_check_suitable->($1, 'Name');
5655 if ($msg =~ s/^ (?: gbp(?:-pq)? : \s* topic \s+ |
5657 (\S+) \s* \n //ixm) {
5658 $patchdir = $gbp_check_suitable->($1, 'Topic');
5663 if (!defined $patchname) {
5664 $patchname = $title;
5665 $patchname =~ s/[.:]$//;
5668 my $converter = new Text::Iconv qw(UTF-8 ASCII//TRANSLIT);
5669 my $translitname = $converter->convert($patchname);
5670 die unless defined $translitname;
5671 $patchname = $translitname;
5674 +(f_ "dgit: patch title transliteration error: %s", $@)
5676 $patchname =~ y/ A-Z/-a-z/;
5677 $patchname =~ y/-a-z0-9_.+=~//cd;
5678 $patchname =~ s/^\W/x-$&/;
5679 $patchname = substr($patchname,0,40);
5680 $patchname .= ".patch";
5682 if (!defined $patchdir) {
5685 if (length $patchdir) {
5686 $patchname = "$patchdir/$patchname";
5688 if ($patchname =~ m{^(.*)/}) {
5689 mkpath "debian/patches/$1";
5694 stat "debian/patches/$patchname$index";
5696 $!==ENOENT or confess "$patchname$index $!";
5698 runcmd @git, qw(checkout -q), $cc;
5700 # We use the tip's changelog so that dpkg-source doesn't
5701 # produce complaining messages from dpkg-parsechangelog. None
5702 # of the information dpkg-source gets from the changelog is
5703 # actually relevant - it gets put into the original message
5704 # which dpkg-source provides our stunt editor, and then
5706 runcmd @git, qw(checkout -q), $target, qw(debian/changelog);
5708 quiltify_dpkg_commit "$patchname$index", $author, $msg,
5709 "Date: $commitdate\n".
5710 "X-Dgit-Generated: $clogp->{Version} $cc\n";
5712 runcmd @git, qw(checkout -q), $cc, qw(debian/changelog);
5715 runcmd @git, qw(checkout -q master);
5718 sub build_maybe_quilt_fixup () {
5719 my ($format,$fopts) = get_source_format;
5720 return unless madformat_wantfixup $format;
5723 check_for_vendor_patches();
5725 if (quiltmode_splitbrain) {
5726 fail <<END unless access_cfg_tagformats_can_splitbrain;
5727 quilt mode $quilt_mode requires split view so server needs to support
5728 both "new" and "maint" tag formats, but config says it doesn't.
5732 my $clogp = parsechangelog();
5733 my $headref = git_rev_parse('HEAD');
5734 my $symref = git_get_symref();
5736 if ($quilt_mode eq 'linear'
5737 && !$fopts->{'single-debian-patch'}
5738 && branch_is_gdr($headref)) {
5739 # This is much faster. It also makes patches that gdr
5740 # likes better for future updates without laundering.
5742 # However, it can fail in some casses where we would
5743 # succeed: if there are existing patches, which correspond
5744 # to a prefix of the branch, but are not in gbp/gdr
5745 # format, gdr will fail (exiting status 7), but we might
5746 # be able to figure out where to start linearising. That
5747 # will be slower so hopefully there's not much to do.
5748 my @cmd = (@git_debrebase,
5749 qw(--noop-ok -funclean-mixed -funclean-ordering
5750 make-patches --quiet-would-amend));
5751 # We tolerate soe snags that gdr wouldn't, by default.
5757 and not ($? == 7*256 or
5758 $? == -1 && $!==ENOENT);
5762 $headref = git_rev_parse('HEAD');
5766 changedir $playground;
5768 my $upstreamversion = upstreamversion $version;
5770 if ($fopts->{'single-debian-patch'}) {
5771 quilt_fixup_singlepatch($clogp, $headref, $upstreamversion);
5773 quilt_fixup_multipatch($clogp, $headref, $upstreamversion);
5777 runcmd_ordryrun_local
5778 @git, qw(pull --ff-only -q), "$playground/work", qw(master);
5781 sub unpack_playtree_mkwork ($) {
5784 mkdir "work" or confess $!;
5786 mktree_in_ud_here();
5787 runcmd @git, qw(reset -q --hard), $headref;
5790 sub unpack_playtree_linkorigs ($$) {
5791 my ($upstreamversion, $fn) = @_;
5792 # calls $fn->($leafname);
5794 my $bpd_abs = bpd_abs();
5795 opendir QFD, $bpd_abs or fail "buildproductsdir: $bpd_abs: $!";
5796 while ($!=0, defined(my $b = readdir QFD)) {
5797 my $f = bpd_abs()."/".$b;
5799 local ($debuglevel) = $debuglevel-1;
5800 printdebug "QF linkorigs $b, $f ?\n";
5802 next unless is_orig_file_of_vsn $b, $upstreamversion;
5803 printdebug "QF linkorigs $b, $f Y\n";
5804 link_ltarget $f, $b or die "$b $!";
5807 die "$buildproductsdir: $!" if $!;
5811 sub quilt_fixup_delete_pc () {
5812 runcmd @git, qw(rm -rqf .pc);
5813 commit_admin +(__ <<END).<<ENDU
5814 Commit removal of .pc (quilt series tracking data)
5817 [dgit ($our_version) upgrade quilt-remove-pc]
5821 sub quilt_fixup_singlepatch ($$$) {
5822 my ($clogp, $headref, $upstreamversion) = @_;
5824 progress __ "starting quiltify (single-debian-patch)";
5826 # dpkg-source --commit generates new patches even if
5827 # single-debian-patch is in debian/source/options. In order to
5828 # get it to generate debian/patches/debian-changes, it is
5829 # necessary to build the source package.
5831 unpack_playtree_linkorigs($upstreamversion, sub { });
5832 unpack_playtree_mkwork($headref);
5834 rmtree("debian/patches");
5836 runcmd @dpkgsource, qw(-b .);
5838 runcmd @dpkgsource, qw(-x), (srcfn $version, ".dsc");
5839 rename srcfn("$upstreamversion", "/debian/patches"),
5840 "work/debian/patches"
5842 or confess "install d/patches: $!";
5845 commit_quilty_patch();
5848 sub quilt_make_fake_dsc ($) {
5849 my ($upstreamversion) = @_;
5851 my $fakeversion="$upstreamversion-~~DGITFAKE";
5853 my $fakedsc=new IO::File 'fake.dsc', '>' or confess $!;
5854 print $fakedsc <<END or confess $!;
5857 Version: $fakeversion
5861 my $dscaddfile=sub {
5864 my $md = new Digest::MD5;
5866 my $fh = new IO::File $b, '<' or die "$b $!";
5867 stat $fh or confess $!;
5871 print $fakedsc " ".$md->hexdigest." $size $b\n" or confess $!;
5874 unpack_playtree_linkorigs($upstreamversion, $dscaddfile);
5876 my @files=qw(debian/source/format debian/rules
5877 debian/control debian/changelog);
5878 foreach my $maybe (qw(debian/patches debian/source/options
5879 debian/tests/control)) {
5880 next unless stat_exists "$maindir/$maybe";
5881 push @files, $maybe;
5884 my $debtar= srcfn $fakeversion,'.debian.tar.gz';
5885 runcmd qw(env GZIP=-1n tar -zcf), "./$debtar", qw(-C), $maindir, @files;
5887 $dscaddfile->($debtar);
5888 close $fakedsc or confess $!;
5891 sub quilt_fakedsc2unapplied ($$) {
5892 my ($headref, $upstreamversion) = @_;
5893 # must be run in the playground
5894 # quilt_make_fake_dsc must have been called
5897 'exec dpkg-source --no-check --skip-patches -x fake.dsc >/dev/null';
5899 my $fakexdir= $package.'-'.(stripepoch $upstreamversion);
5900 rename $fakexdir, "fake" or die "$fakexdir $!";
5904 remove_stray_gits(__ "source package");
5905 mktree_in_ud_here();
5909 rmtree 'debian'; # git checkout commitish paths does not delete!
5910 runcmd @git, qw(checkout -f), $headref, qw(-- debian);
5911 my $unapplied=git_add_write_tree();
5912 printdebug "fake orig tree object $unapplied\n";
5916 sub quilt_check_splitbrain_cache ($$) {
5917 my ($headref, $upstreamversion) = @_;
5918 # Called only if we are in (potentially) split brain mode.
5919 # Called in playground.
5920 # Computes the cache key and looks in the cache.
5921 # Returns ($dgit_view_commitid, $cachekey) or (undef, $cachekey)
5923 my $splitbrain_cachekey;
5926 "dgit: split brain (separate dgit view) may be needed (--quilt=%s).",
5928 # we look in the reflog of dgit-intern/quilt-cache
5929 # we look for an entry whose message is the key for the cache lookup
5930 my @cachekey = (qw(dgit), $our_version);
5931 push @cachekey, $upstreamversion;
5932 push @cachekey, $quilt_mode;
5933 push @cachekey, $headref;
5935 push @cachekey, hashfile('fake.dsc');
5937 my $srcshash = Digest::SHA->new(256);
5938 my %sfs = ( %INC, '$0(dgit)' => $0 );
5939 foreach my $sfk (sort keys %sfs) {
5940 next unless $sfk =~ m/^\$0\b/ || $sfk =~ m{^Debian/Dgit\b};
5941 $srcshash->add($sfk," ");
5942 $srcshash->add(hashfile($sfs{$sfk}));
5943 $srcshash->add("\n");
5945 push @cachekey, $srcshash->hexdigest();
5946 $splitbrain_cachekey = "@cachekey";
5948 printdebug "splitbrain cachekey $splitbrain_cachekey\n";
5950 my $cachehit = reflog_cache_lookup
5951 "refs/$splitbraincache", $splitbrain_cachekey;
5954 unpack_playtree_mkwork($headref);
5955 my $saved = maybe_split_brain_save $headref, $cachehit, "cache-hit";
5956 if ($cachehit ne $headref) {
5957 progress f_ "dgit view: found cached (%s)", $saved;
5958 runcmd @git, qw(checkout -q -b dgit-view), $cachehit;
5960 return ($cachehit, $splitbrain_cachekey);
5962 progress __ "dgit view: found cached, no changes required";
5963 return ($headref, $splitbrain_cachekey);
5966 printdebug "splitbrain cache miss\n";
5967 return (undef, $splitbrain_cachekey);
5970 sub quilt_fixup_multipatch ($$$) {
5971 my ($clogp, $headref, $upstreamversion) = @_;
5973 progress f_ "examining quilt state (multiple patches, %s mode)",
5977 # - honour any existing .pc in case it has any strangeness
5978 # - determine the git commit corresponding to the tip of
5979 # the patch stack (if there is one)
5980 # - if there is such a git commit, convert each subsequent
5981 # git commit into a quilt patch with dpkg-source --commit
5982 # - otherwise convert all the differences in the tree into
5983 # a single git commit
5987 # Our git tree doesn't necessarily contain .pc. (Some versions of
5988 # dgit would include the .pc in the git tree.) If there isn't
5989 # one, we need to generate one by unpacking the patches that we
5992 # We first look for a .pc in the git tree. If there is one, we
5993 # will use it. (This is not the normal case.)
5995 # Otherwise need to regenerate .pc so that dpkg-source --commit
5996 # can work. We do this as follows:
5997 # 1. Collect all relevant .orig from parent directory
5998 # 2. Generate a debian.tar.gz out of
5999 # debian/{patches,rules,source/format,source/options}
6000 # 3. Generate a fake .dsc containing just these fields:
6001 # Format Source Version Files
6002 # 4. Extract the fake .dsc
6003 # Now the fake .dsc has a .pc directory.
6004 # (In fact we do this in every case, because in future we will
6005 # want to search for a good base commit for generating patches.)
6007 # Then we can actually do the dpkg-source --commit
6008 # 1. Make a new working tree with the same object
6009 # store as our main tree and check out the main
6011 # 2. Copy .pc from the fake's extraction, if necessary
6012 # 3. Run dpkg-source --commit
6013 # 4. If the result has changes to debian/, then
6014 # - git add them them
6015 # - git add .pc if we had a .pc in-tree
6017 # 5. If we had a .pc in-tree, delete it, and git commit
6018 # 6. Back in the main tree, fast forward to the new HEAD
6020 # Another situation we may have to cope with is gbp-style
6021 # patches-unapplied trees.
6023 # We would want to detect these, so we know to escape into
6024 # quilt_fixup_gbp. However, this is in general not possible.
6025 # Consider a package with a one patch which the dgit user reverts
6026 # (with git revert or the moral equivalent).
6028 # That is indistinguishable in contents from a patches-unapplied
6029 # tree. And looking at the history to distinguish them is not
6030 # useful because the user might have made a confusing-looking git
6031 # history structure (which ought to produce an error if dgit can't
6032 # cope, not a silent reintroduction of an unwanted patch).
6034 # So gbp users will have to pass an option. But we can usually
6035 # detect their failure to do so: if the tree is not a clean
6036 # patches-applied tree, quilt linearisation fails, but the tree
6037 # _is_ a clean patches-unapplied tree, we can suggest that maybe
6038 # they want --quilt=unapplied.
6040 # To help detect this, when we are extracting the fake dsc, we
6041 # first extract it with --skip-patches, and then apply the patches
6042 # afterwards with dpkg-source --before-build. That lets us save a
6043 # tree object corresponding to .origs.
6045 my $splitbrain_cachekey;
6047 quilt_make_fake_dsc($upstreamversion);
6049 if (quiltmode_splitbrain()) {
6051 ($cachehit, $splitbrain_cachekey) =
6052 quilt_check_splitbrain_cache($headref, $upstreamversion);
6053 return if $cachehit;
6055 my $unapplied=quilt_fakedsc2unapplied($headref, $upstreamversion);
6059 my @bbcmd = (qw(sh -ec), 'exec dpkg-source --before-build . >/dev/null');
6061 if (system @bbcmd) {
6062 failedcmd @bbcmd if $? < 0;
6064 failed to apply your git tree's patch stack (from debian/patches/) to
6065 the corresponding upstream tarball(s). Your source tree and .orig
6066 are probably too inconsistent. dgit can only fix up certain kinds of
6067 anomaly (depending on the quilt mode). Please see --quilt= in dgit(1).
6073 unpack_playtree_mkwork($headref);
6076 if (stat_exists ".pc") {
6078 progress __ "Tree already contains .pc - will use it then delete it.";
6081 rename '../fake/.pc','.pc' or confess $!;
6084 changedir '../fake';
6086 my $oldtiptree=git_add_write_tree();
6087 printdebug "fake o+d/p tree object $unapplied\n";
6088 changedir '../work';
6091 # We calculate some guesswork now about what kind of tree this might
6092 # be. This is mostly for error reporting.
6098 # O = orig, without patches applied
6099 # A = "applied", ie orig with H's debian/patches applied
6100 O2H => quiltify_trees_differ($unapplied,$headref, 1,
6101 \%editedignores, \@unrepres),
6102 H2A => quiltify_trees_differ($headref, $oldtiptree,1),
6103 O2A => quiltify_trees_differ($unapplied,$oldtiptree,1),
6107 foreach my $b (qw(01 02)) {
6108 foreach my $v (qw(O2H O2A H2A)) {
6109 push @dl, ($diffbits->{$v} & $b) ? '##' : '==';
6112 printdebug "differences \@dl @dl.\n";
6115 "%s: base trees orig=%.20s o+d/p=%.20s",
6116 $us, $unapplied, $oldtiptree;
6118 "%s: quilt differences: src: %s orig %s gitignores: %s orig %s\n".
6119 "%s: quilt differences: HEAD %s o+d/p HEAD %s o+d/p",
6120 $us, $dl[0], $dl[1], $dl[3], $dl[4],
6121 $us, $dl[2], $dl[5];
6124 print STDERR f_ "dgit: cannot represent change: %s: %s\n",
6127 forceable_fail [qw(unrepresentable)], __ <<END;
6128 HEAD has changes to .orig[s] which are not representable by `3.0 (quilt)'
6133 if (!($diffbits->{O2H} & $diffbits->{O2A})) {
6134 push @failsuggestion, [ 'unapplied', __
6135 "This might be a patches-unapplied branch." ];
6136 } elsif (!($diffbits->{H2A} & $diffbits->{O2A})) {
6137 push @failsuggestion, [ 'applied', __
6138 "This might be a patches-applied branch." ];
6140 push @failsuggestion, [ 'quilt-mode', __
6141 "Maybe you need one of --[quilt=]gbp --[quilt=]dpm --quilt=unapplied ?" ];
6143 push @failsuggestion, [ 'gitattrs', __
6144 "Warning: Tree has .gitattributes. See GITATTRIBUTES in dgit(7)." ]
6145 if stat_exists '.gitattributes';
6147 push @failsuggestion, [ 'origs', __
6148 "Maybe orig tarball(s) are not identical to git representation?" ];
6150 if (quiltmode_splitbrain()) {
6151 quiltify_splitbrain($clogp, $unapplied, $headref, $oldtiptree,
6152 $diffbits, \%editedignores,
6153 $splitbrain_cachekey);
6157 progress f_ "starting quiltify (multiple patches, %s mode)", $quilt_mode;
6158 quiltify($clogp,$headref,$oldtiptree,\@failsuggestion);
6160 if (!open P, '>>', ".pc/applied-patches") {
6161 $!==&ENOENT or confess $!;
6166 commit_quilty_patch();
6168 if ($mustdeletepc) {
6169 quilt_fixup_delete_pc();
6173 sub quilt_fixup_editor () {
6174 my $descfn = $ENV{$fakeeditorenv};
6175 my $editing = $ARGV[$#ARGV];
6176 open I1, '<', $descfn or confess "$descfn: $!";
6177 open I2, '<', $editing or confess "$editing: $!";
6178 unlink $editing or confess "$editing: $!";
6179 open O, '>', $editing or confess "$editing: $!";
6180 while (<I1>) { print O or confess $!; } I1->error and confess $!;
6183 $copying ||= m/^\-\-\- /;
6184 next unless $copying;
6185 print O or confess $!;
6187 I2->error and confess $!;
6192 sub maybe_apply_patches_dirtily () {
6193 return unless $quilt_mode =~ m/gbp|unapplied/;
6194 print STDERR __ <<END or confess $!;
6196 dgit: Building, or cleaning with rules target, in patches-unapplied tree.
6197 dgit: Have to apply the patches - making the tree dirty.
6198 dgit: (Consider specifying --clean=git and (or) using dgit sbuild.)
6201 $patches_applied_dirtily = 01;
6202 $patches_applied_dirtily |= 02 unless stat_exists '.pc';
6203 runcmd qw(dpkg-source --before-build .);
6206 sub maybe_unapply_patches_again () {
6207 progress __ "dgit: Unapplying patches again to tidy up the tree."
6208 if $patches_applied_dirtily;
6209 runcmd qw(dpkg-source --after-build .)
6210 if $patches_applied_dirtily & 01;
6212 if $patches_applied_dirtily & 02;
6213 $patches_applied_dirtily = 0;
6216 #----- other building -----
6218 sub clean_tree_check_git ($$$) {
6219 my ($honour_ignores, $message, $ignmessage) = @_;
6220 my @cmd = (@git, qw(clean -dn));
6221 push @cmd, qw(-x) unless $honour_ignores;
6222 my $leftovers = cmdoutput @cmd;
6223 if (length $leftovers) {
6224 print STDERR $leftovers, "\n" or confess $!;
6225 $message .= $ignmessage if $honour_ignores;
6230 sub clean_tree_check_git_wd ($) {
6232 return if $cleanmode =~ m{no-check};
6233 return if $patches_applied_dirtily; # yuk
6234 clean_tree_check_git +($cleanmode !~ m{all-check}),
6235 $message, "\n".__ <<END;
6236 If this is just missing .gitignore entries, use a different clean
6237 mode, eg --clean=dpkg-source,no-check (-wdn/-wddn) to ignore them
6238 or --clean=git (-wg/-wgf) to use \`git clean' instead.
6242 sub clean_tree_check () {
6243 # This function needs to not care about modified but tracked files.
6244 # That was done by check_not_dirty, and by now we may have run
6245 # the rules clean target which might modify tracked files (!)
6246 if ($cleanmode =~ m{^check}) {
6247 clean_tree_check_git +($cleanmode =~ m{ignores}), __
6248 "tree contains uncommitted files and --clean=check specified", '';
6249 } elsif ($cleanmode =~ m{^dpkg-source}) {
6250 clean_tree_check_git_wd __
6251 "tree contains uncommitted files (NB dgit didn't run rules clean)";
6252 } elsif ($cleanmode =~ m{^git}) {
6253 clean_tree_check_git 1, __
6254 "tree contains uncommited, untracked, unignored files\n".
6255 "You can use --clean=git[-ff],always (-wga/-wgfa) to delete them.", '';
6256 } elsif ($cleanmode eq 'none') {
6258 confess "$cleanmode ?";
6263 # We always clean the tree ourselves, rather than leave it to the
6264 # builder (dpkg-source, or soemthing which calls dpkg-source).
6265 if ($cleanmode =~ m{^dpkg-source}) {
6266 my @cmd = @dpkgbuildpackage;
6267 push @cmd, qw(-d) if $cleanmode =~ m{^dpkg-source-d};
6268 push @cmd, qw(-T clean);
6269 maybe_apply_patches_dirtily();
6270 runcmd_ordryrun_local @cmd;
6271 clean_tree_check_git_wd __
6272 "tree contains uncommitted files (after running rules clean)";
6273 } elsif ($cleanmode =~ m{^git(?!-)}) {
6274 runcmd_ordryrun_local @git, qw(clean -xdf);
6275 } elsif ($cleanmode =~ m{^git-ff}) {
6276 runcmd_ordryrun_local @git, qw(clean -xdff);
6277 } elsif ($cleanmode =~ m{^check}) {
6279 } elsif ($cleanmode eq 'none') {
6281 confess "$cleanmode ?";
6286 badusage __ "clean takes no additional arguments" if @ARGV;
6289 maybe_unapply_patches_again();
6292 # return values from massage_dbp_args are one or both of these flags
6293 sub WANTSRC_SOURCE () { 01; } # caller should build source (separately)
6294 sub WANTSRC_BUILDER () { 02; } # caller should run dpkg-buildpackage
6296 sub build_or_push_prep_early () {
6297 our $build_or_push_prep_early_done //= 0;
6298 return if $build_or_push_prep_early_done++;
6299 badusage f_ "-p is not allowed with dgit %s", $subcommand
6300 if defined $package;
6301 my $clogp = parsechangelog();
6302 $isuite = getfield $clogp, 'Distribution';
6303 $package = getfield $clogp, 'Source';
6304 $version = getfield $clogp, 'Version';
6305 $dscfn = dscfn($version);
6308 sub build_prep_early () {
6309 build_or_push_prep_early();
6314 sub build_prep ($) {
6318 if (!building_source_in_playtree() || ($wantsrc & WANTSRC_BUILDER)
6319 # Clean the tree because we're going to use the contents of
6320 # $maindir. (We trying to include dirty changes in the source
6321 # package, or we are running the builder in $maindir.)
6322 || $cleanmode =~ m{always}) {
6323 # Or because the user asked us to.
6326 # We don't actually need to do anything in $maindir, but we
6327 # should do some kind of cleanliness check because (i) the
6328 # user may have forgotten a `git add', and (ii) if the user
6329 # said -wc we should still do the check.
6332 build_maybe_quilt_fixup();
6334 my $pat = changespat $version;
6335 foreach my $f (glob "$buildproductsdir/$pat") {
6338 fail f_ "remove old changes file %s: %s", $f, $!;
6340 progress f_ "would remove %s", $f;
6346 sub changesopts_initial () {
6347 my @opts =@changesopts[1..$#changesopts];
6350 sub changesopts_version () {
6351 if (!defined $changes_since_version) {
6354 @vsns = archive_query('archive_query');
6355 my @quirk = access_quirk();
6356 if ($quirk[0] eq 'backports') {
6357 local $isuite = $quirk[2];
6359 canonicalise_suite();
6360 push @vsns, archive_query('archive_query');
6366 "archive query failed (queried because --since-version not specified)";
6369 @vsns = map { $_->[0] } @vsns;
6370 @vsns = sort { -version_compare($a, $b) } @vsns;
6371 $changes_since_version = $vsns[0];
6372 progress f_ "changelog will contain changes since %s", $vsns[0];
6374 $changes_since_version = '_';
6375 progress __ "package seems new, not specifying -v<version>";
6378 if ($changes_since_version ne '_') {
6379 return ("-v$changes_since_version");
6385 sub changesopts () {
6386 return (changesopts_initial(), changesopts_version());
6389 sub massage_dbp_args ($;$) {
6390 my ($cmd,$xargs) = @_;
6391 # Since we split the source build out so we can do strange things
6392 # to it, massage the arguments to dpkg-buildpackage so that the
6393 # main build doessn't build source (or add an argument to stop it
6394 # building source by default).
6395 debugcmd '#massaging#', @$cmd if $debuglevel>1;
6396 # -nc has the side effect of specifying -b if nothing else specified
6397 # and some combinations of -S, -b, et al, are errors, rather than
6398 # later simply overriding earlie. So we need to:
6399 # - search the command line for these options
6400 # - pick the last one
6401 # - perhaps add our own as a default
6402 # - perhaps adjust it to the corresponding non-source-building version
6404 foreach my $l ($cmd, $xargs) {
6406 @$l = grep { !(m/^-[SgGFABb]$|^--build=/s and $dmode=$_) } @$l;
6409 #print STDERR "MASS1 ",Dumper($cmd, $xargs, $dmode);
6410 my $r = WANTSRC_BUILDER;
6411 printdebug "massage split $dmode.\n";
6412 if ($dmode =~ s/^--build=//) {
6414 my @d = split /,/, $dmode;
6415 $r |= WANTSRC_SOURCE if grep { s/^full$/binary/ } @d;
6416 $r |= WANTSRC_SOURCE if grep { s/^source$// } @d;
6417 $r |= WANTSRC_BUILDER if grep { m/./ } @d;
6418 fail __ "Wanted to build nothing!" unless $r;
6419 $dmode = '--build='. join ',', grep m/./, @d;
6422 $dmode =~ m/[S]/ ? WANTSRC_SOURCE :
6423 $dmode =~ y/gGF/ABb/ ? WANTSRC_SOURCE | WANTSRC_BUILDER :
6424 $dmode =~ m/[ABb]/ ? WANTSRC_BUILDER :
6427 printdebug "massage done $r $dmode.\n";
6429 #print STDERR "MASS2 ",Dumper($cmd, $xargs, $r);
6435 my $wasdir = must_getcwd();
6436 changedir $buildproductsdir;
6441 # this sub must run with CWD=$buildproductsdir (eg in in_bpd)
6442 sub postbuild_mergechanges ($) {
6443 my ($msg_if_onlyone) = @_;
6444 # If there is only one .changes file, fail with $msg_if_onlyone,
6445 # or if that is undef, be a no-op.
6446 # Returns the changes file to report to the user.
6447 my $pat = changespat $version;
6448 my @changesfiles = grep { !m/_multi\.changes/ } glob $pat;
6449 @changesfiles = sort {
6450 ($b =~ m/_source\.changes$/ <=> $a =~ m/_source\.changes$/)
6454 if (@changesfiles==1) {
6455 fail +(f_ <<END, "@changesfiles").$msg_if_onlyone
6456 only one changes file from build (%s)
6458 if defined $msg_if_onlyone;
6459 $result = $changesfiles[0];
6460 } elsif (@changesfiles==2) {
6461 my $binchanges = parsecontrol($changesfiles[1], "binary changes file");
6462 foreach my $l (split /\n/, getfield $binchanges, 'Files') {
6463 fail f_ "%s found in binaries changes file %s", $l, $binchanges
6466 runcmd_ordryrun_local @mergechanges, @changesfiles;
6467 my $multichanges = changespat $version,'multi';
6469 stat_exists $multichanges or fail f_
6470 "%s unexpectedly not created by build", $multichanges;
6471 foreach my $cf (glob $pat) {
6472 next if $cf eq $multichanges;
6473 rename "$cf", "$cf.inmulti" or fail f_
6474 "install new changes %s\{,.inmulti}: %s", $cf, $!;
6477 $result = $multichanges;
6479 fail f_ "wrong number of different changes files (%s)",
6482 printdone f_ "build successful, results in %s\n", $result
6486 sub midbuild_checkchanges () {
6487 my $pat = changespat $version;
6488 return if $rmchanges;
6489 my @unwanted = map { s#.*/##; $_; } glob "$bpd_glob/$pat";
6491 $_ ne changespat $version,'source' and
6492 $_ ne changespat $version,'multi'
6494 fail +(f_ <<END, $pat, "@unwanted")
6495 changes files other than source matching %s already present; building would result in ambiguity about the intended results.
6496 Suggest you delete %s.
6501 sub midbuild_checkchanges_vanilla ($) {
6503 midbuild_checkchanges() if $wantsrc == (WANTSRC_SOURCE|WANTSRC_BUILDER);
6506 sub postbuild_mergechanges_vanilla ($) {
6508 if ($wantsrc == (WANTSRC_SOURCE|WANTSRC_BUILDER)) {
6510 postbuild_mergechanges(undef);
6513 printdone __ "build successful\n";
6519 $buildproductsdir eq '..' or print STDERR +(f_ <<END, $us, $us);
6520 %s: warning: build-products-dir set, but not supported by dpkg-buildpackage
6521 %s: warning: build-products-dir will be ignored; files will go to ..
6523 $buildproductsdir = '..';
6524 my @dbp = (@dpkgbuildpackage, qw(-us -uc), changesopts_initial(), @ARGV);
6525 my $wantsrc = massage_dbp_args \@dbp;
6526 build_prep($wantsrc);
6527 if ($wantsrc & WANTSRC_SOURCE) {
6529 midbuild_checkchanges_vanilla $wantsrc;
6531 if ($wantsrc & WANTSRC_BUILDER) {
6532 push @dbp, changesopts_version();
6533 maybe_apply_patches_dirtily();
6534 runcmd_ordryrun_local @dbp;
6536 maybe_unapply_patches_again();
6537 postbuild_mergechanges_vanilla $wantsrc;
6541 $quilt_mode //= 'gbp';
6547 # gbp can make .origs out of thin air. In my tests it does this
6548 # even for a 1.0 format package, with no origs present. So I
6549 # guess it keys off just the version number. We don't know
6550 # exactly what .origs ought to exist, but let's assume that we
6551 # should run gbp if: the version has an upstream part and the main
6553 my $upstreamversion = upstreamversion $version;
6554 my $origfnpat = srcfn $upstreamversion, '.orig.tar.*';
6555 my $gbp_make_orig = $version =~ m/-/ && !(() = glob "$bpd_glob/$origfnpat");
6557 if ($gbp_make_orig) {
6559 $cleanmode = 'none'; # don't do it again
6562 my @dbp = @dpkgbuildpackage;
6564 my $wantsrc = massage_dbp_args \@dbp, \@ARGV;
6566 if (!length $gbp_build[0]) {
6567 if (length executable_on_path('git-buildpackage')) {
6568 $gbp_build[0] = qw(git-buildpackage);
6570 $gbp_build[0] = 'gbp buildpackage';
6573 my @cmd = opts_opt_multi_cmd [], @gbp_build;
6575 push @cmd, (qw(-us -uc --git-no-sign-tags),
6576 "--git-builder=".(shellquote @dbp));
6578 if ($gbp_make_orig) {
6579 my $priv = dgit_privdir();
6580 my $ok = "$priv/origs-gen-ok";
6581 unlink $ok or $!==&ENOENT or confess $!;
6582 my @origs_cmd = @cmd;
6583 push @origs_cmd, qw(--git-cleaner=true);
6584 push @origs_cmd, "--git-prebuild=".
6585 "touch ".(shellquote $ok)." ".(shellquote "$priv/no-such-dir/ok");
6586 push @origs_cmd, @ARGV;
6588 debugcmd @origs_cmd;
6590 do { local $!; stat_exists $ok; }
6591 or failedcmd @origs_cmd;
6593 dryrun_report @origs_cmd;
6597 build_prep($wantsrc);
6598 if ($wantsrc & WANTSRC_SOURCE) {
6600 midbuild_checkchanges_vanilla $wantsrc;
6602 push @cmd, '--git-cleaner=true';
6604 maybe_unapply_patches_again();
6605 if ($wantsrc & WANTSRC_BUILDER) {
6606 push @cmd, changesopts();
6607 runcmd_ordryrun_local @cmd, @ARGV;
6609 postbuild_mergechanges_vanilla $wantsrc;
6611 sub cmd_git_build { cmd_gbp_build(); } # compatibility with <= 1.0
6613 sub building_source_in_playtree {
6614 # If $includedirty, we have to build the source package from the
6615 # working tree, not a playtree, so that uncommitted changes are
6616 # included (copying or hardlinking them into the playtree could
6619 # Note that if we are building a source package in split brain
6620 # mode we do not support including uncommitted changes, because
6621 # that makes quilt fixup too hard. I.e. ($split_brain && (dgit is
6622 # building a source package)) => !$includedirty
6623 return !$includedirty;
6627 $sourcechanges = changespat $version,'source';
6629 unlink "$buildproductsdir/$sourcechanges" or $!==ENOENT
6630 or fail f_ "remove %s: %s", $sourcechanges, $!;
6632 my @cmd = (@dpkgsource, qw(-b --));
6634 if (building_source_in_playtree()) {
6636 my $headref = git_rev_parse('HEAD');
6637 # If we are in split brain, there is already a playtree with
6638 # the thing we should package into a .dsc (thanks to quilt
6639 # fixup). If not, make a playtree
6640 prep_ud() unless $split_brain;
6641 changedir $playground;
6642 unless ($split_brain) {
6643 my $upstreamversion = upstreamversion $version;
6644 unpack_playtree_linkorigs($upstreamversion, sub { });
6645 unpack_playtree_mkwork($headref);
6649 $leafdir = basename $maindir;
6651 if ($buildproductsdir ne '..') {
6652 # Well, we are going to run dpkg-source -b which consumes
6653 # origs from .. and generates output there. To make this
6654 # work when the bpd is not .. , we would have to (i) link
6655 # origs from bpd to .. , (ii) check for files that
6656 # dpkg-source -b would/might overwrite, and afterwards
6657 # (iii) move all the outputs back to the bpd (iv) except
6658 # for the origs which should be deleted from .. if they
6659 # weren't there beforehand. And if there is an error and
6660 # we don't run to completion we would necessarily leave a
6661 # mess. This is too much. The real way to fix this
6662 # is for dpkg-source to have bpd support.
6663 confess unless $includedirty;
6665 "--include-dirty not supported with --build-products-dir, sorry";
6670 runcmd_ordryrun_local @cmd, $leafdir;
6673 runcmd_ordryrun_local qw(sh -ec),
6674 'exec >../$1; shift; exec "$@"','x', $sourcechanges,
6675 @dpkggenchanges, qw(-S), changesopts();
6678 printdebug "moving $dscfn, $sourcechanges, etc. to ".bpd_abs()."\n";
6679 $dsc = parsecontrol($dscfn, "source package");
6683 printdebug " renaming ($why) $l\n";
6684 rename_link_xf 0, "$l", bpd_abs()."/$l"
6685 or fail f_ "put in place new built file (%s): %s", $l, $@;
6687 foreach my $l (split /\n/, getfield $dsc, 'Files') {
6688 $l =~ m/\S+$/ or next;
6691 $mv->('dsc', $dscfn);
6692 $mv->('changes', $sourcechanges);
6697 sub cmd_build_source {
6698 badusage __ "build-source takes no additional arguments" if @ARGV;
6699 build_prep(WANTSRC_SOURCE);
6701 maybe_unapply_patches_again();
6702 printdone f_ "source built, results in %s and %s",
6703 $dscfn, $sourcechanges;
6706 sub cmd_push_source {
6709 "dgit push-source: --include-dirty/--ignore-dirty does not make".
6710 "sense with push-source!"
6712 build_maybe_quilt_fixup();
6714 my $changes = parsecontrol("$buildproductsdir/$changesfile",
6715 __ "source changes file");
6716 unless (test_source_only_changes($changes)) {
6717 fail __ "user-specified changes file is not source-only";
6720 # Building a source package is very fast, so just do it
6722 confess "er, patches are applied dirtily but shouldn't be.."
6723 if $patches_applied_dirtily;
6724 $changesfile = $sourcechanges;
6729 sub binary_builder {
6730 my ($bbuilder, $pbmc_msg, @args) = @_;
6731 build_prep(WANTSRC_SOURCE);
6733 midbuild_checkchanges();
6736 stat_exists $dscfn or fail f_
6737 "%s (in build products dir): %s", $dscfn, $!;
6738 stat_exists $sourcechanges or fail f_
6739 "%s (in build products dir): %s", $sourcechanges, $!;
6741 runcmd_ordryrun_local @$bbuilder, @args;
6743 maybe_unapply_patches_again();
6745 postbuild_mergechanges($pbmc_msg);
6751 binary_builder(\@sbuild, (__ <<END), qw(-d), $isuite, @ARGV, $dscfn);
6752 perhaps you need to pass -A ? (sbuild's default is to build only
6753 arch-specific binaries; dgit 1.4 used to override that.)
6758 my ($pbuilder) = @_;
6760 # @ARGV is allowed to contain only things that should be passed to
6761 # pbuilder under debbuildopts; just massage those
6762 my $wantsrc = massage_dbp_args \@ARGV;
6764 "you asked for a builder but your debbuildopts didn't ask for".
6765 " any binaries -- is this really what you meant?"
6766 unless $wantsrc & WANTSRC_BUILDER;
6768 "we must build a .dsc to pass to the builder but your debbuiltopts".
6769 " forbids the building of a source package; cannot continue"
6770 unless $wantsrc & WANTSRC_SOURCE;
6771 # We do not want to include the verb "build" in @pbuilder because
6772 # the user can customise @pbuilder and they shouldn't be required
6773 # to include "build" in their customised value. However, if the
6774 # user passes any additional args to pbuilder using the dgit
6775 # option --pbuilder:foo, such args need to come after the "build"
6776 # verb. opts_opt_multi_cmd does all of that.
6777 binary_builder([opts_opt_multi_cmd ["build"], @$pbuilder], undef,
6778 qw(--debbuildopts), "@ARGV", qw(--distribution), $isuite,
6783 pbuilder(\@pbuilder);
6786 sub cmd_cowbuilder {
6787 pbuilder(\@cowbuilder);
6790 sub cmd_quilt_fixup {
6791 badusage "incorrect arguments to dgit quilt-fixup" if @ARGV;
6794 build_maybe_quilt_fixup();
6797 sub cmd_print_unapplied_treeish {
6798 badusage __ "incorrect arguments to dgit print-unapplied-treeish"
6800 my $headref = git_rev_parse('HEAD');
6801 my $clogp = commit_getclogp $headref;
6802 $package = getfield $clogp, 'Source';
6803 $version = getfield $clogp, 'Version';
6804 $isuite = getfield $clogp, 'Distribution';
6805 $csuite = $isuite; # we want this to be offline!
6809 changedir $playground;
6810 my $uv = upstreamversion $version;
6811 quilt_make_fake_dsc($uv);
6812 my $u = quilt_fakedsc2unapplied($headref, $uv);
6813 print $u, "\n" or confess $!;
6816 sub import_dsc_result {
6817 my ($dstref, $newhash, $what_log, $what_msg) = @_;
6818 my @cmd = (git_update_ref_cmd $what_log, $dstref, $newhash);
6820 check_gitattrs($newhash, __ "source tree");
6822 progress f_ "dgit: import-dsc: %s", $what_msg;
6825 sub cmd_import_dsc {
6829 last unless $ARGV[0] =~ m/^-/;
6832 if (m/^--require-valid-signature$/) {
6835 badusage f_ "unknown dgit import-dsc sub-option \`%s'", $_;
6839 badusage __ "usage: dgit import-dsc .../PATH/TO/.DSC BRANCH"
6841 my ($dscfn, $dstbranch) = @ARGV;
6843 badusage __ "dry run makes no sense with import-dsc"
6846 my $force = $dstbranch =~ s/^\+// ? +1 :
6847 $dstbranch =~ s/^\.\.// ? -1 :
6849 my $info = $force ? " $&" : '';
6850 $info = "$dscfn$info";
6852 my $specbranch = $dstbranch;
6853 $dstbranch = "refs/heads/$dstbranch" unless $dstbranch =~ m#^refs/#;
6854 $dstbranch = cmdoutput @git, qw(check-ref-format --normalize), $dstbranch;
6856 my @symcmd = (@git, qw(symbolic-ref -q HEAD));
6857 my $chead = cmdoutput_errok @symcmd;
6858 defined $chead or $?==256 or failedcmd @symcmd;
6860 fail f_ "%s is checked out - will not update it", $dstbranch
6861 if defined $chead and $chead eq $dstbranch;
6863 my $oldhash = git_get_ref $dstbranch;
6865 open D, "<", $dscfn or fail f_ "open import .dsc (%s): %s", $dscfn, $!;
6866 $dscdata = do { local $/ = undef; <D>; };
6867 D->error and fail f_ "read %s: %s", $dscfn, $!;
6870 # we don't normally need this so import it here
6871 use Dpkg::Source::Package;
6872 my $dp = new Dpkg::Source::Package filename => $dscfn,
6873 require_valid_signature => $needsig;
6875 local $SIG{__WARN__} = sub {
6877 return unless $needsig;
6878 fail __ "import-dsc signature check failed";
6880 if (!$dp->is_signed()) {
6881 warn f_ "%s: warning: importing unsigned .dsc\n", $us;
6883 my $r = $dp->check_signature();
6884 confess "->check_signature => $r" if $needsig && $r;
6890 $package = getfield $dsc, 'Source';
6892 parse_dsc_field($dsc, __ "Dgit metadata in .dsc")
6893 unless forceing [qw(import-dsc-with-dgit-field)];
6894 parse_dsc_field_def_dsc_distro();
6896 $isuite = 'DGIT-IMPORT-DSC';
6897 $idistro //= $dsc_distro;
6901 if (defined $dsc_hash) {
6903 "dgit: import-dsc of .dsc with Dgit field, using git hash";
6904 resolve_dsc_field_commit undef, undef;
6906 if (defined $dsc_hash) {
6907 my @cmd = (qw(sh -ec),
6908 "echo $dsc_hash | git cat-file --batch-check");
6909 my $objgot = cmdoutput @cmd;
6910 if ($objgot =~ m#^\w+ missing\b#) {
6911 fail f_ <<END, $dsc_hash
6912 .dsc contains Dgit field referring to object %s
6913 Your git tree does not have that object. Try `git fetch' from a
6914 plausible server (browse.dgit.d.o? salsa?), and try the import-dsc again.
6917 if ($oldhash && !is_fast_fwd $oldhash, $dsc_hash) {
6919 progress __ "Not fast forward, forced update.";
6921 fail f_ "Not fast forward to %s", $dsc_hash;
6924 import_dsc_result $dstbranch, $dsc_hash,
6925 "dgit import-dsc (Dgit): $info",
6926 f_ "updated git ref %s", $dstbranch;
6930 fail f_ <<END, $dstbranch, $specbranch, $specbranch
6931 Branch %s already exists
6932 Specify ..%s for a pseudo-merge, binding in existing history
6933 Specify +%s to overwrite, discarding existing history
6935 if $oldhash && !$force;
6937 my @dfi = dsc_files_info();
6938 foreach my $fi (@dfi) {
6939 my $f = $fi->{Filename};
6940 my $here = "$buildproductsdir/$f";
6943 fail f_ "lstat %s works but stat gives %s !", $here, $!;
6945 fail f_ "stat %s: %s", $here, $! unless $! == ENOENT;
6947 if ($dscfn =~ m#^(?:\./+)?\.\./+#) {
6949 } elsif ($dscfn =~ m#^/#) {
6953 "cannot import %s which seems to be inside working tree!",
6956 $there =~ s#/+[^/]+$## or fail f_
6957 "import %s requires .../%s, but it does not exist",
6960 my $test = $there =~ m{^/} ? $there : "../$there";
6961 stat $test or fail f_
6962 "import %s requires %s, but: %s", $dscfn, $test, $!;
6963 symlink $there, $here or fail f_
6964 "symlink %s to %s: %s", $there, $here, $!;
6965 progress f_ "made symlink %s -> %s", $here, $there;
6966 # print STDERR Dumper($fi);
6968 my @mergeinputs = generate_commits_from_dsc();
6969 die unless @mergeinputs == 1;
6971 my $newhash = $mergeinputs[0]{Commit};
6976 "Import, forced update - synthetic orphan git history.";
6977 } elsif ($force < 0) {
6978 progress __ "Import, merging.";
6979 my $tree = cmdoutput @git, qw(rev-parse), "$newhash:";
6980 my $version = getfield $dsc, 'Version';
6981 my $clogp = commit_getclogp $newhash;
6982 my $authline = clogp_authline $clogp;
6983 $newhash = make_commit_text <<ENDU
6991 .(f_ <<END, $package, $version, $dstbranch);
6992 Merge %s (%s) import into %s
6995 die; # caught earlier
6999 import_dsc_result $dstbranch, $newhash,
7000 "dgit import-dsc: $info",
7001 f_ "results are in git ref %s", $dstbranch;
7004 sub pre_archive_api_query () {
7005 not_necessarily_a_tree();
7007 sub cmd_archive_api_query {
7008 badusage __ "need only 1 subpath argument" unless @ARGV==1;
7009 my ($subpath) = @ARGV;
7010 local $isuite = 'DGIT-API-QUERY-CMD';
7011 my @cmd = archive_api_query_cmd($subpath);
7014 exec @cmd or fail f_ "exec curl: %s\n", $!;
7017 sub repos_server_url () {
7018 $package = '_dgit-repos-server';
7019 local $access_forpush = 1;
7020 local $isuite = 'DGIT-REPOS-SERVER';
7021 my $url = access_giturl();
7024 sub pre_clone_dgit_repos_server () {
7025 not_necessarily_a_tree();
7027 sub cmd_clone_dgit_repos_server {
7028 badusage __ "need destination argument" unless @ARGV==1;
7029 my ($destdir) = @ARGV;
7030 my $url = repos_server_url();
7031 my @cmd = (@git, qw(clone), $url, $destdir);
7033 exec @cmd or fail f_ "exec git clone: %s\n", $!;
7036 sub pre_print_dgit_repos_server_source_url () {
7037 not_necessarily_a_tree();
7039 sub cmd_print_dgit_repos_server_source_url {
7041 "no arguments allowed to dgit print-dgit-repos-server-source-url"
7043 my $url = repos_server_url();
7044 print $url, "\n" or confess $!;
7047 sub pre_print_dpkg_source_ignores {
7048 not_necessarily_a_tree();
7050 sub cmd_print_dpkg_source_ignores {
7052 "no arguments allowed to dgit print-dpkg-source-ignores"
7054 print "@dpkg_source_ignores\n" or confess $!;
7057 sub cmd_setup_mergechangelogs {
7058 badusage __ "no arguments allowed to dgit setup-mergechangelogs"
7060 local $isuite = 'DGIT-SETUP-TREE';
7061 setup_mergechangelogs(1);
7064 sub cmd_setup_useremail {
7065 badusage __ "no arguments allowed to dgit setup-useremail" if @ARGV;
7066 local $isuite = 'DGIT-SETUP-TREE';
7070 sub cmd_setup_gitattributes {
7071 badusage __ "no arguments allowed to dgit setup-useremail" if @ARGV;
7072 local $isuite = 'DGIT-SETUP-TREE';
7076 sub cmd_setup_new_tree {
7077 badusage __ "no arguments allowed to dgit setup-tree" if @ARGV;
7078 local $isuite = 'DGIT-SETUP-TREE';
7082 #---------- argument parsing and main program ----------
7085 print "dgit version $our_version\n" or confess $!;
7089 our (%valopts_long, %valopts_short);
7090 our (%funcopts_long);
7092 our (@modeopt_cfgs);
7094 sub defvalopt ($$$$) {
7095 my ($long,$short,$val_re,$how) = @_;
7096 my $oi = { Long => $long, Short => $short, Re => $val_re, How => $how };
7097 $valopts_long{$long} = $oi;
7098 $valopts_short{$short} = $oi;
7099 # $how subref should:
7100 # do whatever assignemnt or thing it likes with $_[0]
7101 # if the option should not be passed on to remote, @rvalopts=()
7102 # or $how can be a scalar ref, meaning simply assign the value
7105 defvalopt '--since-version', '-v', '[^_]+|_', \$changes_since_version;
7106 defvalopt '--distro', '-d', '.+', \$idistro;
7107 defvalopt '', '-k', '.+', \$keyid;
7108 defvalopt '--existing-package','', '.*', \$existing_package;
7109 defvalopt '--build-products-dir','','.*', \$buildproductsdir;
7110 defvalopt '--clean', '', $cleanmode_re, \$cleanmode;
7111 defvalopt '--package', '-p', $package_re, \$package;
7112 defvalopt '--quilt', '', $quilt_modes_re, \$quilt_mode;
7114 defvalopt '', '-C', '.+', sub {
7115 ($changesfile) = (@_);
7116 if ($changesfile =~ s#^(.*)/##) {
7117 $buildproductsdir = $1;
7121 defvalopt '--initiator-tempdir','','.*', sub {
7122 ($initiator_tempdir) = (@_);
7123 $initiator_tempdir =~ m#^/# or
7124 badusage __ "--initiator-tempdir must be used specify an".
7125 " absolute, not relative, directory."
7128 sub defoptmodes ($@) {
7129 my ($varref, $cfgkey, $default, %optmap) = @_;
7131 while (my ($opt,$val) = each %optmap) {
7132 $funcopts_long{$opt} = sub { $$varref = $val; };
7133 $permit{$val} = $val;
7135 push @modeopt_cfgs, {
7138 Default => $default,
7143 defoptmodes \$dodep14tag, qw( dep14tag want
7146 --always-dep14tag always );
7151 if (defined $ENV{'DGIT_SSH'}) {
7152 @ssh = string_to_ssh $ENV{'DGIT_SSH'};
7153 } elsif (defined $ENV{'GIT_SSH'}) {
7154 @ssh = ($ENV{'GIT_SSH'});
7162 if (!defined $val) {
7163 badusage f_ "%s needs a value", $what unless @ARGV;
7165 push @rvalopts, $val;
7167 badusage f_ "bad value \`%s' for %s", $val, $what unless
7168 $val =~ m/^$oi->{Re}$(?!\n)/s;
7169 my $how = $oi->{How};
7170 if (ref($how) eq 'SCALAR') {
7175 push @ropts, @rvalopts;
7179 last unless $ARGV[0] =~ m/^-/;
7183 if (m/^--dry-run$/) {
7186 } elsif (m/^--damp-run$/) {
7189 } elsif (m/^--no-sign$/) {
7192 } elsif (m/^--help$/) {
7194 } elsif (m/^--version$/) {
7196 } elsif (m/^--new$/) {
7199 } elsif (m/^--([-0-9a-z]+)=(.+)/s &&
7200 ($om = $opts_opt_map{$1}) &&
7204 } elsif (m/^--([-0-9a-z]+):(.*)/s &&
7205 !$opts_opt_cmdonly{$1} &&
7206 ($om = $opts_opt_map{$1})) {
7209 } elsif (m/^--([-0-9a-z]+)\!:(.*)/s &&
7210 !$opts_opt_cmdonly{$1} &&
7211 ($om = $opts_opt_map{$1})) {
7213 my $cmd = shift @$om;
7214 @$om = ($cmd, grep { $_ ne $2 } @$om);
7215 } elsif (m/^--(gbp|dpm)$/s) {
7216 push @ropts, "--quilt=$1";
7218 } elsif (m/^--(?:ignore|include)-dirty$/s) {
7221 } elsif (m/^--no-quilt-fixup$/s) {
7223 $quilt_mode = 'nocheck';
7224 } elsif (m/^--no-rm-on-error$/s) {
7227 } elsif (m/^--no-chase-dsc-distro$/s) {
7229 $chase_dsc_distro = 0;
7230 } elsif (m/^--overwrite$/s) {
7232 $overwrite_version = '';
7233 } elsif (m/^--overwrite=(.+)$/s) {
7235 $overwrite_version = $1;
7236 } elsif (m/^--delayed=(\d+)$/s) {
7239 } elsif (my ($k,$v) =
7240 m/^--save-(dgit-view)=(.+)$/s ||
7241 m/^--(dgit-view)-save=(.+)$/s
7244 $v =~ s#^(?!refs/)#refs/heads/#;
7245 $internal_object_save{$k} = $v;
7246 } elsif (m/^--(no-)?rm-old-changes$/s) {
7249 } elsif (m/^--deliberately-($deliberately_re)$/s) {
7251 push @deliberatelies, $&;
7252 } elsif (m/^--force-(.*)/ && defined $forceopts{$1}) {
7256 } elsif (m/^--force-/) {
7258 f_ "%s: warning: ignoring unknown force option %s\n",
7261 } elsif (m/^--dgit-tag-format=(old|new)$/s) {
7262 # undocumented, for testing
7264 $tagformat_want = [ $1, 'command line', 1 ];
7265 # 1 menas overrides distro configuration
7266 } elsif (m/^--config-lookup-explode=(.+)$/s) {
7267 # undocumented, for testing
7269 $gitcfgs{cmdline}{$1} = 'CONFIG-LOOKUP-EXPLODE';
7270 # ^ it's supposed to be an array ref
7271 } elsif (m/^(--[-0-9a-z]+)(=|$)/ && ($oi = $valopts_long{$1})) {
7272 $val = $2 ? $' : undef; #';
7273 $valopt->($oi->{Long});
7274 } elsif ($funcopts_long{$_}) {
7276 $funcopts_long{$_}();
7278 badusage f_ "unknown long option \`%s'", $_;
7285 } elsif (s/^-L/-/) {
7288 } elsif (s/^-h/-/) {
7290 } elsif (s/^-D/-/) {
7294 } elsif (s/^-N/-/) {
7299 push @changesopts, $_;
7301 } elsif (s/^-wn$//s) {
7303 $cleanmode = 'none';
7304 } elsif (s/^-wg(f?)(a?)$//s) {
7307 $cleanmode .= '-ff' if $1;
7308 $cleanmode .= ',always' if $2;
7309 } elsif (s/^-wd(d?)([na]?)$//s) {
7311 $cleanmode = 'dpkg-source';
7312 $cleanmode .= '-d' if $1;
7313 $cleanmode .= ',no-check' if $2 eq 'n';
7314 $cleanmode .= ',all-check' if $2 eq 'a';
7315 } elsif (s/^-wc$//s) {
7317 $cleanmode = 'check';
7318 } elsif (s/^-wci$//s) {
7320 $cleanmode = 'check,ignores';
7321 } elsif (s/^-c([^=]*)\=(.*)$//s) {
7322 push @git, '-c', $&;
7323 $gitcfgs{cmdline}{$1} = [ $2 ];
7324 } elsif (s/^-c([^=]+)$//s) {
7325 push @git, '-c', $&;
7326 $gitcfgs{cmdline}{$1} = [ 'true' ];
7327 } elsif (m/^-[a-zA-Z]/ && ($oi = $valopts_short{$&})) {
7329 $val = undef unless length $val;
7330 $valopt->($oi->{Short});
7333 badusage f_ "unknown short option \`%s'", $_;
7340 sub check_env_sanity () {
7341 my $blocked = new POSIX::SigSet;
7342 sigprocmask SIG_UNBLOCK, $blocked, $blocked or confess $!;
7345 foreach my $name (qw(PIPE CHLD)) {
7346 my $signame = "SIG$name";
7347 my $signum = eval "POSIX::$signame" // die;
7348 die f_ "%s is set to something other than SIG_DFL\n",
7350 if defined $SIG{$name} and $SIG{$name} ne 'DEFAULT';
7351 $blocked->ismember($signum) and
7352 die f_ "%s is blocked\n", $signame;
7358 On entry to dgit, %s
7359 This is a bug produced by something in your execution environment.
7365 sub parseopts_late_defaults () {
7366 $isuite //= cfg("dgit-distro.$idistro.default-suite", 'RETURN-UNDEF')
7367 if defined $idistro;
7368 $isuite //= cfg('dgit.default.default-suite');
7370 foreach my $k (keys %opts_opt_map) {
7371 my $om = $opts_opt_map{$k};
7373 my $v = access_cfg("cmd-$k", 'RETURN-UNDEF');
7375 badcfg f_ "cannot set command for %s", $k
7376 unless length $om->[0];
7380 foreach my $c (access_cfg_cfgs("opts-$k")) {
7382 map { $_ ? @$_ : () }
7383 map { $gitcfgs{$_}{$c} }
7384 reverse @gitcfgsources;
7385 printdebug "CL $c ", (join " ", map { shellquote } @vl),
7386 "\n" if $debuglevel >= 4;
7388 badcfg f_ "cannot configure options for %s", $k
7389 if $opts_opt_cmdonly{$k};
7390 my $insertpos = $opts_cfg_insertpos{$k};
7391 @$om = ( @$om[0..$insertpos-1],
7393 @$om[$insertpos..$#$om] );
7397 if (!defined $rmchanges) {
7398 local $access_forpush;
7399 $rmchanges = access_cfg_bool(0, 'rm-old-changes');
7402 if (!defined $quilt_mode) {
7403 local $access_forpush;
7404 $quilt_mode = cfg('dgit.force.quilt-mode', 'RETURN-UNDEF')
7405 // access_cfg('quilt-mode', 'RETURN-UNDEF')
7407 $quilt_mode =~ m/^($quilt_modes_re)$/
7408 or badcfg f_ "unknown quilt-mode \`%s'", $quilt_mode;
7412 foreach my $moc (@modeopt_cfgs) {
7413 local $access_forpush;
7414 my $vr = $moc->{Var};
7415 next if defined $$vr;
7416 $$vr = access_cfg($moc->{Key}, 'RETURN-UNDEF') // $moc->{Default};
7417 my $v = $moc->{Vals}{$$vr};
7418 badcfg f_ "unknown %s setting \`%s'", $moc->{Key}, $$vr
7423 fail __ "dgit: --include-dirty is not supported in split view quilt mode"
7424 if $split_brain && $includedirty;
7426 if (!defined $cleanmode) {
7427 local $access_forpush;
7428 $cleanmode = access_cfg('clean-mode-newer', 'RETURN-UNDEF');
7429 $cleanmode = undef if $cleanmode && $cleanmode !~ m/^$cleanmode_re$/;
7431 $cleanmode //= access_cfg('clean-mode', 'RETURN-UNDEF');
7432 $cleanmode //= 'dpkg-source';
7434 badcfg f_ "unknown clean-mode \`%s'", $cleanmode unless
7435 $cleanmode =~ m/$cleanmode_re/;
7438 $buildproductsdir //= access_cfg('build-products-dir', 'RETURN-UNDEF');
7439 $buildproductsdir //= '..';
7440 $bpd_glob = $buildproductsdir;
7441 $bpd_glob =~ s#[][\\{}*?~]#\\$&#g;
7444 setlocale(LC_MESSAGES, "");
7447 if ($ENV{$fakeeditorenv}) {
7449 quilt_fixup_editor();
7455 print STDERR __ "DRY RUN ONLY\n" if $dryrun_level > 1;
7456 print STDERR __ "DAMP RUN - WILL MAKE LOCAL (UNSIGNED) CHANGES\n"
7457 if $dryrun_level == 1;
7459 print STDERR __ $helpmsg or confess $!;
7462 $cmd = $subcommand = shift @ARGV;
7465 my $pre_fn = ${*::}{"pre_$cmd"};
7466 $pre_fn->() if $pre_fn;
7468 if ($invoked_in_git_tree) {
7469 changedir_git_toplevel();
7474 my $fn = ${*::}{"cmd_$cmd"};
7475 $fn or badusage f_ "unknown operation %s", $cmd;