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;
36 use File::Temp qw(tempdir);
39 use Dpkg::Compression;
40 use Dpkg::Compression::Process;
46 use List::MoreUtils qw(pairwise);
47 use Text::Glob qw(match_glob);
48 use Fcntl qw(:DEFAULT :flock);
53 our $our_version = 'UNRELEASED'; ###substituted###
54 our $absurdity = undef; ###substituted###
56 our @rpushprotovsn_support = qw(4 5); # 5 drops tag format specification
67 our $dryrun_level = 0;
69 our $buildproductsdir;
72 our $includedirty = 0;
76 our $existing_package = 'dpkg';
78 our $changes_since_version;
80 our $overwrite_version; # undef: not specified; '': check changelog
82 our $quilt_modes_re = 'linear|smash|auto|nofix|nocheck|gbp|dpm|unapplied';
84 our $splitview_modes_re = qr{auto|always|never};
86 our %internal_object_save;
87 our $we_are_responder;
88 our $we_are_initiator;
89 our $initiator_tempdir;
90 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 quiltify_trees_differ ($$;$$$);
168 sub setup_gitattrs(;$);
169 sub check_gitattrs($$);
176 our $supplementary_message = '';
177 our $made_split_brain = 0;
180 # Interactions between quilt mode and split brain
181 # (currently, split brain only implemented iff
182 # madformat_wantfixup && quiltmode_splitting)
184 # source format sane `3.0 (quilt)'
185 # madformat_wantfixup()
187 # quilt mode normal quiltmode
188 # (eg linear) _splitbrain
190 # ------------ ------------------------------------------------
192 # no split no q cache no q cache forbidden,
193 # brain PM on master q fixup on master prevented
194 # !do_split_brain() PM on master
196 # split brain no q cache q fixup cached, to dgit view
197 # PM in dgit view PM in dgit view
199 # PM = pseudomerge to make ff, due to overwrite (or split view)
200 # "no q cache" = do not record in cache on build, do not check cache
201 # `3.0 (quilt)' with --quilt=nocheck is treated as sane format
205 return unless forkcheck_mainprocess();
206 print STDERR "! $_\n" foreach $supplementary_message =~ m/^.+$/mg;
209 our $remotename = 'dgit';
210 our @ourdscfield = qw(Dgit Vcs-Dgit-Master);
214 if (!defined $absurdity) {
216 $absurdity =~ s{/[^/]+$}{/absurd} or die;
219 sub madformat ($) { $_[0] eq '3.0 (quilt)' }
221 sub lbranch () { return "$branchprefix/$csuite"; }
222 my $lbranch_re = '^refs/heads/'.$branchprefix.'/([^/.]+)$';
223 sub lref () { return "refs/heads/".lbranch(); }
224 sub lrref () { return "refs/remotes/$remotename/".server_branch($csuite); }
225 sub rrref () { return server_ref($csuite); }
228 my ($vsn, $sfx) = @_;
229 return &source_file_leafname($package, $vsn, $sfx);
231 sub is_orig_file_of_vsn ($$) {
232 my ($f, $upstreamvsn) = @_;
233 return is_orig_file_of_p_v($f, $package, $upstreamvsn);
238 return srcfn($vsn,".dsc");
241 sub changespat ($;$) {
242 my ($vsn, $arch) = @_;
243 return "${package}_".(stripepoch $vsn)."_".($arch//'*').".changes";
252 return unless forkcheck_mainprocess();
253 foreach my $f (@end) {
255 print STDERR "$us: cleanup: $@" if length $@;
260 print STDERR f_ "%s: invalid configuration: %s\n", $us, "@_";
264 sub forceable_fail ($$) {
265 my ($forceoptsl, $msg) = @_;
266 fail $msg unless grep { $forceopts{$_} } @$forceoptsl;
267 print STDERR +(__ "warning: overriding problem due to --force:\n"). $msg;
271 my ($forceoptsl) = @_;
272 my @got = grep { $forceopts{$_} } @$forceoptsl;
273 return 0 unless @got;
275 "warning: skipping checks or functionality due to --force-%s\n",
279 sub no_such_package () {
280 print STDERR f_ "%s: source package %s does not exist in suite %s\n",
281 $us, $package, $isuite;
285 sub deliberately ($) {
287 return !!grep { $_ eq "--deliberately-$enquiry" } @deliberatelies;
290 sub deliberately_not_fast_forward () {
291 foreach (qw(not-fast-forward fresh-repo)) {
292 return 1 if deliberately($_) || deliberately("TEST-dgit-only-$_");
296 sub quiltmode_splitting () {
297 $quilt_mode =~ m/gbp|dpm|unapplied/;
300 sub do_split_brain () { !!($do_split_brain // confess) }
302 sub opts_opt_multi_cmd {
305 push @cmd, split /\s+/, shift @_;
312 return opts_opt_multi_cmd [], @gbp_pq;
315 sub dgit_privdir () {
316 our $dgit_privdir_made //= ensure_a_playground 'dgit';
320 my $r = $buildproductsdir;
321 $r = "$maindir/$r" unless $r =~ m{^/};
325 sub get_tree_of_commit ($) {
326 my ($commitish) = @_;
327 my $cdata = cmdoutput @git, qw(cat-file commit), $commitish;
328 $cdata =~ m/\n\n/; $cdata = $`;
329 $cdata =~ m/^tree (\w+)$/m or confess "cdata $cdata ?";
333 sub branch_gdr_info ($$) {
334 my ($symref, $head) = @_;
335 my ($status, $msg, $current, $ffq_prev, $gdrlast) =
336 gdr_ffq_prev_branchinfo($symref);
337 return () unless $status eq 'branch';
338 $ffq_prev = git_get_ref $ffq_prev;
339 $gdrlast = git_get_ref $gdrlast;
340 $gdrlast &&= is_fast_fwd $gdrlast, $head;
341 return ($ffq_prev, $gdrlast);
344 sub branch_is_gdr_unstitched_ff ($$$) {
345 my ($symref, $head, $ancestor) = @_;
346 my ($ffq_prev, $gdrlast) = branch_gdr_info($symref, $head);
347 return 0 unless $ffq_prev;
348 return 0 unless !defined $ancestor or is_fast_fwd $ancestor, $ffq_prev;
352 sub branch_is_gdr ($) {
354 # This is quite like git-debrebase's keycommits.
355 # We have our own implementation because:
356 # - our algorighm can do fewer tests so is faster
357 # - it saves testing to see if gdr is installed
359 # NB we use this jsut for deciding whether to run gdr make-patches
360 # Before reusing this algorithm for somthing else, its
361 # suitability should be reconsidered.
364 local $Debian::Dgit::debugcmd_when_debuglevel = 3;
365 printdebug "branch_is_gdr $head...\n";
366 my $get_patches = sub {
367 my $t = git_cat_file "$_[0]:debian/patches", [qw(missing tree)];
370 my $tip_patches = $get_patches->($head);
373 my $cdata = git_cat_file $walk, 'commit';
374 my ($hdrs,$msg) = $cdata =~ m{\n\n} ? ($`,$') : ($cdata,'');
375 if ($msg =~ m{^\[git-debrebase\ (
376 anchor | changelog | make-patches |
377 merged-breakwater | pseudomerge
379 # no need to analyse this - it's sufficient
380 # (gdr classifications: Anchor, MergedBreakwaters)
381 # (made by gdr: Pseudomerge, Changelog)
382 printdebug "branch_is_gdr $walk gdr $1 YES\n";
385 my @parents = ($hdrs =~ m/^parent (\w+)$/gm);
387 my $walk_tree = get_tree_of_commit $walk;
388 foreach my $p (@parents) {
389 my $p_tree = get_tree_of_commit $p;
390 if ($p_tree eq $walk_tree) { # pseudomerge contriburor
391 # (gdr classification: Pseudomerge; not made by gdr)
392 printdebug "branch_is_gdr $walk unmarked pseudomerge\n"
398 # some other non-gdr merge
399 # (gdr classification: VanillaMerge, DgitImportUnpatched, ?)
400 printdebug "branch_is_gdr $walk ?-2-merge NO\n";
404 # (gdr classification: ?)
405 printdebug "branch_is_gdr $walk ?-octopus NO\n";
409 printdebug "branch_is_gdr $walk origin\n";
412 if ($get_patches->($walk) ne $tip_patches) {
413 # Our parent added, removed, or edited patches, and wasn't
414 # a gdr make-patches commit. gdr make-patches probably
415 # won't do that well, then.
416 # (gdr classification of parent: AddPatches or ?)
417 printdebug "branch_is_gdr $walk ?-patches NO\n";
420 if ($tip_patches eq '' and
421 !defined git_cat_file "$walk~:debian" and
422 !quiltify_trees_differ "$walk~", $walk
424 # (gdr classification of parent: BreakwaterStart
425 printdebug "branch_is_gdr $walk unmarked BreakwaterStart YES\n";
428 # (gdr classification: Upstream Packaging Mixed Changelog)
429 printdebug "branch_is_gdr $walk plain\n"
435 #---------- remote protocol support, common ----------
437 # remote push initiator/responder protocol:
438 # $ dgit remote-push-build-host <n-rargs> <rargs>... <push-args>...
439 # where <rargs> is <push-host-dir> <supported-proto-vsn>,... ...
440 # < dgit-remote-push-ready <actual-proto-vsn>
447 # > supplementary-message NBYTES
452 # > file parsed-changelog
453 # [indicates that output of dpkg-parsechangelog follows]
454 # > data-block NBYTES
455 # > [NBYTES bytes of data (no newline)]
456 # [maybe some more blocks]
465 # > param head DGIT-VIEW-HEAD
466 # > param csuite SUITE
467 # > param tagformat new # $protovsn == 4
468 # > param maint-view MAINT-VIEW-HEAD
470 # > param buildinfo-filename P_V_X.buildinfo # zero or more times
471 # > file buildinfo # for buildinfos to sign
473 # > previously REFNAME=OBJNAME # if --deliberately-not-fast-forward
474 # # goes into tag, for replay prevention
477 # [indicates that signed tag is wanted]
478 # < data-block NBYTES
479 # < [NBYTES bytes of data (no newline)]
480 # [maybe some more blocks]
484 # > want signed-dsc-changes
485 # < data-block NBYTES [transfer of signed dsc]
487 # < data-block NBYTES [transfer of signed changes]
489 # < data-block NBYTES [transfer of each signed buildinfo
490 # [etc] same number and order as "file buildinfo"]
498 sub i_child_report () {
499 # Sees if our child has died, and reap it if so. Returns a string
500 # describing how it died if it failed, or undef otherwise.
501 return undef unless $i_child_pid;
502 my $got = waitpid $i_child_pid, WNOHANG;
503 return undef if $got <= 0;
504 die unless $got == $i_child_pid;
505 $i_child_pid = undef;
506 return undef unless $?;
507 return f_ "build host child %s", waitstatusmsg();
512 fail f_ "connection lost: %s", $! if $fh->error;
513 fail f_ "protocol violation; %s not expected", $m;
516 sub badproto_badread ($$) {
518 fail f_ "connection lost: %s", $! if $!;
519 my $report = i_child_report();
520 fail $report if defined $report;
521 badproto $fh, f_ "eof (reading %s)", $wh;
524 sub protocol_expect (&$) {
525 my ($match, $fh) = @_;
528 defined && chomp or badproto_badread $fh, __ "protocol message";
536 badproto $fh, f_ "\`%s'", $_;
539 sub protocol_send_file ($$) {
540 my ($fh, $ourfn) = @_;
541 open PF, "<", $ourfn or die "$ourfn: $!";
544 my $got = read PF, $d, 65536;
545 die "$ourfn: $!" unless defined $got;
547 print $fh "data-block ".length($d)."\n" or confess "$!";
548 print $fh $d or confess "$!";
550 PF->error and die "$ourfn $!";
551 print $fh "data-end\n" or confess "$!";
555 sub protocol_read_bytes ($$) {
556 my ($fh, $nbytes) = @_;
557 $nbytes =~ m/^[1-9]\d{0,5}$|^0$/ or badproto \*RO, __ "bad byte count";
559 my $got = read $fh, $d, $nbytes;
560 $got==$nbytes or badproto_badread $fh, __ "data block";
564 sub protocol_receive_file ($$) {
565 my ($fh, $ourfn) = @_;
566 printdebug "() $ourfn\n";
567 open PF, ">", $ourfn or die "$ourfn: $!";
569 my ($y,$l) = protocol_expect {
570 m/^data-block (.*)$/ ? (1,$1) :
571 m/^data-end$/ ? (0,) :
575 my $d = protocol_read_bytes $fh, $l;
576 print PF $d or confess "$!";
578 close PF or confess "$!";
581 #---------- remote protocol support, responder ----------
583 sub responder_send_command ($) {
585 return unless $we_are_responder;
586 # called even without $we_are_responder
587 printdebug ">> $command\n";
588 print PO $command, "\n" or confess "$!";
591 sub responder_send_file ($$) {
592 my ($keyword, $ourfn) = @_;
593 return unless $we_are_responder;
594 printdebug "]] $keyword $ourfn\n";
595 responder_send_command "file $keyword";
596 protocol_send_file \*PO, $ourfn;
599 sub responder_receive_files ($@) {
600 my ($keyword, @ourfns) = @_;
601 die unless $we_are_responder;
602 printdebug "[[ $keyword @ourfns\n";
603 responder_send_command "want $keyword";
604 foreach my $fn (@ourfns) {
605 protocol_receive_file \*PI, $fn;
608 protocol_expect { m/^files-end$/ } \*PI;
611 #---------- remote protocol support, initiator ----------
613 sub initiator_expect (&) {
615 protocol_expect { &$match } \*RO;
618 #---------- end remote code ----------
621 if ($we_are_responder) {
623 responder_send_command "progress ".length($m) or confess "$!";
624 print PO $m or confess "$!";
634 $ua = LWP::UserAgent->new();
638 progress "downloading $what...";
639 my $r = $ua->get(@_) or confess "$!";
640 return undef if $r->code == 404;
641 $r->is_success or fail f_ "failed to fetch %s: %s",
642 $what, $r->status_line;
643 return $r->decoded_content(charset => 'none');
646 our ($dscdata,$dscurl,$dsc,$dsc_checked,$skew_warning_vsn);
648 sub act_local () { return $dryrun_level <= 1; }
649 sub act_scary () { return !$dryrun_level; }
652 if (!$dryrun_level) {
653 progress f_ "%s ok: %s", $us, "@_";
655 progress f_ "would be ok: %s (but dry run only)", "@_";
660 printcmd(\*STDERR,$debugprefix."#",@_);
663 sub runcmd_ordryrun {
671 sub runcmd_ordryrun_local {
679 our $helpmsg = i_ <<END;
681 dgit [dgit-opts] clone [dgit-opts] package [suite] [./dir|/dir]
682 dgit [dgit-opts] fetch|pull [dgit-opts] [suite]
683 dgit [dgit-opts] build [dpkg-buildpackage-opts]
684 dgit [dgit-opts] sbuild [sbuild-opts]
685 dgit [dgit-opts] pbuilder|cowbuilder [debbuildopts]
686 dgit [dgit-opts] push [dgit-opts] [suite]
687 dgit [dgit-opts] push-source [dgit-opts] [suite]
688 dgit [dgit-opts] rpush build-host:build-dir ...
689 important dgit options:
690 -k<keyid> sign tag and package with <keyid> instead of default
691 --dry-run -n do not change anything, but go through the motions
692 --damp-run -L like --dry-run but make local changes, without signing
693 --new -N allow introducing a new package
694 --debug -D increase debug level
695 -c<name>=<value> set git config option (used directly by dgit too)
698 our $later_warning_msg = i_ <<END;
699 Perhaps the upload is stuck in incoming. Using the version from git.
703 print STDERR f_ "%s: %s\n%s", $us, "@_", __ $helpmsg or confess "$!";
708 @ARGV or badusage __ "too few arguments";
709 return scalar shift @ARGV;
713 not_necessarily_a_tree();
716 print __ $helpmsg or confess "$!";
720 our $td = $ENV{DGIT_TEST_DUMMY_DIR} || "DGIT_TEST_DUMMY_DIR-unset";
722 our %defcfg = ('dgit.default.distro' => 'debian',
723 'dgit.default.default-suite' => 'unstable',
724 'dgit.default.old-dsc-distro' => 'debian',
725 'dgit-suite.*-security.distro' => 'debian-security',
726 'dgit.default.username' => '',
727 'dgit.default.archive-query-default-component' => 'main',
728 'dgit.default.ssh' => 'ssh',
729 'dgit.default.archive-query' => 'madison:',
730 'dgit.default.sshpsql-dbname' => 'service=projectb',
731 'dgit.default.aptget-components' => 'main',
732 'dgit.default.source-only-uploads' => 'ok',
733 'dgit.dsc-url-proto-ok.http' => 'true',
734 'dgit.dsc-url-proto-ok.https' => 'true',
735 'dgit.dsc-url-proto-ok.git' => 'true',
736 'dgit.vcs-git.suites', => 'sid', # ;-separated
737 'dgit.default.dsc-url-proto-ok' => 'false',
738 # old means "repo server accepts pushes with old dgit tags"
739 # new means "repo server accepts pushes with new dgit tags"
740 # maint means "repo server accepts split brain pushes"
741 # hist means "repo server may have old pushes without new tag"
742 # ("hist" is implied by "old")
743 'dgit-distro.debian.archive-query' => 'ftpmasterapi:',
744 'dgit-distro.debian.git-check' => 'url',
745 'dgit-distro.debian.git-check-suffix' => '/info/refs',
746 'dgit-distro.debian.new-private-pushers' => 't',
747 'dgit-distro.debian.source-only-uploads' => 'not-wholly-new',
748 'dgit-distro.debian/push.git-url' => '',
749 'dgit-distro.debian/push.git-host' => 'push.dgit.debian.org',
750 'dgit-distro.debian/push.git-user-force' => 'dgit',
751 'dgit-distro.debian/push.git-proto' => 'git+ssh://',
752 'dgit-distro.debian/push.git-path' => '/dgit/debian/repos',
753 'dgit-distro.debian/push.git-create' => 'true',
754 'dgit-distro.debian/push.git-check' => 'ssh-cmd',
755 'dgit-distro.debian.archive-query-url', 'https://api.ftp-master.debian.org/',
756 # 'dgit-distro.debian.archive-query-tls-key',
757 # '/etc/ssl/certs/%HOST%.pem:/etc/dgit/%HOST%.pem',
758 # ^ this does not work because curl is broken nowadays
759 # Fixing #790093 properly will involve providing providing the key
760 # in some pacagke and maybe updating these paths.
762 # 'dgit-distro.debian.archive-query-tls-curl-args',
763 # '--ca-path=/etc/ssl/ca-debian',
764 # ^ this is a workaround but works (only) on DSA-administered machines
765 'dgit-distro.debian.git-url' => 'https://git.dgit.debian.org',
766 'dgit-distro.debian.git-url-suffix' => '',
767 'dgit-distro.debian.upload-host' => 'ftp-master', # for dput
768 'dgit-distro.debian.mirror' => 'http://ftp.debian.org/debian/',
769 'dgit-distro.debian-security.archive-query' => 'aptget:',
770 'dgit-distro.debian-security.mirror' => 'http://security.debian.org/debian-security/',
771 'dgit-distro.debian-security.aptget-suite-map' => 's#-security$#/updates#',
772 'dgit-distro.debian-security.aptget-suite-rmap' => 's#$#-security#',
773 'dgit-distro.debian-security.nominal-distro' => 'debian',
774 'dgit-distro.debian.backports-quirk' => '(squeeze)-backports*',
775 'dgit-distro.debian-backports.mirror' => 'http://backports.debian.org/debian-backports/',
776 'dgit-distro.ubuntu.git-check' => 'false',
777 'dgit-distro.ubuntu.mirror' => 'http://archive.ubuntu.com/ubuntu',
778 'dgit-distro.test-dummy.ssh' => "$td/ssh",
779 'dgit-distro.test-dummy.username' => "alice",
780 'dgit-distro.test-dummy.git-check' => "ssh-cmd",
781 'dgit-distro.test-dummy.git-create' => "ssh-cmd",
782 'dgit-distro.test-dummy.git-url' => "$td/git",
783 'dgit-distro.test-dummy.git-host' => "git",
784 'dgit-distro.test-dummy.git-path' => "$td/git",
785 'dgit-distro.test-dummy.archive-query' => "dummycatapi:",
786 'dgit-distro.test-dummy.archive-query-url' => "file://$td/aq/",
787 'dgit-distro.test-dummy.mirror' => "file://$td/mirror/",
788 'dgit-distro.test-dummy.upload-host' => 'test-dummy',
792 our @gitcfgsources = qw(cmdline local global system);
793 our $invoked_in_git_tree = 1;
795 sub git_slurp_config () {
796 # This algoritm is a bit subtle, but this is needed so that for
797 # options which we want to be single-valued, we allow the
798 # different config sources to override properly. See #835858.
799 foreach my $src (@gitcfgsources) {
800 next if $src eq 'cmdline';
801 # we do this ourselves since git doesn't handle it
803 $gitcfgs{$src} = git_slurp_config_src $src;
807 sub git_get_config ($) {
809 foreach my $src (@gitcfgsources) {
810 my $l = $gitcfgs{$src}{$c};
811 confess "internal error ($l $c)" if $l && !ref $l;
812 printdebug"C $c ".(defined $l ?
813 join " ", map { messagequote "'$_'" } @$l :
818 f_ "multiple values for %s (in %s git config)", $c, $src
820 $l->[0] =~ m/\n/ and badcfg f_
821 "value for config option %s (in %s git config) contains newline(s)!",
830 return undef if $c =~ /RETURN-UNDEF/;
831 printdebug "C? $c\n" if $debuglevel >= 5;
832 my $v = git_get_config($c);
833 return $v if defined $v;
834 my $dv = $defcfg{$c};
836 printdebug "CD $c $dv\n" if $debuglevel >= 4;
841 "need value for one of: %s\n".
842 "%s: distro or suite appears not to be (properly) supported",
846 sub not_necessarily_a_tree () {
847 # needs to be called from pre_*
848 @gitcfgsources = grep { $_ ne 'local' } @gitcfgsources;
849 $invoked_in_git_tree = 0;
852 sub access_basedistro__noalias () {
853 if (defined $idistro) {
856 my $def = cfg("dgit-suite.$isuite.distro", 'RETURN-UNDEF');
857 return $def if defined $def;
858 foreach my $src (@gitcfgsources, 'internal') {
859 my $kl = $src eq 'internal' ? \%defcfg : $gitcfgs{$src};
861 foreach my $k (keys %$kl) {
862 next unless $k =~ m#^dgit-suite\.(.*)\.distro$#;
864 next unless match_glob $dpat, $isuite;
868 return cfg("dgit.default.distro");
872 sub access_basedistro () {
873 my $noalias = access_basedistro__noalias();
874 my $canon = cfg("dgit-distro.$noalias.alias-canon",'RETURN-UNDEF');
875 return $canon // $noalias;
878 sub access_nomdistro () {
879 my $base = access_basedistro();
880 my $r = cfg("dgit-distro.$base.nominal-distro",'RETURN-UNDEF') // $base;
881 $r =~ m/^$distro_re$/ or badcfg
882 f_ "bad syntax for (nominal) distro \`%s' (does not match %s)",
883 $r, "/^$distro_re$/";
887 sub access_quirk () {
888 # returns (quirk name, distro to use instead or undef, quirk-specific info)
889 my $basedistro = access_basedistro();
890 my $backports_quirk = cfg("dgit-distro.$basedistro.backports-quirk",
892 if (defined $backports_quirk) {
893 my $re = $backports_quirk;
894 $re =~ s/[^-0-9a-z_\%*()]/\\$&/ig;
896 $re =~ s/\%/([-0-9a-z_]+)/
897 or $re =~ m/[()]/ or badcfg __ "backports-quirk needs \% or ( )";
898 if ($isuite =~ m/^$re$/) {
899 return ('backports',"$basedistro-backports",$1);
902 return ('none',undef);
907 sub parse_cfg_bool ($$$) {
908 my ($what,$def,$v) = @_;
911 $v =~ m/^[ty1]/ ? 1 :
912 $v =~ m/^[fn0]/ ? 0 :
913 badcfg f_ "%s needs t (true, y, 1) or f (false, n, 0) not \`%s'",
917 sub access_forpush_config () {
918 my $d = access_basedistro();
922 parse_cfg_bool('new-private-pushers', 0,
923 cfg("dgit-distro.$d.new-private-pushers",
926 my $v = cfg("dgit-distro.$d.readonly", 'RETURN-UNDEF');
929 $v =~ m/^[ty1]/ ? 0 : # force readonly, forpush = 0
930 $v =~ m/^[fn0]/ ? 1 : # force nonreadonly, forpush = 1
931 $v =~ m/^[a]/ ? '' : # auto, forpush = ''
933 "readonly needs t (true, y, 1) or f (false, n, 0) or a (auto)";
936 sub access_forpush () {
937 $access_forpush //= access_forpush_config();
938 return $access_forpush;
941 sub default_from_access_cfg ($$$;$) {
942 my ($var, $keybase, $defval, $permit_re) = @_;
943 return if defined $$var;
945 $$var = access_cfg("$keybase-newer", 'RETURN-UNDEF');
946 $$var = undef if $$var && $$var !~ m/^$permit_re$/;
948 $$var //= access_cfg($keybase, 'RETURN-UNDEF');
951 badcfg f_ "unknown %s \`%s'", $keybase, $$var
952 if defined $permit_re and $$var !~ m/$permit_re/;
956 confess +(__ 'internal error').' '.Dumper($access_forpush)," ?" if
957 defined $access_forpush and !$access_forpush;
958 badcfg __ "pushing but distro is configured readonly"
959 if access_forpush_config() eq '0';
961 $supplementary_message = __ <<'END' unless $we_are_responder;
962 Push failed, before we got started.
963 You can retry the push, after fixing the problem, if you like.
965 parseopts_late_defaults();
969 parseopts_late_defaults();
972 sub determine_whether_split_brain () {
973 my ($format,) = get_source_format();
976 local $access_forpush;
977 default_from_access_cfg(\$splitview_mode, 'split-view', 'auto',
978 $splitview_modes_re);
979 $do_split_brain = 1 if $splitview_mode eq 'always';
982 printdebug "format $format, quilt mode $quilt_mode\n";
984 if (madformat_wantfixup($format) && quiltmode_splitting()) {
985 $splitview_mode ne 'never' or
986 fail f_ "dgit: quilt mode \`%s' (for format \`%s')".
987 " implies split view, but split-view set to \`%s'",
988 $quilt_mode, $format, $splitview_mode;
991 $do_split_brain //= 0;
994 sub supplementary_message ($) {
996 if (!$we_are_responder) {
997 $supplementary_message = $msg;
1000 responder_send_command "supplementary-message ".length($msg)
1002 print PO $msg or confess "$!";
1006 sub access_distros () {
1007 # Returns list of distros to try, in order
1010 # 0. `instead of' distro name(s) we have been pointed to
1011 # 1. the access_quirk distro, if any
1012 # 2a. the user's specified distro, or failing that } basedistro
1013 # 2b. the distro calculated from the suite }
1014 my @l = access_basedistro();
1016 my (undef,$quirkdistro) = access_quirk();
1017 unshift @l, $quirkdistro;
1018 unshift @l, $instead_distro;
1019 @l = grep { defined } @l;
1021 push @l, access_nomdistro();
1023 if (access_forpush()) {
1024 @l = map { ("$_/push", $_) } @l;
1029 sub access_cfg_cfgs (@) {
1032 # The nesting of these loops determines the search order. We put
1033 # the key loop on the outside so that we search all the distros
1034 # for each key, before going on to the next key. That means that
1035 # if access_cfg is called with a more specific, and then a less
1036 # specific, key, an earlier distro can override the less specific
1037 # without necessarily overriding any more specific keys. (If the
1038 # distro wants to override the more specific keys it can simply do
1039 # so; whereas if we did the loop the other way around, it would be
1040 # impossible to for an earlier distro to override a less specific
1041 # key but not the more specific ones without restating the unknown
1042 # values of the more specific keys.
1045 # We have to deal with RETURN-UNDEF specially, so that we don't
1046 # terminate the search prematurely.
1048 if (m/RETURN-UNDEF/) { push @rundef, $_; last; }
1051 foreach my $d (access_distros()) {
1052 push @cfgs, map { "dgit-distro.$d.$_" } @realkeys;
1054 push @cfgs, map { "dgit.default.$_" } @realkeys;
1055 push @cfgs, @rundef;
1059 sub access_cfg (@) {
1061 my (@cfgs) = access_cfg_cfgs(@keys);
1062 my $value = cfg(@cfgs);
1066 sub access_cfg_bool ($$) {
1067 my ($def, @keys) = @_;
1068 parse_cfg_bool($keys[0], $def, access_cfg(@keys, 'RETURN-UNDEF'));
1071 sub string_to_ssh ($) {
1073 if ($spec =~ m/\s/) {
1074 return qw(sh -ec), 'exec '.$spec.' "$@"', 'x';
1080 sub access_cfg_ssh () {
1081 my $gitssh = access_cfg('ssh', 'RETURN-UNDEF');
1082 if (!defined $gitssh) {
1085 return string_to_ssh $gitssh;
1089 sub access_runeinfo ($) {
1091 return ": dgit ".access_basedistro()." $info ;";
1094 sub access_someuserhost ($) {
1096 my $user = access_cfg("$some-user-force", 'RETURN-UNDEF');
1097 defined($user) && length($user) or
1098 $user = access_cfg("$some-user",'username');
1099 my $host = access_cfg("$some-host");
1100 return length($user) ? "$user\@$host" : $host;
1103 sub access_gituserhost () {
1104 return access_someuserhost('git');
1107 sub access_giturl (;$) {
1108 my ($optional) = @_;
1109 my $url = access_cfg('git-url','RETURN-UNDEF');
1112 my $proto = access_cfg('git-proto', 'RETURN-UNDEF');
1113 return undef unless defined $proto;
1116 access_gituserhost().
1117 access_cfg('git-path');
1119 $suffix = access_cfg('git-url-suffix','RETURN-UNDEF');
1122 return "$url/$package$suffix";
1125 sub commit_getclogp ($) {
1126 # Returns the parsed changelog hashref for a particular commit
1128 our %commit_getclogp_memo;
1129 my $memo = $commit_getclogp_memo{$objid};
1130 return $memo if $memo;
1132 my $mclog = dgit_privdir()."clog";
1133 runcmd shell_cmd "exec >$mclog", @git, qw(cat-file blob),
1134 "$objid:debian/changelog";
1135 $commit_getclogp_memo{$objid} = parsechangelog("-l$mclog");
1138 sub parse_dscdata () {
1139 my $dscfh = new IO::File \$dscdata, '<' or confess "$!";
1140 printdebug Dumper($dscdata) if $debuglevel>1;
1141 $dsc = parsecontrolfh($dscfh,$dscurl,1);
1142 printdebug Dumper($dsc) if $debuglevel>1;
1147 sub archive_query ($;@) {
1148 my ($method) = shift @_;
1149 fail __ "this operation does not support multiple comma-separated suites"
1151 my $query = access_cfg('archive-query','RETURN-UNDEF');
1152 $query =~ s/^(\w+):// or badcfg "invalid archive-query method \`$query'";
1155 { no strict qw(refs); &{"${method}_${proto}"}($proto,$data,@_); }
1158 sub archive_query_prepend_mirror {
1159 my $m = access_cfg('mirror');
1160 return map { [ $_->[0], $m.$_->[1], @$_[2..$#$_] ] } @_;
1163 sub pool_dsc_subpath ($$) {
1164 my ($vsn,$component) = @_; # $package is implict arg
1165 my $prefix = substr($package, 0, $package =~ m/^l/ ? 4 : 1);
1166 return "/pool/$component/$prefix/$package/".dscfn($vsn);
1169 sub cfg_apply_map ($$$) {
1170 my ($varref, $what, $mapspec) = @_;
1171 return unless $mapspec;
1173 printdebug "config $what EVAL{ $mapspec; }\n";
1175 eval "package Dgit::Config; $mapspec;";
1180 #---------- `ftpmasterapi' archive query method (nascent) ----------
1182 sub archive_api_query_cmd ($) {
1184 my @cmd = (@curl, qw(-sS));
1185 my $url = access_cfg('archive-query-url');
1186 if ($url =~ m#^https://([-.0-9a-z]+)/#) {
1188 my $keys = access_cfg('archive-query-tls-key','RETURN-UNDEF') //'';
1189 foreach my $key (split /\:/, $keys) {
1190 $key =~ s/\%HOST\%/$host/g;
1192 fail "for $url: stat $key: $!" unless $!==ENOENT;
1195 fail f_ "config requested specific TLS key but do not know".
1196 " how to get curl to use exactly that EE key (%s)",
1198 # push @cmd, "--cacert", $key, "--capath", "/dev/enoent";
1199 # # Sadly the above line does not work because of changes
1200 # # to gnutls. The real fix for #790093 may involve
1201 # # new curl options.
1204 # Fixing #790093 properly will involve providing a value
1205 # for this on clients.
1206 my $kargs = access_cfg('archive-query-tls-curl-ca-args','RETURN-UNDEF');
1207 push @cmd, split / /, $kargs if defined $kargs;
1209 push @cmd, $url.$subpath;
1213 sub api_query ($$;$) {
1215 my ($data, $subpath, $ok404) = @_;
1216 badcfg __ "ftpmasterapi archive query method takes no data part"
1218 my @cmd = archive_api_query_cmd($subpath);
1219 my $url = $cmd[$#cmd];
1220 push @cmd, qw(-w %{http_code});
1221 my $json = cmdoutput @cmd;
1222 unless ($json =~ s/\d+\d+\d$//) {
1223 failedcmd_report_cmd undef, @cmd;
1224 fail __ "curl failed to print 3-digit HTTP code";
1227 return undef if $code eq '404' && $ok404;
1228 fail f_ "fetch of %s gave HTTP code %s", $url, $code
1229 unless $url =~ m#^file://# or $code =~ m/^2/;
1230 return decode_json($json);
1233 sub canonicalise_suite_ftpmasterapi {
1234 my ($proto,$data) = @_;
1235 my $suites = api_query($data, 'suites');
1237 foreach my $entry (@$suites) {
1239 my $v = $entry->{$_};
1240 defined $v && $v eq $isuite;
1241 } qw(codename name);
1242 push @matched, $entry;
1244 fail f_ "unknown suite %s, maybe -d would help", $isuite
1248 @matched==1 or die f_ "multiple matches for suite %s\n", $isuite;
1249 $cn = "$matched[0]{codename}";
1250 defined $cn or die f_ "suite %s info has no codename\n", $isuite;
1251 $cn =~ m/^$suite_re$/
1252 or die f_ "suite %s maps to bad codename\n", $isuite;
1254 die +(__ "bad ftpmaster api response: ")."$@\n".Dumper(\@matched)
1259 sub archive_query_ftpmasterapi {
1260 my ($proto,$data) = @_;
1261 my $info = api_query($data, "dsc_in_suite/$isuite/$package");
1263 my $digester = Digest::SHA->new(256);
1264 foreach my $entry (@$info) {
1266 my $vsn = "$entry->{version}";
1267 my ($ok,$msg) = version_check $vsn;
1268 die f_ "bad version: %s\n", $msg unless $ok;
1269 my $component = "$entry->{component}";
1270 $component =~ m/^$component_re$/ or die __ "bad component";
1271 my $filename = "$entry->{filename}";
1272 $filename && $filename !~ m#[^-+:._~0-9a-zA-Z/]|^[/.]|/[/.]#
1273 or die __ "bad filename";
1274 my $sha256sum = "$entry->{sha256sum}";
1275 $sha256sum =~ m/^[0-9a-f]+$/ or die __ "bad sha256sum";
1276 push @rows, [ $vsn, "/pool/$component/$filename",
1277 $digester, $sha256sum ];
1279 die +(__ "bad ftpmaster api response: ")."$@\n".Dumper($entry)
1282 @rows = sort { -version_compare($a->[0],$b->[0]) } @rows;
1283 return archive_query_prepend_mirror @rows;
1286 sub file_in_archive_ftpmasterapi {
1287 my ($proto,$data,$filename) = @_;
1288 my $pat = $filename;
1291 $pat =~ s#[^-+_.0-9a-z/]# sprintf '%%%02x', ord $& #ge;
1292 my $info = api_query($data, "file_in_archive/$pat", 1);
1295 sub package_not_wholly_new_ftpmasterapi {
1296 my ($proto,$data,$pkg) = @_;
1297 my $info = api_query($data,"madison?package=${pkg}&f=json");
1301 #---------- `aptget' archive query method ----------
1304 our $aptget_releasefile;
1305 our $aptget_configpath;
1307 sub aptget_aptget () { return @aptget, qw(-c), $aptget_configpath; }
1308 sub aptget_aptcache () { return @aptcache, qw(-c), $aptget_configpath; }
1310 sub aptget_cache_clean {
1311 runcmd_ordryrun_local qw(sh -ec),
1312 'cd "$1"; find -atime +30 -type f -print0 | xargs -0r rm --',
1316 sub aptget_lock_acquire () {
1317 my $lockfile = "$aptget_base/lock";
1318 open APTGET_LOCK, '>', $lockfile or confess "open $lockfile: $!";
1319 flock APTGET_LOCK, LOCK_EX or confess "lock $lockfile: $!";
1322 sub aptget_prep ($) {
1324 return if defined $aptget_base;
1326 badcfg __ "aptget archive query method takes no data part"
1329 my $cache = $ENV{XDG_CACHE_DIR} // "$ENV{HOME}/.cache";
1332 ensuredir "$cache/dgit";
1334 access_cfg('aptget-cachekey','RETURN-UNDEF')
1335 // access_nomdistro();
1337 $aptget_base = "$cache/dgit/aptget";
1338 ensuredir $aptget_base;
1340 my $quoted_base = $aptget_base;
1341 confess "$quoted_base contains bad chars, cannot continue"
1342 if $quoted_base =~ m/["\\]/; # apt.conf(5) says no escaping :-/
1344 ensuredir $aptget_base;
1346 aptget_lock_acquire();
1348 aptget_cache_clean();
1350 $aptget_configpath = "$aptget_base/apt.conf#$cachekey";
1351 my $sourceslist = "source.list#$cachekey";
1353 my $aptsuites = $isuite;
1354 cfg_apply_map(\$aptsuites, 'suite map',
1355 access_cfg('aptget-suite-map', 'RETURN-UNDEF'));
1357 open SRCS, ">", "$aptget_base/$sourceslist" or confess "$!";
1358 printf SRCS "deb-src %s %s %s\n",
1359 access_cfg('mirror'),
1361 access_cfg('aptget-components')
1364 ensuredir "$aptget_base/cache";
1365 ensuredir "$aptget_base/lists";
1367 open CONF, ">", $aptget_configpath or confess "$!";
1369 Debug::NoLocking "true";
1370 APT::Get::List-Cleanup "false";
1371 #clear APT::Update::Post-Invoke-Success;
1372 Dir::Etc::SourceList "$quoted_base/$sourceslist";
1373 Dir::State::Lists "$quoted_base/lists";
1374 Dir::Etc::preferences "$quoted_base/preferences";
1375 Dir::Cache::srcpkgcache "$quoted_base/cache/srcs#$cachekey";
1376 Dir::Cache::pkgcache "$quoted_base/cache/pkgs#$cachekey";
1379 foreach my $key (qw(
1382 Dir::Cache::Archives
1383 Dir::Etc::SourceParts
1384 Dir::Etc::preferencesparts
1386 ensuredir "$aptget_base/$key";
1387 print CONF "$key \"$quoted_base/$key\";\n" or confess "$!";
1390 my $oldatime = (time // confess "$!") - 1;
1391 foreach my $oldlist (<$aptget_base/lists/*Release>) {
1392 next unless stat_exists $oldlist;
1393 my ($mtime) = (stat _)[9];
1394 utime $oldatime, $mtime, $oldlist or die "$oldlist $!";
1397 runcmd_ordryrun_local aptget_aptget(), qw(update);
1400 foreach my $oldlist (<$aptget_base/lists/*Release>) {
1401 next unless stat_exists $oldlist;
1402 my ($atime) = (stat _)[8];
1403 next if $atime == $oldatime;
1404 push @releasefiles, $oldlist;
1406 my @inreleasefiles = grep { m#/InRelease$# } @releasefiles;
1407 @releasefiles = @inreleasefiles if @inreleasefiles;
1408 if (!@releasefiles) {
1409 fail f_ <<END, $isuite, $cache;
1410 apt seemed to not to update dgit's cached Release files for %s.
1412 is on a filesystem mounted `noatime'; if so, please use `relatime'.)
1415 confess "apt updated too many Release files (@releasefiles), erk"
1416 unless @releasefiles == 1;
1418 ($aptget_releasefile) = @releasefiles;
1421 sub canonicalise_suite_aptget {
1422 my ($proto,$data) = @_;
1425 my $release = parsecontrol $aptget_releasefile, "Release file", 1;
1427 foreach my $name (qw(Codename Suite)) {
1428 my $val = $release->{$name};
1430 printdebug "release file $name: $val\n";
1431 $val =~ m/^$suite_re$/o or fail f_
1432 "Release file (%s) specifies intolerable %s",
1433 $aptget_releasefile, $name;
1434 cfg_apply_map(\$val, 'suite rmap',
1435 access_cfg('aptget-suite-rmap', 'RETURN-UNDEF'));
1442 sub archive_query_aptget {
1443 my ($proto,$data) = @_;
1446 ensuredir "$aptget_base/source";
1447 foreach my $old (<$aptget_base/source/*.dsc>) {
1448 unlink $old or die "$old: $!";
1451 my $showsrc = cmdoutput aptget_aptcache(), qw(showsrc), $package;
1452 return () unless $showsrc =~ m/^package:\s*\Q$package\E\s*$/mi;
1453 # avoids apt-get source failing with ambiguous error code
1455 runcmd_ordryrun_local
1456 shell_cmd 'cd "$1"/source; shift', $aptget_base,
1457 aptget_aptget(), qw(--download-only --only-source source), $package;
1459 my @dscs = <$aptget_base/source/*.dsc>;
1460 fail __ "apt-get source did not produce a .dsc" unless @dscs;
1461 fail f_ "apt-get source produced several .dscs (%s)", "@dscs"
1464 my $pre_dsc = parsecontrol $dscs[0], $dscs[0], 1;
1467 my $uri = "file://". uri_escape $dscs[0];
1468 $uri =~ s{\%2f}{/}gi;
1469 return [ (getfield $pre_dsc, 'Version'), $uri ];
1472 sub file_in_archive_aptget () { return undef; }
1473 sub package_not_wholly_new_aptget () { return undef; }
1475 #---------- `dummyapicat' archive query method ----------
1476 # (untranslated, because this is for testing purposes etc.)
1478 sub archive_query_dummycatapi { archive_query_ftpmasterapi @_; }
1479 sub canonicalise_suite_dummycatapi { canonicalise_suite_ftpmasterapi @_; }
1481 sub dummycatapi_run_in_mirror ($@) {
1482 # runs $fn with FIA open onto rune
1483 my ($rune, $argl, $fn) = @_;
1485 my $mirror = access_cfg('mirror');
1486 $mirror =~ s#^file://#/# or die "$mirror ?";
1487 my @cmd = (qw(sh -ec), 'cd "$1"; shift'."\n".$rune,
1488 qw(x), $mirror, @$argl);
1489 debugcmd "-|", @cmd;
1490 open FIA, "-|", @cmd or confess "$!";
1492 close FIA or ($!==0 && $?==141) or die failedcmd @cmd;
1496 sub file_in_archive_dummycatapi ($$$) {
1497 my ($proto,$data,$filename) = @_;
1499 dummycatapi_run_in_mirror '
1500 find -name "$1" -print0 |
1502 ', [$filename], sub {
1505 printdebug "| $_\n";
1506 m/^(\w+) (\S+)$/ or die "$_ ?";
1507 push @out, { sha256sum => $1, filename => $2 };
1513 sub package_not_wholly_new_dummycatapi {
1514 my ($proto,$data,$pkg) = @_;
1515 dummycatapi_run_in_mirror "
1516 find -name ${pkg}_*.dsc
1523 #---------- `madison' archive query method ----------
1525 sub archive_query_madison {
1526 return archive_query_prepend_mirror
1527 map { [ @$_[0..1] ] } madison_get_parse(@_);
1530 sub madison_get_parse {
1531 my ($proto,$data) = @_;
1532 die unless $proto eq 'madison';
1533 if (!length $data) {
1534 $data= access_cfg('madison-distro','RETURN-UNDEF');
1535 $data //= access_basedistro();
1537 $rmad{$proto,$data,$package} ||= cmdoutput
1538 qw(rmadison -asource),"-s$isuite","-u$data",$package;
1539 my $rmad = $rmad{$proto,$data,$package};
1542 foreach my $l (split /\n/, $rmad) {
1543 $l =~ m{^ \s*( [^ \t|]+ )\s* \|
1544 \s*( [^ \t|]+ )\s* \|
1545 \s*( [^ \t|/]+ )(?:/([^ \t|/]+))? \s* \|
1546 \s*( [^ \t|]+ )\s* }x or die "$rmad ?";
1547 $1 eq $package or die "$rmad $package ?";
1554 $component = access_cfg('archive-query-default-component');
1556 $5 eq 'source' or die "$rmad ?";
1557 push @out, [$vsn,pool_dsc_subpath($vsn,$component),$newsuite];
1559 return sort { -version_compare($a->[0],$b->[0]); } @out;
1562 sub canonicalise_suite_madison {
1563 # madison canonicalises for us
1564 my @r = madison_get_parse(@_);
1566 "unable to canonicalise suite using package %s".
1567 " which does not appear to exist in suite %s;".
1568 " --existing-package may help",
1573 sub file_in_archive_madison { return undef; }
1574 sub package_not_wholly_new_madison { return undef; }
1576 #---------- `sshpsql' archive query method ----------
1577 # (untranslated, because this is obsolete)
1580 my ($data,$runeinfo,$sql) = @_;
1581 if (!length $data) {
1582 $data= access_someuserhost('sshpsql').':'.
1583 access_cfg('sshpsql-dbname');
1585 $data =~ m/:/ or badcfg "invalid sshpsql method string \`$data'";
1586 my ($userhost,$dbname) = ($`,$'); #';
1588 my @cmd = (access_cfg_ssh, $userhost,
1589 access_runeinfo("ssh-psql $runeinfo").
1590 " export LC_MESSAGES=C; export LC_CTYPE=C;".
1591 " ".shellquote qw(psql -A), $dbname, qw(-c), $sql);
1593 open P, "-|", @cmd or confess "$!";
1596 printdebug(">|$_|\n");
1599 $!=0; $?=0; close P or failedcmd @cmd;
1601 my $nrows = pop @rows;
1602 $nrows =~ s/^\((\d+) rows?\)$/$1/ or die "$nrows ?";
1603 @rows == $nrows+1 or die "$nrows ".(scalar @rows)." ?";
1604 @rows = map { [ split /\|/, $_ ] } @rows;
1605 my $ncols = scalar @{ shift @rows };
1606 die if grep { scalar @$_ != $ncols } @rows;
1610 sub sql_injection_check {
1611 foreach (@_) { die "$_ $& ?" if m{[^-+=:_.,/0-9a-zA-Z]}; }
1614 sub archive_query_sshpsql ($$) {
1615 my ($proto,$data) = @_;
1616 sql_injection_check $isuite, $package;
1617 my @rows = sshpsql($data, "archive-query $isuite $package", <<END);
1618 SELECT source.version, component.name, files.filename, files.sha256sum
1620 JOIN src_associations ON source.id = src_associations.source
1621 JOIN suite ON suite.id = src_associations.suite
1622 JOIN dsc_files ON dsc_files.source = source.id
1623 JOIN files_archive_map ON files_archive_map.file_id = dsc_files.file
1624 JOIN component ON component.id = files_archive_map.component_id
1625 JOIN files ON files.id = dsc_files.file
1626 WHERE ( suite.suite_name='$isuite' OR suite.codename='$isuite' )
1627 AND source.source='$package'
1628 AND files.filename LIKE '%.dsc';
1630 @rows = sort { -version_compare($a->[0],$b->[0]) } @rows;
1631 my $digester = Digest::SHA->new(256);
1633 my ($vsn,$component,$filename,$sha256sum) = @$_;
1634 [ $vsn, "/pool/$component/$filename",$digester,$sha256sum ];
1636 return archive_query_prepend_mirror @rows;
1639 sub canonicalise_suite_sshpsql ($$) {
1640 my ($proto,$data) = @_;
1641 sql_injection_check $isuite;
1642 my @rows = sshpsql($data, "canonicalise-suite $isuite", <<END);
1643 SELECT suite.codename
1644 FROM suite where suite_name='$isuite' or codename='$isuite';
1646 @rows = map { $_->[0] } @rows;
1647 fail "unknown suite $isuite" unless @rows;
1648 die "ambiguous $isuite: @rows ?" if @rows>1;
1652 sub file_in_archive_sshpsql ($$$) { return undef; }
1653 sub package_not_wholly_new_sshpsql ($$$) { return undef; }
1655 #---------- `dummycat' archive query method ----------
1656 # (untranslated, because this is for testing purposes etc.)
1658 sub canonicalise_suite_dummycat ($$) {
1659 my ($proto,$data) = @_;
1660 my $dpath = "$data/suite.$isuite";
1661 if (!open C, "<", $dpath) {
1662 $!==ENOENT or die "$dpath: $!";
1663 printdebug "dummycat canonicalise_suite $isuite $dpath ENOENT\n";
1667 chomp or die "$dpath: $!";
1669 printdebug "dummycat canonicalise_suite $isuite $dpath = $_\n";
1673 sub archive_query_dummycat ($$) {
1674 my ($proto,$data) = @_;
1675 canonicalise_suite();
1676 my $dpath = "$data/package.$csuite.$package";
1677 if (!open C, "<", $dpath) {
1678 $!==ENOENT or die "$dpath: $!";
1679 printdebug "dummycat query $csuite $package $dpath ENOENT\n";
1687 printdebug "dummycat query $csuite $package $dpath | $_\n";
1688 my @row = split /\s+/, $_;
1689 @row==2 or die "$dpath: $_ ?";
1692 C->error and die "$dpath: $!";
1694 return archive_query_prepend_mirror
1695 sort { -version_compare($a->[0],$b->[0]); } @rows;
1698 sub file_in_archive_dummycat () { return undef; }
1699 sub package_not_wholly_new_dummycat () { return undef; }
1701 #---------- archive query entrypoints and rest of program ----------
1703 sub canonicalise_suite () {
1704 return if defined $csuite;
1705 fail f_ "cannot operate on %s suite", $isuite if $isuite eq 'UNRELEASED';
1706 $csuite = archive_query('canonicalise_suite');
1707 if ($isuite ne $csuite) {
1708 progress f_ "canonical suite name for %s is %s", $isuite, $csuite;
1710 progress f_ "canonical suite name is %s", $csuite;
1714 sub get_archive_dsc () {
1715 canonicalise_suite();
1716 my @vsns = archive_query('archive_query');
1717 foreach my $vinfo (@vsns) {
1718 my ($vsn,$vsn_dscurl,$digester,$digest) = @$vinfo;
1719 $dscurl = $vsn_dscurl;
1720 $dscdata = url_get($dscurl);
1722 $skew_warning_vsn = $vsn if !defined $skew_warning_vsn;
1727 $digester->add($dscdata);
1728 my $got = $digester->hexdigest();
1730 fail f_ "%s has hash %s but archive told us to expect %s",
1731 $dscurl, $got, $digest;
1734 my $fmt = getfield $dsc, 'Format';
1735 $format_ok{$fmt} or forceable_fail [qw(unsupported-source-format)],
1736 f_ "unsupported source format %s, sorry", $fmt;
1738 $dsc_checked = !!$digester;
1739 printdebug "get_archive_dsc: Version ".(getfield $dsc, 'Version')."\n";
1743 printdebug "get_archive_dsc: nothing in archive, returning undef\n";
1746 sub check_for_git ();
1747 sub check_for_git () {
1749 my $how = access_cfg('git-check');
1750 if ($how eq 'ssh-cmd') {
1752 (access_cfg_ssh, access_gituserhost(),
1753 access_runeinfo("git-check $package").
1754 " set -e; cd ".access_cfg('git-path').";".
1755 " if test -d $package.git; then echo 1; else echo 0; fi");
1756 my $r= cmdoutput @cmd;
1757 if (defined $r and $r =~ m/^divert (\w+)$/) {
1759 my ($usedistro,) = access_distros();
1760 # NB that if we are pushing, $usedistro will be $distro/push
1761 $instead_distro= cfg("dgit-distro.$usedistro.diverts.$divert");
1762 $instead_distro =~ s{^/}{ access_basedistro()."/" }e;
1763 progress f_ "diverting to %s (using config for %s)",
1764 $divert, $instead_distro;
1765 return check_for_git();
1767 failedcmd @cmd unless defined $r and $r =~ m/^[01]$/;
1769 } elsif ($how eq 'url') {
1770 my $prefix = access_cfg('git-check-url','git-url');
1771 my $suffix = access_cfg('git-check-suffix','git-suffix',
1772 'RETURN-UNDEF') // '.git';
1773 my $url = "$prefix/$package$suffix";
1774 my @cmd = (@curl, qw(-sS -I), $url);
1775 my $result = cmdoutput @cmd;
1776 $result =~ s/^\S+ 200 .*\n\r?\n//;
1777 # curl -sS -I with https_proxy prints
1778 # HTTP/1.0 200 Connection established
1779 $result =~ m/^\S+ (404|200) /s or
1780 fail +(__ "unexpected results from git check query - ").
1781 Dumper($prefix, $result);
1783 if ($code eq '404') {
1785 } elsif ($code eq '200') {
1790 } elsif ($how eq 'true') {
1792 } elsif ($how eq 'false') {
1795 badcfg f_ "unknown git-check \`%s'", $how;
1799 sub create_remote_git_repo () {
1800 my $how = access_cfg('git-create');
1801 if ($how eq 'ssh-cmd') {
1803 (access_cfg_ssh, access_gituserhost(),
1804 access_runeinfo("git-create $package").
1805 "set -e; cd ".access_cfg('git-path').";".
1806 " cp -a _template $package.git");
1807 } elsif ($how eq 'true') {
1810 badcfg f_ "unknown git-create \`%s'", $how;
1814 our ($dsc_hash,$lastpush_mergeinput);
1815 our ($dsc_distro, $dsc_hint_tag, $dsc_hint_url);
1819 dgit_privdir(); # ensures that $dgit_privdir_made is based on $maindir
1820 $playground = fresh_playground 'dgit/unpack';
1823 sub mktree_in_ud_here () {
1824 playtree_setup $gitcfgs{local};
1827 sub git_write_tree () {
1828 my $tree = cmdoutput @git, qw(write-tree);
1829 $tree =~ m/^\w+$/ or die "$tree ?";
1833 sub git_add_write_tree () {
1834 runcmd @git, qw(add -Af .);
1835 return git_write_tree();
1838 sub remove_stray_gits ($) {
1840 my @gitscmd = qw(find -name .git -prune -print0);
1841 debugcmd "|",@gitscmd;
1842 open GITS, "-|", @gitscmd or confess "$!";
1847 print STDERR f_ "%s: warning: removing from %s: %s\n",
1848 $us, $what, (messagequote $_);
1852 $!=0; $?=0; close GITS or failedcmd @gitscmd;
1855 sub mktree_in_ud_from_only_subdir ($;$) {
1856 my ($what,$raw) = @_;
1857 # changes into the subdir
1860 confess "expected one subdir but found @dirs ?" unless @dirs==1;
1861 $dirs[0] =~ m#^([^/]+)/\.$# or die;
1865 remove_stray_gits($what);
1866 mktree_in_ud_here();
1868 my ($format, $fopts) = get_source_format();
1869 if (madformat($format)) {
1874 my $tree=git_add_write_tree();
1875 return ($tree,$dir);
1878 our @files_csum_info_fields =
1879 (['Checksums-Sha256','Digest::SHA', 'new(256)', 'sha256sum'],
1880 ['Checksums-Sha1', 'Digest::SHA', 'new(1)', 'sha1sum'],
1881 ['Files', 'Digest::MD5', 'new()', 'md5sum']);
1883 sub dsc_files_info () {
1884 foreach my $csumi (@files_csum_info_fields) {
1885 my ($fname, $module, $method) = @$csumi;
1886 my $field = $dsc->{$fname};
1887 next unless defined $field;
1888 eval "use $module; 1;" or die $@;
1890 foreach (split /\n/, $field) {
1892 m/^(\w+) (\d+) (\S+)$/ or
1893 fail f_ "could not parse .dsc %s line \`%s'", $fname, $_;
1894 my $digester = eval "$module"."->$method;" or die $@;
1899 Digester => $digester,
1904 fail f_ "missing any supported Checksums-* or Files field in %s",
1905 $dsc->get_option('name');
1909 map { $_->{Filename} } dsc_files_info();
1912 sub files_compare_inputs (@) {
1917 my $showinputs = sub {
1918 return join "; ", map { $_->get_option('name') } @$inputs;
1921 foreach my $in (@$inputs) {
1923 my $in_name = $in->get_option('name');
1925 printdebug "files_compare_inputs $in_name\n";
1927 foreach my $csumi (@files_csum_info_fields) {
1928 my ($fname) = @$csumi;
1929 printdebug "files_compare_inputs $in_name $fname\n";
1931 my $field = $in->{$fname};
1932 next unless defined $field;
1935 foreach (split /\n/, $field) {
1938 my ($info, $f) = m/^(\w+ \d+) (?:\S+ \S+ )?(\S+)$/ or
1939 fail "could not parse $in_name $fname line \`$_'";
1941 printdebug "files_compare_inputs $in_name $fname $f\n";
1945 my $re = \ $record{$f}{$fname};
1947 $fchecked{$f}{$in_name} = 1;
1950 "hash or size of %s varies in %s fields (between: %s)",
1951 $f, $fname, $showinputs->();
1956 @files = sort @files;
1957 $expected_files //= \@files;
1958 "@$expected_files" eq "@files" or
1959 fail f_ "file list in %s varies between hash fields!",
1963 fail f_ "%s has no files list field(s)", $in_name;
1965 printdebug "files_compare_inputs ".Dumper(\%fchecked, \%record)
1968 grep { keys %$_ == @$inputs-1 } values %fchecked
1969 or fail f_ "no file appears in all file lists (looked in: %s)",
1973 sub is_orig_file_in_dsc ($$) {
1974 my ($f, $dsc_files_info) = @_;
1975 return 0 if @$dsc_files_info <= 1;
1976 # One file means no origs, and the filename doesn't have a "what
1977 # part of dsc" component. (Consider versions ending `.orig'.)
1978 return 0 unless $f =~ m/\.$orig_f_tail_re$/o;
1982 # This function determines whether a .changes file is source-only from
1983 # the point of view of dak. Thus, it permits *_source.buildinfo
1986 # It does not, however, permit any other buildinfo files. After a
1987 # source-only upload, the buildds will try to upload files like
1988 # foo_1.2.3_amd64.buildinfo. If the package maintainer included files
1989 # named like this in their (otherwise) source-only upload, the uploads
1990 # of the buildd can be rejected by dak. Fixing the resultant
1991 # situation can require manual intervention. So we block such
1992 # .buildinfo files when the user tells us to perform a source-only
1993 # upload (such as when using the push-source subcommand with the -C
1994 # option, which calls this function).
1996 # Note, though, that when dgit is told to prepare a source-only
1997 # upload, such as when subcommands like build-source and push-source
1998 # without -C are used, dgit has a more restrictive notion of
1999 # source-only .changes than dak: such uploads will never include
2000 # *_source.buildinfo files. This is because there is no use for such
2001 # files when using a tool like dgit to produce the source package, as
2002 # dgit ensures the source is identical to git HEAD.
2003 sub test_source_only_changes ($) {
2005 foreach my $l (split /\n/, getfield $changes, 'Files') {
2006 $l =~ m/\S+$/ or next;
2007 # \.tar\.[a-z0-9]+ covers orig.tar and the tarballs in native packages
2008 unless ($& =~ m/(?:\.dsc|\.diff\.gz|\.tar\.[a-z0-9]+|_source\.buildinfo)$/) {
2009 print f_ "purportedly source-only changes polluted by %s\n", $&;
2016 sub changes_update_origs_from_dsc ($$$$) {
2017 my ($dsc, $changes, $upstreamvsn, $changesfile) = @_;
2019 printdebug "checking origs needed ($upstreamvsn)...\n";
2020 $_ = getfield $changes, 'Files';
2021 m/^\w+ \d+ (\S+ \S+) \S+$/m or
2022 fail __ "cannot find section/priority from .changes Files field";
2023 my $placementinfo = $1;
2025 printdebug "checking origs needed placement '$placementinfo'...\n";
2026 foreach my $l (split /\n/, getfield $dsc, 'Files') {
2027 $l =~ m/\S+$/ or next;
2029 printdebug "origs $file | $l\n";
2030 next unless is_orig_file_of_vsn $file, $upstreamvsn;
2031 printdebug "origs $file is_orig\n";
2032 my $have = archive_query('file_in_archive', $file);
2033 if (!defined $have) {
2034 print STDERR __ <<END;
2035 archive does not support .orig check; hope you used --ch:--sa/-sd if needed
2041 printdebug "origs $file \$#\$have=$#$have\n";
2042 foreach my $h (@$have) {
2045 foreach my $csumi (@files_csum_info_fields) {
2046 my ($fname, $module, $method, $archivefield) = @$csumi;
2047 next unless defined $h->{$archivefield};
2048 $_ = $dsc->{$fname};
2049 next unless defined;
2050 m/^(\w+) .* \Q$file\E$/m or
2051 fail f_ ".dsc %s missing entry for %s", $fname, $file;
2052 if ($h->{$archivefield} eq $1) {
2056 "%s: %s (archive) != %s (local .dsc)",
2057 $archivefield, $h->{$archivefield}, $1;
2060 confess "$file ".Dumper($h)." ?!" if $same && @differ;
2064 f_ "archive %s: %s", $h->{filename}, join "; ", @differ
2067 printdebug "origs $file f.same=$found_same".
2068 " #f._differ=$#found_differ\n";
2069 if (@found_differ && !$found_same) {
2071 (f_ "archive contains %s with different checksum", $file),
2074 # Now we edit the changes file to add or remove it
2075 foreach my $csumi (@files_csum_info_fields) {
2076 my ($fname, $module, $method, $archivefield) = @$csumi;
2077 next unless defined $changes->{$fname};
2079 # in archive, delete from .changes if it's there
2080 $changed{$file} = "removed" if
2081 $changes->{$fname} =~ s/\n.* \Q$file\E$(?:)$//m;
2082 } elsif ($changes->{$fname} =~ m/^.* \Q$file\E$(?:)$/m) {
2083 # not in archive, but it's here in the .changes
2085 my $dsc_data = getfield $dsc, $fname;
2086 $dsc_data =~ m/^(.* \Q$file\E$)$/m or die "$dsc_data $file ?";
2088 $extra =~ s/ \d+ /$&$placementinfo /
2089 or confess "$fname $extra >$dsc_data< ?"
2090 if $fname eq 'Files';
2091 $changes->{$fname} .= "\n". $extra;
2092 $changed{$file} = "added";
2097 foreach my $file (keys %changed) {
2099 "edited .changes for archive .orig contents: %s %s",
2100 $changed{$file}, $file;
2102 my $chtmp = "$changesfile.tmp";
2103 $changes->save($chtmp);
2105 rename $chtmp,$changesfile or die "$changesfile $!";
2107 progress f_ "[new .changes left in %s]", $changesfile;
2110 progress f_ "%s already has appropriate .orig(s) (if any)",
2115 sub clogp_authline ($) {
2117 my $author = getfield $clogp, 'Maintainer';
2118 if ($author =~ m/^[^"\@]+\,/) {
2119 # single entry Maintainer field with unquoted comma
2120 $author = ($& =~ y/,//rd).$'; # strip the comma
2122 # git wants a single author; any remaining commas in $author
2123 # are by now preceded by @ (or "). It seems safer to punt on
2124 # "..." for now rather than attempting to dequote or something.
2125 $author =~ s#,.*##ms unless $author =~ m/"/;
2126 my $date = cmdoutput qw(date), '+%s %z', qw(-d), getfield($clogp,'Date');
2127 my $authline = "$author $date";
2128 $authline =~ m/$git_authline_re/o or
2129 fail f_ "unexpected commit author line format \`%s'".
2130 " (was generated from changelog Maintainer field)",
2132 return ($1,$2,$3) if wantarray;
2136 sub vendor_patches_distro ($$) {
2137 my ($checkdistro, $what) = @_;
2138 return unless defined $checkdistro;
2140 my $series = "debian/patches/\L$checkdistro\E.series";
2141 printdebug "checking for vendor-specific $series ($what)\n";
2143 if (!open SERIES, "<", $series) {
2144 confess "$series $!" unless $!==ENOENT;
2151 print STDERR __ <<END;
2153 Unfortunately, this source package uses a feature of dpkg-source where
2154 the same source package unpacks to different source code on different
2155 distros. dgit cannot safely operate on such packages on affected
2156 distros, because the meaning of source packages is not stable.
2158 Please ask the distro/maintainer to remove the distro-specific series
2159 files and use a different technique (if necessary, uploading actually
2160 different packages, if different distros are supposed to have
2164 fail f_ "Found active distro-specific series file for".
2165 " %s (%s): %s, cannot continue",
2166 $checkdistro, $what, $series;
2168 die "$series $!" if SERIES->error;
2172 sub check_for_vendor_patches () {
2173 # This dpkg-source feature doesn't seem to be documented anywhere!
2174 # But it can be found in the changelog (reformatted):
2176 # commit 4fa01b70df1dc4458daee306cfa1f987b69da58c
2177 # Author: Raphael Hertzog <hertzog@debian.org>
2178 # Date: Sun Oct 3 09:36:48 2010 +0200
2180 # dpkg-source: correctly create .pc/.quilt_series with alternate
2183 # If you have debian/patches/ubuntu.series and you were
2184 # unpacking the source package on ubuntu, quilt was still
2185 # directed to debian/patches/series instead of
2186 # debian/patches/ubuntu.series.
2188 # debian/changelog | 3 +++
2189 # scripts/Dpkg/Source/Package/V3/quilt.pm | 4 +++-
2190 # 2 files changed, 6 insertions(+), 1 deletion(-)
2193 vendor_patches_distro($ENV{DEB_VENDOR}, "DEB_VENDOR");
2194 vendor_patches_distro(Dpkg::Vendor::get_current_vendor(),
2195 __ "Dpkg::Vendor \`current vendor'");
2196 vendor_patches_distro(access_basedistro(),
2197 __ "(base) distro being accessed");
2198 vendor_patches_distro(access_nomdistro(),
2199 __ "(nominal) distro being accessed");
2202 sub check_bpd_exists () {
2203 stat $buildproductsdir
2204 or fail f_ "build-products-dir %s is not accessible: %s\n",
2205 $buildproductsdir, $!;
2208 sub dotdot_bpd_transfer_origs ($$$) {
2209 my ($bpd_abs, $upstreamversion, $wanted) = @_;
2210 # checks is_orig_file_of_vsn and if
2211 # calls $wanted->{$leaf} and expects boolish
2213 return if $buildproductsdir eq '..';
2216 my $dotdot = $maindir;
2217 $dotdot =~ s{/[^/]+$}{};
2218 opendir DD, $dotdot or fail "opendir .. ($dotdot): $!";
2219 while ($!=0, defined(my $leaf = readdir DD)) {
2221 local ($debuglevel) = $debuglevel-1;
2222 printdebug "DD_BPD $leaf ?\n";
2224 next unless is_orig_file_of_vsn $leaf, $upstreamversion;
2225 next unless $wanted->($leaf);
2226 next if lstat "$bpd_abs/$leaf";
2229 "%s: found orig(s) in .. missing from build-products-dir, transferring:\n",
2232 $! == &ENOENT or fail f_
2233 "check orig file %s in bpd %s: %s", $leaf, $bpd_abs, $!;
2234 lstat "$dotdot/$leaf" or fail f_
2235 "check orig file %s in ..: %s", $leaf, $!;
2237 stat "$dotdot/$leaf" or fail f_
2238 "check target of orig symlink %s in ..: %s", $leaf, $!;
2239 my $ltarget = readlink "$dotdot/$leaf" or
2240 die "readlink $dotdot/$leaf: $!";
2241 if ($ltarget !~ m{^/}) {
2242 $ltarget = "$dotdot/$ltarget";
2244 symlink $ltarget, "$bpd_abs/$leaf"
2245 or die "$ltarget $bpd_abs $leaf: $!";
2247 "%s: cloned orig symlink from ..: %s\n",
2249 } elsif (link "$dotdot/$leaf", "$bpd_abs/$leaf") {
2251 "%s: hardlinked orig from ..: %s\n",
2253 } elsif ($! != EXDEV) {
2254 fail f_ "failed to make %s a hardlink to %s: %s",
2255 "$bpd_abs/$leaf", "$dotdot/$leaf", $!;
2257 symlink "$bpd_abs/$leaf", "$dotdot/$leaf"
2258 or die "$bpd_abs $dotdot $leaf $!";
2260 "%s: symmlinked orig from .. on other filesystem: %s\n",
2264 die "$dotdot; $!" if $!;
2268 sub generate_commits_from_dsc () {
2269 # See big comment in fetch_from_archive, below.
2270 # See also README.dsc-import.
2272 changedir $playground;
2274 my $bpd_abs = bpd_abs();
2275 my $upstreamv = upstreamversion $dsc->{version};
2276 my @dfi = dsc_files_info();
2278 dotdot_bpd_transfer_origs $bpd_abs, $upstreamv,
2279 sub { grep { $_->{Filename} eq $_[0] } @dfi };
2281 foreach my $fi (@dfi) {
2282 my $f = $fi->{Filename};
2283 die "$f ?" if $f =~ m#/|^\.|\.dsc$|\.tmp$#;
2284 my $upper_f = "$bpd_abs/$f";
2286 printdebug "considering reusing $f: ";
2288 if (link_ltarget "$upper_f,fetch", $f) {
2289 printdebug "linked (using ...,fetch).\n";
2290 } elsif ((printdebug "($!) "),
2292 fail f_ "accessing %s: %s", "$buildproductsdir/$f,fetch", $!;
2293 } elsif (link_ltarget $upper_f, $f) {
2294 printdebug "linked.\n";
2295 } elsif ((printdebug "($!) "),
2297 fail f_ "accessing %s: %s", "$buildproductsdir/$f", $!;
2299 printdebug "absent.\n";
2303 complete_file_from_dsc('.', $fi, \$refetched)
2306 printdebug "considering saving $f: ";
2308 if (rename_link_xf 1, $f, $upper_f) {
2309 printdebug "linked.\n";
2310 } elsif ((printdebug "($@) "),
2312 fail f_ "saving %s: %s", "$buildproductsdir/$f", $@;
2313 } elsif (!$refetched) {
2314 printdebug "no need.\n";
2315 } elsif (rename_link_xf 1, $f, "$upper_f,fetch") {
2316 printdebug "linked (using ...,fetch).\n";
2317 } elsif ((printdebug "($@) "),
2319 fail f_ "saving %s: %s", "$buildproductsdir/$f,fetch", $@;
2321 printdebug "cannot.\n";
2325 # We unpack and record the orig tarballs first, so that we only
2326 # need disk space for one private copy of the unpacked source.
2327 # But we can't make them into commits until we have the metadata
2328 # from the debian/changelog, so we record the tree objects now and
2329 # make them into commits later.
2331 my $orig_f_base = srcfn $upstreamv, '';
2333 foreach my $fi (@dfi) {
2334 # We actually import, and record as a commit, every tarball
2335 # (unless there is only one file, in which case there seems
2338 my $f = $fi->{Filename};
2339 printdebug "import considering $f ";
2340 (printdebug "only one dfi\n"), next if @dfi == 1;
2341 (printdebug "not tar\n"), next unless $f =~ m/\.tar(\.\w+)?$/;
2342 (printdebug "signature\n"), next if $f =~ m/$orig_f_sig_re$/o;
2346 $f =~ m/^\Q$orig_f_base\E\.([^._]+)?\.tar(?:\.\w+)?$/;
2348 printdebug "Y ", (join ' ', map { $_//"(none)" }
2349 $compr_ext, $orig_f_part
2352 my $input = new IO::File $f, '<' or die "$f $!";
2356 if (defined $compr_ext) {
2358 Dpkg::Compression::compression_guess_from_filename $f;
2359 fail "Dpkg::Compression cannot handle file $f in source package"
2360 if defined $compr_ext && !defined $cname;
2362 new Dpkg::Compression::Process compression => $cname;
2363 @compr_cmd = $compr_proc->get_uncompress_cmdline();
2364 my $compr_fh = new IO::Handle;
2365 my $compr_pid = open $compr_fh, "-|" // confess "$!";
2367 open STDIN, "<&", $input or confess "$!";
2369 die "dgit (child): exec $compr_cmd[0]: $!\n";
2374 rmtree "_unpack-tar";
2375 mkdir "_unpack-tar" or confess "$!";
2376 my @tarcmd = qw(tar -x -f -
2377 --no-same-owner --no-same-permissions
2378 --no-acls --no-xattrs --no-selinux);
2379 my $tar_pid = fork // confess "$!";
2381 chdir "_unpack-tar" or confess "$!";
2382 open STDIN, "<&", $input or confess "$!";
2384 die f_ "dgit (child): exec %s: %s", $tarcmd[0], $!;
2386 $!=0; (waitpid $tar_pid, 0) == $tar_pid or confess "$!";
2387 !$? or failedcmd @tarcmd;
2390 (@compr_cmd ? ($?==SIGPIPE || failedcmd @compr_cmd)
2392 # finally, we have the results in "tarball", but maybe
2393 # with the wrong permissions
2395 runcmd qw(chmod -R +rwX _unpack-tar);
2396 changedir "_unpack-tar";
2397 remove_stray_gits($f);
2398 mktree_in_ud_here();
2400 my ($tree) = git_add_write_tree();
2401 my $tentries = cmdoutput @git, qw(ls-tree -z), $tree;
2402 if ($tentries =~ m/^\d+ tree (\w+)\t[^\000]+\000$/s) {
2404 printdebug "one subtree $1\n";
2406 printdebug "multiple subtrees\n";
2409 rmtree "_unpack-tar";
2411 my $ent = [ $f, $tree ];
2413 Orig => !!$orig_f_part,
2414 Sort => (!$orig_f_part ? 2 :
2415 $orig_f_part =~ m/-/g ? 1 :
2423 # put any without "_" first (spec is not clear whether files
2424 # are always in the usual order). Tarballs without "_" are
2425 # the main orig or the debian tarball.
2426 $a->{Sort} <=> $b->{Sort} or
2430 my $any_orig = grep { $_->{Orig} } @tartrees;
2432 my $dscfn = "$package.dsc";
2434 my $treeimporthow = 'package';
2436 open D, ">", $dscfn or die "$dscfn: $!";
2437 print D $dscdata or die "$dscfn: $!";
2438 close D or die "$dscfn: $!";
2439 my @cmd = qw(dpkg-source);
2440 push @cmd, '--no-check' if $dsc_checked;
2441 if (madformat $dsc->{format}) {
2442 push @cmd, '--skip-patches';
2443 $treeimporthow = 'unpatched';
2445 push @cmd, qw(-x --), $dscfn;
2448 my ($tree,$dir) = mktree_in_ud_from_only_subdir(__ "source package");
2449 if (madformat $dsc->{format}) {
2450 check_for_vendor_patches();
2454 if (madformat $dsc->{format}) {
2455 my @pcmd = qw(dpkg-source --before-build .);
2456 runcmd shell_cmd 'exec >/dev/null', @pcmd;
2458 $dappliedtree = git_add_write_tree();
2461 my @clogcmd = qw(dpkg-parsechangelog --format rfc822 --all);
2465 printdebug "import clog search...\n";
2466 parsechangelog_loop \@clogcmd, (__ "package changelog"), sub {
2467 my ($thisstanza, $desc) = @_;
2468 no warnings qw(exiting);
2470 $clogp //= $thisstanza;
2472 printdebug "import clog $thisstanza->{version} $desc...\n";
2474 last if !$any_orig; # we don't need $r1clogp
2476 # We look for the first (most recent) changelog entry whose
2477 # version number is lower than the upstream version of this
2478 # package. Then the last (least recent) previous changelog
2479 # entry is treated as the one which introduced this upstream
2480 # version and used for the synthetic commits for the upstream
2483 # One might think that a more sophisticated algorithm would be
2484 # necessary. But: we do not want to scan the whole changelog
2485 # file. Stopping when we see an earlier version, which
2486 # necessarily then is an earlier upstream version, is the only
2487 # realistic way to do that. Then, either the earliest
2488 # changelog entry we have seen so far is indeed the earliest
2489 # upload of this upstream version; or there are only changelog
2490 # entries relating to later upstream versions (which is not
2491 # possible unless the changelog and .dsc disagree about the
2492 # version). Then it remains to choose between the physically
2493 # last entry in the file, and the one with the lowest version
2494 # number. If these are not the same, we guess that the
2495 # versions were created in a non-monotonic order rather than
2496 # that the changelog entries have been misordered.
2498 printdebug "import clog $thisstanza->{version} vs $upstreamv...\n";
2500 last if version_compare($thisstanza->{version}, $upstreamv) < 0;
2501 $r1clogp = $thisstanza;
2503 printdebug "import clog $r1clogp->{version} becomes r1\n";
2506 $clogp or fail __ "package changelog has no entries!";
2508 my $authline = clogp_authline $clogp;
2509 my $changes = getfield $clogp, 'Changes';
2510 $changes =~ s/^\n//; # Changes: \n
2511 my $cversion = getfield $clogp, 'Version';
2514 $r1clogp //= $clogp; # maybe there's only one entry;
2515 my $r1authline = clogp_authline $r1clogp;
2516 # Strictly, r1authline might now be wrong if it's going to be
2517 # unused because !$any_orig. Whatever.
2519 printdebug "import tartrees authline $authline\n";
2520 printdebug "import tartrees r1authline $r1authline\n";
2522 foreach my $tt (@tartrees) {
2523 printdebug "import tartree $tt->{F} $tt->{Tree}\n";
2525 my $mbody = f_ "Import %s", $tt->{F};
2526 $tt->{Commit} = hash_commit_text($tt->{Orig} ? <<END_O : <<END_T);
2529 committer $r1authline
2533 [dgit import orig $tt->{F}]
2541 [dgit import tarball $package $cversion $tt->{F}]
2546 printdebug "import main commit\n";
2548 open C, ">../commit.tmp" or confess "$!";
2549 print C <<END or confess "$!";
2552 print C <<END or confess "$!" foreach @tartrees;
2555 print C <<END or confess "$!";
2561 [dgit import $treeimporthow $package $cversion]
2564 close C or confess "$!";
2565 my $rawimport_hash = hash_commit qw(../commit.tmp);
2567 if (madformat $dsc->{format}) {
2568 printdebug "import apply patches...\n";
2570 # regularise the state of the working tree so that
2571 # the checkout of $rawimport_hash works nicely.
2572 my $dappliedcommit = hash_commit_text(<<END);
2579 runcmd @git, qw(checkout -q -b dapplied), $dappliedcommit;
2581 runcmd @git, qw(checkout -q -b unpa), $rawimport_hash;
2583 # We need the answers to be reproducible
2584 my @authline = clogp_authline($clogp);
2585 local $ENV{GIT_COMMITTER_NAME} = $authline[0];
2586 local $ENV{GIT_COMMITTER_EMAIL} = $authline[1];
2587 local $ENV{GIT_COMMITTER_DATE} = $authline[2];
2588 local $ENV{GIT_AUTHOR_NAME} = $authline[0];
2589 local $ENV{GIT_AUTHOR_EMAIL} = $authline[1];
2590 local $ENV{GIT_AUTHOR_DATE} = $authline[2];
2592 my $path = $ENV{PATH} or die;
2594 # we use ../../gbp-pq-output, which (given that we are in
2595 # $playground/PLAYTREE, and $playground is .git/dgit/unpack,
2598 foreach my $use_absurd (qw(0 1)) {
2599 runcmd @git, qw(checkout -q unpa);
2600 runcmd @git, qw(update-ref -d refs/heads/patch-queue/unpa);
2601 local $ENV{PATH} = $path;
2604 progress "warning: $@";
2605 $path = "$absurdity:$path";
2606 progress f_ "%s: trying slow absurd-git-apply...", $us;
2607 rename "../../gbp-pq-output","../../gbp-pq-output.0"
2612 die "forbid absurd git-apply\n" if $use_absurd
2613 && forceing [qw(import-gitapply-no-absurd)];
2614 die "only absurd git-apply!\n" if !$use_absurd
2615 && forceing [qw(import-gitapply-absurd)];
2617 local $ENV{DGIT_ABSURD_DEBUG} = $debuglevel if $use_absurd;
2618 local $ENV{PATH} = $path if $use_absurd;
2620 my @showcmd = (gbp_pq, qw(import));
2621 my @realcmd = shell_cmd
2622 'exec >/dev/null 2>>../../gbp-pq-output', @showcmd;
2623 debugcmd "+",@realcmd;
2624 if (system @realcmd) {
2625 die f_ "%s failed: %s\n",
2626 +(shellquote @showcmd),
2627 failedcmd_waitstatus();
2630 my $gapplied = git_rev_parse('HEAD');
2631 my $gappliedtree = cmdoutput @git, qw(rev-parse HEAD:);
2632 $gappliedtree eq $dappliedtree or
2633 fail f_ <<END, $gapplied, $gappliedtree, $dappliedtree;
2634 gbp-pq import and dpkg-source disagree!
2635 gbp-pq import gave commit %s
2636 gbp-pq import gave tree %s
2637 dpkg-source --before-build gave tree %s
2639 $rawimport_hash = $gapplied;
2644 { local $@; eval { runcmd qw(cat ../../gbp-pq-output); }; }
2649 progress f_ "synthesised git commit from .dsc %s", $cversion;
2651 my $rawimport_mergeinput = {
2652 Commit => $rawimport_hash,
2653 Info => __ "Import of source package",
2655 my @output = ($rawimport_mergeinput);
2657 if ($lastpush_mergeinput) {
2658 my $oldclogp = mergeinfo_getclogp($lastpush_mergeinput);
2659 my $oversion = getfield $oldclogp, 'Version';
2661 version_compare($oversion, $cversion);
2663 @output = ($rawimport_mergeinput, $lastpush_mergeinput,
2664 { ReverseParents => 1,
2665 Message => (f_ <<END, $package, $cversion, $csuite) });
2666 Record %s (%s) in archive suite %s
2668 } elsif ($vcmp > 0) {
2669 print STDERR f_ <<END, $cversion, $oversion,
2671 Version actually in archive: %s (older)
2672 Last version pushed with dgit: %s (newer or same)
2675 __ $later_warning_msg or confess "$!";
2676 @output = $lastpush_mergeinput;
2678 # Same version. Use what's in the server git branch,
2679 # discarding our own import. (This could happen if the
2680 # server automatically imports all packages into git.)
2681 @output = $lastpush_mergeinput;
2689 sub complete_file_from_dsc ($$;$) {
2690 our ($dstdir, $fi, $refetched) = @_;
2691 # Ensures that we have, in $dstdir, the file $fi, with the correct
2692 # contents. (Downloading it from alongside $dscurl if necessary.)
2693 # If $refetched is defined, can overwrite "$dstdir/$fi->{Filename}"
2694 # and will set $$refetched=1 if it did so (or tried to).
2696 my $f = $fi->{Filename};
2697 my $tf = "$dstdir/$f";
2701 my $checkhash = sub {
2702 open F, "<", "$tf" or die "$tf: $!";
2703 $fi->{Digester}->reset();
2704 $fi->{Digester}->addfile(*F);
2705 F->error and confess "$!";
2706 $got = $fi->{Digester}->hexdigest();
2707 return $got eq $fi->{Hash};
2710 if (stat_exists $tf) {
2711 if ($checkhash->()) {
2712 progress f_ "using existing %s", $f;
2716 fail f_ "file %s has hash %s but .dsc demands hash %s".
2717 " (perhaps you should delete this file?)",
2718 $f, $got, $fi->{Hash};
2720 progress f_ "need to fetch correct version of %s", $f;
2721 unlink $tf or die "$tf $!";
2724 printdebug "$tf does not exist, need to fetch\n";
2728 $furl =~ s{/[^/]+$}{};
2730 die "$f ?" unless $f =~ m/^\Q${package}\E_/;
2731 die "$f ?" if $f =~ m#/#;
2732 runcmd_ordryrun_local @curl,qw(-f -o),$tf,'--',"$furl";
2733 return 0 if !act_local();
2736 fail f_ "file %s has hash %s but .dsc demands hash %s".
2737 " (got wrong file from archive!)",
2738 $f, $got, $fi->{Hash};
2743 sub ensure_we_have_orig () {
2744 my @dfi = dsc_files_info();
2745 foreach my $fi (@dfi) {
2746 my $f = $fi->{Filename};
2747 next unless is_orig_file_in_dsc($f, \@dfi);
2748 complete_file_from_dsc($buildproductsdir, $fi)
2753 #---------- git fetch ----------
2755 sub lrfetchrefs () { return "refs/dgit-fetch/".access_basedistro(); }
2756 sub lrfetchref () { return lrfetchrefs.'/'.server_branch($csuite); }
2758 # We fetch some parts of lrfetchrefs/*. Ideally we delete these
2759 # locally fetched refs because they have unhelpful names and clutter
2760 # up gitk etc. So we track whether we have "used up" head ref (ie,
2761 # whether we have made another local ref which refers to this object).
2763 # (If we deleted them unconditionally, then we might end up
2764 # re-fetching the same git objects each time dgit fetch was run.)
2766 # So, each use of lrfetchrefs needs to be accompanied by arrangements
2767 # in git_fetch_us to fetch the refs in question, and possibly a call
2768 # to lrfetchref_used.
2770 our (%lrfetchrefs_f, %lrfetchrefs_d);
2771 # $lrfetchrefs_X{lrfetchrefs."/heads/whatever"} = $objid
2773 sub lrfetchref_used ($) {
2774 my ($fullrefname) = @_;
2775 my $objid = $lrfetchrefs_f{$fullrefname};
2776 $lrfetchrefs_d{$fullrefname} = $objid if defined $objid;
2779 sub git_lrfetch_sane {
2780 my ($url, $supplementary, @specs) = @_;
2781 # Make a 'refs/'.lrfetchrefs.'/*' be just like on server,
2782 # at least as regards @specs. Also leave the results in
2783 # %lrfetchrefs_f, and arrange for lrfetchref_used to be
2784 # able to clean these up.
2786 # With $supplementary==1, @specs must not contain wildcards
2787 # and we add to our previous fetches (non-atomically).
2789 # This is rather miserable:
2790 # When git fetch --prune is passed a fetchspec ending with a *,
2791 # it does a plausible thing. If there is no * then:
2792 # - it matches subpaths too, even if the supplied refspec
2793 # starts refs, and behaves completely madly if the source
2794 # has refs/refs/something. (See, for example, Debian #NNNN.)
2795 # - if there is no matching remote ref, it bombs out the whole
2797 # We want to fetch a fixed ref, and we don't know in advance
2798 # if it exists, so this is not suitable.
2800 # Our workaround is to use git ls-remote. git ls-remote has its
2801 # own qairks. Notably, it has the absurd multi-tail-matching
2802 # behaviour: git ls-remote R refs/foo can report refs/foo AND
2803 # refs/refs/foo etc.
2805 # Also, we want an idempotent snapshot, but we have to make two
2806 # calls to the remote: one to git ls-remote and to git fetch. The
2807 # solution is use git ls-remote to obtain a target state, and
2808 # git fetch to try to generate it. If we don't manage to generate
2809 # the target state, we try again.
2811 printdebug "git_lrfetch_sane suppl=$supplementary specs @specs\n";
2813 my $specre = join '|', map {
2816 my $wildcard = $x =~ s/\\\*$/.*/;
2817 die if $wildcard && $supplementary;
2820 printdebug "git_lrfetch_sane specre=$specre\n";
2821 my $wanted_rref = sub {
2823 return m/^(?:$specre)$/;
2826 my $fetch_iteration = 0;
2829 printdebug "git_lrfetch_sane iteration $fetch_iteration\n";
2830 if (++$fetch_iteration > 10) {
2831 fail __ "too many iterations trying to get sane fetch!";
2834 my @look = map { "refs/$_" } @specs;
2835 my @lcmd = (@git, qw(ls-remote -q --refs), $url, @look);
2839 open GITLS, "-|", @lcmd or confess "$!";
2841 printdebug "=> ", $_;
2842 m/^(\w+)\s+(\S+)\n/ or die "ls-remote $_ ?";
2843 my ($objid,$rrefname) = ($1,$2);
2844 if (!$wanted_rref->($rrefname)) {
2845 print STDERR f_ <<END, "@look", $rrefname;
2846 warning: git ls-remote %s reported %s; this is silly, ignoring it.
2850 $wantr{$rrefname} = $objid;
2853 close GITLS or failedcmd @lcmd;
2855 # OK, now %want is exactly what we want for refs in @specs
2857 !m/\*$/ && !exists $wantr{"refs/$_"} ? () :
2858 "+refs/$_:".lrfetchrefs."/$_";
2861 printdebug "git_lrfetch_sane fspecs @fspecs\n";
2863 my @fcmd = (@git, qw(fetch -p -n -q), $url, @fspecs);
2864 runcmd_ordryrun_local @fcmd if @fspecs;
2866 if (!$supplementary) {
2867 %lrfetchrefs_f = ();
2871 git_for_each_ref(lrfetchrefs, sub {
2872 my ($objid,$objtype,$lrefname,$reftail) = @_;
2873 $lrfetchrefs_f{$lrefname} = $objid;
2874 $objgot{$objid} = 1;
2877 if ($supplementary) {
2881 foreach my $lrefname (sort keys %lrfetchrefs_f) {
2882 my $rrefname = 'refs'.substr($lrefname, length lrfetchrefs);
2883 if (!exists $wantr{$rrefname}) {
2884 if ($wanted_rref->($rrefname)) {
2886 git-fetch @fspecs created $lrefname which git ls-remote @look didn't list.
2889 print STDERR f_ <<END, "@fspecs", $lrefname
2890 warning: git fetch %s created %s; this is silly, deleting it.
2893 runcmd_ordryrun_local @git, qw(update-ref -d), $lrefname;
2894 delete $lrfetchrefs_f{$lrefname};
2898 foreach my $rrefname (sort keys %wantr) {
2899 my $lrefname = lrfetchrefs.substr($rrefname, 4);
2900 my $got = $lrfetchrefs_f{$lrefname} // '<none>';
2901 my $want = $wantr{$rrefname};
2902 next if $got eq $want;
2903 if (!defined $objgot{$want}) {
2904 fail __ <<END unless act_local();
2905 --dry-run specified but we actually wanted the results of git fetch,
2906 so this is not going to work. Try running dgit fetch first,
2907 or using --damp-run instead of --dry-run.
2909 print STDERR f_ <<END, $lrefname, $want;
2910 warning: git ls-remote suggests we want %s
2911 warning: and it should refer to %s
2912 warning: but git fetch didn't fetch that object to any relevant ref.
2913 warning: This may be due to a race with someone updating the server.
2914 warning: Will try again...
2916 next FETCH_ITERATION;
2919 git-fetch @fspecs made $lrefname=$got but want git ls-remote @look says $want
2921 runcmd_ordryrun_local @git, qw(update-ref -m),
2922 "dgit fetch git fetch fixup", $lrefname, $want;
2923 $lrfetchrefs_f{$lrefname} = $want;
2928 if (defined $csuite) {
2929 printdebug "git_lrfetch_sane: tidying any old suite lrfetchrefs\n";
2930 git_for_each_ref("refs/dgit-fetch/$csuite", sub {
2931 my ($objid,$objtype,$lrefname,$reftail) = @_;
2932 next if $lrfetchrefs_f{$lrefname}; # $csuite eq $distro ?
2933 runcmd_ordryrun_local @git, qw(update-ref -d), $lrefname;
2937 printdebug "git_lrfetch_sane: git fetch --no-insane emulation complete\n",
2938 Dumper(\%lrfetchrefs_f);
2941 sub git_fetch_us () {
2942 # Want to fetch only what we are going to use, unless
2943 # deliberately-not-ff, in which case we must fetch everything.
2945 my @specs = deliberately_not_fast_forward ? qw(tags/*) :
2946 map { "tags/$_" } debiantags('*',access_nomdistro);
2947 push @specs, server_branch($csuite);
2948 push @specs, $rewritemap;
2949 push @specs, qw(heads/*) if deliberately_not_fast_forward;
2951 my $url = access_giturl();
2952 git_lrfetch_sane $url, 0, @specs;
2955 my @tagpats = debiantags('*',access_nomdistro);
2957 git_for_each_ref([map { "refs/tags/$_" } @tagpats], sub {
2958 my ($objid,$objtype,$fullrefname,$reftail) = @_;
2959 printdebug "currently $fullrefname=$objid\n";
2960 $here{$fullrefname} = $objid;
2962 git_for_each_ref([map { lrfetchrefs."/tags/".$_ } @tagpats], sub {
2963 my ($objid,$objtype,$fullrefname,$reftail) = @_;
2964 my $lref = "refs".substr($fullrefname, length(lrfetchrefs));
2965 printdebug "offered $lref=$objid\n";
2966 if (!defined $here{$lref}) {
2967 my @upd = (@git, qw(update-ref), $lref, $objid, '');
2968 runcmd_ordryrun_local @upd;
2969 lrfetchref_used $fullrefname;
2970 } elsif ($here{$lref} eq $objid) {
2971 lrfetchref_used $fullrefname;
2973 print STDERR f_ "Not updating %s from %s to %s.\n",
2974 $lref, $here{$lref}, $objid;
2979 #---------- dsc and archive handling ----------
2981 sub mergeinfo_getclogp ($) {
2982 # Ensures thit $mi->{Clogp} exists and returns it
2984 $mi->{Clogp} = commit_getclogp($mi->{Commit});
2987 sub mergeinfo_version ($) {
2988 return getfield( (mergeinfo_getclogp $_[0]), 'Version' );
2991 sub fetch_from_archive_record_1 ($) {
2993 runcmd git_update_ref_cmd "dgit fetch $csuite", 'DGIT_ARCHIVE', $hash;
2994 cmdoutput @git, qw(log -n2), $hash;
2995 # ... gives git a chance to complain if our commit is malformed
2998 sub fetch_from_archive_record_2 ($) {
3000 my @upd_cmd = (git_update_ref_cmd 'dgit fetch', lrref(), $hash);
3004 dryrun_report @upd_cmd;
3008 sub parse_dsc_field_def_dsc_distro () {
3009 $dsc_distro //= cfg qw(dgit.default.old-dsc-distro
3010 dgit.default.distro);
3013 sub parse_dsc_field ($$) {
3014 my ($dsc, $what) = @_;
3016 foreach my $field (@ourdscfield) {
3017 $f = $dsc->{$field};
3022 progress f_ "%s: NO git hash", $what;
3023 parse_dsc_field_def_dsc_distro();
3024 } elsif (($dsc_hash, $dsc_distro, $dsc_hint_tag, $dsc_hint_url)
3025 = $f =~ m/^(\w+)\s+($distro_re)\s+($versiontag_re)\s+(\S+)(?:\s|$)/) {
3026 progress f_ "%s: specified git info (%s)", $what, $dsc_distro;
3027 $dsc_hint_tag = [ $dsc_hint_tag ];
3028 } elsif ($f =~ m/^\w+\s*$/) {
3030 parse_dsc_field_def_dsc_distro();
3031 $dsc_hint_tag = [ debiantags +(getfield $dsc, 'Version'),
3033 progress f_ "%s: specified git hash", $what;
3035 fail f_ "%s: invalid Dgit info", $what;
3039 sub resolve_dsc_field_commit ($$) {
3040 my ($already_distro, $already_mapref) = @_;
3042 return unless defined $dsc_hash;
3045 defined $already_mapref &&
3046 ($already_distro eq $dsc_distro || !$chase_dsc_distro)
3047 ? $already_mapref : undef;
3051 my ($what, @fetch) = @_;
3053 local $idistro = $dsc_distro;
3054 my $lrf = lrfetchrefs;
3056 if (!$chase_dsc_distro) {
3057 progress f_ "not chasing .dsc distro %s: not fetching %s",
3062 progress f_ ".dsc names distro %s: fetching %s", $dsc_distro, $what;
3064 my $url = access_giturl();
3065 if (!defined $url) {
3066 defined $dsc_hint_url or fail f_ <<END, $dsc_distro;
3067 .dsc Dgit metadata is in context of distro %s
3068 for which we have no configured url and .dsc provides no hint
3071 $dsc_hint_url =~ m#^([-+0-9a-zA-Z]+):# ? $1 :
3072 $dsc_hint_url =~ m#^/# ? 'file' : 'bad-syntax';
3073 parse_cfg_bool "dsc-url-proto-ok", 'false',
3074 cfg("dgit.dsc-url-proto-ok.$proto",
3075 "dgit.default.dsc-url-proto-ok")
3076 or fail f_ <<END, $dsc_distro, $proto;
3077 .dsc Dgit metadata is in context of distro %s
3078 for which we have no configured url;
3079 .dsc provides hinted url with protocol %s which is unsafe.
3080 (can be overridden by config - consult documentation)
3082 $url = $dsc_hint_url;
3085 git_lrfetch_sane $url, 1, @fetch;
3090 my $rewrite_enable = do {
3091 local $idistro = $dsc_distro;
3092 access_cfg('rewrite-map-enable', 'RETURN-UNDEF');
3095 if (parse_cfg_bool 'rewrite-map-enable', 'true', $rewrite_enable) {
3096 if (!defined $mapref) {
3097 my $lrf = $do_fetch->((__ "rewrite map"), $rewritemap) or return;
3098 $mapref = $lrf.'/'.$rewritemap;
3100 my $rewritemapdata = git_cat_file $mapref.':map';
3101 if (defined $rewritemapdata
3102 && $rewritemapdata =~ m/^$dsc_hash(?:[ \t](\w+))/m) {
3104 "server's git history rewrite map contains a relevant entry!";
3107 if (defined $dsc_hash) {
3108 progress __ "using rewritten git hash in place of .dsc value";
3110 progress __ "server data says .dsc hash is to be disregarded";
3115 if (!defined git_cat_file $dsc_hash) {
3116 my @tags = map { "tags/".$_ } @$dsc_hint_tag;
3117 my $lrf = $do_fetch->((__ "additional commits"), @tags) &&
3118 defined git_cat_file $dsc_hash
3119 or fail f_ <<END, $dsc_hash;
3120 .dsc Dgit metadata requires commit %s
3121 but we could not obtain that object anywhere.
3123 foreach my $t (@tags) {
3124 my $fullrefname = $lrf.'/'.$t;
3125 # print STDERR "CHK $t $fullrefname ".Dumper(\%lrfetchrefs_f);
3126 next unless $lrfetchrefs_f{$fullrefname};
3127 next unless is_fast_fwd "$fullrefname~0", $dsc_hash;
3128 lrfetchref_used $fullrefname;
3133 sub fetch_from_archive () {
3135 ensure_setup_existing_tree();
3137 # Ensures that lrref() is what is actually in the archive, one way
3138 # or another, according to us - ie this client's
3139 # appropritaely-updated archive view. Also returns the commit id.
3140 # If there is nothing in the archive, leaves lrref alone and
3141 # returns undef. git_fetch_us must have already been called.
3145 parse_dsc_field($dsc, __ 'last upload to archive');
3146 resolve_dsc_field_commit access_basedistro,
3147 lrfetchrefs."/".$rewritemap
3149 progress __ "no version available from the archive";
3152 # If the archive's .dsc has a Dgit field, there are three
3153 # relevant git commitids we need to choose between and/or merge
3155 # 1. $dsc_hash: the Dgit field from the archive
3156 # 2. $lastpush_hash: the suite branch on the dgit git server
3157 # 3. $lastfetch_hash: our local tracking brach for the suite
3159 # These may all be distinct and need not be in any fast forward
3162 # If the dsc was pushed to this suite, then the server suite
3163 # branch will have been updated; but it might have been pushed to
3164 # a different suite and copied by the archive. Conversely a more
3165 # recent version may have been pushed with dgit but not appeared
3166 # in the archive (yet).
3168 # $lastfetch_hash may be awkward because archive imports
3169 # (particularly, imports of Dgit-less .dscs) are performed only as
3170 # needed on individual clients, so different clients may perform a
3171 # different subset of them - and these imports are only made
3172 # public during push. So $lastfetch_hash may represent a set of
3173 # imports different to a subsequent upload by a different dgit
3176 # Our approach is as follows:
3178 # As between $dsc_hash and $lastpush_hash: if $lastpush_hash is a
3179 # descendant of $dsc_hash, then it was pushed by a dgit user who
3180 # had based their work on $dsc_hash, so we should prefer it.
3181 # Otherwise, $dsc_hash was installed into this suite in the
3182 # archive other than by a dgit push, and (necessarily) after the
3183 # last dgit push into that suite (since a dgit push would have
3184 # been descended from the dgit server git branch); thus, in that
3185 # case, we prefer the archive's version (and produce a
3186 # pseudo-merge to overwrite the dgit server git branch).
3188 # (If there is no Dgit field in the archive's .dsc then
3189 # generate_commit_from_dsc uses the version numbers to decide
3190 # whether the suite branch or the archive is newer. If the suite
3191 # branch is newer it ignores the archive's .dsc; otherwise it
3192 # generates an import of the .dsc, and produces a pseudo-merge to
3193 # overwrite the suite branch with the archive contents.)
3195 # The outcome of that part of the algorithm is the `public view',
3196 # and is same for all dgit clients: it does not depend on any
3197 # unpublished history in the local tracking branch.
3199 # As between the public view and the local tracking branch: The
3200 # local tracking branch is only updated by dgit fetch, and
3201 # whenever dgit fetch runs it includes the public view in the
3202 # local tracking branch. Therefore if the public view is not
3203 # descended from the local tracking branch, the local tracking
3204 # branch must contain history which was imported from the archive
3205 # but never pushed; and, its tip is now out of date. So, we make
3206 # a pseudo-merge to overwrite the old imports and stitch the old
3209 # Finally: we do not necessarily reify the public view (as
3210 # described above). This is so that we do not end up stacking two
3211 # pseudo-merges. So what we actually do is figure out the inputs
3212 # to any public view pseudo-merge and put them in @mergeinputs.
3215 # $mergeinputs[]{Commit}
3216 # $mergeinputs[]{Info}
3217 # $mergeinputs[0] is the one whose tree we use
3218 # @mergeinputs is in the order we use in the actual commit)
3221 # $mergeinputs[]{Message} is a commit message to use
3222 # $mergeinputs[]{ReverseParents} if def specifies that parent
3223 # list should be in opposite order
3224 # Such an entry has no Commit or Info. It applies only when found
3225 # in the last entry. (This ugliness is to support making
3226 # identical imports to previous dgit versions.)
3228 my $lastpush_hash = git_get_ref(lrfetchref());
3229 printdebug "previous reference hash=$lastpush_hash\n";
3230 $lastpush_mergeinput = $lastpush_hash && {
3231 Commit => $lastpush_hash,
3232 Info => (__ "dgit suite branch on dgit git server"),
3235 my $lastfetch_hash = git_get_ref(lrref());
3236 printdebug "fetch_from_archive: lastfetch=$lastfetch_hash\n";
3237 my $lastfetch_mergeinput = $lastfetch_hash && {
3238 Commit => $lastfetch_hash,
3239 Info => (__ "dgit client's archive history view"),
3242 my $dsc_mergeinput = $dsc_hash && {
3243 Commit => $dsc_hash,
3244 Info => (__ "Dgit field in .dsc from archive"),
3248 my $del_lrfetchrefs = sub {
3251 printdebug "del_lrfetchrefs...\n";
3252 foreach my $fullrefname (sort keys %lrfetchrefs_d) {
3253 my $objid = $lrfetchrefs_d{$fullrefname};
3254 printdebug "del_lrfetchrefs: $objid $fullrefname\n";
3256 $gur ||= new IO::Handle;
3257 open $gur, "|-", qw(git update-ref --stdin) or confess "$!";
3259 printf $gur "delete %s %s\n", $fullrefname, $objid;
3262 close $gur or failedcmd "git update-ref delete lrfetchrefs";
3266 if (defined $dsc_hash) {
3267 ensure_we_have_orig();
3268 if (!$lastpush_hash || $dsc_hash eq $lastpush_hash) {
3269 @mergeinputs = $dsc_mergeinput
3270 } elsif (is_fast_fwd($dsc_hash,$lastpush_hash)) {
3271 print STDERR f_ <<END, $dsc_hash, $lastpush_hash,
3273 Git commit in archive is behind the last version allegedly pushed/uploaded.
3274 Commit referred to by archive: %s
3275 Last version pushed with dgit: %s
3278 __ $later_warning_msg or confess "$!";
3279 @mergeinputs = ($lastpush_mergeinput);
3281 # Archive has .dsc which is not a descendant of the last dgit
3282 # push. This can happen if the archive moves .dscs about.
3283 # Just follow its lead.
3284 if (is_fast_fwd($lastpush_hash,$dsc_hash)) {
3285 progress __ "archive .dsc names newer git commit";
3286 @mergeinputs = ($dsc_mergeinput);
3288 progress __ "archive .dsc names other git commit, fixing up";
3289 @mergeinputs = ($dsc_mergeinput, $lastpush_mergeinput);
3293 @mergeinputs = generate_commits_from_dsc();
3294 # We have just done an import. Now, our import algorithm might
3295 # have been improved. But even so we do not want to generate
3296 # a new different import of the same package. So if the
3297 # version numbers are the same, just use our existing version.
3298 # If the version numbers are different, the archive has changed
3299 # (perhaps, rewound).
3300 if ($lastfetch_mergeinput &&
3301 !version_compare( (mergeinfo_version $lastfetch_mergeinput),
3302 (mergeinfo_version $mergeinputs[0]) )) {
3303 @mergeinputs = ($lastfetch_mergeinput);
3305 } elsif ($lastpush_hash) {
3306 # only in git, not in the archive yet
3307 @mergeinputs = ($lastpush_mergeinput);
3308 print STDERR f_ <<END,
3310 Package not found in the archive, but has allegedly been pushed using dgit.
3313 __ $later_warning_msg or confess "$!";
3315 printdebug "nothing found!\n";
3316 if (defined $skew_warning_vsn) {
3317 print STDERR f_ <<END, $skew_warning_vsn or confess "$!";
3319 Warning: relevant archive skew detected.
3320 Archive allegedly contains %s
3321 But we were not able to obtain any version from the archive or git.
3325 unshift @end, $del_lrfetchrefs;
3329 if ($lastfetch_hash &&
3331 my $h = $_->{Commit};
3332 $h and is_fast_fwd($lastfetch_hash, $h);
3333 # If true, one of the existing parents of this commit
3334 # is a descendant of the $lastfetch_hash, so we'll
3335 # be ff from that automatically.
3339 push @mergeinputs, $lastfetch_mergeinput;
3342 printdebug "fetch mergeinfos:\n";
3343 foreach my $mi (@mergeinputs) {
3345 printdebug " commit $mi->{Commit} $mi->{Info}\n";
3347 printdebug sprintf " ReverseParents=%d Message=%s",
3348 $mi->{ReverseParents}, $mi->{Message};
3352 my $compat_info= pop @mergeinputs
3353 if $mergeinputs[$#mergeinputs]{Message};
3355 @mergeinputs = grep { defined $_->{Commit} } @mergeinputs;
3358 if (@mergeinputs > 1) {
3360 my $tree_commit = $mergeinputs[0]{Commit};
3362 my $tree = get_tree_of_commit $tree_commit;;
3364 # We use the changelog author of the package in question the
3365 # author of this pseudo-merge. This is (roughly) correct if
3366 # this commit is simply representing aa non-dgit upload.
3367 # (Roughly because it does not record sponsorship - but we
3368 # don't have sponsorship info because that's in the .changes,
3369 # which isn't in the archivw.)
3371 # But, it might be that we are representing archive history
3372 # updates (including in-archive copies). These are not really
3373 # the responsibility of the person who created the .dsc, but
3374 # there is no-one whose name we should better use. (The
3375 # author of the .dsc-named commit is clearly worse.)
3377 my $useclogp = mergeinfo_getclogp $mergeinputs[0];
3378 my $author = clogp_authline $useclogp;
3379 my $cversion = getfield $useclogp, 'Version';
3381 my $mcf = dgit_privdir()."/mergecommit";
3382 open MC, ">", $mcf or die "$mcf $!";
3383 print MC <<END or confess "$!";
3387 my @parents = grep { $_->{Commit} } @mergeinputs;
3388 @parents = reverse @parents if $compat_info->{ReverseParents};
3389 print MC <<END or confess "$!" foreach @parents;
3393 print MC <<END or confess "$!";
3399 if (defined $compat_info->{Message}) {
3400 print MC $compat_info->{Message} or confess "$!";
3402 print MC f_ <<END, $package, $cversion, $csuite or confess "$!";
3403 Record %s (%s) in archive suite %s
3407 my $message_add_info = sub {
3409 my $mversion = mergeinfo_version $mi;
3410 printf MC " %-20s %s\n", $mversion, $mi->{Info}
3414 $message_add_info->($mergeinputs[0]);
3415 print MC __ <<END or confess "$!";
3416 should be treated as descended from
3418 $message_add_info->($_) foreach @mergeinputs[1..$#mergeinputs];
3421 close MC or confess "$!";
3422 $hash = hash_commit $mcf;
3424 $hash = $mergeinputs[0]{Commit};
3426 printdebug "fetch hash=$hash\n";
3429 my ($lasth, $what) = @_;
3430 return unless $lasth;
3431 confess "$lasth $hash $what ?" unless is_fast_fwd($lasth, $hash);
3434 $chkff->($lastpush_hash, __ 'dgit repo server tip (last push)')
3436 $chkff->($lastfetch_hash, __ 'local tracking tip (last fetch)');
3438 fetch_from_archive_record_1($hash);
3440 if (defined $skew_warning_vsn) {
3441 printdebug "SKEW CHECK WANT $skew_warning_vsn\n";
3442 my $gotclogp = commit_getclogp($hash);
3443 my $got_vsn = getfield $gotclogp, 'Version';
3444 printdebug "SKEW CHECK GOT $got_vsn\n";
3445 if (version_compare($got_vsn, $skew_warning_vsn) < 0) {
3446 print STDERR f_ <<END, $skew_warning_vsn, $got_vsn or confess "$!";
3448 Warning: archive skew detected. Using the available version:
3449 Archive allegedly contains %s
3450 We were able to obtain only %s
3456 if ($lastfetch_hash ne $hash) {
3457 fetch_from_archive_record_2($hash);
3460 lrfetchref_used lrfetchref();
3462 check_gitattrs($hash, __ "fetched source tree");
3464 unshift @end, $del_lrfetchrefs;
3468 sub set_local_git_config ($$) {
3470 runcmd @git, qw(config), $k, $v;
3473 sub setup_mergechangelogs (;$) {
3475 return unless $always || access_cfg_bool(1, 'setup-mergechangelogs');
3477 my $driver = 'dpkg-mergechangelogs';
3478 my $cb = "merge.$driver";
3479 confess unless defined $maindir;
3480 my $attrs = "$maindir_gitcommon/info/attributes";
3481 ensuredir "$maindir_gitcommon/info";
3483 open NATTRS, ">", "$attrs.new" or die "$attrs.new $!";
3484 if (!open ATTRS, "<", $attrs) {
3485 $!==ENOENT or die "$attrs: $!";
3489 next if m{^debian/changelog\s};
3490 print NATTRS $_, "\n" or confess "$!";
3492 ATTRS->error and confess "$!";
3495 print NATTRS "debian/changelog merge=$driver\n" or confess "$!";
3498 set_local_git_config "$cb.name", __ 'debian/changelog merge driver';
3499 set_local_git_config "$cb.driver", 'dpkg-mergechangelogs -m %O %A %B %A';
3501 rename "$attrs.new", "$attrs" or die "$attrs: $!";
3504 sub setup_useremail (;$) {
3506 return unless $always || access_cfg_bool(1, 'setup-useremail');
3509 my ($k, $envvar) = @_;
3510 my $v = access_cfg("user-$k", 'RETURN-UNDEF') // $ENV{$envvar};
3511 return unless defined $v;
3512 set_local_git_config "user.$k", $v;
3515 $setup->('email', 'DEBEMAIL');
3516 $setup->('name', 'DEBFULLNAME');
3519 sub ensure_setup_existing_tree () {
3520 my $k = "remote.$remotename.skipdefaultupdate";
3521 my $c = git_get_config $k;
3522 return if defined $c;
3523 set_local_git_config $k, 'true';
3526 sub open_main_gitattrs () {
3527 confess 'internal error no maindir' unless defined $maindir;
3528 my $gai = new IO::File "$maindir_gitcommon/info/attributes"
3530 or die "open $maindir_gitcommon/info/attributes: $!";
3534 our $gitattrs_ourmacro_re = qr{^\[attr\]dgit-defuse-attrs\s};
3536 sub is_gitattrs_setup () {
3539 # 1: gitattributes set up and should be left alone
3541 # 0: there is a dgit-defuse-attrs but it needs fixing
3542 # undef: there is none
3543 my $gai = open_main_gitattrs();
3544 return 0 unless $gai;
3546 next unless m{$gitattrs_ourmacro_re};
3547 return 1 if m{\s-working-tree-encoding\s};
3548 printdebug "is_gitattrs_setup: found old macro\n";
3551 $gai->error and confess "$!";
3552 printdebug "is_gitattrs_setup: found nothing\n";
3556 sub setup_gitattrs (;$) {
3558 return unless $always || access_cfg_bool(1, 'setup-gitattributes');
3560 my $already = is_gitattrs_setup();
3563 [attr]dgit-defuse-attrs already found, and proper, in .git/info/attributes
3564 not doing further gitattributes setup
3568 my $new = "[attr]dgit-defuse-attrs $negate_harmful_gitattrs";
3569 my $af = "$maindir_gitcommon/info/attributes";
3570 ensuredir "$maindir_gitcommon/info";
3572 open GAO, "> $af.new" or confess "$!";
3573 print GAO <<END, __ <<ENDT or confess "$!" unless defined $already;
3577 # ^ see GITATTRIBUTES in dgit(7) and dgit setup-new-tree in dgit(1)
3579 my $gai = open_main_gitattrs();
3582 if (m{$gitattrs_ourmacro_re}) {
3583 die unless defined $already;
3587 print GAO $_, "\n" or confess "$!";
3589 $gai->error and confess "$!";
3591 close GAO or confess "$!";
3592 rename "$af.new", "$af" or fail f_ "install %s: %s", $af, $!;
3595 sub setup_new_tree () {
3596 setup_mergechangelogs();
3601 sub check_gitattrs ($$) {
3602 my ($treeish, $what) = @_;
3604 return if is_gitattrs_setup;
3607 my @cmd = (@git, qw(ls-tree -lrz --), "${treeish}:");
3609 my $gafl = new IO::File;
3610 open $gafl, "-|", @cmd or confess "$!";
3613 s/^\d+\s+\w+\s+\w+\s+(\d+)\t// or die;
3615 next unless m{(?:^|/)\.gitattributes$};
3617 # oh dear, found one
3618 print STDERR f_ <<END, $what;
3619 dgit: warning: %s contains .gitattributes
3620 dgit: .gitattributes not (fully) defused. Recommended: dgit setup-new-tree.
3625 # tree contains no .gitattributes files
3626 $?=0; $!=0; close $gafl or failedcmd @cmd;
3630 sub multisuite_suite_child ($$$) {
3631 my ($tsuite, $mergeinputs, $fn) = @_;
3632 # in child, sets things up, calls $fn->(), and returns undef
3633 # in parent, returns canonical suite name for $tsuite
3634 my $canonsuitefh = IO::File::new_tmpfile;
3635 my $pid = fork // confess "$!";
3639 $us .= " [$isuite]";
3640 $debugprefix .= " ";
3641 progress f_ "fetching %s...", $tsuite;
3642 canonicalise_suite();
3643 print $canonsuitefh $csuite, "\n" or confess "$!";
3644 close $canonsuitefh or confess "$!";
3648 waitpid $pid,0 == $pid or confess "$!";
3649 fail f_ "failed to obtain %s: %s", $tsuite, waitstatusmsg()
3651 seek $canonsuitefh,0,0 or confess "$!";
3652 local $csuite = <$canonsuitefh>;
3653 confess "$!" unless defined $csuite && chomp $csuite;
3655 printdebug "multisuite $tsuite missing\n";
3658 printdebug "multisuite $tsuite ok (canon=$csuite)\n";
3659 push @$mergeinputs, {
3666 sub fork_for_multisuite ($) {
3667 my ($before_fetch_merge) = @_;
3668 # if nothing unusual, just returns ''
3671 # returns 0 to caller in child, to do first of the specified suites
3672 # in child, $csuite is not yet set
3674 # returns 1 to caller in parent, to finish up anything needed after
3675 # in parent, $csuite is set to canonicalised portmanteau
3677 my $org_isuite = $isuite;
3678 my @suites = split /\,/, $isuite;
3679 return '' unless @suites > 1;
3680 printdebug "fork_for_multisuite: @suites\n";
3684 my $cbasesuite = multisuite_suite_child($suites[0], \@mergeinputs,
3686 return 0 unless defined $cbasesuite;
3688 fail f_ "package %s missing in (base suite) %s", $package, $cbasesuite
3689 unless @mergeinputs;
3691 my @csuites = ($cbasesuite);
3693 $before_fetch_merge->();
3695 foreach my $tsuite (@suites[1..$#suites]) {
3696 $tsuite =~ s/^-/$cbasesuite-/;
3697 my $csubsuite = multisuite_suite_child($tsuite, \@mergeinputs,
3704 $csubsuite =~ s/^\Q$cbasesuite\E-/-/;
3705 push @csuites, $csubsuite;
3708 foreach my $mi (@mergeinputs) {
3709 my $ref = git_get_ref $mi->{Ref};
3710 die "$mi->{Ref} ?" unless length $ref;
3711 $mi->{Commit} = $ref;
3714 $csuite = join ",", @csuites;
3716 my $previous = git_get_ref lrref;
3718 unshift @mergeinputs, {
3719 Commit => $previous,
3720 Info => (__ "local combined tracking branch"),
3722 "archive seems to have rewound: local tracking branch is ahead!"),
3726 foreach my $ix (0..$#mergeinputs) {
3727 $mergeinputs[$ix]{Index} = $ix;
3730 @mergeinputs = sort {
3731 -version_compare(mergeinfo_version $a,
3732 mergeinfo_version $b) # highest version first
3734 $a->{Index} <=> $b->{Index}; # earliest in spec first
3740 foreach my $mi (@mergeinputs) {
3741 printdebug "multisuite merge check $mi->{Info}\n";
3742 foreach my $previous (@needed) {
3743 next unless is_fast_fwd $mi->{Commit}, $previous->{Commit};
3744 printdebug "multisuite merge un-needed $previous->{Info}\n";
3748 printdebug "multisuite merge this-needed\n";
3749 $mi->{Character} = '+';
3752 $needed[0]{Character} = '*';
3754 my $output = $needed[0]{Commit};
3757 printdebug "multisuite merge nontrivial\n";
3758 my $tree = cmdoutput qw(git rev-parse), $needed[0]{Commit}.':';
3760 my $commit = "tree $tree\n";
3761 my $msg = f_ "Combine archive branches %s [dgit]\n\n".
3762 "Input branches:\n",
3765 foreach my $mi (sort { $a->{Index} <=> $b->{Index} } @mergeinputs) {
3766 printdebug "multisuite merge include $mi->{Info}\n";
3767 $mi->{Character} //= ' ';
3768 $commit .= "parent $mi->{Commit}\n";
3769 $msg .= sprintf " %s %-25s %s\n",
3771 (mergeinfo_version $mi),
3774 my $authline = clogp_authline mergeinfo_getclogp $needed[0];
3775 $msg .= __ "\nKey\n".
3776 " * marks the highest version branch, which choose to use\n".
3777 " + marks each branch which was not already an ancestor\n\n";
3779 "[dgit multi-suite $csuite]\n";
3781 "author $authline\n".
3782 "committer $authline\n\n";
3783 $output = hash_commit_text $commit.$msg;
3784 printdebug "multisuite merge generated $output\n";
3787 fetch_from_archive_record_1($output);
3788 fetch_from_archive_record_2($output);
3790 progress f_ "calculated combined tracking suite %s", $csuite;
3795 sub clone_set_head () {
3796 open H, "> .git/HEAD" or confess "$!";
3797 print H "ref: ".lref()."\n" or confess "$!";
3798 close H or confess "$!";
3800 sub clone_finish ($) {
3802 runcmd @git, qw(reset --hard), lrref();
3803 runcmd qw(bash -ec), <<'END';
3805 git ls-tree -r --name-only -z HEAD | \
3806 xargs -0r touch -h -r . --
3808 printdone f_ "ready for work in %s", $dstdir;
3812 # in multisuite, returns twice!
3813 # once in parent after first suite fetched,
3814 # and then again in child after everything is finished
3816 badusage __ "dry run makes no sense with clone" unless act_local();
3818 my $multi_fetched = fork_for_multisuite(sub {
3819 printdebug "multi clone before fetch merge\n";
3823 if ($multi_fetched) {
3824 printdebug "multi clone after fetch merge\n";
3826 clone_finish($dstdir);
3829 printdebug "clone main body\n";
3831 mkdir $dstdir or fail f_ "create \`%s': %s", $dstdir, $!;
3835 canonicalise_suite();
3836 my $hasgit = check_for_git();
3838 runcmd @git, qw(init -q);
3842 my $giturl = access_giturl(1);
3843 if (defined $giturl) {
3844 runcmd @git, qw(remote add), 'origin', $giturl;
3847 progress __ "fetching existing git history";
3849 runcmd_ordryrun_local @git, qw(fetch origin);
3851 progress __ "starting new git history";
3853 fetch_from_archive() or no_such_package;
3854 my $vcsgiturl = $dsc->{'Vcs-Git'};
3855 if (length $vcsgiturl) {
3856 $vcsgiturl =~ s/\s+-b\s+\S+//g;
3857 runcmd @git, qw(remote add vcs-git), $vcsgiturl;
3859 clone_finish($dstdir);
3863 canonicalise_suite();
3864 if (check_for_git()) {
3867 fetch_from_archive() or no_such_package();
3869 my $vcsgiturl = $dsc && $dsc->{'Vcs-Git'};
3870 if (length $vcsgiturl and
3871 (grep { $csuite eq $_ }
3873 cfg 'dgit.vcs-git.suites')) {
3874 my $current = cfg 'remote.vcs-git.url', 'RETURN-UNDEF';
3875 if (defined $current && $current ne $vcsgiturl) {
3876 print STDERR f_ <<END, $csuite;
3877 FYI: Vcs-Git in %s has different url to your vcs-git remote.
3878 Your vcs-git remote url may be out of date. Use dgit update-vcs-git ?
3882 printdone f_ "fetched into %s", lrref();
3886 my $multi_fetched = fork_for_multisuite(sub { });
3887 fetch_one() unless $multi_fetched; # parent
3888 finish 0 if $multi_fetched eq '0'; # child
3893 runcmd_ordryrun_local @git, qw(merge -m),
3894 (f_ "Merge from %s [dgit]", $csuite),
3896 printdone f_ "fetched to %s and merged into HEAD", lrref();
3899 sub check_not_dirty () {
3900 my @forbid = qw(local-options local-patch-header);
3901 @forbid = map { "debian/source/$_" } @forbid;
3902 foreach my $f (@forbid) {
3903 if (stat_exists $f) {
3904 fail f_ "git tree contains %s", $f;
3908 my @cmd = (@git, qw(status -uall --ignored --porcelain));
3909 push @cmd, qw(debian/source/format debian/source/options);
3912 my $bad = cmdoutput @cmd;
3915 "you have uncommitted changes to critical files, cannot continue:\n").
3919 return if $includedirty;
3921 git_check_unmodified();
3924 sub commit_admin ($) {
3927 runcmd_ordryrun_local @git, qw(commit -m), $m;
3930 sub quiltify_nofix_bail ($$) {
3931 my ($headinfo, $xinfo) = @_;
3932 if ($quilt_mode eq 'nofix') {
3934 "quilt fixup required but quilt mode is \`nofix'\n".
3935 "HEAD commit%s differs from tree implied by debian/patches%s",
3940 sub commit_quilty_patch () {
3941 my $output = cmdoutput @git, qw(status --ignored --porcelain);
3943 foreach my $l (split /\n/, $output) {
3944 next unless $l =~ m/\S/;
3945 if ($l =~ m{^(?:[?!][?!]| [MADRC]) (.pc|debian/patches)}) {
3949 delete $adds{'.pc'}; # if there wasn't one before, don't add it
3951 progress __ "nothing quilty to commit, ok.";
3954 quiltify_nofix_bail "", __ " (wanted to commit patch update)";
3955 my @adds = map { s/[][*?\\]/\\$&/g; $_; } sort keys %adds;
3956 runcmd_ordryrun_local @git, qw(add -f), @adds;
3957 commit_admin +(__ <<ENDT).<<END
3958 Commit Debian 3.0 (quilt) metadata
3961 [dgit ($our_version) quilt-fixup]
3965 sub get_source_format () {
3967 if (open F, "debian/source/options") {
3971 s/\s+$//; # ignore missing final newline
3973 my ($k, $v) = ($`, $'); #');
3974 $v =~ s/^"(.*)"$/$1/;
3980 F->error and confess "$!";
3983 confess "$!" unless $!==&ENOENT;
3986 if (!open F, "debian/source/format") {
3987 confess "$!" unless $!==&ENOENT;
3991 F->error and confess "$!";
3993 return ($_, \%options);
3996 sub madformat_wantfixup ($) {
3998 return 0 unless $format eq '3.0 (quilt)';
3999 our $quilt_mode_warned;
4000 if ($quilt_mode eq 'nocheck') {
4001 progress f_ "Not doing any fixup of \`%s'".
4002 " due to ----no-quilt-fixup or --quilt=nocheck", $format
4003 unless $quilt_mode_warned++;
4006 progress f_ "Format \`%s', need to check/update patch stack", $format
4007 unless $quilt_mode_warned++;
4011 sub maybe_split_brain_save ($$$) {
4012 my ($headref, $dgitview, $msg) = @_;
4013 # => message fragment "$saved" describing disposition of $dgitview
4014 # (used inside parens, in the English texts)
4015 my $save = $internal_object_save{'dgit-view'};
4016 return f_ "commit id %s", $dgitview unless defined $save;
4017 my @cmd = (shell_cmd 'cd "$1"; shift', $maindir,
4019 "dgit --dgit-view-save $msg HEAD=$headref",
4022 return f_ "and left in %s", $save;
4025 # An "infopair" is a tuple [ $thing, $what ]
4026 # (often $thing is a commit hash; $what is a description)
4028 sub infopair_cond_equal ($$) {
4030 $x->[0] eq $y->[0] or fail <<END;
4031 $x->[1] ($x->[0]) not equal to $y->[1] ($y->[0])
4035 sub infopair_lrf_tag_lookup ($$) {
4036 my ($tagnames, $what) = @_;
4037 # $tagname may be an array ref
4038 my @tagnames = ref $tagnames ? @$tagnames : ($tagnames);
4039 printdebug "infopair_lrfetchref_tag_lookup $what @tagnames\n";
4040 foreach my $tagname (@tagnames) {
4041 my $lrefname = lrfetchrefs."/tags/$tagname";
4042 my $tagobj = $lrfetchrefs_f{$lrefname};
4043 next unless defined $tagobj;
4044 printdebug "infopair_lrfetchref_tag_lookup $tagobj $tagname $what\n";
4045 return [ git_rev_parse($tagobj), $what ];
4047 fail @tagnames==1 ? (f_ <<END, $what, "@tagnames")
4048 Wanted tag %s (%s) on dgit server, but not found
4050 : (f_ <<END, $what, "@tagnames");
4051 Wanted tag %s (one of: %s) on dgit server, but not found
4055 sub infopair_cond_ff ($$) {
4056 my ($anc,$desc) = @_;
4057 is_fast_fwd($anc->[0], $desc->[0]) or
4058 fail f_ <<END, $anc->[1], $anc->[0], $desc->[1], $desc->[0];
4059 %s (%s) .. %s (%s) is not fast forward
4063 sub pseudomerge_version_check ($$) {
4064 my ($clogp, $archive_hash) = @_;
4066 my $arch_clogp = commit_getclogp $archive_hash;
4067 my $i_arch_v = [ (getfield $arch_clogp, 'Version'),
4068 __ 'version currently in archive' ];
4069 if (defined $overwrite_version) {
4070 if (length $overwrite_version) {
4071 infopair_cond_equal([ $overwrite_version,
4072 '--overwrite= version' ],
4075 my $v = $i_arch_v->[0];
4077 "Checking package changelog for archive version %s ...", $v;
4080 my @xa = ("-f$v", "-t$v");
4081 my $vclogp = parsechangelog @xa;
4084 [ (getfield $vclogp, $fn),
4085 (f_ "%s field from dpkg-parsechangelog %s",
4088 my $cv = $gf->('Version');
4089 infopair_cond_equal($i_arch_v, $cv);
4090 $cd = $gf->('Distribution');
4094 $@ =~ s/^dgit: //gm;
4096 f_ "Perhaps debian/changelog does not mention %s ?", $v;
4098 fail f_ <<END, $cd->[1], $cd->[0], $v
4100 Your tree seems to based on earlier (not uploaded) %s.
4102 if $cd->[0] =~ m/UNRELEASED/;
4106 printdebug "pseudomerge_version_check i_arch_v @$i_arch_v\n";
4110 sub pseudomerge_hash_commit ($$$$ $$) {
4111 my ($clogp, $dgitview, $archive_hash, $i_arch_v,
4112 $msg_cmd, $msg_msg) = @_;
4113 progress f_ "Declaring that HEAD includes all changes in %s...",
4116 my $tree = cmdoutput qw(git rev-parse), "${dgitview}:";
4117 my $authline = clogp_authline $clogp;
4121 !defined $overwrite_version ? ""
4122 : !length $overwrite_version ? " --overwrite"
4123 : " --overwrite=".$overwrite_version;
4125 # Contributing parent is the first parent - that makes
4126 # git rev-list --first-parent DTRT.
4127 my $pmf = dgit_privdir()."/pseudomerge";
4128 open MC, ">", $pmf or die "$pmf $!";
4129 print MC <<END or confess "$!";
4132 parent $archive_hash
4140 close MC or confess "$!";
4142 return hash_commit($pmf);
4145 sub splitbrain_pseudomerge ($$$$) {
4146 my ($clogp, $maintview, $dgitview, $archive_hash) = @_;
4147 # => $merged_dgitview
4148 printdebug "splitbrain_pseudomerge...\n";
4150 # We: debian/PREVIOUS HEAD($maintview)
4151 # expect: o ----------------- o
4154 # a/d/PREVIOUS $dgitview
4157 # we do: `------------------ o
4161 return $dgitview unless defined $archive_hash;
4162 return $dgitview if deliberately_not_fast_forward();
4164 printdebug "splitbrain_pseudomerge...\n";
4166 my $i_arch_v = pseudomerge_version_check($clogp, $archive_hash);
4168 if (!defined $overwrite_version) {
4169 progress __ "Checking that HEAD includes all changes in archive...";
4172 return $dgitview if is_fast_fwd $archive_hash, $dgitview;
4174 if (defined $overwrite_version) {
4176 my $t_dep14 = debiantag_maintview $i_arch_v->[0], access_nomdistro;
4177 my $i_dep14 = infopair_lrf_tag_lookup($t_dep14,
4178 __ "maintainer view tag");
4179 my $t_dgit = debiantag_new $i_arch_v->[0], access_nomdistro;
4180 my $i_dgit = infopair_lrf_tag_lookup($t_dgit, __ "dgit view tag");
4181 my $i_archive = [ $archive_hash, __ "current archive contents" ];
4183 printdebug "splitbrain_pseudomerge i_archive @$i_archive\n";
4185 infopair_cond_equal($i_dgit, $i_archive);
4186 infopair_cond_ff($i_dep14, $i_dgit);
4187 infopair_cond_ff($i_dep14, [ $maintview, 'HEAD' ]);
4190 $@ =~ s/^\n//; chomp $@;
4191 print STDERR <<END.(__ <<ENDT);
4194 | Not fast forward; maybe --overwrite is needed ? Please see dgit(1).
4199 my $arch_v = $i_arch_v->[0];
4200 my $r = pseudomerge_hash_commit
4201 $clogp, $dgitview, $archive_hash, $i_arch_v,
4202 "dgit --quilt=$quilt_mode",
4203 (defined $overwrite_version
4204 ? f_ "Declare fast forward from %s\n", $arch_v
4205 : f_ "Make fast forward from %s\n", $arch_v);
4207 maybe_split_brain_save $maintview, $r, "pseudomerge";
4209 progress f_ "Made pseudo-merge of %s into dgit view.", $arch_v;
4213 sub plain_overwrite_pseudomerge ($$$) {
4214 my ($clogp, $head, $archive_hash) = @_;
4216 printdebug "plain_overwrite_pseudomerge...";
4218 my $i_arch_v = pseudomerge_version_check($clogp, $archive_hash);
4220 return $head if is_fast_fwd $archive_hash, $head;
4222 my $m = f_ "Declare fast forward from %s", $i_arch_v->[0];
4224 my $r = pseudomerge_hash_commit
4225 $clogp, $head, $archive_hash, $i_arch_v,
4228 runcmd git_update_ref_cmd $m, 'HEAD', $r, $head;
4230 progress f_ "Make pseudo-merge of %s into your HEAD.", $i_arch_v->[0];
4234 sub push_parse_changelog ($) {
4237 my $clogp = Dpkg::Control::Hash->new();
4238 $clogp->load($clogpfn) or die;
4240 my $clogpackage = getfield $clogp, 'Source';
4241 $package //= $clogpackage;
4242 fail f_ "-p specified %s but changelog specified %s",
4243 $package, $clogpackage
4244 unless $package eq $clogpackage;
4245 my $cversion = getfield $clogp, 'Version';
4247 if (!$we_are_initiator) {
4248 # rpush initiator can't do this because it doesn't have $isuite yet
4249 my $tag = debiantag_new($cversion, access_nomdistro);
4250 runcmd @git, qw(check-ref-format), $tag;
4253 my $dscfn = dscfn($cversion);
4255 return ($clogp, $cversion, $dscfn);
4258 sub push_parse_dsc ($$$) {
4259 my ($dscfn,$dscfnwhat, $cversion) = @_;
4260 $dsc = parsecontrol($dscfn,$dscfnwhat);
4261 my $dversion = getfield $dsc, 'Version';
4262 my $dscpackage = getfield $dsc, 'Source';
4263 ($dscpackage eq $package && $dversion eq $cversion) or
4264 fail f_ "%s is for %s %s but debian/changelog is for %s %s",
4265 $dscfn, $dscpackage, $dversion,
4266 $package, $cversion;
4269 sub push_tagwants ($$$$) {
4270 my ($cversion, $dgithead, $maintviewhead, $tfbase) = @_;
4273 TagFn => \&debiantag_new,
4278 if (defined $maintviewhead) {
4280 TagFn => \&debiantag_maintview,
4281 Objid => $maintviewhead,
4282 TfSuffix => '-maintview',
4285 } elsif ($dodep14tag ne 'no') {
4287 TagFn => \&debiantag_maintview,
4289 TfSuffix => '-dgit',
4293 foreach my $tw (@tagwants) {
4294 $tw->{Tag} = $tw->{TagFn}($cversion, access_nomdistro);
4295 $tw->{Tfn} = sub { $tfbase.$tw->{TfSuffix}.$_[0]; };
4297 printdebug 'push_tagwants: ', Dumper(\@_, \@tagwants);
4301 sub push_mktags ($$ $$ $) {
4303 $changesfile,$changesfilewhat,
4306 die unless $tagwants->[0]{View} eq 'dgit';
4308 my $declaredistro = access_nomdistro();
4309 my $reader_giturl = do { local $access_forpush=0; access_giturl(); };
4310 $dsc->{$ourdscfield[0]} = join " ",
4311 $tagwants->[0]{Objid}, $declaredistro, $tagwants->[0]{Tag},
4313 $dsc->save("$dscfn.tmp") or confess "$!";
4315 my $changes = parsecontrol($changesfile,$changesfilewhat);
4316 foreach my $field (qw(Source Distribution Version)) {
4317 $changes->{$field} eq $clogp->{$field} or
4318 fail f_ "changes field %s \`%s' does not match changelog \`%s'",
4319 $field, $changes->{$field}, $clogp->{$field};
4322 my $cversion = getfield $clogp, 'Version';
4323 my $clogsuite = getfield $clogp, 'Distribution';
4325 # We make the git tag by hand because (a) that makes it easier
4326 # to control the "tagger" (b) we can do remote signing
4327 my $authline = clogp_authline $clogp;
4328 my $delibs = join(" ", "",@deliberatelies);
4332 my $tfn = $tw->{Tfn};
4333 my $head = $tw->{Objid};
4334 my $tag = $tw->{Tag};
4336 open TO, '>', $tfn->('.tmp') or confess "$!";
4337 print TO <<END or confess "$!";
4344 if ($tw->{View} eq 'dgit') {
4345 print TO f_ <<ENDT, $package, $cversion, $clogsuite, $csuite
4346 %s release %s for %s (%s) [dgit]
4349 print TO <<END or confess "$!";
4350 [dgit distro=$declaredistro$delibs]
4352 foreach my $ref (sort keys %previously) {
4353 print TO <<END or confess "$!";
4354 [dgit previously:$ref=$previously{$ref}]
4357 } elsif ($tw->{View} eq 'maint') {
4358 print TO f_ <<END, $package, $cversion, $clogsuite, $csuite,
4359 %s release %s for %s (%s)
4360 (maintainer view tag generated by dgit --quilt=%s)
4365 confess Dumper($tw)."?";
4368 close TO or confess "$!";
4370 my $tagobjfn = $tfn->('.tmp');
4372 if (!defined $keyid) {
4373 $keyid = access_cfg('keyid','RETURN-UNDEF');
4375 if (!defined $keyid) {
4376 $keyid = getfield $clogp, 'Maintainer';
4378 unlink $tfn->('.tmp.asc') or $!==&ENOENT or confess "$!";
4379 my @sign_cmd = (@gpg, qw(--detach-sign --armor));
4380 push @sign_cmd, qw(-u),$keyid if defined $keyid;
4381 push @sign_cmd, $tfn->('.tmp');
4382 runcmd_ordryrun @sign_cmd;
4384 $tagobjfn = $tfn->('.signed.tmp');
4385 runcmd shell_cmd "exec >$tagobjfn", qw(cat --),
4386 $tfn->('.tmp'), $tfn->('.tmp.asc');
4392 my @r = map { $mktag->($_); } @$tagwants;
4396 sub sign_changes ($) {
4397 my ($changesfile) = @_;
4399 my @debsign_cmd = @debsign;
4400 push @debsign_cmd, "-k$keyid" if defined $keyid;
4401 push @debsign_cmd, "-p$gpg[0]" if $gpg[0] ne 'gpg';
4402 push @debsign_cmd, $changesfile;
4403 runcmd_ordryrun @debsign_cmd;
4408 printdebug "actually entering push\n";
4410 supplementary_message(__ <<'END');
4411 Push failed, while checking state of the archive.
4412 You can retry the push, after fixing the problem, if you like.
4414 if (check_for_git()) {
4417 my $archive_hash = fetch_from_archive();
4418 if (!$archive_hash) {
4420 fail __ "package appears to be new in this suite;".
4421 " if this is intentional, use --new";
4424 supplementary_message(__ <<'END');
4425 Push failed, while preparing your push.
4426 You can retry the push, after fixing the problem, if you like.
4431 access_giturl(); # check that success is vaguely likely
4432 rpush_handle_protovsn_bothends() if $we_are_initiator;
4434 my $clogpfn = dgit_privdir()."/changelog.822.tmp";
4435 runcmd shell_cmd "exec >$clogpfn", qw(dpkg-parsechangelog);
4437 responder_send_file('parsed-changelog', $clogpfn);
4439 my ($clogp, $cversion, $dscfn) =
4440 push_parse_changelog("$clogpfn");
4442 my $dscpath = "$buildproductsdir/$dscfn";
4443 stat_exists $dscpath or
4444 fail f_ "looked for .dsc %s, but %s; maybe you forgot to build",
4447 responder_send_file('dsc', $dscpath);
4449 push_parse_dsc($dscpath, $dscfn, $cversion);
4451 my $format = getfield $dsc, 'Format';
4453 my $symref = git_get_symref();
4454 my $actualhead = git_rev_parse('HEAD');
4456 if (branch_is_gdr_unstitched_ff($symref, $actualhead, $archive_hash)) {
4457 if (quiltmode_splitting()) {
4458 my ($ffq_prev, $gdrlast) = branch_gdr_info($symref, $actualhead);
4459 fail f_ <<END, $ffq_prev, $quilt_mode;
4460 Branch is managed by git-debrebase (%s
4461 exists), but quilt mode (%s) implies a split view.
4462 Pass the right --quilt option or adjust your git config.
4463 Or, maybe, run git-debrebase forget-was-ever-debrebase.
4466 runcmd_ordryrun_local @git_debrebase, 'stitch';
4467 $actualhead = git_rev_parse('HEAD');
4470 my $dgithead = $actualhead;
4471 my $maintviewhead = undef;
4473 my $upstreamversion = upstreamversion $clogp->{Version};
4475 if (madformat_wantfixup($format)) {
4476 # user might have not used dgit build, so maybe do this now:
4477 if (do_split_brain()) {
4478 changedir $playground;
4480 ($dgithead, $cachekey) =
4481 quilt_check_splitbrain_cache($actualhead, $upstreamversion);
4482 $dgithead or fail f_
4483 "--quilt=%s but no cached dgit view:
4484 perhaps HEAD changed since dgit build[-source] ?",
4487 if (!do_split_brain()) {
4488 # In split brain mode, do not attempt to incorporate dirty
4489 # stuff from the user's working tree. That would be mad.
4490 commit_quilty_patch();
4493 if (do_split_brain()) {
4494 $made_split_brain = 1;
4495 $dgithead = splitbrain_pseudomerge($clogp,
4496 $actualhead, $dgithead,
4498 $maintviewhead = $actualhead;
4500 prep_ud(); # so _only_subdir() works, below
4503 if (defined $overwrite_version && !defined $maintviewhead
4505 $dgithead = plain_overwrite_pseudomerge($clogp,
4513 if ($archive_hash) {
4514 if (is_fast_fwd($archive_hash, $dgithead)) {
4516 } elsif (deliberately_not_fast_forward) {
4519 fail __ "dgit push: HEAD is not a descendant".
4520 " of the archive's version.\n".
4521 "To overwrite the archive's contents,".
4522 " pass --overwrite[=VERSION].\n".
4523 "To rewind history, if permitted by the archive,".
4524 " use --deliberately-not-fast-forward.";
4528 confess unless !!$made_split_brain == do_split_brain();
4530 changedir $playground;
4531 progress f_ "checking that %s corresponds to HEAD", $dscfn;
4532 runcmd qw(dpkg-source -x --),
4533 $dscpath =~ m#^/# ? $dscpath : "$maindir/$dscpath";
4534 my ($tree,$dir) = mktree_in_ud_from_only_subdir("source package");
4535 check_for_vendor_patches() if madformat($dsc->{format});
4537 my @diffcmd = (@git, qw(diff --quiet), $tree, $dgithead);
4538 debugcmd "+",@diffcmd;
4540 my $r = system @diffcmd;
4543 my $referent = $made_split_brain ? $dgithead : 'HEAD';
4544 my $diffs = cmdoutput @git, qw(diff --stat), $tree, $dgithead;
4547 my $raw = cmdoutput @git,
4548 qw(diff --no-renames -z -r --raw), $tree, $dgithead;
4550 foreach (split /\0/, $raw) {
4551 if (defined $changed) {
4552 push @mode_changes, "$changed: $_\n" if $changed;
4555 } elsif (m/^:0+ 0+ /) {
4557 } elsif (m/^:(?:10*)?(\d+) (?:10*)?(\d+) /) {
4558 $changed = "Mode change from $1 to $2"
4563 if (@mode_changes) {
4564 fail +(f_ <<ENDT, $dscfn).<<END
4565 HEAD specifies a different tree to %s:
4569 .(join '', @mode_changes)
4570 .(f_ <<ENDT, $tree, $referent);
4571 There is a problem with your source tree (see dgit(7) for some hints).
4572 To see a full diff, run git diff %s %s
4576 fail +(f_ <<ENDT, $dscfn).<<END.(f_ <<ENDT, $tree, $referent);
4577 HEAD specifies a different tree to %s:
4581 Perhaps you forgot to build. Or perhaps there is a problem with your
4582 source tree (see dgit(7) for some hints). To see a full diff, run
4589 if (!$changesfile) {
4590 my $pat = changespat $cversion;
4591 my @cs = glob "$buildproductsdir/$pat";
4592 fail f_ "failed to find unique changes file".
4593 " (looked for %s in %s);".
4594 " perhaps you need to use dgit -C",
4595 $pat, $buildproductsdir
4597 ($changesfile) = @cs;
4599 $changesfile = "$buildproductsdir/$changesfile";
4602 # Check that changes and .dsc agree enough
4603 $changesfile =~ m{[^/]*$};
4604 my $changes = parsecontrol($changesfile,$&);
4605 files_compare_inputs($dsc, $changes)
4606 unless forceing [qw(dsc-changes-mismatch)];
4608 # Check whether this is a source only upload
4609 my $hasdebs = $changes->{Files} =~ m{\.deb$}m;
4610 my $sourceonlypolicy = access_cfg 'source-only-uploads';
4611 if ($sourceonlypolicy eq 'ok') {
4612 } elsif ($sourceonlypolicy eq 'always') {
4613 forceable_fail [qw(uploading-binaries)],
4614 __ "uploading binaries, although distro policy is source only"
4616 } elsif ($sourceonlypolicy eq 'never') {
4617 forceable_fail [qw(uploading-source-only)],
4618 __ "source-only upload, although distro policy requires .debs"
4620 } elsif ($sourceonlypolicy eq 'not-wholly-new') {
4621 forceable_fail [qw(uploading-source-only)],
4622 f_ "source-only upload, even though package is entirely NEW\n".
4623 "(this is contrary to policy in %s)",
4627 && !(archive_query('package_not_wholly_new', $package) // 1);
4629 badcfg f_ "unknown source-only-uploads policy \`%s'",
4633 # Perhaps adjust .dsc to contain right set of origs
4634 changes_update_origs_from_dsc($dsc, $changes, $upstreamversion,
4636 unless forceing [qw(changes-origs-exactly)];
4638 # Checks complete, we're going to try and go ahead:
4640 responder_send_file('changes',$changesfile);
4641 responder_send_command("param head $dgithead");
4642 responder_send_command("param csuite $csuite");
4643 responder_send_command("param isuite $isuite");
4644 responder_send_command("param tagformat new"); # needed in $protovsn==4
4645 if (defined $maintviewhead) {
4646 responder_send_command("param maint-view $maintviewhead");
4649 # Perhaps send buildinfo(s) for signing
4650 my $changes_files = getfield $changes, 'Files';
4651 my @buildinfos = ($changes_files =~ m/ .* (\S+\.buildinfo)$/mg);
4652 foreach my $bi (@buildinfos) {
4653 responder_send_command("param buildinfo-filename $bi");
4654 responder_send_file('buildinfo', "$buildproductsdir/$bi");
4657 if (deliberately_not_fast_forward) {
4658 git_for_each_ref(lrfetchrefs, sub {
4659 my ($objid,$objtype,$lrfetchrefname,$reftail) = @_;
4660 my $rrefname= substr($lrfetchrefname, length(lrfetchrefs) + 1);
4661 responder_send_command("previously $rrefname=$objid");
4662 $previously{$rrefname} = $objid;
4666 my @tagwants = push_tagwants($cversion, $dgithead, $maintviewhead,
4667 dgit_privdir()."/tag");
4670 supplementary_message(__ <<'END');
4671 Push failed, while signing the tag.
4672 You can retry the push, after fixing the problem, if you like.
4674 # If we manage to sign but fail to record it anywhere, it's fine.
4675 if ($we_are_responder) {
4676 @tagobjfns = map { $_->{Tfn}('.signed-tmp') } @tagwants;
4677 responder_receive_files('signed-tag', @tagobjfns);
4679 @tagobjfns = push_mktags($clogp,$dscpath,
4680 $changesfile,$changesfile,
4683 supplementary_message(__ <<'END');
4684 Push failed, *after* signing the tag.
4685 If you want to try again, you should use a new version number.
4688 pairwise { $a->{TagObjFn} = $b } @tagwants, @tagobjfns;
4690 foreach my $tw (@tagwants) {
4691 my $tag = $tw->{Tag};
4692 my $tagobjfn = $tw->{TagObjFn};
4694 cmdoutput @git, qw(hash-object -w -t tag), $tagobjfn;
4695 runcmd_ordryrun @git, qw(verify-tag), $tag_obj_hash;
4696 runcmd_ordryrun_local
4697 @git, qw(update-ref), "refs/tags/$tag", $tag_obj_hash;
4700 supplementary_message(__ <<'END');
4701 Push failed, while updating the remote git repository - see messages above.
4702 If you want to try again, you should use a new version number.
4704 if (!check_for_git()) {
4705 create_remote_git_repo();
4708 my @pushrefs = $forceflag.$dgithead.":".rrref();
4709 foreach my $tw (@tagwants) {
4710 push @pushrefs, $forceflag."refs/tags/$tw->{Tag}";
4713 runcmd_ordryrun @git,
4714 qw(-c push.followTags=false push), access_giturl(), @pushrefs;
4715 runcmd_ordryrun git_update_ref_cmd 'dgit push', lrref(), $dgithead;
4717 supplementary_message(__ <<'END');
4718 Push failed, while obtaining signatures on the .changes and .dsc.
4719 If it was just that the signature failed, you may try again by using
4720 debsign by hand to sign the changes file (see the command dgit tried,
4721 above), and then dput that changes file to complete the upload.
4722 If you need to change the package, you must use a new version number.
4724 if ($we_are_responder) {
4725 my $dryrunsuffix = act_local() ? "" : ".tmp";
4726 my @rfiles = ($dscpath, $changesfile);
4727 push @rfiles, map { "$buildproductsdir/$_" } @buildinfos;
4728 responder_receive_files('signed-dsc-changes',
4729 map { "$_$dryrunsuffix" } @rfiles);
4732 rename "$dscpath.tmp",$dscpath or die "$dscfn $!";
4734 progress f_ "[new .dsc left in %s.tmp]", $dscpath;
4736 sign_changes $changesfile;
4739 supplementary_message(f_ <<END, $changesfile);
4740 Push failed, while uploading package(s) to the archive server.
4741 You can retry the upload of exactly these same files with dput of:
4743 If that .changes file is broken, you will need to use a new version
4744 number for your next attempt at the upload.
4746 my $host = access_cfg('upload-host','RETURN-UNDEF');
4747 my @hostarg = defined($host) ? ($host,) : ();
4748 runcmd_ordryrun @dput, @hostarg, $changesfile;
4749 printdone f_ "pushed and uploaded %s", $cversion;
4751 supplementary_message('');
4752 responder_send_command("complete");
4756 not_necessarily_a_tree();
4761 badusage __ "-p is not allowed with clone; specify as argument instead"
4762 if defined $package;
4765 } elsif (@ARGV==2 && $ARGV[1] =~ m#^\w#) {
4766 ($package,$isuite) = @ARGV;
4767 } elsif (@ARGV==2 && $ARGV[1] =~ m#^[./]#) {
4768 ($package,$dstdir) = @ARGV;
4769 } elsif (@ARGV==3) {
4770 ($package,$isuite,$dstdir) = @ARGV;
4772 badusage __ "incorrect arguments to dgit clone";
4776 $dstdir ||= "$package";
4777 if (stat_exists $dstdir) {
4778 fail f_ "%s already exists", $dstdir;
4782 if ($rmonerror && !$dryrun_level) {
4783 $cwd_remove= getcwd();
4785 return unless defined $cwd_remove;
4786 if (!chdir "$cwd_remove") {
4787 return if $!==&ENOENT;
4788 confess "chdir $cwd_remove: $!";
4790 printdebug "clone rmonerror removing $dstdir\n";
4792 rmtree($dstdir) or fail f_ "remove %s: %s\n", $dstdir, $!;
4793 } elsif (grep { $! == $_ }
4794 (ENOENT, ENOTDIR, EACCES, EPERM, ELOOP)) {
4796 print STDERR f_ "check whether to remove %s: %s\n",
4803 $cwd_remove = undef;
4806 sub branchsuite () {
4807 my $branch = git_get_symref();
4808 if (defined $branch && $branch =~ m#$lbranch_re#o) {
4815 sub package_from_d_control () {
4816 if (!defined $package) {
4817 my $sourcep = parsecontrol('debian/control','debian/control');
4818 $package = getfield $sourcep, 'Source';
4822 sub fetchpullargs () {
4823 package_from_d_control();
4825 $isuite = branchsuite();
4827 my $clogp = parsechangelog();
4828 my $clogsuite = getfield $clogp, 'Distribution';
4829 $isuite= $clogsuite if $clogsuite ne 'UNRELEASED';
4831 } elsif (@ARGV==1) {
4834 badusage __ "incorrect arguments to dgit fetch or dgit pull";
4848 determine_whether_split_brain();
4849 if (do_split_brain()) {
4850 my ($format, $fopts) = get_source_format();
4851 madformat($format) and fail f_ <<END, $quilt_mode
4852 dgit pull not yet supported in split view mode (including with view-splitting quilt modes)
4860 package_from_d_control();
4861 @ARGV==1 or badusage __ "dgit checkout needs a suite argument";
4865 foreach my $canon (qw(0 1)) {
4870 canonicalise_suite();
4872 if (length git_get_ref lref()) {
4873 # local branch already exists, yay
4876 if (!length git_get_ref lrref()) {
4884 runcmd (@git, qw(update-ref), lref(), lrref(), '');
4887 local $ENV{GIT_REFLOG_ACTION} = git_reflog_action_msg
4888 "dgit checkout $isuite";
4889 runcmd (@git, qw(checkout), lbranch());
4892 sub cmd_update_vcs_git () {
4894 if (@ARGV==0 || $ARGV[0] =~ m/^-/) {
4895 ($specsuite,) = split /\;/, cfg 'dgit.vcs-git.suites';
4897 ($specsuite) = (@ARGV);
4902 if ($ARGV[0] eq '-') {
4904 } elsif ($ARGV[0] eq '-') {
4909 package_from_d_control();
4911 if ($specsuite eq '.') {
4912 $ctrl = parsecontrol 'debian/control', 'debian/control';
4914 $isuite = $specsuite;
4918 my $url = getfield $ctrl, 'Vcs-Git';
4921 my $orgurl = cfg 'remote.vcs-git.url', 'RETURN-UNDEF';
4922 if (!defined $orgurl) {
4923 print STDERR f_ "setting up vcs-git: %s\n", $url;
4924 @cmd = (@git, qw(remote add vcs-git), $url);
4925 } elsif ($orgurl eq $url) {
4926 print STDERR f_ "vcs git already configured: %s\n", $url;
4928 print STDERR f_ "changing vcs-git url to: %s\n", $url;
4929 @cmd = (@git, qw(remote set-url vcs-git), $url);
4931 runcmd_ordryrun_local @cmd;
4933 print f_ "fetching (%s)\n", "@ARGV";
4934 runcmd_ordryrun_local @git, qw(fetch vcs-git), @ARGV;
4940 build_or_push_prep_early();
4942 build_or_push_prep_modes();
4946 } elsif (@ARGV==1) {
4947 ($specsuite) = (@ARGV);
4949 badusage f_ "incorrect arguments to dgit %s", $subcommand;
4952 local ($package) = $existing_package; # this is a hack
4953 canonicalise_suite();
4955 canonicalise_suite();
4957 if (defined $specsuite &&
4958 $specsuite ne $isuite &&
4959 $specsuite ne $csuite) {
4960 fail f_ "dgit %s: changelog specifies %s (%s)".
4961 " but command line specifies %s",
4962 $subcommand, $isuite, $csuite, $specsuite;
4971 #---------- remote commands' implementation ----------
4973 sub pre_remote_push_build_host {
4974 my ($nrargs) = shift @ARGV;
4975 my (@rargs) = @ARGV[0..$nrargs-1];
4976 @ARGV = @ARGV[$nrargs..$#ARGV];
4978 my ($dir,$vsnwant) = @rargs;
4979 # vsnwant is a comma-separated list; we report which we have
4980 # chosen in our ready response (so other end can tell if they
4983 $we_are_responder = 1;
4984 $us .= " (build host)";
4986 open PI, "<&STDIN" or confess "$!";
4987 open STDIN, "/dev/null" or confess "$!";
4988 open PO, ">&STDOUT" or confess "$!";
4990 open STDOUT, ">&STDERR" or confess "$!";
4994 ($protovsn) = grep {
4995 $vsnwant =~ m{^(?:.*,)?$_(?:,.*)?$}
4996 } @rpushprotovsn_support;
4998 fail f_ "build host has dgit rpush protocol versions %s".
4999 " but invocation host has %s",
5000 (join ",", @rpushprotovsn_support), $vsnwant
5001 unless defined $protovsn;
5005 sub cmd_remote_push_build_host {
5006 responder_send_command("dgit-remote-push-ready $protovsn");
5010 sub pre_remote_push_responder { pre_remote_push_build_host(); }
5011 sub cmd_remote_push_responder { cmd_remote_push_build_host(); }
5012 # ... for compatibility with proto vsn.1 dgit (just so that user gets
5013 # a good error message)
5015 sub rpush_handle_protovsn_bothends () {
5022 my $report = i_child_report();
5023 if (defined $report) {
5024 printdebug "($report)\n";
5025 } elsif ($i_child_pid) {
5026 printdebug "(killing build host child $i_child_pid)\n";
5027 kill 15, $i_child_pid;
5029 if (defined $i_tmp && !defined $initiator_tempdir) {
5031 eval { rmtree $i_tmp; };
5036 return unless forkcheck_mainprocess();
5041 my ($base,$selector,@args) = @_;
5042 $selector =~ s/\-/_/g;
5043 { no strict qw(refs); &{"${base}_${selector}"}(@args); }
5047 not_necessarily_a_tree();
5052 if ($host =~ m/^((?:[^][]|\[[^][]*\])*)\:/) {
5060 push @rargs, join ",", @rpushprotovsn_support;
5063 push @rdgit, @ropts;
5064 push @rdgit, qw(remote-push-build-host), (scalar @rargs), @rargs;
5066 my @cmd = (@ssh, $host, shellquote @rdgit);
5069 $we_are_initiator=1;
5071 if (defined $initiator_tempdir) {
5072 rmtree $initiator_tempdir;
5073 mkdir $initiator_tempdir, 0700
5074 or fail f_ "create %s: %s", $initiator_tempdir, $!;
5075 $i_tmp = $initiator_tempdir;
5079 $i_child_pid = open2(\*RO, \*RI, @cmd);
5081 ($protovsn) = initiator_expect { m/^dgit-remote-push-ready (\S+)/ };
5082 die "$protovsn ?" unless grep { $_ eq $protovsn } @rpushprotovsn_support;
5085 my ($icmd,$iargs) = initiator_expect {
5086 m/^(\S+)(?: (.*))?$/;
5089 i_method "i_resp", $icmd, $iargs;
5093 sub i_resp_progress ($) {
5095 my $msg = protocol_read_bytes \*RO, $rhs;
5099 sub i_resp_supplementary_message ($) {
5101 $supplementary_message = protocol_read_bytes \*RO, $rhs;
5104 sub i_resp_complete {
5105 my $pid = $i_child_pid;
5106 $i_child_pid = undef; # prevents killing some other process with same pid
5107 printdebug "waiting for build host child $pid...\n";
5108 my $got = waitpid $pid, 0;
5109 confess "$!" unless $got == $pid;
5110 fail f_ "build host child failed: %s", waitstatusmsg() if $?;
5113 printdebug __ "all done\n";
5117 sub i_resp_file ($) {
5119 my $localname = i_method "i_localname", $keyword;
5120 my $localpath = "$i_tmp/$localname";
5121 stat_exists $localpath and
5122 badproto \*RO, f_ "file %s (%s) twice", $keyword, $localpath;
5123 protocol_receive_file \*RO, $localpath;
5124 i_method "i_file", $keyword;
5129 sub i_resp_param ($) {
5130 $_[0] =~ m/^(\S+) (.*)$/ or badproto \*RO, __ "bad param spec";
5134 sub i_resp_previously ($) {
5135 $_[0] =~ m#^(refs/tags/\S+)=(\w+)$#
5136 or badproto \*RO, __ "bad previously spec";
5137 my $r = system qw(git check-ref-format), $1;
5138 confess "bad previously ref spec ($r)" if $r;
5139 $previously{$1} = $2;
5144 sub i_resp_want ($) {
5146 die "$keyword ?" if $i_wanted{$keyword}++;
5148 defined $i_param{'csuite'} or badproto \*RO, "premature desire, no csuite";
5149 $isuite = $i_param{'isuite'} // $i_param{'csuite'};
5150 die unless $isuite =~ m/^$suite_re$/;
5153 rpush_handle_protovsn_bothends();
5155 my @localpaths = i_method "i_want", $keyword;
5156 printdebug "[[ $keyword @localpaths\n";
5157 foreach my $localpath (@localpaths) {
5158 protocol_send_file \*RI, $localpath;
5160 print RI "files-end\n" or confess "$!";
5163 our ($i_clogp, $i_version, $i_dscfn, $i_changesfn, @i_buildinfos);
5165 sub i_localname_parsed_changelog {
5166 return "remote-changelog.822";
5168 sub i_file_parsed_changelog {
5169 ($i_clogp, $i_version, $i_dscfn) =
5170 push_parse_changelog "$i_tmp/remote-changelog.822";
5171 die if $i_dscfn =~ m#/|^\W#;
5174 sub i_localname_dsc {
5175 defined $i_dscfn or badproto \*RO, "dsc (before parsed-changelog)";
5180 sub i_localname_buildinfo ($) {
5181 my $bi = $i_param{'buildinfo-filename'};
5182 defined $bi or badproto \*RO, "buildinfo before filename";
5183 defined $i_changesfn or badproto \*RO, "buildinfo before changes";
5184 $bi =~ m{^\Q$package\E_[!-.0-~]*\.buildinfo$}s
5185 or badproto \*RO, "improper buildinfo filename";
5188 sub i_file_buildinfo {
5189 my $bi = $i_param{'buildinfo-filename'};
5190 my $bd = parsecontrol "$i_tmp/$bi", $bi;
5191 my $ch = parsecontrol "$i_tmp/$i_changesfn", 'changes';
5192 if (!forceing [qw(buildinfo-changes-mismatch)]) {
5193 files_compare_inputs($bd, $ch);
5194 (getfield $bd, $_) eq (getfield $ch, $_) or
5195 fail f_ "buildinfo mismatch in field %s", $_
5196 foreach qw(Source Version);
5197 !defined $bd->{$_} or
5198 fail f_ "buildinfo contains forbidden field %s", $_
5199 foreach qw(Changes Changed-by Distribution);
5201 push @i_buildinfos, $bi;
5202 delete $i_param{'buildinfo-filename'};
5205 sub i_localname_changes {
5206 defined $i_dscfn or badproto \*RO, "dsc (before parsed-changelog)";
5207 $i_changesfn = $i_dscfn;
5208 $i_changesfn =~ s/\.dsc$/_dgit.changes/ or die;
5209 return $i_changesfn;
5211 sub i_file_changes { }
5213 sub i_want_signed_tag {
5214 printdebug Dumper(\%i_param, $i_dscfn);
5215 defined $i_param{'head'} && defined $i_dscfn && defined $i_clogp
5216 && defined $i_param{'csuite'}
5217 or badproto \*RO, "premature desire for signed-tag";
5218 my $head = $i_param{'head'};
5219 die if $head =~ m/[^0-9a-f]/ || $head !~ m/^../;
5221 my $maintview = $i_param{'maint-view'};
5222 die if defined $maintview && $maintview =~ m/[^0-9a-f]/;
5224 if ($protovsn == 4) {
5225 my $p = $i_param{'tagformat'} // '<undef>';
5227 or badproto \*RO, "tag format mismatch: $p vs. new";
5230 die unless $i_param{'csuite'} =~ m/^$suite_re$/;
5232 push_parse_dsc $i_dscfn, 'remote dsc', $i_version;
5234 my @tagwants = push_tagwants $i_version, $head, $maintview, "tag";
5237 push_mktags $i_clogp, $i_dscfn,
5238 $i_changesfn, (__ 'remote changes file'),
5242 sub i_want_signed_dsc_changes {
5243 rename "$i_dscfn.tmp","$i_dscfn" or die "$i_dscfn $!";
5244 sign_changes $i_changesfn;
5245 return ($i_dscfn, $i_changesfn, @i_buildinfos);
5248 #---------- building etc. ----------
5254 #----- `3.0 (quilt)' handling -----
5256 our $fakeeditorenv = 'DGIT_FAKE_EDITOR_QUILT';
5258 sub quiltify_dpkg_commit ($$$;$) {
5259 my ($patchname,$author,$msg, $xinfo) = @_;
5262 mkpath '.git/dgit'; # we are in playtree
5263 my $descfn = ".git/dgit/quilt-description.tmp";
5264 open O, '>', $descfn or confess "$descfn: $!";
5265 $msg =~ s/\n+/\n\n/;
5266 print O <<END or confess "$!";
5268 ${xinfo}Subject: $msg
5272 close O or confess "$!";
5275 local $ENV{'EDITOR'} = cmdoutput qw(realpath --), $0;
5276 local $ENV{'VISUAL'} = $ENV{'EDITOR'};
5277 local $ENV{$fakeeditorenv} = cmdoutput qw(realpath --), $descfn;
5278 runcmd @dpkgsource, qw(--commit --include-removal .), $patchname;
5282 sub quiltify_trees_differ ($$;$$$) {
5283 my ($x,$y,$finegrained,$ignorenamesr,$unrepres) = @_;
5284 # returns true iff the two tree objects differ other than in debian/
5285 # with $finegrained,
5286 # returns bitmask 01 - differ in upstream files except .gitignore
5287 # 02 - differ in .gitignore
5288 # if $ignorenamesr is defined, $ingorenamesr->{$fn}
5289 # is set for each modified .gitignore filename $fn
5290 # if $unrepres is defined, array ref to which is appeneded
5291 # a list of unrepresentable changes (removals of upstream files
5294 my @cmd = (@git, qw(diff-tree -z --no-renames));
5295 push @cmd, qw(--name-only) unless $unrepres;
5296 push @cmd, qw(-r) if $finegrained || $unrepres;
5298 my $diffs= cmdoutput @cmd;
5301 foreach my $f (split /\0/, $diffs) {
5302 if ($unrepres && !@lmodes) {
5303 @lmodes = $f =~ m/^\:(\w+) (\w+) \w+ \w+ / or die "$_ ?";
5306 my ($oldmode,$newmode) = @lmodes;
5309 next if $f =~ m#^debian(?:/.*)?$#s;
5313 die __ "not a plain file or symlink\n"
5314 unless $newmode =~ m/^(?:10|12)\d{4}$/ ||
5315 $oldmode =~ m/^(?:10|12)\d{4}$/;
5316 if ($oldmode =~ m/[^0]/ &&
5317 $newmode =~ m/[^0]/) {
5318 # both old and new files exist
5319 die __ "mode or type changed\n" if $oldmode ne $newmode;
5320 die __ "modified symlink\n" unless $newmode =~ m/^10/;
5321 } elsif ($oldmode =~ m/[^0]/) {
5323 die __ "deletion of symlink\n"
5324 unless $oldmode =~ m/^10/;
5327 die __ "creation with non-default mode\n"
5328 unless $newmode =~ m/^100644$/ or
5329 $newmode =~ m/^120000$/;
5333 local $/="\n"; chomp $@;
5334 push @$unrepres, [ $f, "$@ ($oldmode->$newmode)" ];
5338 my $isignore = $f =~ m#^(?:.*/)?.gitignore$#s;
5339 $r |= $isignore ? 02 : 01;
5340 $ignorenamesr->{$f}=1 if $ignorenamesr && $isignore;
5342 printdebug "quiltify_trees_differ $x $y => $r\n";
5346 sub quiltify_tree_sentinelfiles ($) {
5347 # lists the `sentinel' files present in the tree
5349 my $r = cmdoutput @git, qw(ls-tree --name-only), $x,
5350 qw(-- debian/rules debian/control);
5355 sub quiltify_splitting ($$$$$$$) {
5356 my ($clogp, $unapplied, $headref, $oldtiptree, $diffbits,
5357 $editedignores, $cachekey) = @_;
5358 my $gitignore_special = 1;
5359 if ($quilt_mode !~ m/gbp|dpm/) {
5360 # treat .gitignore just like any other upstream file
5361 $diffbits = { %$diffbits };
5362 $_ = !!$_ foreach values %$diffbits;
5363 $gitignore_special = 0;
5365 # We would like any commits we generate to be reproducible
5366 my @authline = clogp_authline($clogp);
5367 local $ENV{GIT_COMMITTER_NAME} = $authline[0];
5368 local $ENV{GIT_COMMITTER_EMAIL} = $authline[1];
5369 local $ENV{GIT_COMMITTER_DATE} = $authline[2];
5370 local $ENV{GIT_AUTHOR_NAME} = $authline[0];
5371 local $ENV{GIT_AUTHOR_EMAIL} = $authline[1];
5372 local $ENV{GIT_AUTHOR_DATE} = $authline[2];
5374 confess unless do_split_brain();
5376 my $fulldiffhint = sub {
5378 my $cmd = "git diff $x $y -- :/ ':!debian'";
5379 $cmd .= " ':!/.gitignore' ':!*/.gitignore'" if $gitignore_special;
5380 return f_ "\nFor full diff showing the problem(s), type:\n %s\n",
5384 if ($quilt_mode =~ m/gbp|unapplied/ &&
5385 ($diffbits->{O2H} & 01)) {
5387 "--quilt=%s specified, implying patches-unapplied git tree\n".
5388 " but git tree differs from orig in upstream files.",
5390 $msg .= $fulldiffhint->($unapplied, 'HEAD');
5391 if (!stat_exists "debian/patches") {
5393 "\n ... debian/patches is missing; perhaps this is a patch queue branch?";
5397 if ($quilt_mode =~ m/dpm/ &&
5398 ($diffbits->{H2A} & 01)) {
5399 fail +(f_ <<END, $quilt_mode). $fulldiffhint->($oldtiptree,'HEAD');
5400 --quilt=%s specified, implying patches-applied git tree
5401 but git tree differs from result of applying debian/patches to upstream
5404 if ($quilt_mode =~ m/gbp|unapplied/ &&
5405 ($diffbits->{O2A} & 01)) { # some patches
5406 progress __ "dgit view: creating patches-applied version using gbp pq";
5407 runcmd shell_cmd 'exec >/dev/null', gbp_pq, qw(import);
5408 # gbp pq import creates a fresh branch; push back to dgit-view
5409 runcmd @git, qw(update-ref refs/heads/dgit-view HEAD);
5410 runcmd @git, qw(checkout -q dgit-view);
5412 if ($quilt_mode =~ m/gbp|dpm/ &&
5413 ($diffbits->{O2A} & 02)) {
5414 fail f_ <<END, $quilt_mode;
5415 --quilt=%s specified, implying that HEAD is for use with a
5416 tool which does not create patches for changes to upstream
5417 .gitignores: but, such patches exist in debian/patches.
5420 if (($diffbits->{O2H} & 02) && # user has modified .gitignore
5421 !($diffbits->{O2A} & 02)) { # patches do not change .gitignore
5423 "dgit view: creating patch to represent .gitignore changes";
5424 ensuredir "debian/patches";
5425 my $gipatch = "debian/patches/auto-gitignore";
5426 open GIPATCH, ">>", "$gipatch" or confess "$gipatch: $!";
5427 stat GIPATCH or confess "$gipatch: $!";
5428 fail f_ "%s already exists; but want to create it".
5429 " to record .gitignore changes",
5432 print GIPATCH +(__ <<END).<<ENDU or die "$gipatch: $!";
5433 Subject: Update .gitignore from Debian packaging branch
5435 The Debian packaging git branch contains these updates to the upstream
5436 .gitignore file(s). This patch is autogenerated, to provide these
5437 updates to users of the official Debian archive view of the package.
5440 [dgit ($our_version) update-gitignore]
5443 close GIPATCH or die "$gipatch: $!";
5444 runcmd shell_cmd "exec >>$gipatch", @git, qw(diff),
5445 $unapplied, $headref, "--", sort keys %$editedignores;
5446 open SERIES, "+>>", "debian/patches/series" or confess "$!";
5447 defined seek SERIES, -1, 2 or $!==EINVAL or confess "$!";
5449 defined read SERIES, $newline, 1 or confess "$!";
5450 print SERIES "\n" or confess "$!" unless $newline eq "\n";
5451 print SERIES "auto-gitignore\n" or confess "$!";
5452 close SERIES or die $!;
5453 runcmd @git, qw(add -f -- debian/patches/series), $gipatch;
5454 commit_admin +(__ <<END).<<ENDU
5455 Commit patch to update .gitignore
5458 [dgit ($our_version) update-gitignore-quilt-fixup]
5463 sub quiltify ($$$$) {
5464 my ($clogp,$target,$oldtiptree,$failsuggestion) = @_;
5466 # Quilt patchification algorithm
5468 # We search backwards through the history of the main tree's HEAD
5469 # (T) looking for a start commit S whose tree object is identical
5470 # to to the patch tip tree (ie the tree corresponding to the
5471 # current dpkg-committed patch series). For these purposes
5472 # `identical' disregards anything in debian/ - this wrinkle is
5473 # necessary because dpkg-source treates debian/ specially.
5475 # We can only traverse edges where at most one of the ancestors'
5476 # trees differs (in changes outside in debian/). And we cannot
5477 # handle edges which change .pc/ or debian/patches. To avoid
5478 # going down a rathole we avoid traversing edges which introduce
5479 # debian/rules or debian/control. And we set a limit on the
5480 # number of edges we are willing to look at.
5482 # If we succeed, we walk forwards again. For each traversed edge
5483 # PC (with P parent, C child) (starting with P=S and ending with
5484 # C=T) to we do this:
5486 # - dpkg-source --commit with a patch name and message derived from C
5487 # After traversing PT, we git commit the changes which
5488 # should be contained within debian/patches.
5490 # The search for the path S..T is breadth-first. We maintain a
5491 # todo list containing search nodes. A search node identifies a
5492 # commit, and looks something like this:
5494 # Commit => $git_commit_id,
5495 # Child => $c, # or undef if P=T
5496 # Whynot => $reason_edge_PC_unsuitable, # in @nots only
5497 # Nontrivial => true iff $p..$c has relevant changes
5504 my %considered; # saves being exponential on some weird graphs
5506 my $t_sentinels = quiltify_tree_sentinelfiles $target;
5509 my ($search,$whynot) = @_;
5510 printdebug " search NOT $search->{Commit} $whynot\n";
5511 $search->{Whynot} = $whynot;
5512 push @nots, $search;
5513 no warnings qw(exiting);
5522 my $c = shift @todo;
5523 next if $considered{$c->{Commit}}++;
5525 $not->($c, __ "maximum search space exceeded") if --$max_work <= 0;
5527 printdebug "quiltify investigate $c->{Commit}\n";
5530 if (!quiltify_trees_differ $c->{Commit}, $oldtiptree) {
5531 printdebug " search finished hooray!\n";
5536 quiltify_nofix_bail " $c->{Commit}", " (tree object $oldtiptree)";
5537 if ($quilt_mode eq 'smash') {
5538 printdebug " search quitting smash\n";
5542 my $c_sentinels = quiltify_tree_sentinelfiles $c->{Commit};
5543 $not->($c, f_ "has %s not %s", $c_sentinels, $t_sentinels)
5544 if $c_sentinels ne $t_sentinels;
5546 my $commitdata = cmdoutput @git, qw(cat-file commit), $c->{Commit};
5547 $commitdata =~ m/\n\n/;
5549 my @parents = ($commitdata =~ m/^parent (\w+)$/gm);
5550 @parents = map { { Commit => $_, Child => $c } } @parents;
5552 $not->($c, __ "root commit") if !@parents;
5554 foreach my $p (@parents) {
5555 $p->{Nontrivial}= quiltify_trees_differ $p->{Commit},$c->{Commit};
5557 my $ndiffers = grep { $_->{Nontrivial} } @parents;
5558 $not->($c, f_ "merge (%s nontrivial parents)", $ndiffers)
5561 foreach my $p (@parents) {
5562 printdebug "considering C=$c->{Commit} P=$p->{Commit}\n";
5564 my @cmd= (@git, qw(diff-tree -r --name-only),
5565 $p->{Commit},$c->{Commit},
5566 qw(-- debian/patches .pc debian/source/format));
5567 my $patchstackchange = cmdoutput @cmd;
5568 if (length $patchstackchange) {
5569 $patchstackchange =~ s/\n/,/g;
5570 $not->($p, f_ "changed %s", $patchstackchange);
5573 printdebug " search queue P=$p->{Commit} ",
5574 ($p->{Nontrivial} ? "NT" : "triv"),"\n";
5580 printdebug "quiltify want to smash\n";
5583 my $x = $_[0]{Commit};
5584 $x =~ s/(.*?[0-9a-z]{8})[0-9a-z]*$/$1/;
5587 if ($quilt_mode eq 'linear') {
5589 "\n%s: error: quilt fixup cannot be linear. Stopped at:\n",
5591 my $all_gdr = !!@nots;
5592 foreach my $notp (@nots) {
5593 my $c = $notp->{Child};
5594 my $cprange = $abbrev->($notp);
5595 $cprange .= "..".$abbrev->($c) if $c;
5596 print STDERR f_ "%s: %s: %s\n",
5597 $us, $cprange, $notp->{Whynot};
5598 $all_gdr &&= $notp->{Child} &&
5599 (git_cat_file $notp->{Child}{Commit}, 'commit')
5600 =~ m{^\[git-debrebase(?! split[: ]).*\]$}m;
5604 [ grep { $_->[0] ne 'quilt-mode' } @$failsuggestion ]
5606 print STDERR "$us: $_->[1]\n" foreach @$failsuggestion;
5608 "quilt history linearisation failed. Search \`quilt fixup' in dgit(7).\n";
5609 } elsif ($quilt_mode eq 'smash') {
5610 } elsif ($quilt_mode eq 'auto') {
5611 progress __ "quilt fixup cannot be linear, smashing...";
5613 confess "$quilt_mode ?";
5616 my $time = $ENV{'GIT_COMMITTER_DATE'} || time;
5617 $time =~ s/\s.*//; # trim timezone from GIT_COMMITTER_DATE
5619 my $msg = cmdoutput @git, qw(log), "-n$ncommits";
5621 quiltify_dpkg_commit "auto-$version-$target-$time",
5622 (getfield $clogp, 'Maintainer'),
5623 (f_ "Automatically generated patch (%s)\n".
5624 "Last (up to) %s git changes, FYI:\n\n",
5625 $clogp->{Version}, $ncommits).
5630 progress __ "quiltify linearisation planning successful, executing...";
5632 for (my $p = $sref_S;
5633 my $c = $p->{Child};
5635 printdebug "quiltify traverse $p->{Commit}..$c->{Commit}\n";
5636 next unless $p->{Nontrivial};
5638 my $cc = $c->{Commit};
5640 my $commitdata = cmdoutput @git, qw(cat-file commit), $cc;
5641 $commitdata =~ m/\n\n/ or die "$c ?";
5644 $commitdata =~ m/^author (.*) \d+ [-+0-9]+$/m or die "$cc ?";
5647 my $commitdate = cmdoutput
5648 @git, qw(log -n1 --pretty=format:%aD), $cc;
5650 $msg =~ s/^(.*)\n*/$1\n/ or die "$cc $msg ?";
5652 my $strip_nls = sub { $msg =~ s/\n+$//; $msg .= "\n"; };
5659 my $gbp_check_suitable = sub {
5664 die __ "contains unexpected slashes\n" if m{//} || m{/$};
5665 die __ "contains leading punctuation\n" if m{^\W} || m{/\W};
5666 die __ "contains bad character(s)\n" if m{[^-a-z0-9_.+=~/]}i;
5667 die __ "is series file\n" if m{$series_filename_re}o;
5668 die __ "too long\n" if length > 200;
5670 return $_ unless $@;
5672 "quiltifying commit %s: ignoring/dropping Gbp-Pq %s: %s",
5677 if ($msg =~ s/^ (?: gbp(?:-pq)? : \s* name \s+ |
5679 (\S+) \s* \n //ixm) {
5680 $patchname = $gbp_check_suitable->($1, 'Name');
5682 if ($msg =~ s/^ (?: gbp(?:-pq)? : \s* topic \s+ |
5684 (\S+) \s* \n //ixm) {
5685 $patchdir = $gbp_check_suitable->($1, 'Topic');
5690 if (!defined $patchname) {
5691 $patchname = $title;
5692 $patchname =~ s/[.:]$//;
5695 my $converter = new Text::Iconv qw(UTF-8 ASCII//TRANSLIT);
5696 my $translitname = $converter->convert($patchname);
5697 die unless defined $translitname;
5698 $patchname = $translitname;
5701 +(f_ "dgit: patch title transliteration error: %s", $@)
5703 $patchname =~ y/ A-Z/-a-z/;
5704 $patchname =~ y/-a-z0-9_.+=~//cd;
5705 $patchname =~ s/^\W/x-$&/;
5706 $patchname = substr($patchname,0,40);
5707 $patchname .= ".patch";
5709 if (!defined $patchdir) {
5712 if (length $patchdir) {
5713 $patchname = "$patchdir/$patchname";
5715 if ($patchname =~ m{^(.*)/}) {
5716 mkpath "debian/patches/$1";
5721 stat "debian/patches/$patchname$index";
5723 $!==ENOENT or confess "$patchname$index $!";
5725 runcmd @git, qw(checkout -q), $cc;
5727 # We use the tip's changelog so that dpkg-source doesn't
5728 # produce complaining messages from dpkg-parsechangelog. None
5729 # of the information dpkg-source gets from the changelog is
5730 # actually relevant - it gets put into the original message
5731 # which dpkg-source provides our stunt editor, and then
5733 runcmd @git, qw(checkout -q), $target, qw(debian/changelog);
5735 quiltify_dpkg_commit "$patchname$index", $author, $msg,
5736 "Date: $commitdate\n".
5737 "X-Dgit-Generated: $clogp->{Version} $cc\n";
5739 runcmd @git, qw(checkout -q), $cc, qw(debian/changelog);
5743 sub build_maybe_quilt_fixup () {
5744 my ($format,$fopts) = get_source_format;
5745 return unless madformat_wantfixup $format;
5748 check_for_vendor_patches();
5750 my $clogp = parsechangelog();
5751 my $headref = git_rev_parse('HEAD');
5752 my $symref = git_get_symref();
5753 my $upstreamversion = upstreamversion $version;
5756 changedir $playground;
5758 my $splitbrain_cachekey;
5760 if (do_split_brain()) {
5762 ($cachehit, $splitbrain_cachekey) =
5763 quilt_check_splitbrain_cache($headref, $upstreamversion);
5770 unpack_playtree_need_cd_work($headref);
5771 if (do_split_brain()) {
5772 runcmd @git, qw(checkout -q -b dgit-view);
5773 # so long as work is not deleted, its current branch will
5774 # remain dgit-view, rather than master, so subsequent calls to
5775 # unpack_playtree_need_cd_work
5776 # will DTRT, resetting dgit-view.
5777 confess if $made_split_brain;
5778 $made_split_brain = 1;
5782 if ($fopts->{'single-debian-patch'}) {
5784 "quilt mode %s does not make sense (or is not supported) with single-debian-patch",
5786 if quiltmode_splitting();
5787 quilt_fixup_singlepatch($clogp, $headref, $upstreamversion);
5789 quilt_fixup_multipatch($clogp, $headref, $upstreamversion,
5790 $splitbrain_cachekey);
5793 if (do_split_brain()) {
5794 my $dgitview = git_rev_parse 'HEAD';
5797 reflog_cache_insert "refs/$splitbraincache",
5798 $splitbrain_cachekey, $dgitview;
5800 changedir "$playground/work";
5802 my $saved = maybe_split_brain_save $headref, $dgitview, __ "converted";
5803 progress f_ "dgit view: created (%s)", $saved;
5807 runcmd_ordryrun_local
5808 @git, qw(pull --ff-only -q), "$playground/work", qw(master);
5811 sub build_check_quilt_splitbrain () {
5812 build_maybe_quilt_fixup();
5815 sub unpack_playtree_need_cd_work ($) {
5818 # prep_ud() must have been called already.
5819 if (!chdir "work") {
5820 # Check in the filesystem because sometimes we run prep_ud
5821 # in between multiple calls to unpack_playtree_need_cd_work.
5822 confess "$!" unless $!==ENOENT;
5823 mkdir "work" or confess "$!";
5825 mktree_in_ud_here();
5827 runcmd @git, qw(reset -q --hard), $headref;
5830 sub unpack_playtree_linkorigs ($$) {
5831 my ($upstreamversion, $fn) = @_;
5832 # calls $fn->($leafname);
5834 my $bpd_abs = bpd_abs();
5836 dotdot_bpd_transfer_origs $bpd_abs, $upstreamversion, sub { 1 };
5838 opendir QFD, $bpd_abs or fail "buildproductsdir: $bpd_abs: $!";
5839 while ($!=0, defined(my $leaf = readdir QFD)) {
5840 my $f = bpd_abs()."/".$leaf;
5842 local ($debuglevel) = $debuglevel-1;
5843 printdebug "QF linkorigs bpd $leaf, $f ?\n";
5845 next unless is_orig_file_of_vsn $leaf, $upstreamversion;
5846 printdebug "QF linkorigs $leaf, $f Y\n";
5847 link_ltarget $f, $leaf or die "$leaf $!";
5850 die "$buildproductsdir: $!" if $!;
5854 sub quilt_fixup_delete_pc () {
5855 runcmd @git, qw(rm -rqf .pc);
5856 commit_admin +(__ <<END).<<ENDU
5857 Commit removal of .pc (quilt series tracking data)
5860 [dgit ($our_version) upgrade quilt-remove-pc]
5864 sub quilt_fixup_singlepatch ($$$) {
5865 my ($clogp, $headref, $upstreamversion) = @_;
5867 progress __ "starting quiltify (single-debian-patch)";
5869 # dpkg-source --commit generates new patches even if
5870 # single-debian-patch is in debian/source/options. In order to
5871 # get it to generate debian/patches/debian-changes, it is
5872 # necessary to build the source package.
5874 unpack_playtree_linkorigs($upstreamversion, sub { });
5875 unpack_playtree_need_cd_work($headref);
5877 rmtree("debian/patches");
5879 runcmd @dpkgsource, qw(-b .);
5881 runcmd @dpkgsource, qw(-x), (srcfn $version, ".dsc");
5882 rename srcfn("$upstreamversion", "/debian/patches"),
5883 "work/debian/patches"
5885 or confess "install d/patches: $!";
5888 commit_quilty_patch();
5891 sub quilt_need_fake_dsc ($) {
5892 # cwd should be playground
5893 my ($upstreamversion) = @_;
5895 return if stat_exists "fake.dsc";
5896 # ^ OK to test this as a sentinel because if we created it
5897 # we must either have done the rest too, or crashed.
5899 my $fakeversion="$upstreamversion-~~DGITFAKE";
5901 my $fakedsc=new IO::File 'fake.dsc', '>' or confess "$!";
5902 print $fakedsc <<END or confess "$!";
5905 Version: $fakeversion
5909 my $dscaddfile=sub {
5912 my $md = new Digest::MD5;
5914 my $fh = new IO::File $leaf, '<' or die "$leaf $!";
5915 stat $fh or confess "$!";
5919 print $fakedsc " ".$md->hexdigest." $size $leaf\n" or confess "$!";
5922 unpack_playtree_linkorigs($upstreamversion, $dscaddfile);
5924 my @files=qw(debian/source/format debian/rules
5925 debian/control debian/changelog);
5926 foreach my $maybe (qw(debian/patches debian/source/options
5927 debian/tests/control)) {
5928 next unless stat_exists "$maindir/$maybe";
5929 push @files, $maybe;
5932 my $debtar= srcfn $fakeversion,'.debian.tar.gz';
5933 runcmd qw(env GZIP=-1n tar -zcf), "./$debtar", qw(-C), $maindir, @files;
5935 $dscaddfile->($debtar);
5936 close $fakedsc or confess "$!";
5939 sub quilt_fakedsc2unapplied ($$) {
5940 my ($headref, $upstreamversion) = @_;
5941 # must be run in the playground
5942 # quilt_need_fake_dsc must have been called
5944 quilt_need_fake_dsc($upstreamversion);
5946 'exec dpkg-source --no-check --skip-patches -x fake.dsc >/dev/null';
5948 my $fakexdir= $package.'-'.(stripepoch $upstreamversion);
5949 rename $fakexdir, "fake" or die "$fakexdir $!";
5953 remove_stray_gits(__ "source package");
5954 mktree_in_ud_here();
5958 rmtree 'debian'; # git checkout commitish paths does not delete!
5959 runcmd @git, qw(checkout -f), $headref, qw(-- debian);
5960 my $unapplied=git_add_write_tree();
5961 printdebug "fake orig tree object $unapplied\n";
5965 sub quilt_check_splitbrain_cache ($$) {
5966 my ($headref, $upstreamversion) = @_;
5967 # Called only if we are in (potentially) split brain mode.
5968 # Called in playground.
5969 # Computes the cache key and looks in the cache.
5970 # Returns ($dgit_view_commitid, $cachekey) or (undef, $cachekey)
5972 quilt_need_fake_dsc($upstreamversion);
5974 my $splitbrain_cachekey;
5977 "dgit: split brain (separate dgit view) may be needed (--quilt=%s).",
5979 # we look in the reflog of dgit-intern/quilt-cache
5980 # we look for an entry whose message is the key for the cache lookup
5981 my @cachekey = (qw(dgit), $our_version);
5982 push @cachekey, $upstreamversion;
5983 push @cachekey, $quilt_mode;
5984 push @cachekey, $headref;
5986 push @cachekey, hashfile('fake.dsc');
5988 my $srcshash = Digest::SHA->new(256);
5989 my %sfs = ( %INC, '$0(dgit)' => $0 );
5990 foreach my $sfk (sort keys %sfs) {
5991 next unless $sfk =~ m/^\$0\b/ || $sfk =~ m{^Debian/Dgit\b};
5992 $srcshash->add($sfk," ");
5993 $srcshash->add(hashfile($sfs{$sfk}));
5994 $srcshash->add("\n");
5996 push @cachekey, $srcshash->hexdigest();
5997 $splitbrain_cachekey = "@cachekey";
5999 printdebug "splitbrain cachekey $splitbrain_cachekey\n";
6001 my $cachehit = reflog_cache_lookup
6002 "refs/$splitbraincache", $splitbrain_cachekey;
6005 unpack_playtree_need_cd_work($headref);
6006 my $saved = maybe_split_brain_save $headref, $cachehit, "cache-hit";
6007 if ($cachehit ne $headref) {
6008 progress f_ "dgit view: found cached (%s)", $saved;
6009 runcmd @git, qw(checkout -q -b dgit-view), $cachehit;
6010 $made_split_brain = 1;
6011 return ($cachehit, $splitbrain_cachekey);
6013 progress __ "dgit view: found cached, no changes required";
6014 return ($headref, $splitbrain_cachekey);
6017 printdebug "splitbrain cache miss\n";
6018 return (undef, $splitbrain_cachekey);
6021 sub quilt_fixup_multipatch ($$$) {
6022 my ($clogp, $headref, $upstreamversion, $splitbrain_cachekey) = @_;
6024 progress f_ "examining quilt state (multiple patches, %s mode)",
6028 # - honour any existing .pc in case it has any strangeness
6029 # - determine the git commit corresponding to the tip of
6030 # the patch stack (if there is one)
6031 # - if there is such a git commit, convert each subsequent
6032 # git commit into a quilt patch with dpkg-source --commit
6033 # - otherwise convert all the differences in the tree into
6034 # a single git commit
6038 # Our git tree doesn't necessarily contain .pc. (Some versions of
6039 # dgit would include the .pc in the git tree.) If there isn't
6040 # one, we need to generate one by unpacking the patches that we
6043 # We first look for a .pc in the git tree. If there is one, we
6044 # will use it. (This is not the normal case.)
6046 # Otherwise need to regenerate .pc so that dpkg-source --commit
6047 # can work. We do this as follows:
6048 # 1. Collect all relevant .orig from parent directory
6049 # 2. Generate a debian.tar.gz out of
6050 # debian/{patches,rules,source/format,source/options}
6051 # 3. Generate a fake .dsc containing just these fields:
6052 # Format Source Version Files
6053 # 4. Extract the fake .dsc
6054 # Now the fake .dsc has a .pc directory.
6055 # (In fact we do this in every case, because in future we will
6056 # want to search for a good base commit for generating patches.)
6058 # Then we can actually do the dpkg-source --commit
6059 # 1. Make a new working tree with the same object
6060 # store as our main tree and check out the main
6062 # 2. Copy .pc from the fake's extraction, if necessary
6063 # 3. Run dpkg-source --commit
6064 # 4. If the result has changes to debian/, then
6065 # - git add them them
6066 # - git add .pc if we had a .pc in-tree
6068 # 5. If we had a .pc in-tree, delete it, and git commit
6069 # 6. Back in the main tree, fast forward to the new HEAD
6071 # Another situation we may have to cope with is gbp-style
6072 # patches-unapplied trees.
6074 # We would want to detect these, so we know to escape into
6075 # quilt_fixup_gbp. However, this is in general not possible.
6076 # Consider a package with a one patch which the dgit user reverts
6077 # (with git revert or the moral equivalent).
6079 # That is indistinguishable in contents from a patches-unapplied
6080 # tree. And looking at the history to distinguish them is not
6081 # useful because the user might have made a confusing-looking git
6082 # history structure (which ought to produce an error if dgit can't
6083 # cope, not a silent reintroduction of an unwanted patch).
6085 # So gbp users will have to pass an option. But we can usually
6086 # detect their failure to do so: if the tree is not a clean
6087 # patches-applied tree, quilt linearisation fails, but the tree
6088 # _is_ a clean patches-unapplied tree, we can suggest that maybe
6089 # they want --quilt=unapplied.
6091 # To help detect this, when we are extracting the fake dsc, we
6092 # first extract it with --skip-patches, and then apply the patches
6093 # afterwards with dpkg-source --before-build. That lets us save a
6094 # tree object corresponding to .origs.
6096 if ($quilt_mode eq 'linear'
6097 && branch_is_gdr($headref)) {
6098 # This is much faster. It also makes patches that gdr
6099 # likes better for future updates without laundering.
6101 # However, it can fail in some casses where we would
6102 # succeed: if there are existing patches, which correspond
6103 # to a prefix of the branch, but are not in gbp/gdr
6104 # format, gdr will fail (exiting status 7), but we might
6105 # be able to figure out where to start linearising. That
6106 # will be slower so hopefully there's not much to do.
6108 unpack_playtree_need_cd_work $headref;
6110 my @cmd = (@git_debrebase,
6111 qw(--noop-ok -funclean-mixed -funclean-ordering
6112 make-patches --quiet-would-amend));
6113 # We tolerate soe snags that gdr wouldn't, by default.
6119 and not ($? == 7*256 or
6120 $? == -1 && $!==ENOENT);
6124 $headref = git_rev_parse('HEAD');
6129 my $unapplied=quilt_fakedsc2unapplied($headref, $upstreamversion);
6133 my @bbcmd = (qw(sh -ec), 'exec dpkg-source --before-build . >/dev/null');
6135 if (system @bbcmd) {
6136 failedcmd @bbcmd if $? < 0;
6138 failed to apply your git tree's patch stack (from debian/patches/) to
6139 the corresponding upstream tarball(s). Your source tree and .orig
6140 are probably too inconsistent. dgit can only fix up certain kinds of
6141 anomaly (depending on the quilt mode). Please see --quilt= in dgit(1).
6147 unpack_playtree_need_cd_work($headref);
6150 if (stat_exists ".pc") {
6152 progress __ "Tree already contains .pc - will use it then delete it.";
6155 rename '../fake/.pc','.pc' or confess "$!";
6158 changedir '../fake';
6160 my $oldtiptree=git_add_write_tree();
6161 printdebug "fake o+d/p tree object $unapplied\n";
6162 changedir '../work';
6165 # We calculate some guesswork now about what kind of tree this might
6166 # be. This is mostly for error reporting.
6172 # O = orig, without patches applied
6173 # A = "applied", ie orig with H's debian/patches applied
6174 O2H => quiltify_trees_differ($unapplied,$headref, 1,
6175 \%editedignores, \@unrepres),
6176 H2A => quiltify_trees_differ($headref, $oldtiptree,1),
6177 O2A => quiltify_trees_differ($unapplied,$oldtiptree,1),
6181 foreach my $bits (qw(01 02)) {
6182 foreach my $v (qw(O2H O2A H2A)) {
6183 push @dl, ($diffbits->{$v} & $bits) ? '##' : '==';
6186 printdebug "differences \@dl @dl.\n";
6189 "%s: base trees orig=%.20s o+d/p=%.20s",
6190 $us, $unapplied, $oldtiptree;
6192 "%s: quilt differences: src: %s orig %s gitignores: %s orig %s\n".
6193 "%s: quilt differences: HEAD %s o+d/p HEAD %s o+d/p",
6194 $us, $dl[0], $dl[1], $dl[3], $dl[4],
6195 $us, $dl[2], $dl[5];
6198 print STDERR f_ "dgit: cannot represent change: %s: %s\n",
6201 forceable_fail [qw(unrepresentable)], __ <<END;
6202 HEAD has changes to .orig[s] which are not representable by `3.0 (quilt)'
6207 if (!($diffbits->{O2H} & $diffbits->{O2A})) {
6208 push @failsuggestion, [ 'unapplied', __
6209 "This might be a patches-unapplied branch." ];
6210 } elsif (!($diffbits->{H2A} & $diffbits->{O2A})) {
6211 push @failsuggestion, [ 'applied', __
6212 "This might be a patches-applied branch." ];
6214 push @failsuggestion, [ 'quilt-mode', __
6215 "Maybe you need one of --[quilt=]gbp --[quilt=]dpm --quilt=unapplied ?" ];
6217 push @failsuggestion, [ 'gitattrs', __
6218 "Warning: Tree has .gitattributes. See GITATTRIBUTES in dgit(7)." ]
6219 if stat_exists '.gitattributes';
6221 push @failsuggestion, [ 'origs', __
6222 "Maybe orig tarball(s) are not identical to git representation?" ];
6224 if (quiltmode_splitting()) {
6225 quiltify_splitting($clogp, $unapplied, $headref, $oldtiptree,
6226 $diffbits, \%editedignores,
6227 $splitbrain_cachekey);
6231 progress f_ "starting quiltify (multiple patches, %s mode)", $quilt_mode;
6232 quiltify($clogp,$headref,$oldtiptree,\@failsuggestion);
6233 runcmd @git, qw(checkout -q), (qw(master dgit-view)[do_split_brain()]);
6235 if (!open P, '>>', ".pc/applied-patches") {
6236 $!==&ENOENT or confess "$!";
6241 commit_quilty_patch();
6243 if ($mustdeletepc) {
6244 quilt_fixup_delete_pc();
6248 sub quilt_fixup_editor () {
6249 my $descfn = $ENV{$fakeeditorenv};
6250 my $editing = $ARGV[$#ARGV];
6251 open I1, '<', $descfn or confess "$descfn: $!";
6252 open I2, '<', $editing or confess "$editing: $!";
6253 unlink $editing or confess "$editing: $!";
6254 open O, '>', $editing or confess "$editing: $!";
6255 while (<I1>) { print O or confess "$!"; } I1->error and confess "$!";
6258 $copying ||= m/^\-\-\- /;
6259 next unless $copying;
6260 print O or confess "$!";
6262 I2->error and confess "$!";
6267 sub maybe_apply_patches_dirtily () {
6268 return unless $quilt_mode =~ m/gbp|unapplied/;
6269 print STDERR __ <<END or confess "$!";
6271 dgit: Building, or cleaning with rules target, in patches-unapplied tree.
6272 dgit: Have to apply the patches - making the tree dirty.
6273 dgit: (Consider specifying --clean=git and (or) using dgit sbuild.)
6276 $patches_applied_dirtily = 01;
6277 $patches_applied_dirtily |= 02 unless stat_exists '.pc';
6278 runcmd qw(dpkg-source --before-build .);
6281 sub maybe_unapply_patches_again () {
6282 progress __ "dgit: Unapplying patches again to tidy up the tree."
6283 if $patches_applied_dirtily;
6284 runcmd qw(dpkg-source --after-build .)
6285 if $patches_applied_dirtily & 01;
6287 if $patches_applied_dirtily & 02;
6288 $patches_applied_dirtily = 0;
6291 #----- other building -----
6293 sub clean_tree_check_git ($$$) {
6294 my ($honour_ignores, $message, $ignmessage) = @_;
6295 my @cmd = (@git, qw(clean -dn));
6296 push @cmd, qw(-x) unless $honour_ignores;
6297 my $leftovers = cmdoutput @cmd;
6298 if (length $leftovers) {
6299 print STDERR $leftovers, "\n" or confess "$!";
6300 $message .= $ignmessage if $honour_ignores;
6305 sub clean_tree_check_git_wd ($) {
6307 return if $cleanmode =~ m{no-check};
6308 return if $patches_applied_dirtily; # yuk
6309 clean_tree_check_git +($cleanmode !~ m{all-check}),
6310 $message, "\n".__ <<END;
6311 If this is just missing .gitignore entries, use a different clean
6312 mode, eg --clean=dpkg-source,no-check (-wdn/-wddn) to ignore them
6313 or --clean=git (-wg/-wgf) to use \`git clean' instead.
6317 sub clean_tree_check () {
6318 # This function needs to not care about modified but tracked files.
6319 # That was done by check_not_dirty, and by now we may have run
6320 # the rules clean target which might modify tracked files (!)
6321 if ($cleanmode =~ m{^check}) {
6322 clean_tree_check_git +($cleanmode =~ m{ignores}), __
6323 "tree contains uncommitted files and --clean=check specified", '';
6324 } elsif ($cleanmode =~ m{^dpkg-source}) {
6325 clean_tree_check_git_wd __
6326 "tree contains uncommitted files (NB dgit didn't run rules clean)";
6327 } elsif ($cleanmode =~ m{^git}) {
6328 clean_tree_check_git 1, __
6329 "tree contains uncommited, untracked, unignored files\n".
6330 "You can use --clean=git[-ff],always (-wga/-wgfa) to delete them.", '';
6331 } elsif ($cleanmode eq 'none') {
6333 confess "$cleanmode ?";
6338 # We always clean the tree ourselves, rather than leave it to the
6339 # builder (dpkg-source, or soemthing which calls dpkg-source).
6340 if ($cleanmode =~ m{^dpkg-source}) {
6341 my @cmd = @dpkgbuildpackage;
6342 push @cmd, qw(-d) if $cleanmode =~ m{^dpkg-source-d};
6343 push @cmd, qw(-T clean);
6344 maybe_apply_patches_dirtily();
6345 runcmd_ordryrun_local @cmd;
6346 clean_tree_check_git_wd __
6347 "tree contains uncommitted files (after running rules clean)";
6348 } elsif ($cleanmode =~ m{^git(?!-)}) {
6349 runcmd_ordryrun_local @git, qw(clean -xdf);
6350 } elsif ($cleanmode =~ m{^git-ff}) {
6351 runcmd_ordryrun_local @git, qw(clean -xdff);
6352 } elsif ($cleanmode =~ m{^check}) {
6354 } elsif ($cleanmode eq 'none') {
6356 confess "$cleanmode ?";
6361 badusage __ "clean takes no additional arguments" if @ARGV;
6364 maybe_unapply_patches_again();
6367 # return values from massage_dbp_args are one or both of these flags
6368 sub WANTSRC_SOURCE () { 01; } # caller should build source (separately)
6369 sub WANTSRC_BUILDER () { 02; } # caller should run dpkg-buildpackage
6371 sub build_or_push_prep_early () {
6372 our $build_or_push_prep_early_done //= 0;
6373 return if $build_or_push_prep_early_done++;
6374 badusage f_ "-p is not allowed with dgit %s", $subcommand
6375 if defined $package;
6376 my $clogp = parsechangelog();
6377 $isuite = getfield $clogp, 'Distribution';
6378 $package = getfield $clogp, 'Source';
6379 $version = getfield $clogp, 'Version';
6380 $dscfn = dscfn($version);
6383 sub build_or_push_prep_modes () {
6384 determine_whether_split_brain();
6386 fail __ "dgit: --include-dirty is not supported with split view".
6387 " (including with view-splitting quilt modes)"
6388 if do_split_brain() && $includedirty;
6391 sub build_prep_early () {
6392 build_or_push_prep_early();
6394 build_or_push_prep_modes();
6398 sub build_prep ($) {
6402 if (!building_source_in_playtree() || ($wantsrc & WANTSRC_BUILDER)
6403 # Clean the tree because we're going to use the contents of
6404 # $maindir. (We trying to include dirty changes in the source
6405 # package, or we are running the builder in $maindir.)
6406 || $cleanmode =~ m{always}) {
6407 # Or because the user asked us to.
6410 # We don't actually need to do anything in $maindir, but we
6411 # should do some kind of cleanliness check because (i) the
6412 # user may have forgotten a `git add', and (ii) if the user
6413 # said -wc we should still do the check.
6416 build_check_quilt_splitbrain();
6418 my $pat = changespat $version;
6419 foreach my $f (glob "$buildproductsdir/$pat") {
6422 fail f_ "remove old changes file %s: %s", $f, $!;
6424 progress f_ "would remove %s", $f;
6430 sub changesopts_initial () {
6431 my @opts =@changesopts[1..$#changesopts];
6434 sub changesopts_version () {
6435 if (!defined $changes_since_version) {
6438 @vsns = archive_query('archive_query');
6439 my @quirk = access_quirk();
6440 if ($quirk[0] eq 'backports') {
6441 local $isuite = $quirk[2];
6443 canonicalise_suite();
6444 push @vsns, archive_query('archive_query');
6450 "archive query failed (queried because --since-version not specified)";
6453 @vsns = map { $_->[0] } @vsns;
6454 @vsns = sort { -version_compare($a, $b) } @vsns;
6455 $changes_since_version = $vsns[0];
6456 progress f_ "changelog will contain changes since %s", $vsns[0];
6458 $changes_since_version = '_';
6459 progress __ "package seems new, not specifying -v<version>";
6462 if ($changes_since_version ne '_') {
6463 return ("-v$changes_since_version");
6469 sub changesopts () {
6470 return (changesopts_initial(), changesopts_version());
6473 sub massage_dbp_args ($;$) {
6474 my ($cmd,$xargs) = @_;
6475 # Since we split the source build out so we can do strange things
6476 # to it, massage the arguments to dpkg-buildpackage so that the
6477 # main build doessn't build source (or add an argument to stop it
6478 # building source by default).
6479 debugcmd '#massaging#', @$cmd if $debuglevel>1;
6480 # -nc has the side effect of specifying -b if nothing else specified
6481 # and some combinations of -S, -b, et al, are errors, rather than
6482 # later simply overriding earlie. So we need to:
6483 # - search the command line for these options
6484 # - pick the last one
6485 # - perhaps add our own as a default
6486 # - perhaps adjust it to the corresponding non-source-building version
6488 foreach my $l ($cmd, $xargs) {
6490 @$l = grep { !(m/^-[SgGFABb]$|^--build=/s and $dmode=$_) } @$l;
6493 #print STDERR "MASS1 ",Dumper($cmd, $xargs, $dmode);
6494 my $r = WANTSRC_BUILDER;
6495 printdebug "massage split $dmode.\n";
6496 if ($dmode =~ s/^--build=//) {
6498 my @d = split /,/, $dmode;
6499 $r |= WANTSRC_SOURCE if grep { s/^full$/binary/ } @d;
6500 $r |= WANTSRC_SOURCE if grep { s/^source$// } @d;
6501 $r |= WANTSRC_BUILDER if grep { m/./ } @d;
6502 fail __ "Wanted to build nothing!" unless $r;
6503 $dmode = '--build='. join ',', grep m/./, @d;
6506 $dmode =~ m/[S]/ ? WANTSRC_SOURCE :
6507 $dmode =~ y/gGF/ABb/ ? WANTSRC_SOURCE | WANTSRC_BUILDER :
6508 $dmode =~ m/[ABb]/ ? WANTSRC_BUILDER :
6511 printdebug "massage done $r $dmode.\n";
6513 #print STDERR "MASS2 ",Dumper($cmd, $xargs, $r);
6519 my $wasdir = must_getcwd();
6520 changedir $buildproductsdir;
6525 # this sub must run with CWD=$buildproductsdir (eg in in_bpd)
6526 sub postbuild_mergechanges ($) {
6527 my ($msg_if_onlyone) = @_;
6528 # If there is only one .changes file, fail with $msg_if_onlyone,
6529 # or if that is undef, be a no-op.
6530 # Returns the changes file to report to the user.
6531 my $pat = changespat $version;
6532 my @changesfiles = grep { !m/_multi\.changes/ } glob $pat;
6533 @changesfiles = sort {
6534 ($b =~ m/_source\.changes$/ <=> $a =~ m/_source\.changes$/)
6538 if (@changesfiles==1) {
6539 fail +(f_ <<END, "@changesfiles").$msg_if_onlyone
6540 only one changes file from build (%s)
6542 if defined $msg_if_onlyone;
6543 $result = $changesfiles[0];
6544 } elsif (@changesfiles==2) {
6545 my $binchanges = parsecontrol($changesfiles[1], "binary changes file");
6546 foreach my $l (split /\n/, getfield $binchanges, 'Files') {
6547 fail f_ "%s found in binaries changes file %s", $l, $binchanges
6550 runcmd_ordryrun_local @mergechanges, @changesfiles;
6551 my $multichanges = changespat $version,'multi';
6553 stat_exists $multichanges or fail f_
6554 "%s unexpectedly not created by build", $multichanges;
6555 foreach my $cf (glob $pat) {
6556 next if $cf eq $multichanges;
6557 rename "$cf", "$cf.inmulti" or fail f_
6558 "install new changes %s\{,.inmulti}: %s", $cf, $!;
6561 $result = $multichanges;
6563 fail f_ "wrong number of different changes files (%s)",
6566 printdone f_ "build successful, results in %s\n", $result
6570 sub midbuild_checkchanges () {
6571 my $pat = changespat $version;
6572 return if $rmchanges;
6573 my @unwanted = map { s#.*/##; $_; } glob "$bpd_glob/$pat";
6575 $_ ne changespat $version,'source' and
6576 $_ ne changespat $version,'multi'
6578 fail +(f_ <<END, $pat, "@unwanted")
6579 changes files other than source matching %s already present; building would result in ambiguity about the intended results.
6580 Suggest you delete %s.
6585 sub midbuild_checkchanges_vanilla ($) {
6587 midbuild_checkchanges() if $wantsrc == (WANTSRC_SOURCE|WANTSRC_BUILDER);
6590 sub postbuild_mergechanges_vanilla ($) {
6592 if ($wantsrc == (WANTSRC_SOURCE|WANTSRC_BUILDER)) {
6594 postbuild_mergechanges(undef);
6597 printdone __ "build successful\n";
6603 $buildproductsdir eq '..' or print STDERR +(f_ <<END, $us, $us);
6604 %s: warning: build-products-dir set, but not supported by dpkg-buildpackage
6605 %s: warning: build-products-dir will be ignored; files will go to ..
6607 $buildproductsdir = '..';
6608 my @dbp = (@dpkgbuildpackage, qw(-us -uc), changesopts_initial(), @ARGV);
6609 my $wantsrc = massage_dbp_args \@dbp;
6610 build_prep($wantsrc);
6611 if ($wantsrc & WANTSRC_SOURCE) {
6613 midbuild_checkchanges_vanilla $wantsrc;
6615 if ($wantsrc & WANTSRC_BUILDER) {
6616 push @dbp, changesopts_version();
6617 maybe_apply_patches_dirtily();
6618 runcmd_ordryrun_local @dbp;
6620 maybe_unapply_patches_again();
6621 postbuild_mergechanges_vanilla $wantsrc;
6625 $quilt_mode //= 'gbp';
6631 # gbp can make .origs out of thin air. In my tests it does this
6632 # even for a 1.0 format package, with no origs present. So I
6633 # guess it keys off just the version number. We don't know
6634 # exactly what .origs ought to exist, but let's assume that we
6635 # should run gbp if: the version has an upstream part and the main
6637 my $upstreamversion = upstreamversion $version;
6638 my $origfnpat = srcfn $upstreamversion, '.orig.tar.*';
6639 my $gbp_make_orig = $version =~ m/-/ && !(() = glob "$bpd_glob/$origfnpat");
6641 if ($gbp_make_orig) {
6643 $cleanmode = 'none'; # don't do it again
6646 my @dbp = @dpkgbuildpackage;
6648 my $wantsrc = massage_dbp_args \@dbp, \@ARGV;
6650 if (!length $gbp_build[0]) {
6651 if (length executable_on_path('git-buildpackage')) {
6652 $gbp_build[0] = qw(git-buildpackage);
6654 $gbp_build[0] = 'gbp buildpackage';
6657 my @cmd = opts_opt_multi_cmd [], @gbp_build;
6659 push @cmd, (qw(-us -uc --git-no-sign-tags),
6660 "--git-builder=".(shellquote @dbp));
6662 if ($gbp_make_orig) {
6663 my $priv = dgit_privdir();
6664 my $ok = "$priv/origs-gen-ok";
6665 unlink $ok or $!==&ENOENT or confess "$!";
6666 my @origs_cmd = @cmd;
6667 push @origs_cmd, qw(--git-cleaner=true);
6668 push @origs_cmd, "--git-prebuild=".
6669 "touch ".(shellquote $ok)." ".(shellquote "$priv/no-such-dir/ok");
6670 push @origs_cmd, @ARGV;
6672 debugcmd @origs_cmd;
6674 do { local $!; stat_exists $ok; }
6675 or failedcmd @origs_cmd;
6677 dryrun_report @origs_cmd;
6681 build_prep($wantsrc);
6682 if ($wantsrc & WANTSRC_SOURCE) {
6684 midbuild_checkchanges_vanilla $wantsrc;
6686 push @cmd, '--git-cleaner=true';
6688 maybe_unapply_patches_again();
6689 if ($wantsrc & WANTSRC_BUILDER) {
6690 push @cmd, changesopts();
6691 runcmd_ordryrun_local @cmd, @ARGV;
6693 postbuild_mergechanges_vanilla $wantsrc;
6695 sub cmd_git_build { cmd_gbp_build(); } # compatibility with <= 1.0
6697 sub building_source_in_playtree {
6698 # If $includedirty, we have to build the source package from the
6699 # working tree, not a playtree, so that uncommitted changes are
6700 # included (copying or hardlinking them into the playtree could
6703 # Note that if we are building a source package in split brain
6704 # mode we do not support including uncommitted changes, because
6705 # that makes quilt fixup too hard. I.e. ($made_split_brain && (dgit is
6706 # building a source package)) => !$includedirty
6707 return !$includedirty;
6711 $sourcechanges = changespat $version,'source';
6713 unlink "$buildproductsdir/$sourcechanges" or $!==ENOENT
6714 or fail f_ "remove %s: %s", $sourcechanges, $!;
6716 # confess unless !!$made_split_brain == do_split_brain();
6718 my @cmd = (@dpkgsource, qw(-b --));
6720 if (building_source_in_playtree()) {
6722 my $headref = git_rev_parse('HEAD');
6723 # If we are in split brain, there is already a playtree with
6724 # the thing we should package into a .dsc (thanks to quilt
6725 # fixup). If not, make a playtree
6726 prep_ud() unless $made_split_brain;
6727 changedir $playground;
6728 unless ($made_split_brain) {
6729 my $upstreamversion = upstreamversion $version;
6730 unpack_playtree_linkorigs($upstreamversion, sub { });
6731 unpack_playtree_need_cd_work($headref);
6735 $leafdir = basename $maindir;
6737 if ($buildproductsdir ne '..') {
6738 # Well, we are going to run dpkg-source -b which consumes
6739 # origs from .. and generates output there. To make this
6740 # work when the bpd is not .. , we would have to (i) link
6741 # origs from bpd to .. , (ii) check for files that
6742 # dpkg-source -b would/might overwrite, and afterwards
6743 # (iii) move all the outputs back to the bpd (iv) except
6744 # for the origs which should be deleted from .. if they
6745 # weren't there beforehand. And if there is an error and
6746 # we don't run to completion we would necessarily leave a
6747 # mess. This is too much. The real way to fix this
6748 # is for dpkg-source to have bpd support.
6749 confess unless $includedirty;
6751 "--include-dirty not supported with --build-products-dir, sorry";
6756 runcmd_ordryrun_local @cmd, $leafdir;
6759 runcmd_ordryrun_local qw(sh -ec),
6760 'exec >../$1; shift; exec "$@"','x', $sourcechanges,
6761 @dpkggenchanges, qw(-S), changesopts();
6764 printdebug "moving $dscfn, $sourcechanges, etc. to ".bpd_abs()."\n";
6765 $dsc = parsecontrol($dscfn, "source package");
6769 printdebug " renaming ($why) $l\n";
6770 rename_link_xf 0, "$l", bpd_abs()."/$l"
6771 or fail f_ "put in place new built file (%s): %s", $l, $@;
6773 foreach my $l (split /\n/, getfield $dsc, 'Files') {
6774 $l =~ m/\S+$/ or next;
6777 $mv->('dsc', $dscfn);
6778 $mv->('changes', $sourcechanges);
6783 sub cmd_build_source {
6784 badusage __ "build-source takes no additional arguments" if @ARGV;
6785 build_prep(WANTSRC_SOURCE);
6787 maybe_unapply_patches_again();
6788 printdone f_ "source built, results in %s and %s",
6789 $dscfn, $sourcechanges;
6792 sub cmd_push_source {
6795 "dgit push-source: --include-dirty/--ignore-dirty does not make".
6796 "sense with push-source!"
6798 build_check_quilt_splitbrain();
6800 my $changes = parsecontrol("$buildproductsdir/$changesfile",
6801 __ "source changes file");
6802 unless (test_source_only_changes($changes)) {
6803 fail __ "user-specified changes file is not source-only";
6806 # Building a source package is very fast, so just do it
6808 confess "er, patches are applied dirtily but shouldn't be.."
6809 if $patches_applied_dirtily;
6810 $changesfile = $sourcechanges;
6815 sub binary_builder {
6816 my ($bbuilder, $pbmc_msg, @args) = @_;
6817 build_prep(WANTSRC_SOURCE);
6819 midbuild_checkchanges();
6822 stat_exists $dscfn or fail f_
6823 "%s (in build products dir): %s", $dscfn, $!;
6824 stat_exists $sourcechanges or fail f_
6825 "%s (in build products dir): %s", $sourcechanges, $!;
6827 runcmd_ordryrun_local @$bbuilder, @args;
6829 maybe_unapply_patches_again();
6831 postbuild_mergechanges($pbmc_msg);
6837 binary_builder(\@sbuild, (__ <<END), qw(-d), $isuite, @ARGV, $dscfn);
6838 perhaps you need to pass -A ? (sbuild's default is to build only
6839 arch-specific binaries; dgit 1.4 used to override that.)
6844 my ($pbuilder) = @_;
6846 # @ARGV is allowed to contain only things that should be passed to
6847 # pbuilder under debbuildopts; just massage those
6848 my $wantsrc = massage_dbp_args \@ARGV;
6850 "you asked for a builder but your debbuildopts didn't ask for".
6851 " any binaries -- is this really what you meant?"
6852 unless $wantsrc & WANTSRC_BUILDER;
6854 "we must build a .dsc to pass to the builder but your debbuiltopts".
6855 " forbids the building of a source package; cannot continue"
6856 unless $wantsrc & WANTSRC_SOURCE;
6857 # We do not want to include the verb "build" in @pbuilder because
6858 # the user can customise @pbuilder and they shouldn't be required
6859 # to include "build" in their customised value. However, if the
6860 # user passes any additional args to pbuilder using the dgit
6861 # option --pbuilder:foo, such args need to come after the "build"
6862 # verb. opts_opt_multi_cmd does all of that.
6863 binary_builder([opts_opt_multi_cmd ["build"], @$pbuilder], undef,
6864 qw(--debbuildopts), "@ARGV", qw(--distribution), $isuite,
6869 pbuilder(\@pbuilder);
6872 sub cmd_cowbuilder {
6873 pbuilder(\@cowbuilder);
6876 sub cmd_quilt_fixup {
6877 badusage "incorrect arguments to dgit quilt-fixup" if @ARGV;
6880 build_maybe_quilt_fixup();
6883 sub cmd_print_unapplied_treeish {
6884 badusage __ "incorrect arguments to dgit print-unapplied-treeish"
6886 my $headref = git_rev_parse('HEAD');
6887 my $clogp = commit_getclogp $headref;
6888 $package = getfield $clogp, 'Source';
6889 $version = getfield $clogp, 'Version';
6890 $isuite = getfield $clogp, 'Distribution';
6891 $csuite = $isuite; # we want this to be offline!
6895 changedir $playground;
6896 my $uv = upstreamversion $version;
6897 my $u = quilt_fakedsc2unapplied($headref, $uv);
6898 print $u, "\n" or confess "$!";
6901 sub import_dsc_result {
6902 my ($dstref, $newhash, $what_log, $what_msg) = @_;
6903 my @cmd = (git_update_ref_cmd $what_log, $dstref, $newhash);
6905 check_gitattrs($newhash, __ "source tree");
6907 progress f_ "dgit: import-dsc: %s", $what_msg;
6910 sub cmd_import_dsc {
6914 last unless $ARGV[0] =~ m/^-/;
6917 if (m/^--require-valid-signature$/) {
6920 badusage f_ "unknown dgit import-dsc sub-option \`%s'", $_;
6924 badusage __ "usage: dgit import-dsc .../PATH/TO/.DSC BRANCH"
6926 my ($dscfn, $dstbranch) = @ARGV;
6928 badusage __ "dry run makes no sense with import-dsc"
6931 my $force = $dstbranch =~ s/^\+// ? +1 :
6932 $dstbranch =~ s/^\.\.// ? -1 :
6934 my $info = $force ? " $&" : '';
6935 $info = "$dscfn$info";
6937 my $specbranch = $dstbranch;
6938 $dstbranch = "refs/heads/$dstbranch" unless $dstbranch =~ m#^refs/#;
6939 $dstbranch = cmdoutput @git, qw(check-ref-format --normalize), $dstbranch;
6941 my @symcmd = (@git, qw(symbolic-ref -q HEAD));
6942 my $chead = cmdoutput_errok @symcmd;
6943 defined $chead or $?==256 or failedcmd @symcmd;
6945 fail f_ "%s is checked out - will not update it", $dstbranch
6946 if defined $chead and $chead eq $dstbranch;
6948 my $oldhash = git_get_ref $dstbranch;
6950 open D, "<", $dscfn or fail f_ "open import .dsc (%s): %s", $dscfn, $!;
6951 $dscdata = do { local $/ = undef; <D>; };
6952 D->error and fail f_ "read %s: %s", $dscfn, $!;
6955 # we don't normally need this so import it here
6956 use Dpkg::Source::Package;
6957 my $dp = new Dpkg::Source::Package filename => $dscfn,
6958 require_valid_signature => $needsig;
6960 local $SIG{__WARN__} = sub {
6962 return unless $needsig;
6963 fail __ "import-dsc signature check failed";
6965 if (!$dp->is_signed()) {
6966 warn f_ "%s: warning: importing unsigned .dsc\n", $us;
6968 my $r = $dp->check_signature();
6969 confess "->check_signature => $r" if $needsig && $r;
6975 $package = getfield $dsc, 'Source';
6977 parse_dsc_field($dsc, __ "Dgit metadata in .dsc")
6978 unless forceing [qw(import-dsc-with-dgit-field)];
6979 parse_dsc_field_def_dsc_distro();
6981 $isuite = 'DGIT-IMPORT-DSC';
6982 $idistro //= $dsc_distro;
6986 if (defined $dsc_hash) {
6988 "dgit: import-dsc of .dsc with Dgit field, using git hash";
6989 resolve_dsc_field_commit undef, undef;
6991 if (defined $dsc_hash) {
6992 my @cmd = (qw(sh -ec),
6993 "echo $dsc_hash | git cat-file --batch-check");
6994 my $objgot = cmdoutput @cmd;
6995 if ($objgot =~ m#^\w+ missing\b#) {
6996 fail f_ <<END, $dsc_hash
6997 .dsc contains Dgit field referring to object %s
6998 Your git tree does not have that object. Try `git fetch' from a
6999 plausible server (browse.dgit.d.o? salsa?), and try the import-dsc again.
7002 if ($oldhash && !is_fast_fwd $oldhash, $dsc_hash) {
7004 progress __ "Not fast forward, forced update.";
7006 fail f_ "Not fast forward to %s", $dsc_hash;
7009 import_dsc_result $dstbranch, $dsc_hash,
7010 "dgit import-dsc (Dgit): $info",
7011 f_ "updated git ref %s", $dstbranch;
7015 fail f_ <<END, $dstbranch, $specbranch, $specbranch
7016 Branch %s already exists
7017 Specify ..%s for a pseudo-merge, binding in existing history
7018 Specify +%s to overwrite, discarding existing history
7020 if $oldhash && !$force;
7022 my @dfi = dsc_files_info();
7023 foreach my $fi (@dfi) {
7024 my $f = $fi->{Filename};
7025 # We transfer all the pieces of the dsc to the bpd, not just
7026 # origs. This is by analogy with dgit fetch, which wants to
7027 # keep them somewhere to avoid downloading them again.
7028 # We make symlinks, though. If the user wants copies, then
7029 # they can copy the parts of the dsc to the bpd using dcmd,
7031 my $here = "$buildproductsdir/$f";
7036 fail f_ "lstat %s works but stat gives %s !", $here, $!;
7038 fail f_ "stat %s: %s", $here, $! unless $! == ENOENT;
7039 printdebug "not in bpd, $f ...\n";
7040 # $f does not exist in bpd, we need to transfer it
7042 $there =~ s{[^/]+$}{$f} or confess "$there ?";
7043 # $there is file we want, relative to user's cwd, or abs
7044 printdebug "not in bpd, $f, test $there ...\n";
7045 stat $there or fail f_
7046 "import %s requires %s, but: %s", $dscfn, $there, $!;
7047 if ($there =~ m#^(?:\./+)?\.\./+#) {
7048 # $there is relative to user's cwd
7049 my $there_from_parent = $';
7050 if ($buildproductsdir !~ m{^/}) {
7051 # abs2rel, despite its name, can take two relative paths
7052 $there = File::Spec->abs2rel($there,$buildproductsdir);
7053 # now $there is relative to bpd, great
7054 printdebug "not in bpd, $f, abs2rel, $there ...\n";
7056 $there = (dirname $maindir)."/$there_from_parent";
7057 # now $there is absoute
7058 printdebug "not in bpd, $f, rel2rel, $there ...\n";
7060 } elsif ($there =~ m#^/#) {
7061 # $there is absolute already
7062 printdebug "not in bpd, $f, abs, $there ...\n";
7065 "cannot import %s which seems to be inside working tree!",
7068 symlink $there, $here or fail f_
7069 "symlink %s to %s: %s", $there, $here, $!;
7070 progress f_ "made symlink %s -> %s", $here, $there;
7071 # print STDERR Dumper($fi);
7073 my @mergeinputs = generate_commits_from_dsc();
7074 die unless @mergeinputs == 1;
7076 my $newhash = $mergeinputs[0]{Commit};
7081 "Import, forced update - synthetic orphan git history.";
7082 } elsif ($force < 0) {
7083 progress __ "Import, merging.";
7084 my $tree = cmdoutput @git, qw(rev-parse), "$newhash:";
7085 my $version = getfield $dsc, 'Version';
7086 my $clogp = commit_getclogp $newhash;
7087 my $authline = clogp_authline $clogp;
7088 $newhash = hash_commit_text <<ENDU
7096 .(f_ <<END, $package, $version, $dstbranch);
7097 Merge %s (%s) import into %s
7100 die; # caught earlier
7104 import_dsc_result $dstbranch, $newhash,
7105 "dgit import-dsc: $info",
7106 f_ "results are in git ref %s", $dstbranch;
7109 sub pre_archive_api_query () {
7110 not_necessarily_a_tree();
7112 sub cmd_archive_api_query {
7113 badusage __ "need only 1 subpath argument" unless @ARGV==1;
7114 my ($subpath) = @ARGV;
7115 local $isuite = 'DGIT-API-QUERY-CMD';
7116 my @cmd = archive_api_query_cmd($subpath);
7119 exec @cmd or fail f_ "exec curl: %s\n", $!;
7122 sub repos_server_url () {
7123 $package = '_dgit-repos-server';
7124 local $access_forpush = 1;
7125 local $isuite = 'DGIT-REPOS-SERVER';
7126 my $url = access_giturl();
7129 sub pre_clone_dgit_repos_server () {
7130 not_necessarily_a_tree();
7132 sub cmd_clone_dgit_repos_server {
7133 badusage __ "need destination argument" unless @ARGV==1;
7134 my ($destdir) = @ARGV;
7135 my $url = repos_server_url();
7136 my @cmd = (@git, qw(clone), $url, $destdir);
7138 exec @cmd or fail f_ "exec git clone: %s\n", $!;
7141 sub pre_print_dgit_repos_server_source_url () {
7142 not_necessarily_a_tree();
7144 sub cmd_print_dgit_repos_server_source_url {
7146 "no arguments allowed to dgit print-dgit-repos-server-source-url"
7148 my $url = repos_server_url();
7149 print $url, "\n" or confess "$!";
7152 sub pre_print_dpkg_source_ignores {
7153 not_necessarily_a_tree();
7155 sub cmd_print_dpkg_source_ignores {
7157 "no arguments allowed to dgit print-dpkg-source-ignores"
7159 print "@dpkg_source_ignores\n" or confess "$!";
7162 sub cmd_setup_mergechangelogs {
7163 badusage __ "no arguments allowed to dgit setup-mergechangelogs"
7165 local $isuite = 'DGIT-SETUP-TREE';
7166 setup_mergechangelogs(1);
7169 sub cmd_setup_useremail {
7170 badusage __ "no arguments allowed to dgit setup-useremail" if @ARGV;
7171 local $isuite = 'DGIT-SETUP-TREE';
7175 sub cmd_setup_gitattributes {
7176 badusage __ "no arguments allowed to dgit setup-useremail" if @ARGV;
7177 local $isuite = 'DGIT-SETUP-TREE';
7181 sub cmd_setup_new_tree {
7182 badusage __ "no arguments allowed to dgit setup-tree" if @ARGV;
7183 local $isuite = 'DGIT-SETUP-TREE';
7187 #---------- argument parsing and main program ----------
7190 print "dgit version $our_version\n" or confess "$!";
7194 our (%valopts_long, %valopts_short);
7195 our (%funcopts_long);
7197 our (@modeopt_cfgs);
7199 sub defvalopt ($$$$) {
7200 my ($long,$short,$val_re,$how) = @_;
7201 my $oi = { Long => $long, Short => $short, Re => $val_re, How => $how };
7202 $valopts_long{$long} = $oi;
7203 $valopts_short{$short} = $oi;
7204 # $how subref should:
7205 # do whatever assignemnt or thing it likes with $_[0]
7206 # if the option should not be passed on to remote, @rvalopts=()
7207 # or $how can be a scalar ref, meaning simply assign the value
7210 defvalopt '--since-version', '-v', '[^_]+|_', \$changes_since_version;
7211 defvalopt '--distro', '-d', '.+', \$idistro;
7212 defvalopt '', '-k', '.+', \$keyid;
7213 defvalopt '--existing-package','', '.*', \$existing_package;
7214 defvalopt '--build-products-dir','','.*', \$buildproductsdir;
7215 defvalopt '--clean', '', $cleanmode_re, \$cleanmode;
7216 defvalopt '--package', '-p', $package_re, \$package;
7217 defvalopt '--quilt', '', $quilt_modes_re, \$quilt_mode;
7219 defvalopt '', '-C', '.+', sub {
7220 ($changesfile) = (@_);
7221 if ($changesfile =~ s#^(.*)/##) {
7222 $buildproductsdir = $1;
7226 defvalopt '--initiator-tempdir','','.*', sub {
7227 ($initiator_tempdir) = (@_);
7228 $initiator_tempdir =~ m#^/# or
7229 badusage __ "--initiator-tempdir must be used specify an".
7230 " absolute, not relative, directory."
7233 sub defoptmodes ($@) {
7234 my ($varref, $cfgkey, $default, %optmap) = @_;
7236 while (my ($opt,$val) = each %optmap) {
7237 $funcopts_long{$opt} = sub { $$varref = $val; };
7238 $permit{$val} = $val;
7240 push @modeopt_cfgs, {
7243 Default => $default,
7248 defoptmodes \$dodep14tag, qw( dep14tag want
7251 --always-dep14tag always );
7256 if (defined $ENV{'DGIT_SSH'}) {
7257 @ssh = string_to_ssh $ENV{'DGIT_SSH'};
7258 } elsif (defined $ENV{'GIT_SSH'}) {
7259 @ssh = ($ENV{'GIT_SSH'});
7267 if (!defined $val) {
7268 badusage f_ "%s needs a value", $what unless @ARGV;
7270 push @rvalopts, $val;
7272 badusage f_ "bad value \`%s' for %s", $val, $what unless
7273 $val =~ m/^$oi->{Re}$(?!\n)/s;
7274 my $how = $oi->{How};
7275 if (ref($how) eq 'SCALAR') {
7280 push @ropts, @rvalopts;
7284 last unless $ARGV[0] =~ m/^-/;
7288 if (m/^--dry-run$/) {
7291 } elsif (m/^--damp-run$/) {
7294 } elsif (m/^--no-sign$/) {
7297 } elsif (m/^--help$/) {
7299 } elsif (m/^--version$/) {
7301 } elsif (m/^--new$/) {
7304 } elsif (m/^--([-0-9a-z]+)=(.+)/s &&
7305 ($om = $opts_opt_map{$1}) &&
7309 } elsif (m/^--([-0-9a-z]+):(.*)/s &&
7310 !$opts_opt_cmdonly{$1} &&
7311 ($om = $opts_opt_map{$1})) {
7314 } elsif (m/^--([-0-9a-z]+)\!:(.*)/s &&
7315 !$opts_opt_cmdonly{$1} &&
7316 ($om = $opts_opt_map{$1})) {
7318 my $cmd = shift @$om;
7319 @$om = ($cmd, grep { $_ ne $2 } @$om);
7320 } elsif (m/^--(gbp|dpm)$/s) {
7321 push @ropts, "--quilt=$1";
7323 } elsif (m/^--(?:ignore|include)-dirty$/s) {
7326 } elsif (m/^--no-quilt-fixup$/s) {
7328 $quilt_mode = 'nocheck';
7329 } elsif (m/^--no-rm-on-error$/s) {
7332 } elsif (m/^--no-chase-dsc-distro$/s) {
7334 $chase_dsc_distro = 0;
7335 } elsif (m/^--overwrite$/s) {
7337 $overwrite_version = '';
7338 } elsif (m/^--split-(?:view|brain)$/s) {
7340 $splitview_mode = 'always';
7341 } elsif (m/^--split-(?:view|brain)=($splitview_modes_re)$/s) {
7343 $splitview_mode = $1;
7344 } elsif (m/^--overwrite=(.+)$/s) {
7346 $overwrite_version = $1;
7347 } elsif (m/^--delayed=(\d+)$/s) {
7350 } elsif (m/^--save-(dgit-view)=(.+)$/s ||
7351 m/^--(dgit-view)-save=(.+)$/s
7353 my ($k,$v) = ($1,$2);
7355 $v =~ s#^(?!refs/)#refs/heads/#;
7356 $internal_object_save{$k} = $v;
7357 } elsif (m/^--(no-)?rm-old-changes$/s) {
7360 } elsif (m/^--deliberately-($deliberately_re)$/s) {
7362 push @deliberatelies, $&;
7363 } elsif (m/^--force-(.*)/ && defined $forceopts{$1}) {
7367 } elsif (m/^--force-/) {
7369 f_ "%s: warning: ignoring unknown force option %s\n",
7372 } elsif (m/^--config-lookup-explode=(.+)$/s) {
7373 # undocumented, for testing
7375 $gitcfgs{cmdline}{$1} = 'CONFIG-LOOKUP-EXPLODE';
7376 # ^ it's supposed to be an array ref
7377 } elsif (m/^(--[-0-9a-z]+)(=|$)/ && ($oi = $valopts_long{$1})) {
7378 $val = $2 ? $' : undef; #';
7379 $valopt->($oi->{Long});
7380 } elsif ($funcopts_long{$_}) {
7382 $funcopts_long{$_}();
7384 badusage f_ "unknown long option \`%s'", $_;
7391 } elsif (s/^-L/-/) {
7394 } elsif (s/^-h/-/) {
7396 } elsif (s/^-D/-/) {
7400 } elsif (s/^-N/-/) {
7405 push @changesopts, $_;
7407 } elsif (s/^-wn$//s) {
7409 $cleanmode = 'none';
7410 } elsif (s/^-wg(f?)(a?)$//s) {
7413 $cleanmode .= '-ff' if $1;
7414 $cleanmode .= ',always' if $2;
7415 } elsif (s/^-wd(d?)([na]?)$//s) {
7417 $cleanmode = 'dpkg-source';
7418 $cleanmode .= '-d' if $1;
7419 $cleanmode .= ',no-check' if $2 eq 'n';
7420 $cleanmode .= ',all-check' if $2 eq 'a';
7421 } elsif (s/^-wc$//s) {
7423 $cleanmode = 'check';
7424 } elsif (s/^-wci$//s) {
7426 $cleanmode = 'check,ignores';
7427 } elsif (s/^-c([^=]*)\=(.*)$//s) {
7428 push @git, '-c', $&;
7429 $gitcfgs{cmdline}{$1} = [ $2 ];
7430 } elsif (s/^-c([^=]+)$//s) {
7431 push @git, '-c', $&;
7432 $gitcfgs{cmdline}{$1} = [ 'true' ];
7433 } elsif (m/^-[a-zA-Z]/ && ($oi = $valopts_short{$&})) {
7435 $val = undef unless length $val;
7436 $valopt->($oi->{Short});
7439 badusage f_ "unknown short option \`%s'", $_;
7446 sub check_env_sanity () {
7447 my $blocked = new POSIX::SigSet;
7448 sigprocmask SIG_UNBLOCK, $blocked, $blocked or confess "$!";
7451 foreach my $name (qw(PIPE CHLD)) {
7452 my $signame = "SIG$name";
7453 my $signum = eval "POSIX::$signame" // die;
7454 die f_ "%s is set to something other than SIG_DFL\n",
7456 if defined $SIG{$name} and $SIG{$name} ne 'DEFAULT';
7457 $blocked->ismember($signum) and
7458 die f_ "%s is blocked\n", $signame;
7464 On entry to dgit, %s
7465 This is a bug produced by something in your execution environment.
7471 sub parseopts_late_defaults () {
7472 $isuite //= cfg("dgit-distro.$idistro.default-suite", 'RETURN-UNDEF')
7473 if defined $idistro;
7474 $isuite //= cfg('dgit.default.default-suite');
7476 foreach my $k (keys %opts_opt_map) {
7477 my $om = $opts_opt_map{$k};
7479 my $v = access_cfg("cmd-$k", 'RETURN-UNDEF');
7481 badcfg f_ "cannot set command for %s", $k
7482 unless length $om->[0];
7486 foreach my $c (access_cfg_cfgs("opts-$k")) {
7488 map { $_ ? @$_ : () }
7489 map { $gitcfgs{$_}{$c} }
7490 reverse @gitcfgsources;
7491 printdebug "CL $c ", (join " ", map { shellquote } @vl),
7492 "\n" if $debuglevel >= 4;
7494 badcfg f_ "cannot configure options for %s", $k
7495 if $opts_opt_cmdonly{$k};
7496 my $insertpos = $opts_cfg_insertpos{$k};
7497 @$om = ( @$om[0..$insertpos-1],
7499 @$om[$insertpos..$#$om] );
7503 if (!defined $rmchanges) {
7504 local $access_forpush;
7505 $rmchanges = access_cfg_bool(0, 'rm-old-changes');
7508 if (!defined $quilt_mode) {
7509 local $access_forpush;
7510 $quilt_mode = cfg('dgit.force.quilt-mode', 'RETURN-UNDEF')
7511 // access_cfg('quilt-mode', 'RETURN-UNDEF')
7513 $quilt_mode =~ m/^($quilt_modes_re)$/
7514 or badcfg f_ "unknown quilt-mode \`%s'", $quilt_mode;
7518 foreach my $moc (@modeopt_cfgs) {
7519 local $access_forpush;
7520 my $vr = $moc->{Var};
7521 next if defined $$vr;
7522 $$vr = access_cfg($moc->{Key}, 'RETURN-UNDEF') // $moc->{Default};
7523 my $v = $moc->{Vals}{$$vr};
7524 badcfg f_ "unknown %s setting \`%s'", $moc->{Key}, $$vr
7530 local $access_forpush;
7531 default_from_access_cfg(\$cleanmode, 'clean-mode', 'dpkg-source',
7535 $buildproductsdir //= access_cfg('build-products-dir', 'RETURN-UNDEF');
7536 $buildproductsdir //= '..';
7537 $bpd_glob = $buildproductsdir;
7538 $bpd_glob =~ s#[][\\{}*?~]#\\$&#g;
7541 setlocale(LC_MESSAGES, "");
7544 if ($ENV{$fakeeditorenv}) {
7546 quilt_fixup_editor();
7552 print STDERR __ "DRY RUN ONLY\n" if $dryrun_level > 1;
7553 print STDERR __ "DAMP RUN - WILL MAKE LOCAL (UNSIGNED) CHANGES\n"
7554 if $dryrun_level == 1;
7556 print STDERR __ $helpmsg or confess "$!";
7559 $cmd = $subcommand = shift @ARGV;
7562 my $pre_fn = ${*::}{"pre_$cmd"};
7563 $pre_fn->() if $pre_fn;
7565 if ($invoked_in_git_tree) {
7566 changedir_git_toplevel();
7571 my $fn = ${*::}{"cmd_$cmd"};
7572 $fn or badusage f_ "unknown operation %s", $cmd;