3 # Integration between git and Debian-style archives
5 # Copyright (C)2013-2015 Ian Jackson
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
28 use Dpkg::Control::Hash;
30 use File::Temp qw(tempdir);
37 use List::Util qw(any);
38 use List::MoreUtils qw(pairwise);
43 our $our_version = 'UNRELEASED'; ###substituted###
45 our @rpushprotovsn_support = qw(4 3 2); # 4 is new tag format
48 our $isuite = 'unstable';
54 our $dryrun_level = 0;
56 our $buildproductsdir = '..';
62 our $existing_package = 'dpkg';
64 our $changes_since_version;
66 our $overwrite_version; # undef: not specified; '': check changelog
68 our $quilt_modes_re = 'linear|smash|auto|nofix|nocheck|gbp|dpm|unapplied';
69 our $we_are_responder;
70 our $initiator_tempdir;
71 our $patches_applied_dirtily = 00;
76 our %format_ok = map { $_=>1 } ("1.0","3.0 (native)","3.0 (quilt)");
78 our $suite_re = '[-+.0-9a-z]+';
79 our $cleanmode_re = 'dpkg-source(?:-d)?|git|git-ff|check|none';
80 our $orig_f_comp_re = 'orig(?:-[-0-9a-z]+)?';
81 our $orig_f_sig_re = '\\.(?:asc|gpg|pgp)';
82 our $orig_f_tail_re = "$orig_f_comp_re\\.tar(?:\\.\\w+)?(?:$orig_f_sig_re)?";
84 our $git_authline_re = '^([^<>]+) \<(\S+)\> (\d+ [-+]\d+)$';
85 our $splitbraincache = 'dgit-intern/quilt-cache';
88 our (@dget) = qw(dget);
89 our (@curl) = qw(curl -f);
90 our (@dput) = qw(dput);
91 our (@debsign) = qw(debsign);
93 our (@sbuild) = qw(sbuild);
95 our (@dgit) = qw(dgit);
96 our (@dpkgbuildpackage) = qw(dpkg-buildpackage -i\.git/ -I.git);
97 our (@dpkgsource) = qw(dpkg-source -i\.git/ -I.git);
98 our (@dpkggenchanges) = qw(dpkg-genchanges);
99 our (@mergechanges) = qw(mergechanges -f);
100 our (@gbp) = qw(gbp);
101 our (@changesopts) = ('');
103 our %opts_opt_map = ('dget' => \@dget, # accept for compatibility
106 'debsign' => \@debsign,
108 'sbuild' => \@sbuild,
112 'dpkg-source' => \@dpkgsource,
113 'dpkg-buildpackage' => \@dpkgbuildpackage,
114 'dpkg-genchanges' => \@dpkggenchanges,
116 'ch' => \@changesopts,
117 'mergechanges' => \@mergechanges);
119 our %opts_opt_cmdonly = ('gpg' => 1, 'git' => 1);
120 our %opts_cfg_insertpos = map {
122 scalar @{ $opts_opt_map{$_} }
123 } keys %opts_opt_map;
125 sub finalise_opts_opts();
131 our $supplementary_message = '';
132 our $need_split_build_invocation = 0;
133 our $split_brain = 0;
137 print STDERR "! $_\n" foreach $supplementary_message =~ m/^.+$/mg;
140 our $remotename = 'dgit';
141 our @ourdscfield = qw(Dgit Vcs-Dgit-Master);
146 my ($v,$distro) = @_;
147 return $tagformatfn->($v, $distro);
150 sub debiantag_maintview ($$) {
151 my ($v,$distro) = @_;
156 sub madformat ($) { $_[0] eq '3.0 (quilt)' }
158 sub lbranch () { return "$branchprefix/$csuite"; }
159 my $lbranch_re = '^refs/heads/'.$branchprefix.'/([^/.]+)$';
160 sub lref () { return "refs/heads/".lbranch(); }
161 sub lrref () { return "refs/remotes/$remotename/".server_branch($csuite); }
162 sub rrref () { return server_ref($csuite); }
164 sub lrfetchrefs () { return "refs/dgit-fetch/$csuite"; }
165 sub lrfetchref () { return lrfetchrefs.'/'.server_branch($csuite); }
167 # We fetch some parts of lrfetchrefs/*. Ideally we delete these
168 # locally fetched refs because they have unhelpful names and clutter
169 # up gitk etc. So we track whether we have "used up" head ref (ie,
170 # whether we have made another local ref which refers to this object).
172 # (If we deleted them unconditionally, then we might end up
173 # re-fetching the same git objects each time dgit fetch was run.)
175 # So, leach use of lrfetchrefs needs to be accompanied by arrangements
176 # in git_fetch_us to fetch the refs in question, and possibly a call
177 # to lrfetchref_used.
179 our (%lrfetchrefs_f, %lrfetchrefs_d);
180 # $lrfetchrefs_X{lrfetchrefs."/heads/whatever"} = $objid
182 sub lrfetchref_used ($) {
183 my ($fullrefname) = @_;
184 my $objid = $lrfetchrefs_f{$fullrefname};
185 $lrfetchrefs_d{$fullrefname} = $objid if defined $objid;
196 return "${package}_".(stripepoch $vsn).$sfx
201 return srcfn($vsn,".dsc");
204 sub changespat ($;$) {
205 my ($vsn, $arch) = @_;
206 return "${package}_".(stripepoch $vsn)."_".($arch//'*').".changes";
215 foreach my $f (@end) {
217 print STDERR "$us: cleanup: $@" if length $@;
221 sub badcfg { print STDERR "$us: invalid configuration: @_\n"; exit 12; }
223 sub no_such_package () {
224 print STDERR "$us: package $package does not exist in suite $isuite\n";
230 printdebug "CD $newdir\n";
231 chdir $newdir or confess "chdir: $newdir: $!";
234 sub deliberately ($) {
236 return !!grep { $_ eq "--deliberately-$enquiry" } @deliberatelies;
239 sub deliberately_not_fast_forward () {
240 foreach (qw(not-fast-forward fresh-repo)) {
241 return 1 if deliberately($_) || deliberately("TEST-dgit-only-$_");
245 sub quiltmode_splitbrain () {
246 $quilt_mode =~ m/gbp|dpm|unapplied/;
250 return (@gbp, qw(pq));
253 #---------- remote protocol support, common ----------
255 # remote push initiator/responder protocol:
256 # $ dgit remote-push-build-host <n-rargs> <rargs>... <push-args>...
257 # where <rargs> is <push-host-dir> <supported-proto-vsn>,... ...
258 # < dgit-remote-push-ready <actual-proto-vsn>
265 # > supplementary-message NBYTES # $protovsn >= 3
270 # > file parsed-changelog
271 # [indicates that output of dpkg-parsechangelog follows]
272 # > data-block NBYTES
273 # > [NBYTES bytes of data (no newline)]
274 # [maybe some more blocks]
283 # > param head DGIT-VIEW-HEAD
284 # > param csuite SUITE
285 # > param tagformat old|new
286 # > param maint-view MAINT-VIEW-HEAD
288 # > previously REFNAME=OBJNAME # if --deliberately-not-fast-forward
289 # # goes into tag, for replay prevention
292 # [indicates that signed tag is wanted]
293 # < data-block NBYTES
294 # < [NBYTES bytes of data (no newline)]
295 # [maybe some more blocks]
299 # > want signed-dsc-changes
300 # < data-block NBYTES [transfer of signed dsc]
302 # < data-block NBYTES [transfer of signed changes]
310 sub i_child_report () {
311 # Sees if our child has died, and reap it if so. Returns a string
312 # describing how it died if it failed, or undef otherwise.
313 return undef unless $i_child_pid;
314 my $got = waitpid $i_child_pid, WNOHANG;
315 return undef if $got <= 0;
316 die unless $got == $i_child_pid;
317 $i_child_pid = undef;
318 return undef unless $?;
319 return "build host child ".waitstatusmsg();
324 fail "connection lost: $!" if $fh->error;
325 fail "protocol violation; $m not expected";
328 sub badproto_badread ($$) {
330 fail "connection lost: $!" if $!;
331 my $report = i_child_report();
332 fail $report if defined $report;
333 badproto $fh, "eof (reading $wh)";
336 sub protocol_expect (&$) {
337 my ($match, $fh) = @_;
340 defined && chomp or badproto_badread $fh, "protocol message";
348 badproto $fh, "\`$_'";
351 sub protocol_send_file ($$) {
352 my ($fh, $ourfn) = @_;
353 open PF, "<", $ourfn or die "$ourfn: $!";
356 my $got = read PF, $d, 65536;
357 die "$ourfn: $!" unless defined $got;
359 print $fh "data-block ".length($d)."\n" or die $!;
360 print $fh $d or die $!;
362 PF->error and die "$ourfn $!";
363 print $fh "data-end\n" or die $!;
367 sub protocol_read_bytes ($$) {
368 my ($fh, $nbytes) = @_;
369 $nbytes =~ m/^[1-9]\d{0,5}$|^0$/ or badproto \*RO, "bad byte count";
371 my $got = read $fh, $d, $nbytes;
372 $got==$nbytes or badproto_badread $fh, "data block";
376 sub protocol_receive_file ($$) {
377 my ($fh, $ourfn) = @_;
378 printdebug "() $ourfn\n";
379 open PF, ">", $ourfn or die "$ourfn: $!";
381 my ($y,$l) = protocol_expect {
382 m/^data-block (.*)$/ ? (1,$1) :
383 m/^data-end$/ ? (0,) :
387 my $d = protocol_read_bytes $fh, $l;
388 print PF $d or die $!;
393 #---------- remote protocol support, responder ----------
395 sub responder_send_command ($) {
397 return unless $we_are_responder;
398 # called even without $we_are_responder
399 printdebug ">> $command\n";
400 print PO $command, "\n" or die $!;
403 sub responder_send_file ($$) {
404 my ($keyword, $ourfn) = @_;
405 return unless $we_are_responder;
406 printdebug "]] $keyword $ourfn\n";
407 responder_send_command "file $keyword";
408 protocol_send_file \*PO, $ourfn;
411 sub responder_receive_files ($@) {
412 my ($keyword, @ourfns) = @_;
413 die unless $we_are_responder;
414 printdebug "[[ $keyword @ourfns\n";
415 responder_send_command "want $keyword";
416 foreach my $fn (@ourfns) {
417 protocol_receive_file \*PI, $fn;
420 protocol_expect { m/^files-end$/ } \*PI;
423 #---------- remote protocol support, initiator ----------
425 sub initiator_expect (&) {
427 protocol_expect { &$match } \*RO;
430 #---------- end remote code ----------
433 if ($we_are_responder) {
435 responder_send_command "progress ".length($m) or die $!;
436 print PO $m or die $!;
446 $ua = LWP::UserAgent->new();
450 progress "downloading $what...";
451 my $r = $ua->get(@_) or die $!;
452 return undef if $r->code == 404;
453 $r->is_success or fail "failed to fetch $what: ".$r->status_line;
454 return $r->decoded_content(charset => 'none');
457 our ($dscdata,$dscurl,$dsc,$dsc_checked,$skew_warning_vsn);
462 failedcmd @_ if system @_;
465 sub act_local () { return $dryrun_level <= 1; }
466 sub act_scary () { return !$dryrun_level; }
469 if (!$dryrun_level) {
470 progress "dgit ok: @_";
472 progress "would be ok: @_ (but dry run only)";
477 printcmd(\*STDERR,$debugprefix."#",@_);
480 sub runcmd_ordryrun {
488 sub runcmd_ordryrun_local {
497 my ($first_shell, @cmd) = @_;
498 return qw(sh -ec), $first_shell.'; exec "$@"', 'x', @cmd;
501 our $helpmsg = <<END;
503 dgit [dgit-opts] clone [dgit-opts] package [suite] [./dir|/dir]
504 dgit [dgit-opts] fetch|pull [dgit-opts] [suite]
505 dgit [dgit-opts] build [dpkg-buildpackage-opts]
506 dgit [dgit-opts] sbuild [sbuild-opts]
507 dgit [dgit-opts] push [dgit-opts] [suite]
508 dgit [dgit-opts] rpush build-host:build-dir ...
509 important dgit options:
510 -k<keyid> sign tag and package with <keyid> instead of default
511 --dry-run -n do not change anything, but go through the motions
512 --damp-run -L like --dry-run but make local changes, without signing
513 --new -N allow introducing a new package
514 --debug -D increase debug level
515 -c<name>=<value> set git config option (used directly by dgit too)
518 our $later_warning_msg = <<END;
519 Perhaps the upload is stuck in incoming. Using the version from git.
523 print STDERR "$us: @_\n", $helpmsg or die $!;
528 @ARGV or badusage "too few arguments";
529 return scalar shift @ARGV;
533 print $helpmsg or die $!;
537 our $td = $ENV{DGIT_TEST_DUMMY_DIR} || "DGIT_TEST_DUMMY_DIR-unset";
539 our %defcfg = ('dgit.default.distro' => 'debian',
540 'dgit.default.username' => '',
541 'dgit.default.archive-query-default-component' => 'main',
542 'dgit.default.ssh' => 'ssh',
543 'dgit.default.archive-query' => 'madison:',
544 'dgit.default.sshpsql-dbname' => 'service=projectb',
545 'dgit.default.dgit-tag-format' => 'old,new,maint',
546 # old means "repo server accepts pushes with old dgit tags"
547 # new means "repo server accepts pushes with new dgit tags"
548 # maint means "repo server accepts split brain pushes"
549 # hist means "repo server may have old pushes without new tag"
550 # ("hist" is implied by "old")
551 'dgit-distro.debian.archive-query' => 'ftpmasterapi:',
552 'dgit-distro.debian.git-check' => 'url',
553 'dgit-distro.debian.git-check-suffix' => '/info/refs',
554 'dgit-distro.debian.new-private-pushers' => 't',
555 'dgit-distro.debian.dgit-tag-format' => 'new',
556 'dgit-distro.debian/push.git-url' => '',
557 'dgit-distro.debian/push.git-host' => 'push.dgit.debian.org',
558 'dgit-distro.debian/push.git-user-force' => 'dgit',
559 'dgit-distro.debian/push.git-proto' => 'git+ssh://',
560 'dgit-distro.debian/push.git-path' => '/dgit/debian/repos',
561 'dgit-distro.debian/push.git-create' => 'true',
562 'dgit-distro.debian/push.git-check' => 'ssh-cmd',
563 'dgit-distro.debian.archive-query-url', 'https://api.ftp-master.debian.org/',
564 # 'dgit-distro.debian.archive-query-tls-key',
565 # '/etc/ssl/certs/%HOST%.pem:/etc/dgit/%HOST%.pem',
566 # ^ this does not work because curl is broken nowadays
567 # Fixing #790093 properly will involve providing providing the key
568 # in some pacagke and maybe updating these paths.
570 # 'dgit-distro.debian.archive-query-tls-curl-args',
571 # '--ca-path=/etc/ssl/ca-debian',
572 # ^ this is a workaround but works (only) on DSA-administered machines
573 'dgit-distro.debian.git-url' => 'https://git.dgit.debian.org',
574 'dgit-distro.debian.git-url-suffix' => '',
575 'dgit-distro.debian.upload-host' => 'ftp-master', # for dput
576 'dgit-distro.debian.mirror' => 'http://ftp.debian.org/debian/',
577 'dgit-distro.debian.backports-quirk' => '(squeeze)-backports*',
578 'dgit-distro.debian-backports.mirror' => 'http://backports.debian.org/debian-backports/',
579 'dgit-distro.ubuntu.git-check' => 'false',
580 'dgit-distro.ubuntu.mirror' => 'http://archive.ubuntu.com/ubuntu',
581 'dgit-distro.test-dummy.ssh' => "$td/ssh",
582 'dgit-distro.test-dummy.username' => "alice",
583 'dgit-distro.test-dummy.git-check' => "ssh-cmd",
584 'dgit-distro.test-dummy.git-create' => "ssh-cmd",
585 'dgit-distro.test-dummy.git-url' => "$td/git",
586 'dgit-distro.test-dummy.git-host' => "git",
587 'dgit-distro.test-dummy.git-path' => "$td/git",
588 'dgit-distro.test-dummy.archive-query' => "ftpmasterapi:",
589 'dgit-distro.test-dummy.archive-query-url' => "file://$td/aq/",
590 'dgit-distro.test-dummy.mirror' => "file://$td/mirror/",
591 'dgit-distro.test-dummy.upload-host' => 'test-dummy',
596 sub git_slurp_config () {
597 local ($debuglevel) = $debuglevel-2;
600 my @cmd = (@git, qw(config -z --get-regexp .*));
603 open GITS, "-|", @cmd or die $!;
606 printdebug "=> ", (messagequote $_), "\n";
608 push @{ $gitcfg{$`} }, $'; #';
612 or ($!==0 && $?==256)
616 sub git_get_config ($) {
619 printdebug"C $c ".(defined $l ? messagequote "'$l'" : "undef")."\n"
622 @$l==1 or badcfg "multiple values for $c" if @$l > 1;
628 return undef if $c =~ /RETURN-UNDEF/;
629 my $v = git_get_config($c);
630 return $v if defined $v;
631 my $dv = $defcfg{$c};
632 return $dv if defined $dv;
634 badcfg "need value for one of: @_\n".
635 "$us: distro or suite appears not to be (properly) supported";
638 sub access_basedistro () {
639 if (defined $idistro) {
642 return cfg("dgit-suite.$isuite.distro",
643 "dgit.default.distro");
647 sub access_quirk () {
648 # returns (quirk name, distro to use instead or undef, quirk-specific info)
649 my $basedistro = access_basedistro();
650 my $backports_quirk = cfg("dgit-distro.$basedistro.backports-quirk",
652 if (defined $backports_quirk) {
653 my $re = $backports_quirk;
654 $re =~ s/[^-0-9a-z_\%*()]/\\$&/ig;
656 $re =~ s/\%/([-0-9a-z_]+)/
657 or $re =~ m/[()]/ or badcfg "backports-quirk needs \% or ( )";
658 if ($isuite =~ m/^$re$/) {
659 return ('backports',"$basedistro-backports",$1);
662 return ('none',undef);
667 sub parse_cfg_bool ($$$) {
668 my ($what,$def,$v) = @_;
671 $v =~ m/^[ty1]/ ? 1 :
672 $v =~ m/^[fn0]/ ? 0 :
673 badcfg "$what needs t (true, y, 1) or f (false, n, 0) not \`$v'";
676 sub access_forpush_config () {
677 my $d = access_basedistro();
681 parse_cfg_bool('new-private-pushers', 0,
682 cfg("dgit-distro.$d.new-private-pushers",
685 my $v = cfg("dgit-distro.$d.readonly", 'RETURN-UNDEF');
688 $v =~ m/^[ty1]/ ? 0 : # force readonly, forpush = 0
689 $v =~ m/^[fn0]/ ? 1 : # force nonreadonly, forpush = 1
690 $v =~ m/^[a]/ ? '' : # auto, forpush = ''
691 badcfg "readonly needs t (true, y, 1) or f (false, n, 0) or a (auto)";
694 sub access_forpush () {
695 $access_forpush //= access_forpush_config();
696 return $access_forpush;
700 die "$access_forpush ?" if ($access_forpush // 1) ne 1;
701 badcfg "pushing but distro is configured readonly"
702 if access_forpush_config() eq '0';
704 $supplementary_message = <<'END' unless $we_are_responder;
705 Push failed, before we got started.
706 You can retry the push, after fixing the problem, if you like.
708 finalise_opts_opts();
712 finalise_opts_opts();
715 sub supplementary_message ($) {
717 if (!$we_are_responder) {
718 $supplementary_message = $msg;
720 } elsif ($protovsn >= 3) {
721 responder_send_command "supplementary-message ".length($msg)
723 print PO $msg or die $!;
727 sub access_distros () {
728 # Returns list of distros to try, in order
731 # 0. `instead of' distro name(s) we have been pointed to
732 # 1. the access_quirk distro, if any
733 # 2a. the user's specified distro, or failing that } basedistro
734 # 2b. the distro calculated from the suite }
735 my @l = access_basedistro();
737 my (undef,$quirkdistro) = access_quirk();
738 unshift @l, $quirkdistro;
739 unshift @l, $instead_distro;
740 @l = grep { defined } @l;
742 if (access_forpush()) {
743 @l = map { ("$_/push", $_) } @l;
748 sub access_cfg_cfgs (@) {
751 # The nesting of these loops determines the search order. We put
752 # the key loop on the outside so that we search all the distros
753 # for each key, before going on to the next key. That means that
754 # if access_cfg is called with a more specific, and then a less
755 # specific, key, an earlier distro can override the less specific
756 # without necessarily overriding any more specific keys. (If the
757 # distro wants to override the more specific keys it can simply do
758 # so; whereas if we did the loop the other way around, it would be
759 # impossible to for an earlier distro to override a less specific
760 # key but not the more specific ones without restating the unknown
761 # values of the more specific keys.
764 # We have to deal with RETURN-UNDEF specially, so that we don't
765 # terminate the search prematurely.
767 if (m/RETURN-UNDEF/) { push @rundef, $_; last; }
770 foreach my $d (access_distros()) {
771 push @cfgs, map { "dgit-distro.$d.$_" } @realkeys;
773 push @cfgs, map { "dgit.default.$_" } @realkeys;
780 my (@cfgs) = access_cfg_cfgs(@keys);
781 my $value = cfg(@cfgs);
785 sub access_cfg_bool ($$) {
786 my ($def, @keys) = @_;
787 parse_cfg_bool($keys[0], $def, access_cfg(@keys, 'RETURN-UNDEF'));
790 sub string_to_ssh ($) {
792 if ($spec =~ m/\s/) {
793 return qw(sh -ec), 'exec '.$spec.' "$@"', 'x';
799 sub access_cfg_ssh () {
800 my $gitssh = access_cfg('ssh', 'RETURN-UNDEF');
801 if (!defined $gitssh) {
804 return string_to_ssh $gitssh;
808 sub access_runeinfo ($) {
810 return ": dgit ".access_basedistro()." $info ;";
813 sub access_someuserhost ($) {
815 my $user = access_cfg("$some-user-force", 'RETURN-UNDEF');
816 defined($user) && length($user) or
817 $user = access_cfg("$some-user",'username');
818 my $host = access_cfg("$some-host");
819 return length($user) ? "$user\@$host" : $host;
822 sub access_gituserhost () {
823 return access_someuserhost('git');
826 sub access_giturl (;$) {
828 my $url = access_cfg('git-url','RETURN-UNDEF');
831 my $proto = access_cfg('git-proto', 'RETURN-UNDEF');
832 return undef unless defined $proto;
835 access_gituserhost().
836 access_cfg('git-path');
838 $suffix = access_cfg('git-url-suffix','RETURN-UNDEF');
841 return "$url/$package$suffix";
844 sub parsecontrolfh ($$;$) {
845 my ($fh, $desc, $allowsigned) = @_;
846 our $dpkgcontrolhash_noissigned;
849 my %opts = ('name' => $desc);
850 $opts{allow_pgp}= $allowsigned || !$dpkgcontrolhash_noissigned;
851 $c = Dpkg::Control::Hash->new(%opts);
852 $c->parse($fh,$desc) or die "parsing of $desc failed";
853 last if $allowsigned;
854 last if $dpkgcontrolhash_noissigned;
855 my $issigned= $c->get_option('is_pgp_signed');
856 if (!defined $issigned) {
857 $dpkgcontrolhash_noissigned= 1;
858 seek $fh, 0,0 or die "seek $desc: $!";
859 } elsif ($issigned) {
860 fail "control file $desc is (already) PGP-signed. ".
861 " Note that dgit push needs to modify the .dsc and then".
862 " do the signature itself";
871 my ($file, $desc) = @_;
872 my $fh = new IO::Handle;
873 open $fh, '<', $file or die "$file: $!";
874 my $c = parsecontrolfh($fh,$desc);
875 $fh->error and die $!;
881 my ($dctrl,$field) = @_;
882 my $v = $dctrl->{$field};
883 return $v if defined $v;
884 fail "missing field $field in ".$dctrl->get_option('name');
888 my $c = Dpkg::Control::Hash->new(name => 'parsed changelog');
889 my $p = new IO::Handle;
890 my @cmd = (qw(dpkg-parsechangelog), @_);
891 open $p, '-|', @cmd or die $!;
893 $?=0; $!=0; close $p or failedcmd @cmd;
897 sub commit_getclogp ($) {
898 # Returns the parsed changelog hashref for a particular commit
900 our %commit_getclogp_memo;
901 my $memo = $commit_getclogp_memo{$objid};
902 return $memo if $memo;
904 my $mclog = ".git/dgit/clog-$objid";
905 runcmd shell_cmd "exec >$mclog", @git, qw(cat-file blob),
906 "$objid:debian/changelog";
907 $commit_getclogp_memo{$objid} = parsechangelog("-l$mclog");
912 defined $d or fail "getcwd failed: $!";
918 sub archive_query ($) {
920 my $query = access_cfg('archive-query','RETURN-UNDEF');
921 $query =~ s/^(\w+):// or badcfg "invalid archive-query method \`$query'";
924 { no strict qw(refs); &{"${method}_${proto}"}($proto,$data); }
927 sub pool_dsc_subpath ($$) {
928 my ($vsn,$component) = @_; # $package is implict arg
929 my $prefix = substr($package, 0, $package =~ m/^l/ ? 4 : 1);
930 return "/pool/$component/$prefix/$package/".dscfn($vsn);
933 #---------- `ftpmasterapi' archive query method (nascent) ----------
935 sub archive_api_query_cmd ($) {
937 my @cmd = qw(curl -sS);
938 my $url = access_cfg('archive-query-url');
939 if ($url =~ m#^https://([-.0-9a-z]+)/#) {
941 my $keys = access_cfg('archive-query-tls-key','RETURN-UNDEF') //'';
942 foreach my $key (split /\:/, $keys) {
943 $key =~ s/\%HOST\%/$host/g;
945 fail "for $url: stat $key: $!" unless $!==ENOENT;
948 fail "config requested specific TLS key but do not know".
949 " how to get curl to use exactly that EE key ($key)";
950 # push @cmd, "--cacert", $key, "--capath", "/dev/enoent";
951 # # Sadly the above line does not work because of changes
952 # # to gnutls. The real fix for #790093 may involve
953 # # new curl options.
956 # Fixing #790093 properly will involve providing a value
957 # for this on clients.
958 my $kargs = access_cfg('archive-query-tls-curl-ca-args','RETURN-UNDEF');
959 push @cmd, split / /, $kargs if defined $kargs;
961 push @cmd, $url.$subpath;
967 my ($data, $subpath) = @_;
968 badcfg "ftpmasterapi archive query method takes no data part"
970 my @cmd = archive_api_query_cmd($subpath);
971 my $json = cmdoutput @cmd;
972 return decode_json($json);
975 sub canonicalise_suite_ftpmasterapi () {
976 my ($proto,$data) = @_;
977 my $suites = api_query($data, 'suites');
979 foreach my $entry (@$suites) {
981 my $v = $entry->{$_};
982 defined $v && $v eq $isuite;
984 push @matched, $entry;
986 fail "unknown suite $isuite" unless @matched;
989 @matched==1 or die "multiple matches for suite $isuite\n";
990 $cn = "$matched[0]{codename}";
991 defined $cn or die "suite $isuite info has no codename\n";
992 $cn =~ m/^$suite_re$/ or die "suite $isuite maps to bad codename\n";
994 die "bad ftpmaster api response: $@\n".Dumper(\@matched)
999 sub archive_query_ftpmasterapi () {
1000 my ($proto,$data) = @_;
1001 my $info = api_query($data, "dsc_in_suite/$isuite/$package");
1003 my $digester = Digest::SHA->new(256);
1004 foreach my $entry (@$info) {
1006 my $vsn = "$entry->{version}";
1007 my ($ok,$msg) = version_check $vsn;
1008 die "bad version: $msg\n" unless $ok;
1009 my $component = "$entry->{component}";
1010 $component =~ m/^$component_re$/ or die "bad component";
1011 my $filename = "$entry->{filename}";
1012 $filename && $filename !~ m#[^-+:._~0-9a-zA-Z/]|^[/.]|/[/.]#
1013 or die "bad filename";
1014 my $sha256sum = "$entry->{sha256sum}";
1015 $sha256sum =~ m/^[0-9a-f]+$/ or die "bad sha256sum";
1016 push @rows, [ $vsn, "/pool/$component/$filename",
1017 $digester, $sha256sum ];
1019 die "bad ftpmaster api response: $@\n".Dumper($entry)
1022 @rows = sort { -version_compare($a->[0],$b->[0]) } @rows;
1026 #---------- `madison' archive query method ----------
1028 sub archive_query_madison {
1029 return map { [ @$_[0..1] ] } madison_get_parse(@_);
1032 sub madison_get_parse {
1033 my ($proto,$data) = @_;
1034 die unless $proto eq 'madison';
1035 if (!length $data) {
1036 $data= access_cfg('madison-distro','RETURN-UNDEF');
1037 $data //= access_basedistro();
1039 $rmad{$proto,$data,$package} ||= cmdoutput
1040 qw(rmadison -asource),"-s$isuite","-u$data",$package;
1041 my $rmad = $rmad{$proto,$data,$package};
1044 foreach my $l (split /\n/, $rmad) {
1045 $l =~ m{^ \s*( [^ \t|]+ )\s* \|
1046 \s*( [^ \t|]+ )\s* \|
1047 \s*( [^ \t|/]+ )(?:/([^ \t|/]+))? \s* \|
1048 \s*( [^ \t|]+ )\s* }x or die "$rmad ?";
1049 $1 eq $package or die "$rmad $package ?";
1056 $component = access_cfg('archive-query-default-component');
1058 $5 eq 'source' or die "$rmad ?";
1059 push @out, [$vsn,pool_dsc_subpath($vsn,$component),$newsuite];
1061 return sort { -version_compare($a->[0],$b->[0]); } @out;
1064 sub canonicalise_suite_madison {
1065 # madison canonicalises for us
1066 my @r = madison_get_parse(@_);
1068 "unable to canonicalise suite using package $package".
1069 " which does not appear to exist in suite $isuite;".
1070 " --existing-package may help";
1074 #---------- `sshpsql' archive query method ----------
1077 my ($data,$runeinfo,$sql) = @_;
1078 if (!length $data) {
1079 $data= access_someuserhost('sshpsql').':'.
1080 access_cfg('sshpsql-dbname');
1082 $data =~ m/:/ or badcfg "invalid sshpsql method string \`$data'";
1083 my ($userhost,$dbname) = ($`,$'); #';
1085 my @cmd = (access_cfg_ssh, $userhost,
1086 access_runeinfo("ssh-psql $runeinfo").
1087 " export LC_MESSAGES=C; export LC_CTYPE=C;".
1088 " ".shellquote qw(psql -A), $dbname, qw(-c), $sql);
1090 open P, "-|", @cmd or die $!;
1093 printdebug(">|$_|\n");
1096 $!=0; $?=0; close P or failedcmd @cmd;
1098 my $nrows = pop @rows;
1099 $nrows =~ s/^\((\d+) rows?\)$/$1/ or die "$nrows ?";
1100 @rows == $nrows+1 or die "$nrows ".(scalar @rows)." ?";
1101 @rows = map { [ split /\|/, $_ ] } @rows;
1102 my $ncols = scalar @{ shift @rows };
1103 die if grep { scalar @$_ != $ncols } @rows;
1107 sub sql_injection_check {
1108 foreach (@_) { die "$_ $& ?" if m{[^-+=:_.,/0-9a-zA-Z]}; }
1111 sub archive_query_sshpsql ($$) {
1112 my ($proto,$data) = @_;
1113 sql_injection_check $isuite, $package;
1114 my @rows = sshpsql($data, "archive-query $isuite $package", <<END);
1115 SELECT source.version, component.name, files.filename, files.sha256sum
1117 JOIN src_associations ON source.id = src_associations.source
1118 JOIN suite ON suite.id = src_associations.suite
1119 JOIN dsc_files ON dsc_files.source = source.id
1120 JOIN files_archive_map ON files_archive_map.file_id = dsc_files.file
1121 JOIN component ON component.id = files_archive_map.component_id
1122 JOIN files ON files.id = dsc_files.file
1123 WHERE ( suite.suite_name='$isuite' OR suite.codename='$isuite' )
1124 AND source.source='$package'
1125 AND files.filename LIKE '%.dsc';
1127 @rows = sort { -version_compare($a->[0],$b->[0]) } @rows;
1128 my $digester = Digest::SHA->new(256);
1130 my ($vsn,$component,$filename,$sha256sum) = @$_;
1131 [ $vsn, "/pool/$component/$filename",$digester,$sha256sum ];
1136 sub canonicalise_suite_sshpsql ($$) {
1137 my ($proto,$data) = @_;
1138 sql_injection_check $isuite;
1139 my @rows = sshpsql($data, "canonicalise-suite $isuite", <<END);
1140 SELECT suite.codename
1141 FROM suite where suite_name='$isuite' or codename='$isuite';
1143 @rows = map { $_->[0] } @rows;
1144 fail "unknown suite $isuite" unless @rows;
1145 die "ambiguous $isuite: @rows ?" if @rows>1;
1149 #---------- `dummycat' archive query method ----------
1151 sub canonicalise_suite_dummycat ($$) {
1152 my ($proto,$data) = @_;
1153 my $dpath = "$data/suite.$isuite";
1154 if (!open C, "<", $dpath) {
1155 $!==ENOENT or die "$dpath: $!";
1156 printdebug "dummycat canonicalise_suite $isuite $dpath ENOENT\n";
1160 chomp or die "$dpath: $!";
1162 printdebug "dummycat canonicalise_suite $isuite $dpath = $_\n";
1166 sub archive_query_dummycat ($$) {
1167 my ($proto,$data) = @_;
1168 canonicalise_suite();
1169 my $dpath = "$data/package.$csuite.$package";
1170 if (!open C, "<", $dpath) {
1171 $!==ENOENT or die "$dpath: $!";
1172 printdebug "dummycat query $csuite $package $dpath ENOENT\n";
1180 printdebug "dummycat query $csuite $package $dpath | $_\n";
1181 my @row = split /\s+/, $_;
1182 @row==2 or die "$dpath: $_ ?";
1185 C->error and die "$dpath: $!";
1187 return sort { -version_compare($a->[0],$b->[0]); } @rows;
1190 #---------- tag format handling ----------
1192 sub access_cfg_tagformats () {
1193 split /\,/, access_cfg('dgit-tag-format');
1196 sub need_tagformat ($$) {
1197 my ($fmt, $why) = @_;
1198 fail "need to use tag format $fmt ($why) but also need".
1199 " to use tag format $tagformat_want->[0] ($tagformat_want->[1])".
1200 " - no way to proceed"
1201 if $tagformat_want && $tagformat_want->[0] ne $fmt;
1202 $tagformat_want = [$fmt, $why, $tagformat_want->[2] // 0];
1205 sub select_tagformat () {
1207 return if $tagformatfn && !$tagformat_want;
1208 die 'bug' if $tagformatfn && $tagformat_want;
1209 # ... $tagformat_want assigned after previous select_tagformat
1211 my (@supported) = grep { $_ =~ m/^(?:old|new)$/ } access_cfg_tagformats();
1212 printdebug "select_tagformat supported @supported\n";
1214 $tagformat_want //= [ $supported[0], "distro access configuration", 0 ];
1215 printdebug "select_tagformat specified @$tagformat_want\n";
1217 my ($fmt,$why,$override) = @$tagformat_want;
1219 fail "target distro supports tag formats @supported".
1220 " but have to use $fmt ($why)"
1222 or grep { $_ eq $fmt } @supported;
1224 $tagformat_want = undef;
1226 $tagformatfn = ${*::}{"debiantag_$fmt"};
1228 fail "trying to use unknown tag format \`$fmt' ($why) !"
1229 unless $tagformatfn;
1232 #---------- archive query entrypoints and rest of program ----------
1234 sub canonicalise_suite () {
1235 return if defined $csuite;
1236 fail "cannot operate on $isuite suite" if $isuite eq 'UNRELEASED';
1237 $csuite = archive_query('canonicalise_suite');
1238 if ($isuite ne $csuite) {
1239 progress "canonical suite name for $isuite is $csuite";
1243 sub get_archive_dsc () {
1244 canonicalise_suite();
1245 my @vsns = archive_query('archive_query');
1246 foreach my $vinfo (@vsns) {
1247 my ($vsn,$subpath,$digester,$digest) = @$vinfo;
1248 $dscurl = access_cfg('mirror').$subpath;
1249 $dscdata = url_get($dscurl);
1251 $skew_warning_vsn = $vsn if !defined $skew_warning_vsn;
1256 $digester->add($dscdata);
1257 my $got = $digester->hexdigest();
1259 fail "$dscurl has hash $got but".
1260 " archive told us to expect $digest";
1262 my $dscfh = new IO::File \$dscdata, '<' or die $!;
1263 printdebug Dumper($dscdata) if $debuglevel>1;
1264 $dsc = parsecontrolfh($dscfh,$dscurl,1);
1265 printdebug Dumper($dsc) if $debuglevel>1;
1266 my $fmt = getfield $dsc, 'Format';
1267 fail "unsupported source format $fmt, sorry" unless $format_ok{$fmt};
1268 $dsc_checked = !!$digester;
1269 printdebug "get_archive_dsc: Version ".(getfield $dsc, 'Version')."\n";
1273 printdebug "get_archive_dsc: nothing in archive, returning undef\n";
1276 sub check_for_git ();
1277 sub check_for_git () {
1279 my $how = access_cfg('git-check');
1280 if ($how eq 'ssh-cmd') {
1282 (access_cfg_ssh, access_gituserhost(),
1283 access_runeinfo("git-check $package").
1284 " set -e; cd ".access_cfg('git-path').";".
1285 " if test -d $package.git; then echo 1; else echo 0; fi");
1286 my $r= cmdoutput @cmd;
1287 if (defined $r and $r =~ m/^divert (\w+)$/) {
1289 my ($usedistro,) = access_distros();
1290 # NB that if we are pushing, $usedistro will be $distro/push
1291 $instead_distro= cfg("dgit-distro.$usedistro.diverts.$divert");
1292 $instead_distro =~ s{^/}{ access_basedistro()."/" }e;
1293 progress "diverting to $divert (using config for $instead_distro)";
1294 return check_for_git();
1296 failedcmd @cmd unless defined $r and $r =~ m/^[01]$/;
1298 } elsif ($how eq 'url') {
1299 my $prefix = access_cfg('git-check-url','git-url');
1300 my $suffix = access_cfg('git-check-suffix','git-suffix',
1301 'RETURN-UNDEF') // '.git';
1302 my $url = "$prefix/$package$suffix";
1303 my @cmd = (qw(curl -sS -I), $url);
1304 my $result = cmdoutput @cmd;
1305 $result =~ s/^\S+ 200 .*\n\r?\n//;
1306 # curl -sS -I with https_proxy prints
1307 # HTTP/1.0 200 Connection established
1308 $result =~ m/^\S+ (404|200) /s or
1309 fail "unexpected results from git check query - ".
1310 Dumper($prefix, $result);
1312 if ($code eq '404') {
1314 } elsif ($code eq '200') {
1319 } elsif ($how eq 'true') {
1321 } elsif ($how eq 'false') {
1324 badcfg "unknown git-check \`$how'";
1328 sub create_remote_git_repo () {
1329 my $how = access_cfg('git-create');
1330 if ($how eq 'ssh-cmd') {
1332 (access_cfg_ssh, access_gituserhost(),
1333 access_runeinfo("git-create $package").
1334 "set -e; cd ".access_cfg('git-path').";".
1335 " cp -a _template $package.git");
1336 } elsif ($how eq 'true') {
1339 badcfg "unknown git-create \`$how'";
1343 our ($dsc_hash,$lastpush_mergeinput);
1345 our $ud = '.git/dgit/unpack';
1355 sub mktree_in_ud_here () {
1356 runcmd qw(git init -q);
1357 runcmd qw(git config gc.auto 0);
1358 rmtree('.git/objects');
1359 symlink '../../../../objects','.git/objects' or die $!;
1362 sub git_write_tree () {
1363 my $tree = cmdoutput @git, qw(write-tree);
1364 $tree =~ m/^\w+$/ or die "$tree ?";
1368 sub remove_stray_gits () {
1369 my @gitscmd = qw(find -name .git -prune -print0);
1370 debugcmd "|",@gitscmd;
1371 open GITS, "-|", @gitscmd or die $!;
1376 print STDERR "$us: warning: removing from source package: ",
1377 (messagequote $_), "\n";
1381 $!=0; $?=0; close GITS or failedcmd @gitscmd;
1384 sub mktree_in_ud_from_only_subdir (;$) {
1387 # changes into the subdir
1389 die "expected one subdir but found @dirs ?" unless @dirs==1;
1390 $dirs[0] =~ m#^([^/]+)/\.$# or die;
1394 remove_stray_gits();
1395 mktree_in_ud_here();
1397 my ($format, $fopts) = get_source_format();
1398 if (madformat($format)) {
1403 runcmd @git, qw(add -Af);
1404 my $tree=git_write_tree();
1405 return ($tree,$dir);
1408 sub dsc_files_info () {
1409 foreach my $csumi (['Checksums-Sha256','Digest::SHA', 'new(256)'],
1410 ['Checksums-Sha1', 'Digest::SHA', 'new(1)'],
1411 ['Files', 'Digest::MD5', 'new()']) {
1412 my ($fname, $module, $method) = @$csumi;
1413 my $field = $dsc->{$fname};
1414 next unless defined $field;
1415 eval "use $module; 1;" or die $@;
1417 foreach (split /\n/, $field) {
1419 m/^(\w+) (\d+) (\S+)$/ or
1420 fail "could not parse .dsc $fname line \`$_'";
1421 my $digester = eval "$module"."->$method;" or die $@;
1426 Digester => $digester,
1431 fail "missing any supported Checksums-* or Files field in ".
1432 $dsc->get_option('name');
1436 map { $_->{Filename} } dsc_files_info();
1439 sub is_orig_file_in_dsc ($$) {
1440 my ($f, $dsc_files_info) = @_;
1441 return 0 if @$dsc_files_info <= 1;
1442 # One file means no origs, and the filename doesn't have a "what
1443 # part of dsc" component. (Consider versions ending `.orig'.)
1444 return 0 unless $f =~ m/\.$orig_f_tail_re$/o;
1448 sub is_orig_file_of_vsn ($$) {
1449 my ($f, $upstreamvsn) = @_;
1450 my $base = srcfn $upstreamvsn, '';
1451 return 0 unless $f =~ m/^\Q$base\E\.$orig_f_tail_re$/;
1455 sub make_commit ($) {
1457 return cmdoutput @git, qw(hash-object -w -t commit), $file;
1460 sub make_commit_text ($) {
1463 my @cmd = (@git, qw(hash-object -w -t commit --stdin));
1465 print Dumper($text) if $debuglevel > 1;
1466 my $child = open2($out, $in, @cmd) or die $!;
1469 print $in $text or die $!;
1470 close $in or die $!;
1472 $h =~ m/^\w+$/ or die;
1474 printdebug "=> $h\n";
1477 waitpid $child, 0 == $child or die "$child $!";
1478 $? and failedcmd @cmd;
1482 sub clogp_authline ($) {
1484 my $author = getfield $clogp, 'Maintainer';
1485 $author =~ s#,.*##ms;
1486 my $date = cmdoutput qw(date), '+%s %z', qw(-d), getfield($clogp,'Date');
1487 my $authline = "$author $date";
1488 $authline =~ m/$git_authline_re/o or
1489 fail "unexpected commit author line format \`$authline'".
1490 " (was generated from changelog Maintainer field)";
1491 return ($1,$2,$3) if wantarray;
1495 sub vendor_patches_distro ($$) {
1496 my ($checkdistro, $what) = @_;
1497 return unless defined $checkdistro;
1499 my $series = "debian/patches/\L$checkdistro\E.series";
1500 printdebug "checking for vendor-specific $series ($what)\n";
1502 if (!open SERIES, "<", $series) {
1503 die "$series $!" unless $!==ENOENT;
1512 Unfortunately, this source package uses a feature of dpkg-source where
1513 the same source package unpacks to different source code on different
1514 distros. dgit cannot safely operate on such packages on affected
1515 distros, because the meaning of source packages is not stable.
1517 Please ask the distro/maintainer to remove the distro-specific series
1518 files and use a different technique (if necessary, uploading actually
1519 different packages, if different distros are supposed to have
1523 fail "Found active distro-specific series file for".
1524 " $checkdistro ($what): $series, cannot continue";
1526 die "$series $!" if SERIES->error;
1530 sub check_for_vendor_patches () {
1531 # This dpkg-source feature doesn't seem to be documented anywhere!
1532 # But it can be found in the changelog (reformatted):
1534 # commit 4fa01b70df1dc4458daee306cfa1f987b69da58c
1535 # Author: Raphael Hertzog <hertzog@debian.org>
1536 # Date: Sun Oct 3 09:36:48 2010 +0200
1538 # dpkg-source: correctly create .pc/.quilt_series with alternate
1541 # If you have debian/patches/ubuntu.series and you were
1542 # unpacking the source package on ubuntu, quilt was still
1543 # directed to debian/patches/series instead of
1544 # debian/patches/ubuntu.series.
1546 # debian/changelog | 3 +++
1547 # scripts/Dpkg/Source/Package/V3/quilt.pm | 4 +++-
1548 # 2 files changed, 6 insertions(+), 1 deletion(-)
1551 vendor_patches_distro($ENV{DEB_VENDOR}, "DEB_VENDOR");
1552 vendor_patches_distro(Dpkg::Vendor::get_current_vendor(),
1553 "Dpkg::Vendor \`current vendor'");
1554 vendor_patches_distro(access_basedistro(),
1555 "distro being accessed");
1558 sub generate_commits_from_dsc () {
1559 # See big comment in fetch_from_archive, below.
1563 my @dfi = dsc_files_info();
1564 foreach my $fi (@dfi) {
1565 my $f = $fi->{Filename};
1566 die "$f ?" if $f =~ m#/|^\.|\.dsc$|\.tmp$#;
1568 link_ltarget "../../../$f", $f
1572 complete_file_from_dsc('.', $fi)
1575 if (is_orig_file_in_dsc($f, \@dfi)) {
1576 link $f, "../../../../$f"
1582 # We unpack and record the orig tarballs first, so that we only
1583 # need disk space for one private copy of the unpacked source.
1584 # But we can't make them into commits until we have the metadata
1585 # from the debian/changelog, so we record the tree objects now and
1586 # make them into commits later.
1588 my $upstreamv = $dsc->{version};
1589 $upstreamv =~ s/-[^-]+$//;
1590 my $orig_f_base = srcfn $upstreamv, '';
1592 foreach my $fi (@dfi) {
1593 # We actually import, and record as a commit, every tarball
1594 # (unless there is only one file, in which case there seems
1597 my $f = $fi->{Filename};
1598 printdebug "import considering $f ";
1599 (printdebug "only one dfi\n"), next if @dfi == 1;
1600 (printdebug "not tar\n"), next unless $f =~ m/\.tar(\.\w+)?$/;
1601 (printdebug "signature\n"), next if $f =~ m/$orig_f_sig_re$/o;
1605 $f =~ m/^\Q$orig_f_base\E\.([^._]+)?\.tar(?:\.\w+)?$/;
1607 printdebug "Y ", (join ' ', map { $_//"(none)" }
1608 $compr_ext, $orig_f_part
1611 my $input = new IO::File $f, '<' or die "$f $!";
1615 if (defined $compr_ext) {
1617 Dpkg::Compression::compression_guess_from_filename $f;
1618 fail "Dpkg::Compression cannot handle file $f in source package"
1619 if defined $compr_ext && !defined $cname;
1621 new Dpkg::Compression::Process compression => $cname;
1622 my @compr_cmd = $compr_proc->get_uncompress_cmdline();
1623 my $compr_fh = new IO::Handle;
1624 my $compr_pid = open $compr_fh, "-|" // die $!;
1626 open STDIN, "<&", $input or die $!;
1628 die "dgit (child): exec $compr_cmd[0]: $!\n";
1633 rmtree "../unpack-tar";
1634 mkdir "../unpack-tar" or die $!;
1635 my @tarcmd = qw(tar -x -f -
1636 --no-same-owner --no-same-permissions
1637 --no-acls --no-xattrs --no-selinux);
1638 my $tar_pid = fork // die $!;
1640 chdir "../unpack-tar" or die $!;
1641 open STDIN, "<&", $input or die $!;
1643 die "dgit (child): exec $tarcmd[0]: $!";
1645 $!=0; (waitpid $tar_pid, 0) == $tar_pid or die $!;
1646 !$? or failedcmd @tarcmd;
1649 (@compr_cmd ? failedcmd @compr_cmd
1651 # finally, we have the results in "tarball", but maybe
1652 # with the wrong permissions
1654 runcmd qw(chmod -R +rwX ../unpack-tar);
1655 changedir "../unpack-tar";
1656 my ($tree) = mktree_in_ud_from_only_subdir(1);
1657 changedir "../../unpack";
1658 rmtree "../unpack-tar";
1660 my $ent = [ $f, $tree ];
1662 Orig => !!$orig_f_part,
1663 Sort => (!$orig_f_part ? 2 :
1664 $orig_f_part =~ m/-/g ? 1 :
1672 # put any without "_" first (spec is not clear whether files
1673 # are always in the usual order). Tarballs without "_" are
1674 # the main orig or the debian tarball.
1675 $a->{Sort} <=> $b->{Sort} or
1679 my $any_orig = grep { $_->{Orig} } @tartrees;
1681 my $dscfn = "$package.dsc";
1683 my $treeimporthow = 'package';
1685 open D, ">", $dscfn or die "$dscfn: $!";
1686 print D $dscdata or die "$dscfn: $!";
1687 close D or die "$dscfn: $!";
1688 my @cmd = qw(dpkg-source);
1689 push @cmd, '--no-check' if $dsc_checked;
1690 if (madformat $dsc->{format}) {
1691 push @cmd, '--skip-patches';
1692 $treeimporthow = 'unpatched';
1694 push @cmd, qw(-x --), $dscfn;
1697 my ($tree,$dir) = mktree_in_ud_from_only_subdir();
1698 if (madformat $dsc->{format}) {
1699 check_for_vendor_patches();
1703 if (madformat $dsc->{format}) {
1704 my @pcmd = qw(dpkg-source --before-build .);
1705 runcmd shell_cmd 'exec >/dev/null', @pcmd;
1707 runcmd @git, qw(add -Af);
1708 $dappliedtree = git_write_tree();
1711 my @clogcmd = qw(dpkg-parsechangelog --format rfc822 --all);
1712 debugcmd "|",@clogcmd;
1713 open CLOGS, "-|", @clogcmd or die $!;
1718 printdebug "import clog search...\n";
1721 my $stanzatext = do { local $/=""; <CLOGS>; };
1722 printdebug "import clogp ".Dumper($stanzatext) if $debuglevel>1;
1723 last if !defined $stanzatext;
1725 my $desc = "package changelog, entry no.$.";
1726 open my $stanzafh, "<", \$stanzatext or die;
1727 my $thisstanza = parsecontrolfh $stanzafh, $desc, 1;
1728 $clogp //= $thisstanza;
1730 printdebug "import clog $thisstanza->{version} $desc...\n";
1732 last if !$any_orig; # we don't need $r1clogp
1734 # We look for the first (most recent) changelog entry whose
1735 # version number is lower than the upstream version of this
1736 # package. Then the last (least recent) previous changelog
1737 # entry is treated as the one which introduced this upstream
1738 # version and used for the synthetic commits for the upstream
1741 # One might think that a more sophisticated algorithm would be
1742 # necessary. But: we do not want to scan the whole changelog
1743 # file. Stopping when we see an earlier version, which
1744 # necessarily then is an earlier upstream version, is the only
1745 # realistic way to do that. Then, either the earliest
1746 # changelog entry we have seen so far is indeed the earliest
1747 # upload of this upstream version; or there are only changelog
1748 # entries relating to later upstream versions (which is not
1749 # possible unless the changelog and .dsc disagree about the
1750 # version). Then it remains to choose between the physically
1751 # last entry in the file, and the one with the lowest version
1752 # number. If these are not the same, we guess that the
1753 # versions were created in a non-monotic order rather than
1754 # that the changelog entries have been misordered.
1756 printdebug "import clog $thisstanza->{version} vs $upstreamv...\n";
1758 last if version_compare($thisstanza->{version}, $upstreamv) < 0;
1759 $r1clogp = $thisstanza;
1761 printdebug "import clog $r1clogp->{version} becomes r1\n";
1763 die $! if CLOGS->error;
1764 close CLOGS or $?==(SIGPIPE<<8) or failedcmd @clogcmd;
1766 $clogp or fail "package changelog has no entries!";
1768 my $authline = clogp_authline $clogp;
1769 my $changes = getfield $clogp, 'Changes';
1770 my $cversion = getfield $clogp, 'Version';
1773 $r1clogp //= $clogp; # maybe there's only one entry;
1774 my $r1authline = clogp_authline $r1clogp;
1775 # Strictly, r1authline might now be wrong if it's going to be
1776 # unused because !$any_orig. Whatever.
1778 printdebug "import tartrees authline $authline\n";
1779 printdebug "import tartrees r1authline $r1authline\n";
1781 foreach my $tt (@tartrees) {
1782 printdebug "import tartree $tt->{F} $tt->{Tree}\n";
1784 $tt->{Commit} = make_commit_text($tt->{Orig} ? <<END_O : <<END_T);
1787 committer $r1authline
1791 [dgit import orig $tt->{F}]
1799 [dgit import tarball $package $cversion $tt->{F}]
1804 printdebug "import main commit\n";
1806 open C, ">../commit.tmp" or die $!;
1807 print C <<END or die $!;
1810 print C <<END or die $! foreach @tartrees;
1813 print C <<END or die $!;
1819 [dgit import $treeimporthow $package $cversion]
1823 my $rawimport_hash = make_commit qw(../commit.tmp);
1825 if (madformat $dsc->{format}) {
1826 printdebug "import apply patches...\n";
1828 # regularise the state of the working tree so that
1829 # the checkout of $rawimport_hash works nicely.
1830 my $dappliedcommit = make_commit_text(<<END);
1837 runcmd @git, qw(checkout -q -b dapplied), $dappliedcommit;
1839 runcmd @git, qw(checkout -q -b unpa), $rawimport_hash;
1841 # We need the answers to be reproducible
1842 my @authline = clogp_authline($clogp);
1843 local $ENV{GIT_COMMITTER_NAME} = $authline[0];
1844 local $ENV{GIT_COMMITTER_EMAIL} = $authline[1];
1845 local $ENV{GIT_COMMITTER_DATE} = $authline[2];
1846 local $ENV{GIT_AUTHOR_NAME} = $authline[0];
1847 local $ENV{GIT_AUTHOR_EMAIL} = $authline[1];
1848 local $ENV{GIT_AUTHOR_DATE} = $authline[2];
1851 runcmd shell_cmd 'exec >/dev/null 2>../../gbp-pq-output',
1855 { local $@; eval { runcmd qw(cat ../../gbp-pq-output); }; }
1859 my $gapplied = git_rev_parse('HEAD');
1860 my $gappliedtree = cmdoutput @git, qw(rev-parse HEAD:);
1861 $gappliedtree eq $dappliedtree or
1863 gbp-pq import and dpkg-source disagree!
1864 gbp-pq import gave commit $gapplied
1865 gbp-pq import gave tree $gappliedtree
1866 dpkg-source --before-build gave tree $dappliedtree
1868 $rawimport_hash = $gapplied;
1871 progress "synthesised git commit from .dsc $cversion";
1873 my $rawimport_mergeinput = {
1874 Commit => $rawimport_hash,
1875 Info => "Import of source package",
1877 my @output = ($rawimport_mergeinput);
1879 if ($lastpush_mergeinput) {
1880 my $oldclogp = mergeinfo_getclogp($lastpush_mergeinput);
1881 my $oversion = getfield $oldclogp, 'Version';
1883 version_compare($oversion, $cversion);
1885 @output = ($rawimport_mergeinput, $lastpush_mergeinput,
1886 { Message => <<END, ReverseParents => 1 });
1887 Record $package ($cversion) in archive suite $csuite
1889 } elsif ($vcmp > 0) {
1890 print STDERR <<END or die $!;
1892 Version actually in archive: $cversion (older)
1893 Last version pushed with dgit: $oversion (newer or same)
1896 @output = $lastpush_mergeinput;
1898 # Same version. Use what's in the server git branch,
1899 # discarding our own import. (This could happen if the
1900 # server automatically imports all packages into git.)
1901 @output = $lastpush_mergeinput;
1904 changedir '../../../..';
1909 sub complete_file_from_dsc ($$) {
1910 our ($dstdir, $fi) = @_;
1911 # Ensures that we have, in $dir, the file $fi, with the correct
1912 # contents. (Downloading it from alongside $dscurl if necessary.)
1914 my $f = $fi->{Filename};
1915 my $tf = "$dstdir/$f";
1918 if (stat_exists $tf) {
1919 progress "using existing $f";
1922 $furl =~ s{/[^/]+$}{};
1924 die "$f ?" unless $f =~ m/^\Q${package}\E_/;
1925 die "$f ?" if $f =~ m#/#;
1926 runcmd_ordryrun_local @curl,qw(-o),$tf,'--',"$furl";
1927 return 0 if !act_local();
1931 open F, "<", "$tf" or die "$tf: $!";
1932 $fi->{Digester}->reset();
1933 $fi->{Digester}->addfile(*F);
1934 F->error and die $!;
1935 my $got = $fi->{Digester}->hexdigest();
1936 $got eq $fi->{Hash} or
1937 fail "file $f has hash $got but .dsc".
1938 " demands hash $fi->{Hash} ".
1939 ($downloaded ? "(got wrong file from archive!)"
1940 : "(perhaps you should delete this file?)");
1945 sub ensure_we_have_orig () {
1946 my @dfi = dsc_files_info();
1947 foreach my $fi (@dfi) {
1948 my $f = $fi->{Filename};
1949 next unless is_orig_file_in_dsc($f, \@dfi);
1950 complete_file_from_dsc('..', $fi)
1955 sub git_fetch_us () {
1956 # Want to fetch only what we are going to use, unless
1957 # deliberately-not-ff, in which case we must fetch everything.
1959 my @specs = deliberately_not_fast_forward ? qw(tags/*) :
1961 (quiltmode_splitbrain
1962 ? (map { $_->('*',access_basedistro) }
1963 \&debiantag_new, \&debiantag_maintview)
1964 : debiantags('*',access_basedistro));
1965 push @specs, server_branch($csuite);
1966 push @specs, qw(heads/*) if deliberately_not_fast_forward;
1968 # This is rather miserable:
1969 # When git-fetch --prune is passed a fetchspec ending with a *,
1970 # it does a plausible thing. If there is no * then:
1971 # - it matches subpaths too, even if the supplied refspec
1972 # starts refs, and behaves completely madly if the source
1973 # has refs/refs/something. (See, for example, Debian #NNNN.)
1974 # - if there is no matching remote ref, it bombs out the whole
1976 # We want to fetch a fixed ref, and we don't know in advance
1977 # if it exists, so this is not suitable.
1979 # Our workaround is to use git-ls-remote. git-ls-remote has its
1980 # own qairks. Notably, it has the absurd multi-tail-matching
1981 # behaviour: git-ls-remote R refs/foo can report refs/foo AND
1982 # refs/refs/foo etc.
1984 # Also, we want an idempotent snapshot, but we have to make two
1985 # calls to the remote: one to git-ls-remote and to git-fetch. The
1986 # solution is use git-ls-remote to obtain a target state, and
1987 # git-fetch to try to generate it. If we don't manage to generate
1988 # the target state, we try again.
1990 my $specre = join '|', map {
1996 printdebug "git_fetch_us specre=$specre\n";
1997 my $wanted_rref = sub {
1999 return m/^(?:$specre)$/o;
2002 my $fetch_iteration = 0;
2005 if (++$fetch_iteration > 10) {
2006 fail "too many iterations trying to get sane fetch!";
2009 my @look = map { "refs/$_" } @specs;
2010 my @lcmd = (@git, qw(ls-remote -q --refs), access_giturl(), @look);
2014 open GITLS, "-|", @lcmd or die $!;
2016 printdebug "=> ", $_;
2017 m/^(\w+)\s+(\S+)\n/ or die "ls-remote $_ ?";
2018 my ($objid,$rrefname) = ($1,$2);
2019 if (!$wanted_rref->($rrefname)) {
2021 warning: git-ls-remote @look reported $rrefname; this is silly, ignoring it.
2025 $wantr{$rrefname} = $objid;
2028 close GITLS or failedcmd @lcmd;
2030 # OK, now %want is exactly what we want for refs in @specs
2032 return () if !m/\*$/ && !exists $wantr{"refs/$_"};
2033 "+refs/$_:".lrfetchrefs."/$_";
2036 my @fcmd = (@git, qw(fetch -p -n -q), access_giturl(), @fspecs);
2037 runcmd_ordryrun_local @git, qw(fetch -p -n -q), access_giturl(),
2040 %lrfetchrefs_f = ();
2043 git_for_each_ref(lrfetchrefs, sub {
2044 my ($objid,$objtype,$lrefname,$reftail) = @_;
2045 $lrfetchrefs_f{$lrefname} = $objid;
2046 $objgot{$objid} = 1;
2049 foreach my $lrefname (sort keys %lrfetchrefs_f) {
2050 my $rrefname = 'refs'.substr($lrefname, length lrfetchrefs);
2051 if (!exists $wantr{$rrefname}) {
2052 if ($wanted_rref->($rrefname)) {
2054 git-fetch @fspecs created $lrefname which git-ls-remote @look didn't list.
2058 warning: git-fetch @fspecs created $lrefname; this is silly, deleting it.
2061 runcmd_ordryrun_local @git, qw(update-ref -d), $lrefname;
2062 delete $lrfetchrefs_f{$lrefname};
2066 foreach my $rrefname (sort keys %wantr) {
2067 my $lrefname = lrfetchrefs.substr($rrefname, 4);
2068 my $got = $lrfetchrefs_f{$lrefname} // '<none>';
2069 my $want = $wantr{$rrefname};
2070 next if $got eq $want;
2071 if (!defined $objgot{$want}) {
2073 warning: git-ls-remote suggests we want $lrefname
2074 warning: and it should refer to $want
2075 warning: but git-fetch didn't fetch that object to any relevant ref.
2076 warning: This may be due to a race with someone updating the server.
2077 warning: Will try again...
2079 next FETCH_ITERATION;
2082 git-fetch @fspecs made $lrefname=$got but want git-ls-remote @look says $want
2084 runcmd_ordryrun_local @git, qw(update-ref -m),
2085 "dgit fetch git-fetch fixup", $lrefname, $want;
2086 $lrfetchrefs_f{$lrefname} = $want;
2090 printdebug "git_fetch_us: git-fetch --no-insane emulation complete\n",
2091 Dumper(\%lrfetchrefs_f);
2094 my @tagpats = debiantags('*',access_basedistro);
2096 git_for_each_ref([map { "refs/tags/$_" } @tagpats], sub {
2097 my ($objid,$objtype,$fullrefname,$reftail) = @_;
2098 printdebug "currently $fullrefname=$objid\n";
2099 $here{$fullrefname} = $objid;
2101 git_for_each_ref([map { lrfetchrefs."/tags/".$_ } @tagpats], sub {
2102 my ($objid,$objtype,$fullrefname,$reftail) = @_;
2103 my $lref = "refs".substr($fullrefname, length(lrfetchrefs));
2104 printdebug "offered $lref=$objid\n";
2105 if (!defined $here{$lref}) {
2106 my @upd = (@git, qw(update-ref), $lref, $objid, '');
2107 runcmd_ordryrun_local @upd;
2108 lrfetchref_used $fullrefname;
2109 } elsif ($here{$lref} eq $objid) {
2110 lrfetchref_used $fullrefname;
2113 "Not updateting $lref from $here{$lref} to $objid.\n";
2118 sub mergeinfo_getclogp ($) {
2119 # Ensures thit $mi->{Clogp} exists and returns it
2121 $mi->{Clogp} = commit_getclogp($mi->{Commit});
2124 sub mergeinfo_version ($) {
2125 return getfield( (mergeinfo_getclogp $_[0]), 'Version' );
2128 sub fetch_from_archive () {
2129 # Ensures that lrref() is what is actually in the archive, one way
2130 # or another, according to us - ie this client's
2131 # appropritaely-updated archive view. Also returns the commit id.
2132 # If there is nothing in the archive, leaves lrref alone and
2133 # returns undef. git_fetch_us must have already been called.
2137 foreach my $field (@ourdscfield) {
2138 $dsc_hash = $dsc->{$field};
2139 last if defined $dsc_hash;
2141 if (defined $dsc_hash) {
2142 $dsc_hash =~ m/\w+/ or fail "invalid hash in .dsc \`$dsc_hash'";
2144 progress "last upload to archive specified git hash";
2146 progress "last upload to archive has NO git hash";
2149 progress "no version available from the archive";
2152 # If the archive's .dsc has a Dgit field, there are three
2153 # relevant git commitids we need to choose between and/or merge
2155 # 1. $dsc_hash: the Dgit field from the archive
2156 # 2. $lastpush_hash: the suite branch on the dgit git server
2157 # 3. $lastfetch_hash: our local tracking brach for the suite
2159 # These may all be distinct and need not be in any fast forward
2162 # If the dsc was pushed to this suite, then the server suite
2163 # branch will have been updated; but it might have been pushed to
2164 # a different suite and copied by the archive. Conversely a more
2165 # recent version may have been pushed with dgit but not appeared
2166 # in the archive (yet).
2168 # $lastfetch_hash may be awkward because archive imports
2169 # (particularly, imports of Dgit-less .dscs) are performed only as
2170 # needed on individual clients, so different clients may perform a
2171 # different subset of them - and these imports are only made
2172 # public during push. So $lastfetch_hash may represent a set of
2173 # imports different to a subsequent upload by a different dgit
2176 # Our approach is as follows:
2178 # As between $dsc_hash and $lastpush_hash: if $lastpush_hash is a
2179 # descendant of $dsc_hash, then it was pushed by a dgit user who
2180 # had based their work on $dsc_hash, so we should prefer it.
2181 # Otherwise, $dsc_hash was installed into this suite in the
2182 # archive other than by a dgit push, and (necessarily) after the
2183 # last dgit push into that suite (since a dgit push would have
2184 # been descended from the dgit server git branch); thus, in that
2185 # case, we prefer the archive's version (and produce a
2186 # pseudo-merge to overwrite the dgit server git branch).
2188 # (If there is no Dgit field in the archive's .dsc then
2189 # generate_commit_from_dsc uses the version numbers to decide
2190 # whether the suite branch or the archive is newer. If the suite
2191 # branch is newer it ignores the archive's .dsc; otherwise it
2192 # generates an import of the .dsc, and produces a pseudo-merge to
2193 # overwrite the suite branch with the archive contents.)
2195 # The outcome of that part of the algorithm is the `public view',
2196 # and is same for all dgit clients: it does not depend on any
2197 # unpublished history in the local tracking branch.
2199 # As between the public view and the local tracking branch: The
2200 # local tracking branch is only updated by dgit fetch, and
2201 # whenever dgit fetch runs it includes the public view in the
2202 # local tracking branch. Therefore if the public view is not
2203 # descended from the local tracking branch, the local tracking
2204 # branch must contain history which was imported from the archive
2205 # but never pushed; and, its tip is now out of date. So, we make
2206 # a pseudo-merge to overwrite the old imports and stitch the old
2209 # Finally: we do not necessarily reify the public view (as
2210 # described above). This is so that we do not end up stacking two
2211 # pseudo-merges. So what we actually do is figure out the inputs
2212 # to any public view pseudo-merge and put them in @mergeinputs.
2215 # $mergeinputs[]{Commit}
2216 # $mergeinputs[]{Info}
2217 # $mergeinputs[0] is the one whose tree we use
2218 # @mergeinputs is in the order we use in the actual commit)
2221 # $mergeinputs[]{Message} is a commit message to use
2222 # $mergeinputs[]{ReverseParents} if def specifies that parent
2223 # list should be in opposite order
2224 # Such an entry has no Commit or Info. It applies only when found
2225 # in the last entry. (This ugliness is to support making
2226 # identical imports to previous dgit versions.)
2228 my $lastpush_hash = git_get_ref(lrfetchref());
2229 printdebug "previous reference hash=$lastpush_hash\n";
2230 $lastpush_mergeinput = $lastpush_hash && {
2231 Commit => $lastpush_hash,
2232 Info => "dgit suite branch on dgit git server",
2235 my $lastfetch_hash = git_get_ref(lrref());
2236 printdebug "fetch_from_archive: lastfetch=$lastfetch_hash\n";
2237 my $lastfetch_mergeinput = $lastfetch_hash && {
2238 Commit => $lastfetch_hash,
2239 Info => "dgit client's archive history view",
2242 my $dsc_mergeinput = $dsc_hash && {
2243 Commit => $dsc_hash,
2244 Info => "Dgit field in .dsc from archive",
2248 my $del_lrfetchrefs = sub {
2251 printdebug "del_lrfetchrefs...\n";
2252 foreach my $fullrefname (sort keys %lrfetchrefs_d) {
2253 my $objid = $lrfetchrefs_d{$fullrefname};
2254 printdebug "del_lrfetchrefs: $objid $fullrefname\n";
2256 $gur ||= new IO::Handle;
2257 open $gur, "|-", qw(git update-ref --stdin) or die $!;
2259 printf $gur "delete %s %s\n", $fullrefname, $objid;
2262 close $gur or failedcmd "git update-ref delete lrfetchrefs";
2266 if (defined $dsc_hash) {
2267 fail "missing remote git history even though dsc has hash -".
2268 " could not find ref ".rref()." at ".access_giturl()
2269 unless $lastpush_hash;
2270 ensure_we_have_orig();
2271 if ($dsc_hash eq $lastpush_hash) {
2272 @mergeinputs = $dsc_mergeinput
2273 } elsif (is_fast_fwd($dsc_hash,$lastpush_hash)) {
2274 print STDERR <<END or die $!;
2276 Git commit in archive is behind the last version allegedly pushed/uploaded.
2277 Commit referred to by archive: $dsc_hash
2278 Last version pushed with dgit: $lastpush_hash
2281 @mergeinputs = ($lastpush_mergeinput);
2283 # Archive has .dsc which is not a descendant of the last dgit
2284 # push. This can happen if the archive moves .dscs about.
2285 # Just follow its lead.
2286 if (is_fast_fwd($lastpush_hash,$dsc_hash)) {
2287 progress "archive .dsc names newer git commit";
2288 @mergeinputs = ($dsc_mergeinput);
2290 progress "archive .dsc names other git commit, fixing up";
2291 @mergeinputs = ($dsc_mergeinput, $lastpush_mergeinput);
2295 @mergeinputs = generate_commits_from_dsc();
2296 # We have just done an import. Now, our import algorithm might
2297 # have been improved. But even so we do not want to generate
2298 # a new different import of the same package. So if the
2299 # version numbers are the same, just use our existing version.
2300 # If the version numbers are different, the archive has changed
2301 # (perhaps, rewound).
2302 if ($lastfetch_mergeinput &&
2303 !version_compare( (mergeinfo_version $lastfetch_mergeinput),
2304 (mergeinfo_version $mergeinputs[0]) )) {
2305 @mergeinputs = ($lastfetch_mergeinput);
2307 } elsif ($lastpush_hash) {
2308 # only in git, not in the archive yet
2309 @mergeinputs = ($lastpush_mergeinput);
2310 print STDERR <<END or die $!;
2312 Package not found in the archive, but has allegedly been pushed using dgit.
2316 printdebug "nothing found!\n";
2317 if (defined $skew_warning_vsn) {
2318 print STDERR <<END or die $!;
2320 Warning: relevant archive skew detected.
2321 Archive allegedly contains $skew_warning_vsn
2322 But we were not able to obtain any version from the archive or git.
2326 unshift @end, $del_lrfetchrefs;
2330 if ($lastfetch_hash &&
2332 my $h = $_->{Commit};
2333 $h and is_fast_fwd($lastfetch_hash, $h);
2334 # If true, one of the existing parents of this commit
2335 # is a descendant of the $lastfetch_hash, so we'll
2336 # be ff from that automatically.
2340 push @mergeinputs, $lastfetch_mergeinput;
2343 printdebug "fetch mergeinfos:\n";
2344 foreach my $mi (@mergeinputs) {
2346 printdebug " commit $mi->{Commit} $mi->{Info}\n";
2348 printdebug sprintf " ReverseParents=%d Message=%s",
2349 $mi->{ReverseParents}, $mi->{Message};
2353 my $compat_info= pop @mergeinputs
2354 if $mergeinputs[$#mergeinputs]{Message};
2356 @mergeinputs = grep { defined $_->{Commit} } @mergeinputs;
2359 if (@mergeinputs > 1) {
2361 my $tree_commit = $mergeinputs[0]{Commit};
2363 my $tree = cmdoutput @git, qw(cat-file commit), $tree_commit;
2364 $tree =~ m/\n\n/; $tree = $`;
2365 $tree =~ m/^tree (\w+)$/m or die "$dsc_hash tree ?";
2368 # We use the changelog author of the package in question the
2369 # author of this pseudo-merge. This is (roughly) correct if
2370 # this commit is simply representing aa non-dgit upload.
2371 # (Roughly because it does not record sponsorship - but we
2372 # don't have sponsorship info because that's in the .changes,
2373 # which isn't in the archivw.)
2375 # But, it might be that we are representing archive history
2376 # updates (including in-archive copies). These are not really
2377 # the responsibility of the person who created the .dsc, but
2378 # there is no-one whose name we should better use. (The
2379 # author of the .dsc-named commit is clearly worse.)
2381 my $useclogp = mergeinfo_getclogp $mergeinputs[0];
2382 my $author = clogp_authline $useclogp;
2383 my $cversion = getfield $useclogp, 'Version';
2385 my $mcf = ".git/dgit/mergecommit";
2386 open MC, ">", $mcf or die "$mcf $!";
2387 print MC <<END or die $!;
2391 my @parents = grep { $_->{Commit} } @mergeinputs;
2392 @parents = reverse @parents if $compat_info->{ReverseParents};
2393 print MC <<END or die $! foreach @parents;
2397 print MC <<END or die $!;
2403 if (defined $compat_info->{Message}) {
2404 print MC $compat_info->{Message} or die $!;
2406 print MC <<END or die $!;
2407 Record $package ($cversion) in archive suite $csuite
2411 my $message_add_info = sub {
2413 my $mversion = mergeinfo_version $mi;
2414 printf MC " %-20s %s\n", $mversion, $mi->{Info}
2418 $message_add_info->($mergeinputs[0]);
2419 print MC <<END or die $!;
2420 should be treated as descended from
2422 $message_add_info->($_) foreach @mergeinputs[1..$#mergeinputs];
2426 $hash = make_commit $mcf;
2428 $hash = $mergeinputs[0]{Commit};
2430 printdebug "fetch hash=$hash\n";
2433 my ($lasth, $what) = @_;
2434 return unless $lasth;
2435 die "$lasth $hash $what ?" unless is_fast_fwd($lasth, $hash);
2438 $chkff->($lastpush_hash, 'dgit repo server tip (last push)');
2439 $chkff->($lastfetch_hash, 'local tracking tip (last fetch)');
2441 runcmd @git, qw(update-ref -m), "dgit fetch $csuite",
2442 'DGIT_ARCHIVE', $hash;
2443 cmdoutput @git, qw(log -n2), $hash;
2444 # ... gives git a chance to complain if our commit is malformed
2446 if (defined $skew_warning_vsn) {
2448 printdebug "SKEW CHECK WANT $skew_warning_vsn\n";
2449 my $gotclogp = commit_getclogp($hash);
2450 my $got_vsn = getfield $gotclogp, 'Version';
2451 printdebug "SKEW CHECK GOT $got_vsn\n";
2452 if (version_compare($got_vsn, $skew_warning_vsn) < 0) {
2453 print STDERR <<END or die $!;
2455 Warning: archive skew detected. Using the available version:
2456 Archive allegedly contains $skew_warning_vsn
2457 We were able to obtain only $got_vsn
2463 if ($lastfetch_hash ne $hash) {
2464 my @upd_cmd = (@git, qw(update-ref -m), 'dgit fetch', lrref(), $hash);
2468 dryrun_report @upd_cmd;
2472 lrfetchref_used lrfetchref();
2474 unshift @end, $del_lrfetchrefs;
2478 sub set_local_git_config ($$) {
2480 runcmd @git, qw(config), $k, $v;
2483 sub setup_mergechangelogs (;$) {
2485 return unless $always || access_cfg_bool(1, 'setup-mergechangelogs');
2487 my $driver = 'dpkg-mergechangelogs';
2488 my $cb = "merge.$driver";
2489 my $attrs = '.git/info/attributes';
2490 ensuredir '.git/info';
2492 open NATTRS, ">", "$attrs.new" or die "$attrs.new $!";
2493 if (!open ATTRS, "<", $attrs) {
2494 $!==ENOENT or die "$attrs: $!";
2498 next if m{^debian/changelog\s};
2499 print NATTRS $_, "\n" or die $!;
2501 ATTRS->error and die $!;
2504 print NATTRS "debian/changelog merge=$driver\n" or die $!;
2507 set_local_git_config "$cb.name", 'debian/changelog merge driver';
2508 set_local_git_config "$cb.driver", 'dpkg-mergechangelogs -m %O %A %B %A';
2510 rename "$attrs.new", "$attrs" or die "$attrs: $!";
2513 sub setup_useremail (;$) {
2515 return unless $always || access_cfg_bool(1, 'setup-useremail');
2518 my ($k, $envvar) = @_;
2519 my $v = access_cfg("user-$k", 'RETURN-UNDEF') // $ENV{$envvar};
2520 return unless defined $v;
2521 set_local_git_config "user.$k", $v;
2524 $setup->('email', 'DEBEMAIL');
2525 $setup->('name', 'DEBFULLNAME');
2528 sub setup_new_tree () {
2529 setup_mergechangelogs();
2535 canonicalise_suite();
2536 badusage "dry run makes no sense with clone" unless act_local();
2537 my $hasgit = check_for_git();
2538 mkdir $dstdir or fail "create \`$dstdir': $!";
2540 runcmd @git, qw(init -q);
2541 my $giturl = access_giturl(1);
2542 if (defined $giturl) {
2543 open H, "> .git/HEAD" or die $!;
2544 print H "ref: ".lref()."\n" or die $!;
2546 runcmd @git, qw(remote add), 'origin', $giturl;
2549 progress "fetching existing git history";
2551 runcmd_ordryrun_local @git, qw(fetch origin);
2553 progress "starting new git history";
2555 fetch_from_archive() or no_such_package;
2556 my $vcsgiturl = $dsc->{'Vcs-Git'};
2557 if (length $vcsgiturl) {
2558 $vcsgiturl =~ s/\s+-b\s+\S+//g;
2559 runcmd @git, qw(remote add vcs-git), $vcsgiturl;
2562 runcmd @git, qw(reset --hard), lrref();
2563 printdone "ready for work in $dstdir";
2567 if (check_for_git()) {
2570 fetch_from_archive() or no_such_package();
2571 printdone "fetched into ".lrref();
2576 runcmd_ordryrun_local @git, qw(merge -m),"Merge from $csuite [dgit]",
2578 printdone "fetched to ".lrref()." and merged into HEAD";
2581 sub check_not_dirty () {
2582 foreach my $f (qw(local-options local-patch-header)) {
2583 if (stat_exists "debian/source/$f") {
2584 fail "git tree contains debian/source/$f";
2588 return if $ignoredirty;
2590 my @cmd = (@git, qw(diff --quiet HEAD));
2592 $!=0; $?=-1; system @cmd;
2595 fail "working tree is dirty (does not match HEAD)";
2601 sub commit_admin ($) {
2604 runcmd_ordryrun_local @git, qw(commit -m), $m;
2607 sub commit_quilty_patch () {
2608 my $output = cmdoutput @git, qw(status --porcelain);
2610 foreach my $l (split /\n/, $output) {
2611 next unless $l =~ m/\S/;
2612 if ($l =~ m{^(?:\?\?| M) (.pc|debian/patches)}) {
2616 delete $adds{'.pc'}; # if there wasn't one before, don't add it
2618 progress "nothing quilty to commit, ok.";
2621 my @adds = map { s/[][*?\\]/\\$&/g; $_; } sort keys %adds;
2622 runcmd_ordryrun_local @git, qw(add -f), @adds;
2623 commit_admin "Commit Debian 3.0 (quilt) metadata";
2626 sub get_source_format () {
2628 if (open F, "debian/source/options") {
2632 s/\s+$//; # ignore missing final newline
2634 my ($k, $v) = ($`, $'); #');
2635 $v =~ s/^"(.*)"$/$1/;
2641 F->error and die $!;
2644 die $! unless $!==&ENOENT;
2647 if (!open F, "debian/source/format") {
2648 die $! unless $!==&ENOENT;
2652 F->error and die $!;
2654 return ($_, \%options);
2657 sub madformat_wantfixup ($) {
2659 return 0 unless $format eq '3.0 (quilt)';
2660 our $quilt_mode_warned;
2661 if ($quilt_mode eq 'nocheck') {
2662 progress "Not doing any fixup of \`$format' due to".
2663 " ----no-quilt-fixup or --quilt=nocheck"
2664 unless $quilt_mode_warned++;
2667 progress "Format \`$format', need to check/update patch stack"
2668 unless $quilt_mode_warned++;
2672 # An "infopair" is a tuple [ $thing, $what ]
2673 # (often $thing is a commit hash; $what is a description)
2675 sub infopair_cond_equal ($$) {
2677 $x->[0] eq $y->[0] or fail <<END;
2678 $x->[1] ($x->[0]) not equal to $y->[1] ($y->[0])
2682 sub infopair_lrf_tag_lookup ($$) {
2683 my ($tagnames, $what) = @_;
2684 # $tagname may be an array ref
2685 my @tagnames = ref $tagnames ? @$tagnames : ($tagnames);
2686 printdebug "infopair_lrfetchref_tag_lookup $what @tagnames\n";
2687 foreach my $tagname (@tagnames) {
2688 my $lrefname = lrfetchrefs."/tags/$tagname";
2689 my $tagobj = $lrfetchrefs_f{$lrefname};
2690 next unless defined $tagobj;
2691 printdebug "infopair_lrfetchref_tag_lookup $tagobj $tagname $what\n";
2692 return [ git_rev_parse($tagobj), $what ];
2694 fail @tagnames==1 ? <<END : <<END;
2695 Wanted tag $what (@tagnames) on dgit server, but not found
2697 Wanted tag $what (one of: @tagnames) on dgit server, but not found
2701 sub infopair_cond_ff ($$) {
2702 my ($anc,$desc) = @_;
2703 is_fast_fwd($anc->[0], $desc->[0]) or fail <<END;
2704 $anc->[1] ($anc->[0]) .. $desc->[1] ($desc->[0]) is not fast forward
2708 sub pseudomerge_version_check ($$) {
2709 my ($clogp, $archive_hash) = @_;
2711 my $arch_clogp = commit_getclogp $archive_hash;
2712 my $i_arch_v = [ (getfield $arch_clogp, 'Version'),
2713 'version currently in archive' ];
2714 if (defined $overwrite_version) {
2715 if (length $overwrite_version) {
2716 infopair_cond_equal([ $overwrite_version,
2717 '--overwrite= version' ],
2720 my $v = $i_arch_v->[0];
2721 progress "Checking package changelog for archive version $v ...";
2723 my @xa = ("-f$v", "-t$v");
2724 my $vclogp = parsechangelog @xa;
2725 my $cv = [ (getfield $vclogp, 'Version'),
2726 "Version field from dpkg-parsechangelog @xa" ];
2727 infopair_cond_equal($i_arch_v, $cv);
2730 $@ =~ s/^dgit: //gm;
2732 "Perhaps debian/changelog does not mention $v ?";
2737 printdebug "pseudomerge_version_check i_arch_v @$i_arch_v\n";
2741 sub pseudomerge_make_commit ($$$$ $$) {
2742 my ($clogp, $dgitview, $archive_hash, $i_arch_v,
2743 $msg_cmd, $msg_msg) = @_;
2744 progress "Declaring that HEAD inciudes all changes in $i_arch_v->[0]...";
2746 my $tree = cmdoutput qw(git rev-parse), "${dgitview}:";
2747 my $authline = clogp_authline $clogp;
2751 !defined $overwrite_version ? ""
2752 : !length $overwrite_version ? " --overwrite"
2753 : " --overwrite=".$overwrite_version;
2756 my $pmf = ".git/dgit/pseudomerge";
2757 open MC, ">", $pmf or die "$pmf $!";
2758 print MC <<END or die $!;
2761 parent $archive_hash
2771 return make_commit($pmf);
2774 sub splitbrain_pseudomerge ($$$$) {
2775 my ($clogp, $maintview, $dgitview, $archive_hash) = @_;
2776 # => $merged_dgitview
2777 printdebug "splitbrain_pseudomerge...\n";
2779 # We: debian/PREVIOUS HEAD($maintview)
2780 # expect: o ----------------- o
2783 # a/d/PREVIOUS $dgitview
2786 # we do: `------------------ o
2790 printdebug "splitbrain_pseudomerge...\n";
2792 my $i_arch_v = pseudomerge_version_check($clogp, $archive_hash);
2794 return $dgitview unless defined $archive_hash;
2796 if (!defined $overwrite_version) {
2797 progress "Checking that HEAD inciudes all changes in archive...";
2800 return $dgitview if is_fast_fwd $archive_hash, $dgitview;
2802 my $t_dep14 = debiantag_maintview $i_arch_v->[0], access_basedistro;
2803 my $i_dep14 = infopair_lrf_tag_lookup($t_dep14, "maintainer view tag");
2804 my $t_dgit = debiantag_new $i_arch_v->[0], access_basedistro;
2805 my $i_dgit = infopair_lrf_tag_lookup($t_dgit, "dgit view tag");
2806 my $i_archive = [ $archive_hash, "current archive contents" ];
2808 printdebug "splitbrain_pseudomerge i_archive @$i_archive\n";
2810 infopair_cond_equal($i_dgit, $i_archive);
2811 infopair_cond_ff($i_dep14, $i_dgit);
2812 $overwrite_version // infopair_cond_ff($i_dep14, [ $maintview, 'HEAD' ]);
2814 my $r = pseudomerge_make_commit
2815 $clogp, $dgitview, $archive_hash, $i_arch_v,
2816 "dgit --quilt=$quilt_mode",
2817 (defined $overwrite_version ? <<END_OVERWR : <<END_MAKEFF);
2818 Declare fast forward from $overwrite_version
2820 Make fast forward from $i_arch_v->[0]
2823 progress "Made pseudo-merge of $i_arch_v->[0] into dgit view.";
2827 sub plain_overwrite_pseudomerge ($$$) {
2828 my ($clogp, $head, $archive_hash) = @_;
2830 printdebug "plain_overwrite_pseudomerge...";
2832 my $i_arch_v = pseudomerge_version_check($clogp, $archive_hash);
2834 my @tagformats = access_cfg_tagformats();
2836 map { $_->($i_arch_v->[0], access_basedistro) }
2837 (grep { m/^(?:old|hist)$/ } @tagformats)
2838 ? \&debiantags : \&debiantag_new;
2839 my $i_overwr = infopair_lrf_tag_lookup \@t_overwr, "previous version tag";
2840 my $i_archive = [ $archive_hash, "current archive contents" ];
2842 infopair_cond_equal($i_overwr, $i_archive);
2844 return $head if is_fast_fwd $archive_hash, $head;
2846 my $m = "Declare fast forward from $i_arch_v->[0]";
2848 my $r = pseudomerge_make_commit
2849 $clogp, $head, $archive_hash, $i_arch_v,
2852 runcmd @git, qw(update-ref -m), $m, 'HEAD', $r, $head;
2854 progress "Make pseudo-merge of $i_arch_v->[0] into your HEAD.";
2858 sub push_parse_changelog ($) {
2861 my $clogp = Dpkg::Control::Hash->new();
2862 $clogp->load($clogpfn) or die;
2864 $package = getfield $clogp, 'Source';
2865 my $cversion = getfield $clogp, 'Version';
2866 my $tag = debiantag($cversion, access_basedistro);
2867 runcmd @git, qw(check-ref-format), $tag;
2869 my $dscfn = dscfn($cversion);
2871 return ($clogp, $cversion, $dscfn);
2874 sub push_parse_dsc ($$$) {
2875 my ($dscfn,$dscfnwhat, $cversion) = @_;
2876 $dsc = parsecontrol($dscfn,$dscfnwhat);
2877 my $dversion = getfield $dsc, 'Version';
2878 my $dscpackage = getfield $dsc, 'Source';
2879 ($dscpackage eq $package && $dversion eq $cversion) or
2880 fail "$dscfn is for $dscpackage $dversion".
2881 " but debian/changelog is for $package $cversion";
2884 sub push_tagwants ($$$$) {
2885 my ($cversion, $dgithead, $maintviewhead, $tfbase) = @_;
2888 TagFn => \&debiantag,
2893 if (defined $maintviewhead) {
2895 TagFn => \&debiantag_maintview,
2896 Objid => $maintviewhead,
2897 TfSuffix => '-maintview',
2901 foreach my $tw (@tagwants) {
2902 $tw->{Tag} = $tw->{TagFn}($cversion, access_basedistro);
2903 $tw->{Tfn} = sub { $tfbase.$tw->{TfSuffix}.$_[0]; };
2905 printdebug 'push_tagwants: ', Dumper(\@_, \@tagwants);
2909 sub push_mktags ($$ $$ $) {
2911 $changesfile,$changesfilewhat,
2914 die unless $tagwants->[0]{View} eq 'dgit';
2916 $dsc->{$ourdscfield[0]} = $tagwants->[0]{Objid};
2917 $dsc->save("$dscfn.tmp") or die $!;
2919 my $changes = parsecontrol($changesfile,$changesfilewhat);
2920 foreach my $field (qw(Source Distribution Version)) {
2921 $changes->{$field} eq $clogp->{$field} or
2922 fail "changes field $field \`$changes->{$field}'".
2923 " does not match changelog \`$clogp->{$field}'";
2926 my $cversion = getfield $clogp, 'Version';
2927 my $clogsuite = getfield $clogp, 'Distribution';
2929 # We make the git tag by hand because (a) that makes it easier
2930 # to control the "tagger" (b) we can do remote signing
2931 my $authline = clogp_authline $clogp;
2932 my $delibs = join(" ", "",@deliberatelies);
2933 my $declaredistro = access_basedistro();
2937 my $tfn = $tw->{Tfn};
2938 my $head = $tw->{Objid};
2939 my $tag = $tw->{Tag};
2941 open TO, '>', $tfn->('.tmp') or die $!;
2942 print TO <<END or die $!;
2949 if ($tw->{View} eq 'dgit') {
2950 print TO <<END or die $!;
2951 $package release $cversion for $clogsuite ($csuite) [dgit]
2952 [dgit distro=$declaredistro$delibs]
2954 foreach my $ref (sort keys %previously) {
2955 print TO <<END or die $!;
2956 [dgit previously:$ref=$previously{$ref}]
2959 } elsif ($tw->{View} eq 'maint') {
2960 print TO <<END or die $!;
2961 $package release $cversion for $clogsuite ($csuite)
2962 (maintainer view tag generated by dgit --quilt=$quilt_mode)
2965 die Dumper($tw)."?";
2970 my $tagobjfn = $tfn->('.tmp');
2972 if (!defined $keyid) {
2973 $keyid = access_cfg('keyid','RETURN-UNDEF');
2975 if (!defined $keyid) {
2976 $keyid = getfield $clogp, 'Maintainer';
2978 unlink $tfn->('.tmp.asc') or $!==&ENOENT or die $!;
2979 my @sign_cmd = (@gpg, qw(--detach-sign --armor));
2980 push @sign_cmd, qw(-u),$keyid if defined $keyid;
2981 push @sign_cmd, $tfn->('.tmp');
2982 runcmd_ordryrun @sign_cmd;
2984 $tagobjfn = $tfn->('.signed.tmp');
2985 runcmd shell_cmd "exec >$tagobjfn", qw(cat --),
2986 $tfn->('.tmp'), $tfn->('.tmp.asc');
2992 my @r = map { $mktag->($_); } @$tagwants;
2996 sub sign_changes ($) {
2997 my ($changesfile) = @_;
2999 my @debsign_cmd = @debsign;
3000 push @debsign_cmd, "-k$keyid" if defined $keyid;
3001 push @debsign_cmd, "-p$gpg[0]" if $gpg[0] ne 'gpg';
3002 push @debsign_cmd, $changesfile;
3003 runcmd_ordryrun @debsign_cmd;
3008 printdebug "actually entering push\n";
3010 supplementary_message(<<'END');
3011 Push failed, while checking state of the archive.
3012 You can retry the push, after fixing the problem, if you like.
3014 if (check_for_git()) {
3017 my $archive_hash = fetch_from_archive();
3018 if (!$archive_hash) {
3020 fail "package appears to be new in this suite;".
3021 " if this is intentional, use --new";
3024 supplementary_message(<<'END');
3025 Push failed, while preparing your push.
3026 You can retry the push, after fixing the problem, if you like.
3029 need_tagformat 'new', "quilt mode $quilt_mode"
3030 if quiltmode_splitbrain;
3034 access_giturl(); # check that success is vaguely likely
3037 my $clogpfn = ".git/dgit/changelog.822.tmp";
3038 runcmd shell_cmd "exec >$clogpfn", qw(dpkg-parsechangelog);
3040 responder_send_file('parsed-changelog', $clogpfn);
3042 my ($clogp, $cversion, $dscfn) =
3043 push_parse_changelog("$clogpfn");
3045 my $dscpath = "$buildproductsdir/$dscfn";
3046 stat_exists $dscpath or
3047 fail "looked for .dsc $dscfn, but $!;".
3048 " maybe you forgot to build";
3050 responder_send_file('dsc', $dscpath);
3052 push_parse_dsc($dscpath, $dscfn, $cversion);
3054 my $format = getfield $dsc, 'Format';
3055 printdebug "format $format\n";
3057 my $actualhead = git_rev_parse('HEAD');
3058 my $dgithead = $actualhead;
3059 my $maintviewhead = undef;
3061 if (madformat_wantfixup($format)) {
3062 # user might have not used dgit build, so maybe do this now:
3063 if (quiltmode_splitbrain()) {
3064 my $upstreamversion = $clogp->{Version};
3065 $upstreamversion =~ s/-[^-]*$//;
3067 quilt_make_fake_dsc($upstreamversion);
3068 my ($dgitview, $cachekey) =
3069 quilt_check_splitbrain_cache($actualhead, $upstreamversion);
3071 "--quilt=$quilt_mode but no cached dgit view:
3072 perhaps tree changed since dgit build[-source] ?";
3074 $dgithead = splitbrain_pseudomerge($clogp,
3075 $actualhead, $dgitview,
3077 $maintviewhead = $actualhead;
3078 changedir '../../../..';
3079 prep_ud(); # so _only_subdir() works, below
3081 commit_quilty_patch();
3085 if (defined $overwrite_version && !defined $maintviewhead) {
3086 $dgithead = plain_overwrite_pseudomerge($clogp,
3094 if ($archive_hash) {
3095 if (is_fast_fwd($archive_hash, $dgithead)) {
3097 } elsif (deliberately_not_fast_forward) {
3100 fail "dgit push: HEAD is not a descendant".
3101 " of the archive's version.\n".
3102 "To overwrite the archive's contents,".
3103 " pass --overwrite[=VERSION].\n".
3104 "To rewind history, if permitted by the archive,".
3105 " use --deliberately-not-fast-forward.";
3110 progress "checking that $dscfn corresponds to HEAD";
3111 runcmd qw(dpkg-source -x --),
3112 $dscpath =~ m#^/# ? $dscpath : "../../../$dscpath";
3113 my ($tree,$dir) = mktree_in_ud_from_only_subdir();
3114 check_for_vendor_patches() if madformat($dsc->{format});
3115 changedir '../../../..';
3116 my $diffopt = $debuglevel>0 ? '--exit-code' : '--quiet';
3117 my @diffcmd = (@git, qw(diff), $diffopt, $tree, $dgithead);
3118 debugcmd "+",@diffcmd;
3120 my $r = system @diffcmd;
3123 fail "$dscfn specifies a different tree to your HEAD commit;".
3124 " perhaps you forgot to build".
3125 ($diffopt eq '--exit-code' ? "" :
3126 " (run with -D to see full diff output)");
3131 if (!$changesfile) {
3132 my $pat = changespat $cversion;
3133 my @cs = glob "$buildproductsdir/$pat";
3134 fail "failed to find unique changes file".
3135 " (looked for $pat in $buildproductsdir);".
3136 " perhaps you need to use dgit -C"
3138 ($changesfile) = @cs;
3140 $changesfile = "$buildproductsdir/$changesfile";
3143 # Checks complete, we're going to try and go ahead:
3145 responder_send_file('changes',$changesfile);
3146 responder_send_command("param head $dgithead");
3147 responder_send_command("param csuite $csuite");
3148 responder_send_command("param tagformat $tagformat");
3149 if (defined $maintviewhead) {
3150 die unless ($protovsn//4) >= 4;
3151 responder_send_command("param maint-view $maintviewhead");
3154 if (deliberately_not_fast_forward) {
3155 git_for_each_ref(lrfetchrefs, sub {
3156 my ($objid,$objtype,$lrfetchrefname,$reftail) = @_;
3157 my $rrefname= substr($lrfetchrefname, length(lrfetchrefs) + 1);
3158 responder_send_command("previously $rrefname=$objid");
3159 $previously{$rrefname} = $objid;
3163 my @tagwants = push_tagwants($cversion, $dgithead, $maintviewhead,
3167 supplementary_message(<<'END');
3168 Push failed, while signing the tag.
3169 You can retry the push, after fixing the problem, if you like.
3171 # If we manage to sign but fail to record it anywhere, it's fine.
3172 if ($we_are_responder) {
3173 @tagobjfns = map { $_->{Tfn}('.signed-tmp') } @tagwants;
3174 responder_receive_files('signed-tag', @tagobjfns);
3176 @tagobjfns = push_mktags($clogp,$dscpath,
3177 $changesfile,$changesfile,
3180 supplementary_message(<<'END');
3181 Push failed, *after* signing the tag.
3182 If you want to try again, you should use a new version number.
3185 pairwise { $a->{TagObjFn} = $b } @tagwants, @tagobjfns;
3187 foreach my $tw (@tagwants) {
3188 my $tag = $tw->{Tag};
3189 my $tagobjfn = $tw->{TagObjFn};
3191 cmdoutput @git, qw(hash-object -w -t tag), $tagobjfn;
3192 runcmd_ordryrun @git, qw(verify-tag), $tag_obj_hash;
3193 runcmd_ordryrun_local
3194 @git, qw(update-ref), "refs/tags/$tag", $tag_obj_hash;
3197 supplementary_message(<<'END');
3198 Push failed, while updating the remote git repository - see messages above.
3199 If you want to try again, you should use a new version number.
3201 if (!check_for_git()) {
3202 create_remote_git_repo();
3205 my @pushrefs = $forceflag.$dgithead.":".rrref();
3206 foreach my $tw (@tagwants) {
3207 push @pushrefs, $forceflag."refs/tags/$tw->{Tag}";
3210 runcmd_ordryrun @git, qw(push),access_giturl(), @pushrefs;
3211 runcmd_ordryrun @git, qw(update-ref -m), 'dgit push', lrref(), $dgithead;
3213 supplementary_message(<<'END');
3214 Push failed, after updating the remote git repository.
3215 If you want to try again, you must use a new version number.
3217 if ($we_are_responder) {
3218 my $dryrunsuffix = act_local() ? "" : ".tmp";
3219 responder_receive_files('signed-dsc-changes',
3220 "$dscpath$dryrunsuffix",
3221 "$changesfile$dryrunsuffix");
3224 rename "$dscpath.tmp",$dscpath or die "$dscfn $!";
3226 progress "[new .dsc left in $dscpath.tmp]";
3228 sign_changes $changesfile;
3231 supplementary_message(<<END);
3232 Push failed, while uploading package(s) to the archive server.
3233 You can retry the upload of exactly these same files with dput of:
3235 If that .changes file is broken, you will need to use a new version
3236 number for your next attempt at the upload.
3238 my $host = access_cfg('upload-host','RETURN-UNDEF');
3239 my @hostarg = defined($host) ? ($host,) : ();
3240 runcmd_ordryrun @dput, @hostarg, $changesfile;
3241 printdone "pushed and uploaded $cversion";
3243 supplementary_message('');
3244 responder_send_command("complete");
3251 badusage "-p is not allowed with clone; specify as argument instead"
3252 if defined $package;
3255 } elsif (@ARGV==2 && $ARGV[1] =~ m#^\w#) {
3256 ($package,$isuite) = @ARGV;
3257 } elsif (@ARGV==2 && $ARGV[1] =~ m#^[./]#) {
3258 ($package,$dstdir) = @ARGV;
3259 } elsif (@ARGV==3) {
3260 ($package,$isuite,$dstdir) = @ARGV;
3262 badusage "incorrect arguments to dgit clone";
3264 $dstdir ||= "$package";
3266 if (stat_exists $dstdir) {
3267 fail "$dstdir already exists";
3271 if ($rmonerror && !$dryrun_level) {
3272 $cwd_remove= getcwd();
3274 return unless defined $cwd_remove;
3275 if (!chdir "$cwd_remove") {
3276 return if $!==&ENOENT;
3277 die "chdir $cwd_remove: $!";
3280 rmtree($dstdir) or die "remove $dstdir: $!\n";
3281 } elsif (!grep { $! == $_ }
3282 (ENOENT, ENOTDIR, EACCES, EPERM, ELOOP)) {
3284 print STDERR "check whether to remove $dstdir: $!\n";
3290 $cwd_remove = undef;
3293 sub branchsuite () {
3294 my $branch = cmdoutput_errok @git, qw(symbolic-ref HEAD);
3295 if ($branch =~ m#$lbranch_re#o) {
3302 sub fetchpullargs () {
3304 if (!defined $package) {
3305 my $sourcep = parsecontrol('debian/control','debian/control');
3306 $package = getfield $sourcep, 'Source';
3309 # $isuite = branchsuite(); # this doesn't work because dak hates canons
3311 my $clogp = parsechangelog();
3312 $isuite = getfield $clogp, 'Distribution';
3314 canonicalise_suite();
3315 progress "fetching from suite $csuite";
3316 } elsif (@ARGV==1) {
3318 canonicalise_suite();
3320 badusage "incorrect arguments to dgit fetch or dgit pull";
3339 badusage "-p is not allowed with dgit push" if defined $package;
3341 my $clogp = parsechangelog();
3342 $package = getfield $clogp, 'Source';
3345 } elsif (@ARGV==1) {
3346 ($specsuite) = (@ARGV);
3348 badusage "incorrect arguments to dgit push";
3350 $isuite = getfield $clogp, 'Distribution';
3352 local ($package) = $existing_package; # this is a hack
3353 canonicalise_suite();
3355 canonicalise_suite();
3357 if (defined $specsuite &&
3358 $specsuite ne $isuite &&
3359 $specsuite ne $csuite) {
3360 fail "dgit push: changelog specifies $isuite ($csuite)".
3361 " but command line specifies $specsuite";
3366 #---------- remote commands' implementation ----------
3368 sub cmd_remote_push_build_host {
3369 my ($nrargs) = shift @ARGV;
3370 my (@rargs) = @ARGV[0..$nrargs-1];
3371 @ARGV = @ARGV[$nrargs..$#ARGV];
3373 my ($dir,$vsnwant) = @rargs;
3374 # vsnwant is a comma-separated list; we report which we have
3375 # chosen in our ready response (so other end can tell if they
3378 $we_are_responder = 1;
3379 $us .= " (build host)";
3383 open PI, "<&STDIN" or die $!;
3384 open STDIN, "/dev/null" or die $!;
3385 open PO, ">&STDOUT" or die $!;
3387 open STDOUT, ">&STDERR" or die $!;
3391 ($protovsn) = grep {
3392 $vsnwant =~ m{^(?:.*,)?$_(?:,.*)?$}
3393 } @rpushprotovsn_support;
3395 fail "build host has dgit rpush protocol versions ".
3396 (join ",", @rpushprotovsn_support).
3397 " but invocation host has $vsnwant"
3398 unless defined $protovsn;
3400 responder_send_command("dgit-remote-push-ready $protovsn");
3401 rpush_handle_protovsn_bothends();
3406 sub cmd_remote_push_responder { cmd_remote_push_build_host(); }
3407 # ... for compatibility with proto vsn.1 dgit (just so that user gets
3408 # a good error message)
3410 sub rpush_handle_protovsn_bothends () {
3411 if ($protovsn < 4) {
3412 need_tagformat 'old', "rpush negotiated protocol $protovsn";
3421 my $report = i_child_report();
3422 if (defined $report) {
3423 printdebug "($report)\n";
3424 } elsif ($i_child_pid) {
3425 printdebug "(killing build host child $i_child_pid)\n";
3426 kill 15, $i_child_pid;
3428 if (defined $i_tmp && !defined $initiator_tempdir) {
3430 eval { rmtree $i_tmp; };
3434 END { i_cleanup(); }
3437 my ($base,$selector,@args) = @_;
3438 $selector =~ s/\-/_/g;
3439 { no strict qw(refs); &{"${base}_${selector}"}(@args); }
3446 if ($host =~ m/^((?:[^][]|\[[^][]*\])*)\:/) {
3454 push @rargs, join ",", @rpushprotovsn_support;
3457 push @rdgit, @ropts;
3458 push @rdgit, qw(remote-push-build-host), (scalar @rargs), @rargs;
3460 my @cmd = (@ssh, $host, shellquote @rdgit);
3463 if (defined $initiator_tempdir) {
3464 rmtree $initiator_tempdir;
3465 mkdir $initiator_tempdir, 0700 or die "$initiator_tempdir: $!";
3466 $i_tmp = $initiator_tempdir;
3470 $i_child_pid = open2(\*RO, \*RI, @cmd);
3472 ($protovsn) = initiator_expect { m/^dgit-remote-push-ready (\S+)/ };
3473 die "$protovsn ?" unless grep { $_ eq $protovsn } @rpushprotovsn_support;
3474 $supplementary_message = '' unless $protovsn >= 3;
3476 fail "rpush negotiated protocol version $protovsn".
3477 " which does not support quilt mode $quilt_mode"
3478 if quiltmode_splitbrain;
3480 rpush_handle_protovsn_bothends();
3482 my ($icmd,$iargs) = initiator_expect {
3483 m/^(\S+)(?: (.*))?$/;
3486 i_method "i_resp", $icmd, $iargs;
3490 sub i_resp_progress ($) {
3492 my $msg = protocol_read_bytes \*RO, $rhs;
3496 sub i_resp_supplementary_message ($) {
3498 $supplementary_message = protocol_read_bytes \*RO, $rhs;
3501 sub i_resp_complete {
3502 my $pid = $i_child_pid;
3503 $i_child_pid = undef; # prevents killing some other process with same pid
3504 printdebug "waiting for build host child $pid...\n";
3505 my $got = waitpid $pid, 0;
3506 die $! unless $got == $pid;
3507 die "build host child failed $?" if $?;
3510 printdebug "all done\n";
3514 sub i_resp_file ($) {
3516 my $localname = i_method "i_localname", $keyword;
3517 my $localpath = "$i_tmp/$localname";
3518 stat_exists $localpath and
3519 badproto \*RO, "file $keyword ($localpath) twice";
3520 protocol_receive_file \*RO, $localpath;
3521 i_method "i_file", $keyword;
3526 sub i_resp_param ($) {
3527 $_[0] =~ m/^(\S+) (.*)$/ or badproto \*RO, "bad param spec";
3531 sub i_resp_previously ($) {
3532 $_[0] =~ m#^(refs/tags/\S+)=(\w+)$#
3533 or badproto \*RO, "bad previously spec";
3534 my $r = system qw(git check-ref-format), $1;
3535 die "bad previously ref spec ($r)" if $r;
3536 $previously{$1} = $2;
3541 sub i_resp_want ($) {
3543 die "$keyword ?" if $i_wanted{$keyword}++;
3544 my @localpaths = i_method "i_want", $keyword;
3545 printdebug "[[ $keyword @localpaths\n";
3546 foreach my $localpath (@localpaths) {
3547 protocol_send_file \*RI, $localpath;
3549 print RI "files-end\n" or die $!;
3552 our ($i_clogp, $i_version, $i_dscfn, $i_changesfn);
3554 sub i_localname_parsed_changelog {
3555 return "remote-changelog.822";
3557 sub i_file_parsed_changelog {
3558 ($i_clogp, $i_version, $i_dscfn) =
3559 push_parse_changelog "$i_tmp/remote-changelog.822";
3560 die if $i_dscfn =~ m#/|^\W#;
3563 sub i_localname_dsc {
3564 defined $i_dscfn or badproto \*RO, "dsc (before parsed-changelog)";
3569 sub i_localname_changes {
3570 defined $i_dscfn or badproto \*RO, "dsc (before parsed-changelog)";
3571 $i_changesfn = $i_dscfn;
3572 $i_changesfn =~ s/\.dsc$/_dgit.changes/ or die;
3573 return $i_changesfn;
3575 sub i_file_changes { }
3577 sub i_want_signed_tag {
3578 printdebug Dumper(\%i_param, $i_dscfn);
3579 defined $i_param{'head'} && defined $i_dscfn && defined $i_clogp
3580 && defined $i_param{'csuite'}
3581 or badproto \*RO, "premature desire for signed-tag";
3582 my $head = $i_param{'head'};
3583 die if $head =~ m/[^0-9a-f]/ || $head !~ m/^../;
3585 my $maintview = $i_param{'maint-view'};
3586 die if defined $maintview && $maintview =~ m/[^0-9a-f]/;
3589 if ($protovsn >= 4) {
3590 my $p = $i_param{'tagformat'} // '<undef>';
3592 or badproto \*RO, "tag format mismatch: $p vs. $tagformat";
3595 die unless $i_param{'csuite'} =~ m/^$suite_re$/;
3597 push_parse_dsc $i_dscfn, 'remote dsc', $i_version;
3599 my @tagwants = push_tagwants $i_version, $head, $maintview, "tag";
3602 push_mktags $i_clogp, $i_dscfn,
3603 $i_changesfn, 'remote changes',
3607 sub i_want_signed_dsc_changes {
3608 rename "$i_dscfn.tmp","$i_dscfn" or die "$i_dscfn $!";
3609 sign_changes $i_changesfn;
3610 return ($i_dscfn, $i_changesfn);
3613 #---------- building etc. ----------
3619 #----- `3.0 (quilt)' handling -----
3621 our $fakeeditorenv = 'DGIT_FAKE_EDITOR_QUILT';
3623 sub quiltify_dpkg_commit ($$$;$) {
3624 my ($patchname,$author,$msg, $xinfo) = @_;
3628 my $descfn = ".git/dgit/quilt-description.tmp";
3629 open O, '>', $descfn or die "$descfn: $!";
3632 $msg =~ s/^\s+$/ ./mg;
3633 print O <<END or die $!;
3643 local $ENV{'EDITOR'} = cmdoutput qw(realpath --), $0;
3644 local $ENV{'VISUAL'} = $ENV{'EDITOR'};
3645 local $ENV{$fakeeditorenv} = cmdoutput qw(realpath --), $descfn;
3646 runcmd @dpkgsource, qw(--commit .), $patchname;
3650 sub quiltify_trees_differ ($$;$$) {
3651 my ($x,$y,$finegrained,$ignorenamesr) = @_;
3652 # returns true iff the two tree objects differ other than in debian/
3653 # with $finegrained,
3654 # returns bitmask 01 - differ in upstream files except .gitignore
3655 # 02 - differ in .gitignore
3656 # if $ignorenamesr is defined, $ingorenamesr->{$fn}
3657 # is set for each modified .gitignore filename $fn
3659 my @cmd = (@git, qw(diff-tree --name-only -z));
3660 push @cmd, qw(-r) if $finegrained;
3662 my $diffs= cmdoutput @cmd;
3664 foreach my $f (split /\0/, $diffs) {
3665 next if $f =~ m#^debian(?:/.*)?$#s;
3666 my $isignore = $f =~ m#^(?:.*/)?.gitignore$#s;
3667 $r |= $isignore ? 02 : 01;
3668 $ignorenamesr->{$f}=1 if $ignorenamesr && $isignore;
3670 printdebug "quiltify_trees_differ $x $y => $r\n";
3674 sub quiltify_tree_sentinelfiles ($) {
3675 # lists the `sentinel' files present in the tree
3677 my $r = cmdoutput @git, qw(ls-tree --name-only), $x,
3678 qw(-- debian/rules debian/control);
3683 sub quiltify_splitbrain_needed () {
3684 if (!$split_brain) {
3685 progress "dgit view: changes are required...";
3686 runcmd @git, qw(checkout -q -b dgit-view);
3691 sub quiltify_splitbrain ($$$$$$) {
3692 my ($clogp, $unapplied, $headref, $diffbits,
3693 $editedignores, $cachekey) = @_;
3694 if ($quilt_mode !~ m/gbp|dpm/) {
3695 # treat .gitignore just like any other upstream file
3696 $diffbits = { %$diffbits };
3697 $_ = !!$_ foreach values %$diffbits;
3699 # We would like any commits we generate to be reproducible
3700 my @authline = clogp_authline($clogp);
3701 local $ENV{GIT_COMMITTER_NAME} = $authline[0];
3702 local $ENV{GIT_COMMITTER_EMAIL} = $authline[1];
3703 local $ENV{GIT_COMMITTER_DATE} = $authline[2];
3704 local $ENV{GIT_AUTHOR_NAME} = $authline[0];
3705 local $ENV{GIT_AUTHOR_EMAIL} = $authline[1];
3706 local $ENV{GIT_AUTHOR_DATE} = $authline[2];
3708 if ($quilt_mode =~ m/gbp|unapplied/ &&
3709 ($diffbits->{H2O} & 01)) {
3711 "--quilt=$quilt_mode specified, implying patches-unapplied git tree\n".
3712 " but git tree differs from orig in upstream files.";
3713 if (!stat_exists "debian/patches") {
3715 "\n ... debian/patches is missing; perhaps this is a patch queue branch?";
3719 if ($quilt_mode =~ m/dpm/ &&
3720 ($diffbits->{H2A} & 01)) {
3722 --quilt=$quilt_mode specified, implying patches-applied git tree
3723 but git tree differs from result of applying debian/patches to upstream
3726 if ($quilt_mode =~ m/gbp|unapplied/ &&
3727 ($diffbits->{O2A} & 01)) { # some patches
3728 quiltify_splitbrain_needed();
3729 progress "dgit view: creating patches-applied version using gbp pq";
3730 runcmd shell_cmd 'exec >/dev/null', gbp_pq, qw(import);
3731 # gbp pq import creates a fresh branch; push back to dgit-view
3732 runcmd @git, qw(update-ref refs/heads/dgit-view HEAD);
3733 runcmd @git, qw(checkout -q dgit-view);
3735 if ($quilt_mode =~ m/gbp|dpm/ &&
3736 ($diffbits->{O2A} & 02)) {
3738 --quilt=$quilt_mode specified, implying that HEAD is for use with a
3739 tool which does not create patches for changes to upstream
3740 .gitignores: but, such patches exist in debian/patches.
3743 if (($diffbits->{H2O} & 02) && # user has modified .gitignore
3744 !($diffbits->{O2A} & 02)) { # patches do not change .gitignore
3745 quiltify_splitbrain_needed();
3746 progress "dgit view: creating patch to represent .gitignore changes";
3747 ensuredir "debian/patches";
3748 my $gipatch = "debian/patches/auto-gitignore";
3749 open GIPATCH, ">>", "$gipatch" or die "$gipatch: $!";
3750 stat GIPATCH or die "$gipatch: $!";
3751 fail "$gipatch already exists; but want to create it".
3752 " to record .gitignore changes" if (stat _)[7];
3753 print GIPATCH <<END or die "$gipatch: $!";
3754 Subject: Update .gitignore from Debian packaging branch
3756 The Debian packaging git branch contains these updates to the upstream
3757 .gitignore file(s). This patch is autogenerated, to provide these
3758 updates to users of the official Debian archive view of the package.
3760 [dgit ($our_version) update-gitignore]
3763 close GIPATCH or die "$gipatch: $!";
3764 runcmd shell_cmd "exec >>$gipatch", @git, qw(diff),
3765 $unapplied, $headref, "--", sort keys %$editedignores;
3766 open SERIES, "+>>", "debian/patches/series" or die $!;
3767 defined seek SERIES, -1, 2 or $!==EINVAL or die $!;
3769 defined read SERIES, $newline, 1 or die $!;
3770 print SERIES "\n" or die $! unless $newline eq "\n";
3771 print SERIES "auto-gitignore\n" or die $!;
3772 close SERIES or die $!;
3773 runcmd @git, qw(add -- debian/patches/series), $gipatch;
3774 commit_admin "Commit patch to update .gitignore";
3777 my $dgitview = git_rev_parse 'refs/heads/dgit-view';
3779 changedir '../../../..';
3780 ensuredir ".git/logs/refs/dgit-intern";
3781 my $makelogfh = new IO::File ".git/logs/refs/$splitbraincache", '>>'
3783 runcmd @git, qw(update-ref -m), $cachekey, "refs/$splitbraincache",
3786 progress "dgit view: created (commit id $dgitview)";
3788 changedir '.git/dgit/unpack/work';
3791 sub quiltify ($$$$) {
3792 my ($clogp,$target,$oldtiptree,$failsuggestion) = @_;
3794 # Quilt patchification algorithm
3796 # We search backwards through the history of the main tree's HEAD
3797 # (T) looking for a start commit S whose tree object is identical
3798 # to to the patch tip tree (ie the tree corresponding to the
3799 # current dpkg-committed patch series). For these purposes
3800 # `identical' disregards anything in debian/ - this wrinkle is
3801 # necessary because dpkg-source treates debian/ specially.
3803 # We can only traverse edges where at most one of the ancestors'
3804 # trees differs (in changes outside in debian/). And we cannot
3805 # handle edges which change .pc/ or debian/patches. To avoid
3806 # going down a rathole we avoid traversing edges which introduce
3807 # debian/rules or debian/control. And we set a limit on the
3808 # number of edges we are willing to look at.
3810 # If we succeed, we walk forwards again. For each traversed edge
3811 # PC (with P parent, C child) (starting with P=S and ending with
3812 # C=T) to we do this:
3814 # - dpkg-source --commit with a patch name and message derived from C
3815 # After traversing PT, we git commit the changes which
3816 # should be contained within debian/patches.
3818 # The search for the path S..T is breadth-first. We maintain a
3819 # todo list containing search nodes. A search node identifies a
3820 # commit, and looks something like this:
3822 # Commit => $git_commit_id,
3823 # Child => $c, # or undef if P=T
3824 # Whynot => $reason_edge_PC_unsuitable, # in @nots only
3825 # Nontrivial => true iff $p..$c has relevant changes
3832 my %considered; # saves being exponential on some weird graphs
3834 my $t_sentinels = quiltify_tree_sentinelfiles $target;
3837 my ($search,$whynot) = @_;
3838 printdebug " search NOT $search->{Commit} $whynot\n";
3839 $search->{Whynot} = $whynot;
3840 push @nots, $search;
3841 no warnings qw(exiting);
3850 my $c = shift @todo;
3851 next if $considered{$c->{Commit}}++;
3853 $not->($c, "maximum search space exceeded") if --$max_work <= 0;
3855 printdebug "quiltify investigate $c->{Commit}\n";
3858 if (!quiltify_trees_differ $c->{Commit}, $oldtiptree) {
3859 printdebug " search finished hooray!\n";
3864 if ($quilt_mode eq 'nofix') {
3865 fail "quilt fixup required but quilt mode is \`nofix'\n".
3866 "HEAD commit $c->{Commit} differs from tree implied by ".
3867 " debian/patches (tree object $oldtiptree)";
3869 if ($quilt_mode eq 'smash') {
3870 printdebug " search quitting smash\n";
3874 my $c_sentinels = quiltify_tree_sentinelfiles $c->{Commit};
3875 $not->($c, "has $c_sentinels not $t_sentinels")
3876 if $c_sentinels ne $t_sentinels;
3878 my $commitdata = cmdoutput @git, qw(cat-file commit), $c->{Commit};
3879 $commitdata =~ m/\n\n/;
3881 my @parents = ($commitdata =~ m/^parent (\w+)$/gm);
3882 @parents = map { { Commit => $_, Child => $c } } @parents;
3884 $not->($c, "root commit") if !@parents;
3886 foreach my $p (@parents) {
3887 $p->{Nontrivial}= quiltify_trees_differ $p->{Commit},$c->{Commit};
3889 my $ndiffers = grep { $_->{Nontrivial} } @parents;
3890 $not->($c, "merge ($ndiffers nontrivial parents)") if $ndiffers > 1;
3892 foreach my $p (@parents) {
3893 printdebug "considering C=$c->{Commit} P=$p->{Commit}\n";
3895 my @cmd= (@git, qw(diff-tree -r --name-only),
3896 $p->{Commit},$c->{Commit}, qw(-- debian/patches .pc));
3897 my $patchstackchange = cmdoutput @cmd;
3898 if (length $patchstackchange) {
3899 $patchstackchange =~ s/\n/,/g;
3900 $not->($p, "changed $patchstackchange");
3903 printdebug " search queue P=$p->{Commit} ",
3904 ($p->{Nontrivial} ? "NT" : "triv"),"\n";
3910 printdebug "quiltify want to smash\n";
3913 my $x = $_[0]{Commit};
3914 $x =~ s/(.*?[0-9a-z]{8})[0-9a-z]*$/$1/;
3917 my $reportnot = sub {
3919 my $s = $abbrev->($notp);
3920 my $c = $notp->{Child};
3921 $s .= "..".$abbrev->($c) if $c;
3922 $s .= ": ".$notp->{Whynot};
3925 if ($quilt_mode eq 'linear') {
3926 print STDERR "$us: quilt fixup cannot be linear. Stopped at:\n";
3927 foreach my $notp (@nots) {
3928 print STDERR "$us: ", $reportnot->($notp), "\n";
3930 print STDERR "$us: $_\n" foreach @$failsuggestion;
3931 fail "quilt fixup naive history linearisation failed.\n".
3932 "Use dpkg-source --commit by hand; or, --quilt=smash for one ugly patch";
3933 } elsif ($quilt_mode eq 'smash') {
3934 } elsif ($quilt_mode eq 'auto') {
3935 progress "quilt fixup cannot be linear, smashing...";
3937 die "$quilt_mode ?";
3940 my $time = $ENV{'GIT_COMMITTER_DATE'} || time;
3941 $time =~ s/\s.*//; # trim timezone from GIT_COMMITTER_DATE
3943 my $msg = cmdoutput @git, qw(log), "-n$ncommits";
3945 quiltify_dpkg_commit "auto-$version-$target-$time",
3946 (getfield $clogp, 'Maintainer'),
3947 "Automatically generated patch ($clogp->{Version})\n".
3948 "Last (up to) $ncommits git changes, FYI:\n\n". $msg;
3952 progress "quiltify linearisation planning successful, executing...";
3954 for (my $p = $sref_S;
3955 my $c = $p->{Child};
3957 printdebug "quiltify traverse $p->{Commit}..$c->{Commit}\n";
3958 next unless $p->{Nontrivial};
3960 my $cc = $c->{Commit};
3962 my $commitdata = cmdoutput @git, qw(cat-file commit), $cc;
3963 $commitdata =~ m/\n\n/ or die "$c ?";
3966 $commitdata =~ m/^author (.*) \d+ [-+0-9]+$/m or die "$cc ?";
3969 $msg =~ s/^(.*)\n*/$1\n/ or die "$cc $msg ?";
3972 my $patchname = $title;
3973 $patchname =~ s/[.:]$//;
3974 $patchname =~ y/ A-Z/-a-z/;
3975 $patchname =~ y/-a-z0-9_.+=~//cd;
3976 $patchname =~ s/^\W/x-$&/;
3977 $patchname = substr($patchname,0,40);
3980 stat "debian/patches/$patchname$index";
3982 $!==ENOENT or die "$patchname$index $!";
3984 runcmd @git, qw(checkout -q), $cc;
3986 # We use the tip's changelog so that dpkg-source doesn't
3987 # produce complaining messages from dpkg-parsechangelog. None
3988 # of the information dpkg-source gets from the changelog is
3989 # actually relevant - it gets put into the original message
3990 # which dpkg-source provides our stunt editor, and then
3992 runcmd @git, qw(checkout -q), $target, qw(debian/changelog);
3994 quiltify_dpkg_commit "$patchname$index", $author, $msg,
3995 "X-Dgit-Generated: $clogp->{Version} $cc\n";
3997 runcmd @git, qw(checkout -q), $cc, qw(debian/changelog);
4000 runcmd @git, qw(checkout -q master);
4003 sub build_maybe_quilt_fixup () {
4004 my ($format,$fopts) = get_source_format;
4005 return unless madformat_wantfixup $format;
4008 check_for_vendor_patches();
4010 if (quiltmode_splitbrain) {
4011 foreach my $needtf (qw(new maint)) {
4012 next if grep { $_ eq $needtf } access_cfg_tagformats;
4014 quilt mode $quilt_mode requires split view so server needs to support
4015 both "new" and "maint" tag formats, but config says it doesn't.
4020 my $clogp = parsechangelog();
4021 my $headref = git_rev_parse('HEAD');
4026 my $upstreamversion=$version;
4027 $upstreamversion =~ s/-[^-]*$//;
4029 if ($fopts->{'single-debian-patch'}) {
4030 quilt_fixup_singlepatch($clogp, $headref, $upstreamversion);
4032 quilt_fixup_multipatch($clogp, $headref, $upstreamversion);
4035 die 'bug' if $split_brain && !$need_split_build_invocation;
4037 changedir '../../../..';
4038 runcmd_ordryrun_local
4039 @git, qw(pull --ff-only -q .git/dgit/unpack/work master);
4042 sub quilt_fixup_mkwork ($) {
4045 mkdir "work" or die $!;
4047 mktree_in_ud_here();
4048 runcmd @git, qw(reset -q --hard), $headref;
4051 sub quilt_fixup_linkorigs ($$) {
4052 my ($upstreamversion, $fn) = @_;
4053 # calls $fn->($leafname);
4055 foreach my $f (<../../../../*>) { #/){
4056 my $b=$f; $b =~ s{.*/}{};
4058 local ($debuglevel) = $debuglevel-1;
4059 printdebug "QF linkorigs $b, $f ?\n";
4061 next unless is_orig_file_of_vsn $b, $upstreamversion;
4062 printdebug "QF linkorigs $b, $f Y\n";
4063 link_ltarget $f, $b or die "$b $!";
4068 sub quilt_fixup_delete_pc () {
4069 runcmd @git, qw(rm -rqf .pc);
4070 commit_admin "Commit removal of .pc (quilt series tracking data)";
4073 sub quilt_fixup_singlepatch ($$$) {
4074 my ($clogp, $headref, $upstreamversion) = @_;
4076 progress "starting quiltify (single-debian-patch)";
4078 # dpkg-source --commit generates new patches even if
4079 # single-debian-patch is in debian/source/options. In order to
4080 # get it to generate debian/patches/debian-changes, it is
4081 # necessary to build the source package.
4083 quilt_fixup_linkorigs($upstreamversion, sub { });
4084 quilt_fixup_mkwork($headref);
4086 rmtree("debian/patches");
4088 runcmd @dpkgsource, qw(-b .);
4090 runcmd @dpkgsource, qw(-x), (srcfn $version, ".dsc");
4091 rename srcfn("$upstreamversion", "/debian/patches"),
4092 "work/debian/patches";
4095 commit_quilty_patch();
4098 sub quilt_make_fake_dsc ($) {
4099 my ($upstreamversion) = @_;
4101 my $fakeversion="$upstreamversion-~~DGITFAKE";
4103 my $fakedsc=new IO::File 'fake.dsc', '>' or die $!;
4104 print $fakedsc <<END or die $!;
4107 Version: $fakeversion
4111 my $dscaddfile=sub {
4114 my $md = new Digest::MD5;
4116 my $fh = new IO::File $b, '<' or die "$b $!";
4121 print $fakedsc " ".$md->hexdigest." $size $b\n" or die $!;
4124 quilt_fixup_linkorigs($upstreamversion, $dscaddfile);
4126 my @files=qw(debian/source/format debian/rules
4127 debian/control debian/changelog);
4128 foreach my $maybe (qw(debian/patches debian/source/options
4129 debian/tests/control)) {
4130 next unless stat_exists "../../../$maybe";
4131 push @files, $maybe;
4134 my $debtar= srcfn $fakeversion,'.debian.tar.gz';
4135 runcmd qw(env GZIP=-1n tar -zcf), "./$debtar", qw(-C ../../..), @files;
4137 $dscaddfile->($debtar);
4138 close $fakedsc or die $!;
4141 sub quilt_check_splitbrain_cache ($$) {
4142 my ($headref, $upstreamversion) = @_;
4143 # Called only if we are in (potentially) split brain mode.
4145 # Computes the cache key and looks in the cache.
4146 # Returns ($dgit_view_commitid, $cachekey) or (undef, $cachekey)
4148 my $splitbrain_cachekey;
4151 "dgit: split brain (separate dgit view) may be needed (--quilt=$quilt_mode).";
4152 # we look in the reflog of dgit-intern/quilt-cache
4153 # we look for an entry whose message is the key for the cache lookup
4154 my @cachekey = (qw(dgit), $our_version);
4155 push @cachekey, $upstreamversion;
4156 push @cachekey, $quilt_mode;
4157 push @cachekey, $headref;
4159 push @cachekey, hashfile('fake.dsc');
4161 my $srcshash = Digest::SHA->new(256);
4162 my %sfs = ( %INC, '$0(dgit)' => $0 );
4163 foreach my $sfk (sort keys %sfs) {
4164 next unless m/^\$0\b/ || m{^Debian/Dgit\b};
4165 $srcshash->add($sfk," ");
4166 $srcshash->add(hashfile($sfs{$sfk}));
4167 $srcshash->add("\n");
4169 push @cachekey, $srcshash->hexdigest();
4170 $splitbrain_cachekey = "@cachekey";
4172 my @cmd = (@git, qw(reflog), '--pretty=format:%H %gs',
4174 printdebug "splitbrain cachekey $splitbrain_cachekey\n";
4175 debugcmd "|(probably)",@cmd;
4176 my $child = open GC, "-|"; defined $child or die $!;
4178 chdir '../../..' or die $!;
4179 if (!stat ".git/logs/refs/$splitbraincache") {
4180 $! == ENOENT or die $!;
4181 printdebug ">(no reflog)\n";
4188 printdebug ">| ", $_, "\n" if $debuglevel > 1;
4189 next unless m/^(\w+) (\S.*\S)$/ && $2 eq $splitbrain_cachekey;
4192 quilt_fixup_mkwork($headref);
4193 if ($cachehit ne $headref) {
4194 progress "dgit view: found cached (commit id $cachehit)";
4195 runcmd @git, qw(checkout -q -b dgit-view), $cachehit;
4197 return ($cachehit, $splitbrain_cachekey);
4199 progress "dgit view: found cached, no changes required";
4200 return ($headref, $splitbrain_cachekey);
4202 die $! if GC->error;
4203 failedcmd unless close GC;
4205 printdebug "splitbrain cache miss\n";
4206 return (undef, $splitbrain_cachekey);
4209 sub quilt_fixup_multipatch ($$$) {
4210 my ($clogp, $headref, $upstreamversion) = @_;
4212 progress "examining quilt state (multiple patches, $quilt_mode mode)";
4215 # - honour any existing .pc in case it has any strangeness
4216 # - determine the git commit corresponding to the tip of
4217 # the patch stack (if there is one)
4218 # - if there is such a git commit, convert each subsequent
4219 # git commit into a quilt patch with dpkg-source --commit
4220 # - otherwise convert all the differences in the tree into
4221 # a single git commit
4225 # Our git tree doesn't necessarily contain .pc. (Some versions of
4226 # dgit would include the .pc in the git tree.) If there isn't
4227 # one, we need to generate one by unpacking the patches that we
4230 # We first look for a .pc in the git tree. If there is one, we
4231 # will use it. (This is not the normal case.)
4233 # Otherwise need to regenerate .pc so that dpkg-source --commit
4234 # can work. We do this as follows:
4235 # 1. Collect all relevant .orig from parent directory
4236 # 2. Generate a debian.tar.gz out of
4237 # debian/{patches,rules,source/format,source/options}
4238 # 3. Generate a fake .dsc containing just these fields:
4239 # Format Source Version Files
4240 # 4. Extract the fake .dsc
4241 # Now the fake .dsc has a .pc directory.
4242 # (In fact we do this in every case, because in future we will
4243 # want to search for a good base commit for generating patches.)
4245 # Then we can actually do the dpkg-source --commit
4246 # 1. Make a new working tree with the same object
4247 # store as our main tree and check out the main
4249 # 2. Copy .pc from the fake's extraction, if necessary
4250 # 3. Run dpkg-source --commit
4251 # 4. If the result has changes to debian/, then
4252 # - git-add them them
4253 # - git-add .pc if we had a .pc in-tree
4255 # 5. If we had a .pc in-tree, delete it, and git-commit
4256 # 6. Back in the main tree, fast forward to the new HEAD
4258 # Another situation we may have to cope with is gbp-style
4259 # patches-unapplied trees.
4261 # We would want to detect these, so we know to escape into
4262 # quilt_fixup_gbp. However, this is in general not possible.
4263 # Consider a package with a one patch which the dgit user reverts
4264 # (with git-revert or the moral equivalent).
4266 # That is indistinguishable in contents from a patches-unapplied
4267 # tree. And looking at the history to distinguish them is not
4268 # useful because the user might have made a confusing-looking git
4269 # history structure (which ought to produce an error if dgit can't
4270 # cope, not a silent reintroduction of an unwanted patch).
4272 # So gbp users will have to pass an option. But we can usually
4273 # detect their failure to do so: if the tree is not a clean
4274 # patches-applied tree, quilt linearisation fails, but the tree
4275 # _is_ a clean patches-unapplied tree, we can suggest that maybe
4276 # they want --quilt=unapplied.
4278 # To help detect this, when we are extracting the fake dsc, we
4279 # first extract it with --skip-patches, and then apply the patches
4280 # afterwards with dpkg-source --before-build. That lets us save a
4281 # tree object corresponding to .origs.
4283 my $splitbrain_cachekey;
4285 quilt_make_fake_dsc($upstreamversion);
4287 if (quiltmode_splitbrain()) {
4289 ($cachehit, $splitbrain_cachekey) =
4290 quilt_check_splitbrain_cache($headref, $upstreamversion);
4291 return if $cachehit;
4295 'exec dpkg-source --no-check --skip-patches -x fake.dsc >/dev/null';
4297 my $fakexdir= $package.'-'.(stripepoch $upstreamversion);
4298 rename $fakexdir, "fake" or die "$fakexdir $!";
4302 remove_stray_gits();
4303 mktree_in_ud_here();
4307 runcmd @git, qw(add -Af .);
4308 my $unapplied=git_write_tree();
4309 printdebug "fake orig tree object $unapplied\n";
4314 'exec dpkg-source --before-build . >/dev/null';
4318 quilt_fixup_mkwork($headref);
4321 if (stat_exists ".pc") {
4323 progress "Tree already contains .pc - will use it then delete it.";
4326 rename '../fake/.pc','.pc' or die $!;
4329 changedir '../fake';
4331 runcmd @git, qw(add -Af .);
4332 my $oldtiptree=git_write_tree();
4333 printdebug "fake o+d/p tree object $unapplied\n";
4334 changedir '../work';
4337 # We calculate some guesswork now about what kind of tree this might
4338 # be. This is mostly for error reporting.
4343 # O = orig, without patches applied
4344 # A = "applied", ie orig with H's debian/patches applied
4345 H2O => quiltify_trees_differ($headref, $unapplied, 1,\%editedignores),
4346 H2A => quiltify_trees_differ($headref, $oldtiptree,1),
4347 O2A => quiltify_trees_differ($unapplied,$oldtiptree,1),
4351 foreach my $b (qw(01 02)) {
4352 foreach my $v (qw(H2O O2A H2A)) {
4353 push @dl, ($diffbits->{$v} & $b) ? '##' : '==';
4356 printdebug "differences \@dl @dl.\n";
4359 "$us: quilt differences: src: %s orig %s gitignores: %s orig %s\n".
4360 "$us: quilt differences: HEAD %s o+d/p HEAD %s o+d/p",
4361 $dl[0], $dl[1], $dl[3], $dl[4],
4365 if (!($diffbits->{H2O} & $diffbits->{O2A})) {
4366 push @failsuggestion, "This might be a patches-unapplied branch.";
4367 } elsif (!($diffbits->{H2A} & $diffbits->{O2A})) {
4368 push @failsuggestion, "This might be a patches-applied branch.";
4370 push @failsuggestion, "Maybe you need to specify one of".
4371 " --quilt=gbp --quilt=dpm --quilt=unapplied ?";
4373 if (quiltmode_splitbrain()) {
4374 quiltify_splitbrain($clogp, $unapplied, $headref,
4375 $diffbits, \%editedignores,
4376 $splitbrain_cachekey);
4380 progress "starting quiltify (multiple patches, $quilt_mode mode)";
4381 quiltify($clogp,$headref,$oldtiptree,\@failsuggestion);
4383 if (!open P, '>>', ".pc/applied-patches") {
4384 $!==&ENOENT or die $!;
4389 commit_quilty_patch();
4391 if ($mustdeletepc) {
4392 quilt_fixup_delete_pc();
4396 sub quilt_fixup_editor () {
4397 my $descfn = $ENV{$fakeeditorenv};
4398 my $editing = $ARGV[$#ARGV];
4399 open I1, '<', $descfn or die "$descfn: $!";
4400 open I2, '<', $editing or die "$editing: $!";
4401 unlink $editing or die "$editing: $!";
4402 open O, '>', $editing or die "$editing: $!";
4403 while (<I1>) { print O or die $!; } I1->error and die $!;
4406 $copying ||= m/^\-\-\- /;
4407 next unless $copying;
4410 I2->error and die $!;
4415 sub maybe_apply_patches_dirtily () {
4416 return unless $quilt_mode =~ m/gbp|unapplied/;
4417 print STDERR <<END or die $!;
4419 dgit: Building, or cleaning with rules target, in patches-unapplied tree.
4420 dgit: Have to apply the patches - making the tree dirty.
4421 dgit: (Consider specifying --clean=git and (or) using dgit sbuild.)
4424 $patches_applied_dirtily = 01;
4425 $patches_applied_dirtily |= 02 unless stat_exists '.pc';
4426 runcmd qw(dpkg-source --before-build .);
4429 sub maybe_unapply_patches_again () {
4430 progress "dgit: Unapplying patches again to tidy up the tree."
4431 if $patches_applied_dirtily;
4432 runcmd qw(dpkg-source --after-build .)
4433 if $patches_applied_dirtily & 01;
4435 if $patches_applied_dirtily & 02;
4436 $patches_applied_dirtily = 0;
4439 #----- other building -----
4441 our $clean_using_builder;
4442 # ^ tree is to be cleaned by dpkg-source's builtin idea that it should
4443 # clean the tree before building (perhaps invoked indirectly by
4444 # whatever we are using to run the build), rather than separately
4445 # and explicitly by us.
4448 return if $clean_using_builder;
4449 if ($cleanmode eq 'dpkg-source') {
4450 maybe_apply_patches_dirtily();
4451 runcmd_ordryrun_local @dpkgbuildpackage, qw(-T clean);
4452 } elsif ($cleanmode eq 'dpkg-source-d') {
4453 maybe_apply_patches_dirtily();
4454 runcmd_ordryrun_local @dpkgbuildpackage, qw(-d -T clean);
4455 } elsif ($cleanmode eq 'git') {
4456 runcmd_ordryrun_local @git, qw(clean -xdf);
4457 } elsif ($cleanmode eq 'git-ff') {
4458 runcmd_ordryrun_local @git, qw(clean -xdff);
4459 } elsif ($cleanmode eq 'check') {
4460 my $leftovers = cmdoutput @git, qw(clean -xdn);
4461 if (length $leftovers) {
4462 print STDERR $leftovers, "\n" or die $!;
4463 fail "tree contains uncommitted files and --clean=check specified";
4465 } elsif ($cleanmode eq 'none') {
4472 badusage "clean takes no additional arguments" if @ARGV;
4475 maybe_unapply_patches_again();
4480 badusage "-p is not allowed when building" if defined $package;
4483 my $clogp = parsechangelog();
4484 $isuite = getfield $clogp, 'Distribution';
4485 $package = getfield $clogp, 'Source';
4486 $version = getfield $clogp, 'Version';
4487 build_maybe_quilt_fixup();
4489 my $pat = changespat $version;
4490 foreach my $f (glob "$buildproductsdir/$pat") {
4492 unlink $f or fail "remove old changes file $f: $!";
4494 progress "would remove $f";
4500 sub changesopts_initial () {
4501 my @opts =@changesopts[1..$#changesopts];
4504 sub changesopts_version () {
4505 if (!defined $changes_since_version) {
4506 my @vsns = archive_query('archive_query');
4507 my @quirk = access_quirk();
4508 if ($quirk[0] eq 'backports') {
4509 local $isuite = $quirk[2];
4511 canonicalise_suite();
4512 push @vsns, archive_query('archive_query');
4515 @vsns = map { $_->[0] } @vsns;
4516 @vsns = sort { -version_compare($a, $b) } @vsns;
4517 $changes_since_version = $vsns[0];
4518 progress "changelog will contain changes since $vsns[0]";
4520 $changes_since_version = '_';
4521 progress "package seems new, not specifying -v<version>";
4524 if ($changes_since_version ne '_') {
4525 return ("-v$changes_since_version");
4531 sub changesopts () {
4532 return (changesopts_initial(), changesopts_version());
4535 sub massage_dbp_args ($;$) {
4536 my ($cmd,$xargs) = @_;
4539 # - if we're going to split the source build out so we can
4540 # do strange things to it, massage the arguments to dpkg-buildpackage
4541 # so that the main build doessn't build source (or add an argument
4542 # to stop it building source by default).
4544 # - add -nc to stop dpkg-source cleaning the source tree,
4545 # unless we're not doing a split build and want dpkg-source
4546 # as cleanmode, in which case we can do nothing
4549 # 0 - source will NOT need to be built separately by caller
4550 # +1 - source will need to be built separately by caller
4551 # +2 - source will need to be built separately by caller AND
4552 # dpkg-buildpackage should not in fact be run at all!
4553 debugcmd '#massaging#', @$cmd if $debuglevel>1;
4554 #print STDERR "MASS0 ",Dumper($cmd, $xargs, $need_split_build_invocation);
4555 if ($cleanmode eq 'dpkg-source' && !$need_split_build_invocation) {
4556 $clean_using_builder = 1;
4559 # -nc has the side effect of specifying -b if nothing else specified
4560 # and some combinations of -S, -b, et al, are errors, rather than
4561 # later simply overriding earlie. So we need to:
4562 # - search the command line for these options
4563 # - pick the last one
4564 # - perhaps add our own as a default
4565 # - perhaps adjust it to the corresponding non-source-building version
4567 foreach my $l ($cmd, $xargs) {
4569 @$l = grep { !(m/^-[SgGFABb]$/s and $dmode=$_) } @$l;
4572 #print STDERR "MASS1 ",Dumper($cmd, $xargs, $dmode);
4574 if ($need_split_build_invocation) {
4575 printdebug "massage split $dmode.\n";
4576 $r = $dmode =~ m/[S]/ ? +2 :
4577 $dmode =~ y/gGF/ABb/ ? +1 :
4578 $dmode =~ m/[ABb]/ ? 0 :
4581 printdebug "massage done $r $dmode.\n";
4583 #print STDERR "MASS2 ",Dumper($cmd, $xargs, $r);
4588 my @dbp = (@dpkgbuildpackage, qw(-us -uc), changesopts_initial(), @ARGV);
4589 my $wantsrc = massage_dbp_args \@dbp;
4596 push @dbp, changesopts_version();
4597 maybe_apply_patches_dirtily();
4598 runcmd_ordryrun_local @dbp;
4600 maybe_unapply_patches_again();
4601 printdone "build successful\n";
4605 my @dbp = @dpkgbuildpackage;
4607 my $wantsrc = massage_dbp_args \@dbp, \@ARGV;
4610 if (length executable_on_path('git-buildpackage')) {
4611 @cmd = qw(git-buildpackage);
4613 @cmd = qw(gbp buildpackage);
4615 push @cmd, (qw(-us -uc --git-no-sign-tags), "--git-builder=@dbp");
4620 if (!$clean_using_builder) {
4621 push @cmd, '--git-cleaner=true';
4625 maybe_unapply_patches_again();
4627 unless (grep { m/^--git-debian-branch|^--git-ignore-branch/ } @ARGV) {
4628 canonicalise_suite();
4629 push @cmd, "--git-debian-branch=".lbranch();
4631 push @cmd, changesopts();
4632 runcmd_ordryrun_local @cmd, @ARGV;
4634 printdone "build successful\n";
4636 sub cmd_git_build { cmd_gbp_build(); } # compatibility with <= 1.0
4639 my $our_cleanmode = $cleanmode;
4640 if ($need_split_build_invocation) {
4641 # Pretend that clean is being done some other way. This
4642 # forces us not to try to use dpkg-buildpackage to clean and
4643 # build source all in one go; and instead we run dpkg-source
4644 # (and build_prep() will do the clean since $clean_using_builder
4646 $our_cleanmode = 'ELSEWHERE';
4648 if ($our_cleanmode =~ m/^dpkg-source/) {
4649 # dpkg-source invocation (below) will clean, so build_prep shouldn't
4650 $clean_using_builder = 1;
4653 $sourcechanges = changespat $version,'source';
4655 unlink "../$sourcechanges" or $!==ENOENT
4656 or fail "remove $sourcechanges: $!";
4658 $dscfn = dscfn($version);
4659 if ($our_cleanmode eq 'dpkg-source') {
4660 maybe_apply_patches_dirtily();
4661 runcmd_ordryrun_local @dpkgbuildpackage, qw(-us -uc -S),
4663 } elsif ($our_cleanmode eq 'dpkg-source-d') {
4664 maybe_apply_patches_dirtily();
4665 runcmd_ordryrun_local @dpkgbuildpackage, qw(-us -uc -S -d),
4668 my @cmd = (@dpkgsource, qw(-b --));
4671 runcmd_ordryrun_local @cmd, "work";
4672 my @udfiles = <${package}_*>;
4673 changedir "../../..";
4674 foreach my $f (@udfiles) {
4675 printdebug "source copy, found $f\n";
4678 ($f =~ m/\.debian\.tar(?:\.\w+)$/ &&
4679 $f eq srcfn($version, $&));
4680 printdebug "source copy, found $f - renaming\n";
4681 rename "$ud/$f", "../$f" or $!==ENOENT
4682 or fail "put in place new source file ($f): $!";
4685 my $pwd = must_getcwd();
4686 my $leafdir = basename $pwd;
4688 runcmd_ordryrun_local @cmd, $leafdir;
4691 runcmd_ordryrun_local qw(sh -ec),
4692 'exec >$1; shift; exec "$@"','x',
4693 "../$sourcechanges",
4694 @dpkggenchanges, qw(-S), changesopts();
4698 sub cmd_build_source {
4699 badusage "build-source takes no additional arguments" if @ARGV;
4701 maybe_unapply_patches_again();
4702 printdone "source built, results in $dscfn and $sourcechanges";
4707 my $pat = changespat $version;
4709 my @unwanted = map { s#^\.\./##; $_; } glob "../$pat";
4710 @unwanted = grep { $_ ne changespat $version,'source' } @unwanted;
4711 fail "changes files other than source matching $pat".
4712 " already present (@unwanted);".
4713 " building would result in ambiguity about the intended results"
4716 my $wasdir = must_getcwd();
4719 stat_exists $dscfn or fail "$dscfn (in parent directory): $!";
4720 stat_exists $sourcechanges
4721 or fail "$sourcechanges (in parent directory): $!";
4723 runcmd_ordryrun_local @sbuild, qw(-d), $isuite, @ARGV, $dscfn;
4724 my @changesfiles = glob $pat;
4725 @changesfiles = sort {
4726 ($b =~ m/_source\.changes$/ <=> $a =~ m/_source\.changes$/)
4729 fail "wrong number of different changes files (@changesfiles)"
4730 unless @changesfiles==2;
4731 my $binchanges = parsecontrol($changesfiles[1], "binary changes file");
4732 foreach my $l (split /\n/, getfield $binchanges, 'Files') {
4733 fail "$l found in binaries changes file $binchanges"
4736 runcmd_ordryrun_local @mergechanges, @changesfiles;
4737 my $multichanges = changespat $version,'multi';
4739 stat_exists $multichanges or fail "$multichanges: $!";
4740 foreach my $cf (glob $pat) {
4741 next if $cf eq $multichanges;
4742 rename "$cf", "$cf.inmulti" or fail "$cf\{,.inmulti}: $!";
4746 maybe_unapply_patches_again();
4747 printdone "build successful, results in $multichanges\n" or die $!;
4750 sub cmd_quilt_fixup {
4751 badusage "incorrect arguments to dgit quilt-fixup" if @ARGV;
4752 my $clogp = parsechangelog();
4753 $version = getfield $clogp, 'Version';
4754 $package = getfield $clogp, 'Source';
4757 build_maybe_quilt_fixup();
4760 sub cmd_archive_api_query {
4761 badusage "need only 1 subpath argument" unless @ARGV==1;
4762 my ($subpath) = @ARGV;
4763 my @cmd = archive_api_query_cmd($subpath);
4765 exec @cmd or fail "exec curl: $!\n";
4768 sub cmd_clone_dgit_repos_server {
4769 badusage "need destination argument" unless @ARGV==1;
4770 my ($destdir) = @ARGV;
4771 $package = '_dgit-repos-server';
4772 my @cmd = (@git, qw(clone), access_giturl(), $destdir);
4774 exec @cmd or fail "exec git clone: $!\n";
4777 sub cmd_setup_mergechangelogs {
4778 badusage "no arguments allowed to dgit setup-mergechangelogs" if @ARGV;
4779 setup_mergechangelogs(1);
4782 sub cmd_setup_useremail {
4783 badusage "no arguments allowed to dgit setup-mergechangelogs" if @ARGV;
4787 sub cmd_setup_new_tree {
4788 badusage "no arguments allowed to dgit setup-tree" if @ARGV;
4792 #---------- argument parsing and main program ----------
4795 print "dgit version $our_version\n" or die $!;
4799 our (%valopts_long, %valopts_short);
4802 sub defvalopt ($$$$) {
4803 my ($long,$short,$val_re,$how) = @_;
4804 my $oi = { Long => $long, Short => $short, Re => $val_re, How => $how };
4805 $valopts_long{$long} = $oi;
4806 $valopts_short{$short} = $oi;
4807 # $how subref should:
4808 # do whatever assignemnt or thing it likes with $_[0]
4809 # if the option should not be passed on to remote, @rvalopts=()
4810 # or $how can be a scalar ref, meaning simply assign the value
4813 defvalopt '--since-version', '-v', '[^_]+|_', \$changes_since_version;
4814 defvalopt '--distro', '-d', '.+', \$idistro;
4815 defvalopt '', '-k', '.+', \$keyid;
4816 defvalopt '--existing-package','', '.*', \$existing_package;
4817 defvalopt '--build-products-dir','','.*', \$buildproductsdir;
4818 defvalopt '--clean', '', $cleanmode_re, \$cleanmode;
4819 defvalopt '--quilt', '', $quilt_modes_re, \$quilt_mode;
4821 defvalopt '', '-c', '.*=.*', sub { push @git, '-c', @_; };
4823 defvalopt '', '-C', '.+', sub {
4824 ($changesfile) = (@_);
4825 if ($changesfile =~ s#^(.*)/##) {
4826 $buildproductsdir = $1;
4830 defvalopt '--initiator-tempdir','','.*', sub {
4831 ($initiator_tempdir) = (@_);
4832 $initiator_tempdir =~ m#^/# or
4833 badusage "--initiator-tempdir must be used specify an".
4834 " absolute, not relative, directory."
4840 if (defined $ENV{'DGIT_SSH'}) {
4841 @ssh = string_to_ssh $ENV{'DGIT_SSH'};
4842 } elsif (defined $ENV{'GIT_SSH'}) {
4843 @ssh = ($ENV{'GIT_SSH'});
4851 if (!defined $val) {
4852 badusage "$what needs a value" unless @ARGV;
4854 push @rvalopts, $val;
4856 badusage "bad value \`$val' for $what" unless
4857 $val =~ m/^$oi->{Re}$(?!\n)/s;
4858 my $how = $oi->{How};
4859 if (ref($how) eq 'SCALAR') {
4864 push @ropts, @rvalopts;
4868 last unless $ARGV[0] =~ m/^-/;
4872 if (m/^--dry-run$/) {
4875 } elsif (m/^--damp-run$/) {
4878 } elsif (m/^--no-sign$/) {
4881 } elsif (m/^--help$/) {
4883 } elsif (m/^--version$/) {
4885 } elsif (m/^--new$/) {
4888 } elsif (m/^--([-0-9a-z]+)=(.+)/s &&
4889 ($om = $opts_opt_map{$1}) &&
4893 } elsif (m/^--([-0-9a-z]+):(.*)/s &&
4894 !$opts_opt_cmdonly{$1} &&
4895 ($om = $opts_opt_map{$1})) {
4898 } elsif (m/^--ignore-dirty$/s) {
4901 } elsif (m/^--no-quilt-fixup$/s) {
4903 $quilt_mode = 'nocheck';
4904 } elsif (m/^--no-rm-on-error$/s) {
4907 } elsif (m/^--overwrite$/s) {
4909 $overwrite_version = '';
4910 } elsif (m/^--overwrite=(.+)$/s) {
4912 $overwrite_version = $1;
4913 } elsif (m/^--(no-)?rm-old-changes$/s) {
4916 } elsif (m/^--deliberately-($deliberately_re)$/s) {
4918 push @deliberatelies, $&;
4919 } elsif (m/^--dgit-tag-format=(old|new)$/s) {
4920 # undocumented, for testing
4922 $tagformat_want = [ $1, 'command line', 1 ];
4923 # 1 menas overrides distro configuration
4924 } elsif (m/^--always-split-source-build$/s) {
4925 # undocumented, for testing
4927 $need_split_build_invocation = 1;
4928 } elsif (m/^(--[-0-9a-z]+)(=|$)/ && ($oi = $valopts_long{$1})) {
4929 $val = $2 ? $' : undef; #';
4930 $valopt->($oi->{Long});
4932 badusage "unknown long option \`$_'";
4939 } elsif (s/^-L/-/) {
4942 } elsif (s/^-h/-/) {
4944 } elsif (s/^-D/-/) {
4948 } elsif (s/^-N/-/) {
4953 push @changesopts, $_;
4955 } elsif (s/^-wn$//s) {
4957 $cleanmode = 'none';
4958 } elsif (s/^-wg$//s) {
4961 } elsif (s/^-wgf$//s) {
4963 $cleanmode = 'git-ff';
4964 } elsif (s/^-wd$//s) {
4966 $cleanmode = 'dpkg-source';
4967 } elsif (s/^-wdd$//s) {
4969 $cleanmode = 'dpkg-source-d';
4970 } elsif (s/^-wc$//s) {
4972 $cleanmode = 'check';
4973 } elsif (m/^-[a-zA-Z]/ && ($oi = $valopts_short{$&})) {
4975 $val = undef unless length $val;
4976 $valopt->($oi->{Short});
4979 badusage "unknown short option \`$_'";
4986 sub finalise_opts_opts () {
4987 foreach my $k (keys %opts_opt_map) {
4988 my $om = $opts_opt_map{$k};
4990 my $v = access_cfg("cmd-$k", 'RETURN-UNDEF');
4992 badcfg "cannot set command for $k"
4993 unless length $om->[0];
4997 foreach my $c (access_cfg_cfgs("opts-$k")) {
4998 my $vl = $gitcfg{$c};
4999 printdebug "CL $c ",
5000 ($vl ? join " ", map { shellquote } @$vl : ""),
5001 "\n" if $debuglevel >= 4;
5003 badcfg "cannot configure options for $k"
5004 if $opts_opt_cmdonly{$k};
5005 my $insertpos = $opts_cfg_insertpos{$k};
5006 @$om = ( @$om[0..$insertpos-1],
5008 @$om[$insertpos..$#$om] );
5013 if ($ENV{$fakeeditorenv}) {
5015 quilt_fixup_editor();
5021 print STDERR "DRY RUN ONLY\n" if $dryrun_level > 1;
5022 print STDERR "DAMP RUN - WILL MAKE LOCAL (UNSIGNED) CHANGES\n"
5023 if $dryrun_level == 1;
5025 print STDERR $helpmsg or die $!;
5028 my $cmd = shift @ARGV;
5031 if (!defined $rmchanges) {
5032 local $access_forpush;
5033 $rmchanges = access_cfg_bool(0, 'rm-old-changes');
5036 if (!defined $quilt_mode) {
5037 local $access_forpush;
5038 $quilt_mode = cfg('dgit.force.quilt-mode', 'RETURN-UNDEF')
5039 // access_cfg('quilt-mode', 'RETURN-UNDEF')
5041 $quilt_mode =~ m/^($quilt_modes_re)$/
5042 or badcfg "unknown quilt-mode \`$quilt_mode'";
5046 $need_split_build_invocation ||= quiltmode_splitbrain();
5048 if (!defined $cleanmode) {
5049 local $access_forpush;
5050 $cleanmode = access_cfg('clean-mode', 'RETURN-UNDEF');
5051 $cleanmode //= 'dpkg-source';
5053 badcfg "unknown clean-mode \`$cleanmode'" unless
5054 $cleanmode =~ m/^($cleanmode_re)$(?!\n)/s;
5057 my $fn = ${*::}{"cmd_$cmd"};
5058 $fn or badusage "unknown operation $cmd";