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;
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';
81 our $git_authline_re = '^([^<>]+) \<(\S+)\> (\d+ [-+]\d+)$';
82 our $splitbraincache = 'dgit-intern/quilt-cache';
85 our (@dget) = qw(dget);
86 our (@curl) = qw(curl -f);
87 our (@dput) = qw(dput);
88 our (@debsign) = qw(debsign);
90 our (@sbuild) = qw(sbuild);
92 our (@dgit) = qw(dgit);
93 our (@dpkgbuildpackage) = qw(dpkg-buildpackage -i\.git/ -I.git);
94 our (@dpkgsource) = qw(dpkg-source -i\.git/ -I.git);
95 our (@dpkggenchanges) = qw(dpkg-genchanges);
96 our (@mergechanges) = qw(mergechanges -f);
98 our (@changesopts) = ('');
100 our %opts_opt_map = ('dget' => \@dget, # accept for compatibility
103 'debsign' => \@debsign,
105 'sbuild' => \@sbuild,
109 'dpkg-source' => \@dpkgsource,
110 'dpkg-buildpackage' => \@dpkgbuildpackage,
111 'dpkg-genchanges' => \@dpkggenchanges,
113 'ch' => \@changesopts,
114 'mergechanges' => \@mergechanges);
116 our %opts_opt_cmdonly = ('gpg' => 1, 'git' => 1);
117 our %opts_cfg_insertpos = map {
119 scalar @{ $opts_opt_map{$_} }
120 } keys %opts_opt_map;
122 sub finalise_opts_opts();
128 our $supplementary_message = '';
129 our $need_split_build_invocation = 0;
130 our $split_brain = 0;
134 print STDERR "! $_\n" foreach $supplementary_message =~ m/^.+$/mg;
137 our $remotename = 'dgit';
138 our @ourdscfield = qw(Dgit Vcs-Dgit-Master);
143 my ($v,$distro) = @_;
144 return $tagformatfn->($v, $distro);
147 sub debiantag_maintview ($$) {
148 my ($v,$distro) = @_;
153 sub lbranch () { return "$branchprefix/$csuite"; }
154 my $lbranch_re = '^refs/heads/'.$branchprefix.'/([^/.]+)$';
155 sub lref () { return "refs/heads/".lbranch(); }
156 sub lrref () { return "refs/remotes/$remotename/".server_branch($csuite); }
157 sub rrref () { return server_ref($csuite); }
159 sub lrfetchrefs () { return "refs/dgit-fetch/$csuite"; }
160 sub lrfetchref () { return lrfetchrefs.'/'.server_branch($csuite); }
162 # We fetch some parts of lrfetchrefs/*. Ideally we delete these
163 # locally fetched refs because they have unhelpful names and clutter
164 # up gitk etc. So we track whether we have "used up" head ref (ie,
165 # whether we have made another local ref which refers to this object).
167 # (If we deleted them unconditionally, then we might end up
168 # re-fetching the same git objects each time dgit fetch was run.)
170 # So, leach use of lrfetchrefs needs to be accompanied by arrangements
171 # in git_fetch_us to fetch the refs in question, and possibly a call
172 # to lrfetchref_used.
174 our (%lrfetchrefs_f, %lrfetchrefs_d);
175 # $lrfetchrefs_X{lrfetchrefs."/heads/whatever"} = $objid
177 sub lrfetchref_used ($) {
178 my ($fullrefname) = @_;
179 my $objid = $lrfetchrefs_f{$fullrefname};
180 $lrfetchrefs_d{$fullrefname} = $objid if defined $objid;
191 return "${package}_".(stripepoch $vsn).$sfx
196 return srcfn($vsn,".dsc");
199 sub changespat ($;$) {
200 my ($vsn, $arch) = @_;
201 return "${package}_".(stripepoch $vsn)."_".($arch//'*').".changes";
210 foreach my $f (@end) {
212 print STDERR "$us: cleanup: $@" if length $@;
216 sub badcfg { print STDERR "$us: invalid configuration: @_\n"; exit 12; }
218 sub no_such_package () {
219 print STDERR "$us: package $package does not exist in suite $isuite\n";
225 printdebug "CD $newdir\n";
226 chdir $newdir or confess "chdir: $newdir: $!";
229 sub deliberately ($) {
231 return !!grep { $_ eq "--deliberately-$enquiry" } @deliberatelies;
234 sub deliberately_not_fast_forward () {
235 foreach (qw(not-fast-forward fresh-repo)) {
236 return 1 if deliberately($_) || deliberately("TEST-dgit-only-$_");
240 sub quiltmode_splitbrain () {
241 $quilt_mode =~ m/gbp|dpm|unapplied/;
244 #---------- remote protocol support, common ----------
246 # remote push initiator/responder protocol:
247 # $ dgit remote-push-build-host <n-rargs> <rargs>... <push-args>...
248 # where <rargs> is <push-host-dir> <supported-proto-vsn>,... ...
249 # < dgit-remote-push-ready <actual-proto-vsn>
256 # > supplementary-message NBYTES # $protovsn >= 3
261 # > file parsed-changelog
262 # [indicates that output of dpkg-parsechangelog follows]
263 # > data-block NBYTES
264 # > [NBYTES bytes of data (no newline)]
265 # [maybe some more blocks]
274 # > param head DGIT-VIEW-HEAD
275 # > param csuite SUITE
276 # > param tagformat old|new
277 # > param maint-view MAINT-VIEW-HEAD
279 # > previously REFNAME=OBJNAME # if --deliberately-not-fast-forward
280 # # goes into tag, for replay prevention
283 # [indicates that signed tag is wanted]
284 # < data-block NBYTES
285 # < [NBYTES bytes of data (no newline)]
286 # [maybe some more blocks]
290 # > want signed-dsc-changes
291 # < data-block NBYTES [transfer of signed dsc]
293 # < data-block NBYTES [transfer of signed changes]
301 sub i_child_report () {
302 # Sees if our child has died, and reap it if so. Returns a string
303 # describing how it died if it failed, or undef otherwise.
304 return undef unless $i_child_pid;
305 my $got = waitpid $i_child_pid, WNOHANG;
306 return undef if $got <= 0;
307 die unless $got == $i_child_pid;
308 $i_child_pid = undef;
309 return undef unless $?;
310 return "build host child ".waitstatusmsg();
315 fail "connection lost: $!" if $fh->error;
316 fail "protocol violation; $m not expected";
319 sub badproto_badread ($$) {
321 fail "connection lost: $!" if $!;
322 my $report = i_child_report();
323 fail $report if defined $report;
324 badproto $fh, "eof (reading $wh)";
327 sub protocol_expect (&$) {
328 my ($match, $fh) = @_;
331 defined && chomp or badproto_badread $fh, "protocol message";
339 badproto $fh, "\`$_'";
342 sub protocol_send_file ($$) {
343 my ($fh, $ourfn) = @_;
344 open PF, "<", $ourfn or die "$ourfn: $!";
347 my $got = read PF, $d, 65536;
348 die "$ourfn: $!" unless defined $got;
350 print $fh "data-block ".length($d)."\n" or die $!;
351 print $fh $d or die $!;
353 PF->error and die "$ourfn $!";
354 print $fh "data-end\n" or die $!;
358 sub protocol_read_bytes ($$) {
359 my ($fh, $nbytes) = @_;
360 $nbytes =~ m/^[1-9]\d{0,5}$|^0$/ or badproto \*RO, "bad byte count";
362 my $got = read $fh, $d, $nbytes;
363 $got==$nbytes or badproto_badread $fh, "data block";
367 sub protocol_receive_file ($$) {
368 my ($fh, $ourfn) = @_;
369 printdebug "() $ourfn\n";
370 open PF, ">", $ourfn or die "$ourfn: $!";
372 my ($y,$l) = protocol_expect {
373 m/^data-block (.*)$/ ? (1,$1) :
374 m/^data-end$/ ? (0,) :
378 my $d = protocol_read_bytes $fh, $l;
379 print PF $d or die $!;
384 #---------- remote protocol support, responder ----------
386 sub responder_send_command ($) {
388 return unless $we_are_responder;
389 # called even without $we_are_responder
390 printdebug ">> $command\n";
391 print PO $command, "\n" or die $!;
394 sub responder_send_file ($$) {
395 my ($keyword, $ourfn) = @_;
396 return unless $we_are_responder;
397 printdebug "]] $keyword $ourfn\n";
398 responder_send_command "file $keyword";
399 protocol_send_file \*PO, $ourfn;
402 sub responder_receive_files ($@) {
403 my ($keyword, @ourfns) = @_;
404 die unless $we_are_responder;
405 printdebug "[[ $keyword @ourfns\n";
406 responder_send_command "want $keyword";
407 foreach my $fn (@ourfns) {
408 protocol_receive_file \*PI, $fn;
411 protocol_expect { m/^files-end$/ } \*PI;
414 #---------- remote protocol support, initiator ----------
416 sub initiator_expect (&) {
418 protocol_expect { &$match } \*RO;
421 #---------- end remote code ----------
424 if ($we_are_responder) {
426 responder_send_command "progress ".length($m) or die $!;
427 print PO $m or die $!;
437 $ua = LWP::UserAgent->new();
441 progress "downloading $what...";
442 my $r = $ua->get(@_) or die $!;
443 return undef if $r->code == 404;
444 $r->is_success or fail "failed to fetch $what: ".$r->status_line;
445 return $r->decoded_content(charset => 'none');
448 our ($dscdata,$dscurl,$dsc,$dsc_checked,$skew_warning_vsn);
453 failedcmd @_ if system @_;
456 sub act_local () { return $dryrun_level <= 1; }
457 sub act_scary () { return !$dryrun_level; }
460 if (!$dryrun_level) {
461 progress "dgit ok: @_";
463 progress "would be ok: @_ (but dry run only)";
468 printcmd(\*STDERR,$debugprefix."#",@_);
471 sub runcmd_ordryrun {
479 sub runcmd_ordryrun_local {
488 my ($first_shell, @cmd) = @_;
489 return qw(sh -ec), $first_shell.'; exec "$@"', 'x', @cmd;
492 our $helpmsg = <<END;
494 dgit [dgit-opts] clone [dgit-opts] package [suite] [./dir|/dir]
495 dgit [dgit-opts] fetch|pull [dgit-opts] [suite]
496 dgit [dgit-opts] build [dpkg-buildpackage-opts]
497 dgit [dgit-opts] sbuild [sbuild-opts]
498 dgit [dgit-opts] push [dgit-opts] [suite]
499 dgit [dgit-opts] rpush build-host:build-dir ...
500 important dgit options:
501 -k<keyid> sign tag and package with <keyid> instead of default
502 --dry-run -n do not change anything, but go through the motions
503 --damp-run -L like --dry-run but make local changes, without signing
504 --new -N allow introducing a new package
505 --debug -D increase debug level
506 -c<name>=<value> set git config option (used directly by dgit too)
509 our $later_warning_msg = <<END;
510 Perhaps the upload is stuck in incoming. Using the version from git.
514 print STDERR "$us: @_\n", $helpmsg or die $!;
519 @ARGV or badusage "too few arguments";
520 return scalar shift @ARGV;
524 print $helpmsg or die $!;
528 our $td = $ENV{DGIT_TEST_DUMMY_DIR} || "DGIT_TEST_DUMMY_DIR-unset";
530 our %defcfg = ('dgit.default.distro' => 'debian',
531 'dgit.default.username' => '',
532 'dgit.default.archive-query-default-component' => 'main',
533 'dgit.default.ssh' => 'ssh',
534 'dgit.default.archive-query' => 'madison:',
535 'dgit.default.sshpsql-dbname' => 'service=projectb',
536 'dgit.default.dgit-tag-format' => 'old,new,maint',
537 'dgit-distro.debian.archive-query' => 'ftpmasterapi:',
538 'dgit-distro.debian.git-check' => 'url',
539 'dgit-distro.debian.git-check-suffix' => '/info/refs',
540 'dgit-distro.debian.new-private-pushers' => 't',
541 'dgit-distro.debian.dgit-tag-format' => 'old',
542 'dgit-distro.debian/push.git-url' => '',
543 'dgit-distro.debian/push.git-host' => 'push.dgit.debian.org',
544 'dgit-distro.debian/push.git-user-force' => 'dgit',
545 'dgit-distro.debian/push.git-proto' => 'git+ssh://',
546 'dgit-distro.debian/push.git-path' => '/dgit/debian/repos',
547 'dgit-distro.debian/push.git-create' => 'true',
548 'dgit-distro.debian/push.git-check' => 'ssh-cmd',
549 'dgit-distro.debian.archive-query-url', 'https://api.ftp-master.debian.org/',
550 # 'dgit-distro.debian.archive-query-tls-key',
551 # '/etc/ssl/certs/%HOST%.pem:/etc/dgit/%HOST%.pem',
552 # ^ this does not work because curl is broken nowadays
553 # Fixing #790093 properly will involve providing providing the key
554 # in some pacagke and maybe updating these paths.
556 # 'dgit-distro.debian.archive-query-tls-curl-args',
557 # '--ca-path=/etc/ssl/ca-debian',
558 # ^ this is a workaround but works (only) on DSA-administered machines
559 'dgit-distro.debian.git-url' => 'https://git.dgit.debian.org',
560 'dgit-distro.debian.git-url-suffix' => '',
561 'dgit-distro.debian.upload-host' => 'ftp-master', # for dput
562 'dgit-distro.debian.mirror' => 'http://ftp.debian.org/debian/',
563 'dgit-distro.debian.backports-quirk' => '(squeeze)-backports*',
564 'dgit-distro.debian-backports.mirror' => 'http://backports.debian.org/debian-backports/',
565 'dgit-distro.ubuntu.git-check' => 'false',
566 'dgit-distro.ubuntu.mirror' => 'http://archive.ubuntu.com/ubuntu',
567 'dgit-distro.test-dummy.ssh' => "$td/ssh",
568 'dgit-distro.test-dummy.username' => "alice",
569 'dgit-distro.test-dummy.git-check' => "ssh-cmd",
570 'dgit-distro.test-dummy.git-create' => "ssh-cmd",
571 'dgit-distro.test-dummy.git-url' => "$td/git",
572 'dgit-distro.test-dummy.git-host' => "git",
573 'dgit-distro.test-dummy.git-path' => "$td/git",
574 'dgit-distro.test-dummy.archive-query' => "ftpmasterapi:",
575 'dgit-distro.test-dummy.archive-query-url' => "file://$td/aq/",
576 'dgit-distro.test-dummy.mirror' => "file://$td/mirror/",
577 'dgit-distro.test-dummy.upload-host' => 'test-dummy',
582 sub git_slurp_config () {
583 local ($debuglevel) = $debuglevel-2;
586 my @cmd = (@git, qw(config -z --get-regexp .*));
589 open GITS, "-|", @cmd or die $!;
592 printdebug "=> ", (messagequote $_), "\n";
594 push @{ $gitcfg{$`} }, $'; #';
598 or ($!==0 && $?==256)
602 sub git_get_config ($) {
605 printdebug"C $c ".(defined $l ? messagequote "'$l'" : "undef")."\n"
608 @$l==1 or badcfg "multiple values for $c" if @$l > 1;
614 return undef if $c =~ /RETURN-UNDEF/;
615 my $v = git_get_config($c);
616 return $v if defined $v;
617 my $dv = $defcfg{$c};
618 return $dv if defined $dv;
620 badcfg "need value for one of: @_\n".
621 "$us: distro or suite appears not to be (properly) supported";
624 sub access_basedistro () {
625 if (defined $idistro) {
628 return cfg("dgit-suite.$isuite.distro",
629 "dgit.default.distro");
633 sub access_quirk () {
634 # returns (quirk name, distro to use instead or undef, quirk-specific info)
635 my $basedistro = access_basedistro();
636 my $backports_quirk = cfg("dgit-distro.$basedistro.backports-quirk",
638 if (defined $backports_quirk) {
639 my $re = $backports_quirk;
640 $re =~ s/[^-0-9a-z_\%*()]/\\$&/ig;
642 $re =~ s/\%/([-0-9a-z_]+)/
643 or $re =~ m/[()]/ or badcfg "backports-quirk needs \% or ( )";
644 if ($isuite =~ m/^$re$/) {
645 return ('backports',"$basedistro-backports",$1);
648 return ('none',undef);
653 sub parse_cfg_bool ($$$) {
654 my ($what,$def,$v) = @_;
657 $v =~ m/^[ty1]/ ? 1 :
658 $v =~ m/^[fn0]/ ? 0 :
659 badcfg "$what needs t (true, y, 1) or f (false, n, 0) not \`$v'";
662 sub access_forpush_config () {
663 my $d = access_basedistro();
667 parse_cfg_bool('new-private-pushers', 0,
668 cfg("dgit-distro.$d.new-private-pushers",
671 my $v = cfg("dgit-distro.$d.readonly", 'RETURN-UNDEF');
674 $v =~ m/^[ty1]/ ? 0 : # force readonly, forpush = 0
675 $v =~ m/^[fn0]/ ? 1 : # force nonreadonly, forpush = 1
676 $v =~ m/^[a]/ ? '' : # auto, forpush = ''
677 badcfg "readonly needs t (true, y, 1) or f (false, n, 0) or a (auto)";
680 sub access_forpush () {
681 $access_forpush //= access_forpush_config();
682 return $access_forpush;
686 die "$access_forpush ?" if ($access_forpush // 1) ne 1;
687 badcfg "pushing but distro is configured readonly"
688 if access_forpush_config() eq '0';
690 $supplementary_message = <<'END' unless $we_are_responder;
691 Push failed, before we got started.
692 You can retry the push, after fixing the problem, if you like.
694 finalise_opts_opts();
698 finalise_opts_opts();
701 sub supplementary_message ($) {
703 if (!$we_are_responder) {
704 $supplementary_message = $msg;
706 } elsif ($protovsn >= 3) {
707 responder_send_command "supplementary-message ".length($msg)
709 print PO $msg or die $!;
713 sub access_distros () {
714 # Returns list of distros to try, in order
717 # 0. `instead of' distro name(s) we have been pointed to
718 # 1. the access_quirk distro, if any
719 # 2a. the user's specified distro, or failing that } basedistro
720 # 2b. the distro calculated from the suite }
721 my @l = access_basedistro();
723 my (undef,$quirkdistro) = access_quirk();
724 unshift @l, $quirkdistro;
725 unshift @l, $instead_distro;
726 @l = grep { defined } @l;
728 if (access_forpush()) {
729 @l = map { ("$_/push", $_) } @l;
734 sub access_cfg_cfgs (@) {
737 # The nesting of these loops determines the search order. We put
738 # the key loop on the outside so that we search all the distros
739 # for each key, before going on to the next key. That means that
740 # if access_cfg is called with a more specific, and then a less
741 # specific, key, an earlier distro can override the less specific
742 # without necessarily overriding any more specific keys. (If the
743 # distro wants to override the more specific keys it can simply do
744 # so; whereas if we did the loop the other way around, it would be
745 # impossible to for an earlier distro to override a less specific
746 # key but not the more specific ones without restating the unknown
747 # values of the more specific keys.
750 # We have to deal with RETURN-UNDEF specially, so that we don't
751 # terminate the search prematurely.
753 if (m/RETURN-UNDEF/) { push @rundef, $_; last; }
756 foreach my $d (access_distros()) {
757 push @cfgs, map { "dgit-distro.$d.$_" } @realkeys;
759 push @cfgs, map { "dgit.default.$_" } @realkeys;
766 my (@cfgs) = access_cfg_cfgs(@keys);
767 my $value = cfg(@cfgs);
771 sub access_cfg_bool ($$) {
772 my ($def, @keys) = @_;
773 parse_cfg_bool($keys[0], $def, access_cfg(@keys, 'RETURN-UNDEF'));
776 sub string_to_ssh ($) {
778 if ($spec =~ m/\s/) {
779 return qw(sh -ec), 'exec '.$spec.' "$@"', 'x';
785 sub access_cfg_ssh () {
786 my $gitssh = access_cfg('ssh', 'RETURN-UNDEF');
787 if (!defined $gitssh) {
790 return string_to_ssh $gitssh;
794 sub access_runeinfo ($) {
796 return ": dgit ".access_basedistro()." $info ;";
799 sub access_someuserhost ($) {
801 my $user = access_cfg("$some-user-force", 'RETURN-UNDEF');
802 defined($user) && length($user) or
803 $user = access_cfg("$some-user",'username');
804 my $host = access_cfg("$some-host");
805 return length($user) ? "$user\@$host" : $host;
808 sub access_gituserhost () {
809 return access_someuserhost('git');
812 sub access_giturl (;$) {
814 my $url = access_cfg('git-url','RETURN-UNDEF');
817 my $proto = access_cfg('git-proto', 'RETURN-UNDEF');
818 return undef unless defined $proto;
821 access_gituserhost().
822 access_cfg('git-path');
824 $suffix = access_cfg('git-url-suffix','RETURN-UNDEF');
827 return "$url/$package$suffix";
830 sub parsecontrolfh ($$;$) {
831 my ($fh, $desc, $allowsigned) = @_;
832 our $dpkgcontrolhash_noissigned;
835 my %opts = ('name' => $desc);
836 $opts{allow_pgp}= $allowsigned || !$dpkgcontrolhash_noissigned;
837 $c = Dpkg::Control::Hash->new(%opts);
838 $c->parse($fh,$desc) or die "parsing of $desc failed";
839 last if $allowsigned;
840 last if $dpkgcontrolhash_noissigned;
841 my $issigned= $c->get_option('is_pgp_signed');
842 if (!defined $issigned) {
843 $dpkgcontrolhash_noissigned= 1;
844 seek $fh, 0,0 or die "seek $desc: $!";
845 } elsif ($issigned) {
846 fail "control file $desc is (already) PGP-signed. ".
847 " Note that dgit push needs to modify the .dsc and then".
848 " do the signature itself";
857 my ($file, $desc) = @_;
858 my $fh = new IO::Handle;
859 open $fh, '<', $file or die "$file: $!";
860 my $c = parsecontrolfh($fh,$desc);
861 $fh->error and die $!;
867 my ($dctrl,$field) = @_;
868 my $v = $dctrl->{$field};
869 return $v if defined $v;
870 fail "missing field $field in ".$v->get_option('name');
874 my $c = Dpkg::Control::Hash->new();
875 my $p = new IO::Handle;
876 my @cmd = (qw(dpkg-parsechangelog), @_);
877 open $p, '-|', @cmd or die $!;
879 $?=0; $!=0; close $p or failedcmd @cmd;
883 sub commit_getclogp ($) {
884 # Returns the parsed changelog hashref for a particular commit
886 our %commit_getclogp_memo;
887 my $memo = $commit_getclogp_memo{$objid};
888 return $memo if $memo;
890 my $mclog = ".git/dgit/clog-$objid";
891 runcmd shell_cmd "exec >$mclog", @git, qw(cat-file blob),
892 "$objid:debian/changelog";
893 $commit_getclogp_memo{$objid} = parsechangelog("-l$mclog");
898 defined $d or fail "getcwd failed: $!";
904 sub archive_query ($) {
906 my $query = access_cfg('archive-query','RETURN-UNDEF');
907 $query =~ s/^(\w+):// or badcfg "invalid archive-query method \`$query'";
910 { no strict qw(refs); &{"${method}_${proto}"}($proto,$data); }
913 sub pool_dsc_subpath ($$) {
914 my ($vsn,$component) = @_; # $package is implict arg
915 my $prefix = substr($package, 0, $package =~ m/^l/ ? 4 : 1);
916 return "/pool/$component/$prefix/$package/".dscfn($vsn);
919 #---------- `ftpmasterapi' archive query method (nascent) ----------
921 sub archive_api_query_cmd ($) {
923 my @cmd = qw(curl -sS);
924 my $url = access_cfg('archive-query-url');
925 if ($url =~ m#^https://([-.0-9a-z]+)/#) {
927 my $keys = access_cfg('archive-query-tls-key','RETURN-UNDEF') //'';
928 foreach my $key (split /\:/, $keys) {
929 $key =~ s/\%HOST\%/$host/g;
931 fail "for $url: stat $key: $!" unless $!==ENOENT;
934 fail "config requested specific TLS key but do not know".
935 " how to get curl to use exactly that EE key ($key)";
936 # push @cmd, "--cacert", $key, "--capath", "/dev/enoent";
937 # # Sadly the above line does not work because of changes
938 # # to gnutls. The real fix for #790093 may involve
939 # # new curl options.
942 # Fixing #790093 properly will involve providing a value
943 # for this on clients.
944 my $kargs = access_cfg('archive-query-tls-curl-ca-args','RETURN-UNDEF');
945 push @cmd, split / /, $kargs if defined $kargs;
947 push @cmd, $url.$subpath;
953 my ($data, $subpath) = @_;
954 badcfg "ftpmasterapi archive query method takes no data part"
956 my @cmd = archive_api_query_cmd($subpath);
957 my $json = cmdoutput @cmd;
958 return decode_json($json);
961 sub canonicalise_suite_ftpmasterapi () {
962 my ($proto,$data) = @_;
963 my $suites = api_query($data, 'suites');
965 foreach my $entry (@$suites) {
967 my $v = $entry->{$_};
968 defined $v && $v eq $isuite;
970 push @matched, $entry;
972 fail "unknown suite $isuite" unless @matched;
975 @matched==1 or die "multiple matches for suite $isuite\n";
976 $cn = "$matched[0]{codename}";
977 defined $cn or die "suite $isuite info has no codename\n";
978 $cn =~ m/^$suite_re$/ or die "suite $isuite maps to bad codename\n";
980 die "bad ftpmaster api response: $@\n".Dumper(\@matched)
985 sub archive_query_ftpmasterapi () {
986 my ($proto,$data) = @_;
987 my $info = api_query($data, "dsc_in_suite/$isuite/$package");
989 my $digester = Digest::SHA->new(256);
990 foreach my $entry (@$info) {
992 my $vsn = "$entry->{version}";
993 my ($ok,$msg) = version_check $vsn;
994 die "bad version: $msg\n" unless $ok;
995 my $component = "$entry->{component}";
996 $component =~ m/^$component_re$/ or die "bad component";
997 my $filename = "$entry->{filename}";
998 $filename && $filename !~ m#[^-+:._~0-9a-zA-Z/]|^[/.]|/[/.]#
999 or die "bad filename";
1000 my $sha256sum = "$entry->{sha256sum}";
1001 $sha256sum =~ m/^[0-9a-f]+$/ or die "bad sha256sum";
1002 push @rows, [ $vsn, "/pool/$component/$filename",
1003 $digester, $sha256sum ];
1005 die "bad ftpmaster api response: $@\n".Dumper($entry)
1008 @rows = sort { -version_compare($a->[0],$b->[0]) } @rows;
1012 #---------- `madison' archive query method ----------
1014 sub archive_query_madison {
1015 return map { [ @$_[0..1] ] } madison_get_parse(@_);
1018 sub madison_get_parse {
1019 my ($proto,$data) = @_;
1020 die unless $proto eq 'madison';
1021 if (!length $data) {
1022 $data= access_cfg('madison-distro','RETURN-UNDEF');
1023 $data //= access_basedistro();
1025 $rmad{$proto,$data,$package} ||= cmdoutput
1026 qw(rmadison -asource),"-s$isuite","-u$data",$package;
1027 my $rmad = $rmad{$proto,$data,$package};
1030 foreach my $l (split /\n/, $rmad) {
1031 $l =~ m{^ \s*( [^ \t|]+ )\s* \|
1032 \s*( [^ \t|]+ )\s* \|
1033 \s*( [^ \t|/]+ )(?:/([^ \t|/]+))? \s* \|
1034 \s*( [^ \t|]+ )\s* }x or die "$rmad ?";
1035 $1 eq $package or die "$rmad $package ?";
1042 $component = access_cfg('archive-query-default-component');
1044 $5 eq 'source' or die "$rmad ?";
1045 push @out, [$vsn,pool_dsc_subpath($vsn,$component),$newsuite];
1047 return sort { -version_compare($a->[0],$b->[0]); } @out;
1050 sub canonicalise_suite_madison {
1051 # madison canonicalises for us
1052 my @r = madison_get_parse(@_);
1054 "unable to canonicalise suite using package $package".
1055 " which does not appear to exist in suite $isuite;".
1056 " --existing-package may help";
1060 #---------- `sshpsql' archive query method ----------
1063 my ($data,$runeinfo,$sql) = @_;
1064 if (!length $data) {
1065 $data= access_someuserhost('sshpsql').':'.
1066 access_cfg('sshpsql-dbname');
1068 $data =~ m/:/ or badcfg "invalid sshpsql method string \`$data'";
1069 my ($userhost,$dbname) = ($`,$'); #';
1071 my @cmd = (access_cfg_ssh, $userhost,
1072 access_runeinfo("ssh-psql $runeinfo").
1073 " export LC_MESSAGES=C; export LC_CTYPE=C;".
1074 " ".shellquote qw(psql -A), $dbname, qw(-c), $sql);
1076 open P, "-|", @cmd or die $!;
1079 printdebug(">|$_|\n");
1082 $!=0; $?=0; close P or failedcmd @cmd;
1084 my $nrows = pop @rows;
1085 $nrows =~ s/^\((\d+) rows?\)$/$1/ or die "$nrows ?";
1086 @rows == $nrows+1 or die "$nrows ".(scalar @rows)." ?";
1087 @rows = map { [ split /\|/, $_ ] } @rows;
1088 my $ncols = scalar @{ shift @rows };
1089 die if grep { scalar @$_ != $ncols } @rows;
1093 sub sql_injection_check {
1094 foreach (@_) { die "$_ $& ?" if m{[^-+=:_.,/0-9a-zA-Z]}; }
1097 sub archive_query_sshpsql ($$) {
1098 my ($proto,$data) = @_;
1099 sql_injection_check $isuite, $package;
1100 my @rows = sshpsql($data, "archive-query $isuite $package", <<END);
1101 SELECT source.version, component.name, files.filename, files.sha256sum
1103 JOIN src_associations ON source.id = src_associations.source
1104 JOIN suite ON suite.id = src_associations.suite
1105 JOIN dsc_files ON dsc_files.source = source.id
1106 JOIN files_archive_map ON files_archive_map.file_id = dsc_files.file
1107 JOIN component ON component.id = files_archive_map.component_id
1108 JOIN files ON files.id = dsc_files.file
1109 WHERE ( suite.suite_name='$isuite' OR suite.codename='$isuite' )
1110 AND source.source='$package'
1111 AND files.filename LIKE '%.dsc';
1113 @rows = sort { -version_compare($a->[0],$b->[0]) } @rows;
1114 my $digester = Digest::SHA->new(256);
1116 my ($vsn,$component,$filename,$sha256sum) = @$_;
1117 [ $vsn, "/pool/$component/$filename",$digester,$sha256sum ];
1122 sub canonicalise_suite_sshpsql ($$) {
1123 my ($proto,$data) = @_;
1124 sql_injection_check $isuite;
1125 my @rows = sshpsql($data, "canonicalise-suite $isuite", <<END);
1126 SELECT suite.codename
1127 FROM suite where suite_name='$isuite' or codename='$isuite';
1129 @rows = map { $_->[0] } @rows;
1130 fail "unknown suite $isuite" unless @rows;
1131 die "ambiguous $isuite: @rows ?" if @rows>1;
1135 #---------- `dummycat' archive query method ----------
1137 sub canonicalise_suite_dummycat ($$) {
1138 my ($proto,$data) = @_;
1139 my $dpath = "$data/suite.$isuite";
1140 if (!open C, "<", $dpath) {
1141 $!==ENOENT or die "$dpath: $!";
1142 printdebug "dummycat canonicalise_suite $isuite $dpath ENOENT\n";
1146 chomp or die "$dpath: $!";
1148 printdebug "dummycat canonicalise_suite $isuite $dpath = $_\n";
1152 sub archive_query_dummycat ($$) {
1153 my ($proto,$data) = @_;
1154 canonicalise_suite();
1155 my $dpath = "$data/package.$csuite.$package";
1156 if (!open C, "<", $dpath) {
1157 $!==ENOENT or die "$dpath: $!";
1158 printdebug "dummycat query $csuite $package $dpath ENOENT\n";
1166 printdebug "dummycat query $csuite $package $dpath | $_\n";
1167 my @row = split /\s+/, $_;
1168 @row==2 or die "$dpath: $_ ?";
1171 C->error and die "$dpath: $!";
1173 return sort { -version_compare($a->[0],$b->[0]); } @rows;
1176 #---------- tag format handling ----------
1178 sub access_cfg_tagformats () {
1179 split /\,/, access_cfg('dgit-tag-format');
1182 sub need_tagformat ($$) {
1183 my ($fmt, $why) = @_;
1184 fail "need to use tag format $fmt ($why) but also need".
1185 " to use tag format $tagformat_want->[0] ($tagformat_want->[1])".
1186 " - no way to proceed"
1187 if $tagformat_want && $tagformat_want->[0] ne $fmt;
1188 $tagformat_want = [$fmt, $why, $tagformat_want->[2] // 0];
1191 sub select_tagformat () {
1193 return if $tagformatfn && !$tagformat_want;
1194 die 'bug' if $tagformatfn && $tagformat_want;
1195 # ... $tagformat_want assigned after previous select_tagformat
1197 my (@supported) = grep { $_ ne 'maint' } access_cfg_tagformats();
1198 printdebug "select_tagformat supported @supported\n";
1200 $tagformat_want //= [ $supported[0], "distro access configuration", 0 ];
1201 printdebug "select_tagformat specified @$tagformat_want\n";
1203 my ($fmt,$why,$override) = @$tagformat_want;
1205 fail "target distro supports tag formats @supported".
1206 " but have to use $fmt ($why)"
1208 or grep { $_ eq $fmt } @supported;
1210 $tagformat_want = undef;
1212 $tagformatfn = ${*::}{"debiantag_$fmt"};
1214 fail "trying to use unknown tag format \`$fmt' ($why) !"
1215 unless $tagformatfn;
1218 #---------- archive query entrypoints and rest of program ----------
1220 sub canonicalise_suite () {
1221 return if defined $csuite;
1222 fail "cannot operate on $isuite suite" if $isuite eq 'UNRELEASED';
1223 $csuite = archive_query('canonicalise_suite');
1224 if ($isuite ne $csuite) {
1225 progress "canonical suite name for $isuite is $csuite";
1229 sub get_archive_dsc () {
1230 canonicalise_suite();
1231 my @vsns = archive_query('archive_query');
1232 foreach my $vinfo (@vsns) {
1233 my ($vsn,$subpath,$digester,$digest) = @$vinfo;
1234 $dscurl = access_cfg('mirror').$subpath;
1235 $dscdata = url_get($dscurl);
1237 $skew_warning_vsn = $vsn if !defined $skew_warning_vsn;
1242 $digester->add($dscdata);
1243 my $got = $digester->hexdigest();
1245 fail "$dscurl has hash $got but".
1246 " archive told us to expect $digest";
1248 my $dscfh = new IO::File \$dscdata, '<' or die $!;
1249 printdebug Dumper($dscdata) if $debuglevel>1;
1250 $dsc = parsecontrolfh($dscfh,$dscurl,1);
1251 printdebug Dumper($dsc) if $debuglevel>1;
1252 my $fmt = getfield $dsc, 'Format';
1253 fail "unsupported source format $fmt, sorry" unless $format_ok{$fmt};
1254 $dsc_checked = !!$digester;
1255 printdebug "get_archive_dsc: Version ".(getfield $dsc, 'Version')."\n";
1259 printdebug "get_archive_dsc: nothing in archive, returning undef\n";
1262 sub check_for_git ();
1263 sub check_for_git () {
1265 my $how = access_cfg('git-check');
1266 if ($how eq 'ssh-cmd') {
1268 (access_cfg_ssh, access_gituserhost(),
1269 access_runeinfo("git-check $package").
1270 " set -e; cd ".access_cfg('git-path').";".
1271 " if test -d $package.git; then echo 1; else echo 0; fi");
1272 my $r= cmdoutput @cmd;
1273 if (defined $r and $r =~ m/^divert (\w+)$/) {
1275 my ($usedistro,) = access_distros();
1276 # NB that if we are pushing, $usedistro will be $distro/push
1277 $instead_distro= cfg("dgit-distro.$usedistro.diverts.$divert");
1278 $instead_distro =~ s{^/}{ access_basedistro()."/" }e;
1279 progress "diverting to $divert (using config for $instead_distro)";
1280 return check_for_git();
1282 failedcmd @cmd unless defined $r and $r =~ m/^[01]$/;
1284 } elsif ($how eq 'url') {
1285 my $prefix = access_cfg('git-check-url','git-url');
1286 my $suffix = access_cfg('git-check-suffix','git-suffix',
1287 'RETURN-UNDEF') // '.git';
1288 my $url = "$prefix/$package$suffix";
1289 my @cmd = (qw(curl -sS -I), $url);
1290 my $result = cmdoutput @cmd;
1291 $result =~ s/^\S+ 200 .*\n\r?\n//;
1292 # curl -sS -I with https_proxy prints
1293 # HTTP/1.0 200 Connection established
1294 $result =~ m/^\S+ (404|200) /s or
1295 fail "unexpected results from git check query - ".
1296 Dumper($prefix, $result);
1298 if ($code eq '404') {
1300 } elsif ($code eq '200') {
1305 } elsif ($how eq 'true') {
1307 } elsif ($how eq 'false') {
1310 badcfg "unknown git-check \`$how'";
1314 sub create_remote_git_repo () {
1315 my $how = access_cfg('git-create');
1316 if ($how eq 'ssh-cmd') {
1318 (access_cfg_ssh, access_gituserhost(),
1319 access_runeinfo("git-create $package").
1320 "set -e; cd ".access_cfg('git-path').";".
1321 " cp -a _template $package.git");
1322 } elsif ($how eq 'true') {
1325 badcfg "unknown git-create \`$how'";
1329 our ($dsc_hash,$lastpush_mergeinput);
1331 our $ud = '.git/dgit/unpack';
1341 sub mktree_in_ud_here () {
1342 runcmd qw(git init -q);
1343 runcmd qw(git config gc.auto 0);
1344 rmtree('.git/objects');
1345 symlink '../../../../objects','.git/objects' or die $!;
1348 sub git_write_tree () {
1349 my $tree = cmdoutput @git, qw(write-tree);
1350 $tree =~ m/^\w+$/ or die "$tree ?";
1354 sub remove_stray_gits () {
1355 my @gitscmd = qw(find -name .git -prune -print0);
1356 debugcmd "|",@gitscmd;
1357 open GITS, "-|", @gitscmd or die $!;
1362 print STDERR "$us: warning: removing from source package: ",
1363 (messagequote $_), "\n";
1367 $!=0; $?=0; close GITS or failedcmd @gitscmd;
1370 sub mktree_in_ud_from_only_subdir () {
1371 # changes into the subdir
1373 die "@dirs ?" unless @dirs==1;
1374 $dirs[0] =~ m#^([^/]+)/\.$# or die;
1378 remove_stray_gits();
1379 mktree_in_ud_here();
1380 my ($format, $fopts) = get_source_format();
1381 if (madformat($format)) {
1384 runcmd @git, qw(add -Af);
1385 my $tree=git_write_tree();
1386 return ($tree,$dir);
1389 sub dsc_files_info () {
1390 foreach my $csumi (['Checksums-Sha256','Digest::SHA', 'new(256)'],
1391 ['Checksums-Sha1', 'Digest::SHA', 'new(1)'],
1392 ['Files', 'Digest::MD5', 'new()']) {
1393 my ($fname, $module, $method) = @$csumi;
1394 my $field = $dsc->{$fname};
1395 next unless defined $field;
1396 eval "use $module; 1;" or die $@;
1398 foreach (split /\n/, $field) {
1400 m/^(\w+) (\d+) (\S+)$/ or
1401 fail "could not parse .dsc $fname line \`$_'";
1402 my $digester = eval "$module"."->$method;" or die $@;
1407 Digester => $digester,
1412 fail "missing any supported Checksums-* or Files field in ".
1413 $dsc->get_option('name');
1417 map { $_->{Filename} } dsc_files_info();
1420 sub is_orig_file ($;$) {
1423 m/\.orig(?:-\w+)?\.tar\.\w+$/ or return 0;
1424 defined $base or return 1;
1428 sub make_commit ($) {
1430 return cmdoutput @git, qw(hash-object -w -t commit), $file;
1433 sub clogp_authline ($) {
1435 my $author = getfield $clogp, 'Maintainer';
1436 $author =~ s#,.*##ms;
1437 my $date = cmdoutput qw(date), '+%s %z', qw(-d), getfield($clogp,'Date');
1438 my $authline = "$author $date";
1439 $authline =~ m/$git_authline_re/o or
1440 fail "unexpected commit author line format \`$authline'".
1441 " (was generated from changelog Maintainer field)";
1442 return ($1,$2,$3) if wantarray;
1446 sub vendor_patches_distro ($$) {
1447 my ($checkdistro, $what) = @_;
1448 return unless defined $checkdistro;
1450 my $series = "debian/patches/\L$checkdistro\E.series";
1451 printdebug "checking for vendor-specific $series ($what)\n";
1453 if (!open SERIES, "<", $series) {
1454 die "$series $!" unless $!==ENOENT;
1463 Unfortunately, this source package uses a feature of dpkg-source where
1464 the same source package unpacks to different source code on different
1465 distros. dgit cannot safely operate on such packages on affected
1466 distros, because the meaning of source packages is not stable.
1468 Please ask the distro/maintainer to remove the distro-specific series
1469 files and use a different technique (if necessary, uploading actually
1470 different packages, if different distros are supposed to have
1474 fail "Found active distro-specific series file for".
1475 " $checkdistro ($what): $series, cannot continue";
1477 die "$series $!" if SERIES->error;
1481 sub check_for_vendor_patches () {
1482 # This dpkg-source feature doesn't seem to be documented anywhere!
1483 # But it can be found in the changelog (reformatted):
1485 # commit 4fa01b70df1dc4458daee306cfa1f987b69da58c
1486 # Author: Raphael Hertzog <hertzog@debian.org>
1487 # Date: Sun Oct 3 09:36:48 2010 +0200
1489 # dpkg-source: correctly create .pc/.quilt_series with alternate
1492 # If you have debian/patches/ubuntu.series and you were
1493 # unpacking the source package on ubuntu, quilt was still
1494 # directed to debian/patches/series instead of
1495 # debian/patches/ubuntu.series.
1497 # debian/changelog | 3 +++
1498 # scripts/Dpkg/Source/Package/V3/quilt.pm | 4 +++-
1499 # 2 files changed, 6 insertions(+), 1 deletion(-)
1502 vendor_patches_distro($ENV{DEB_VENDOR}, "DEB_VENDOR");
1503 vendor_patches_distro(Dpkg::Vendor::get_current_vendor(),
1504 "Dpkg::Vendor \`current vendor'");
1505 vendor_patches_distro(access_basedistro(),
1506 "distro being accessed");
1509 sub generate_commits_from_dsc () {
1510 # See big comment in fetch_from_archive, below.
1514 foreach my $fi (dsc_files_info()) {
1515 my $f = $fi->{Filename};
1516 die "$f ?" if $f =~ m#/|^\.|\.dsc$|\.tmp$#;
1518 link_ltarget "../../../$f", $f
1522 complete_file_from_dsc('.', $fi)
1525 if (is_orig_file($f)) {
1526 link $f, "../../../../$f"
1532 my $dscfn = "$package.dsc";
1534 open D, ">", $dscfn or die "$dscfn: $!";
1535 print D $dscdata or die "$dscfn: $!";
1536 close D or die "$dscfn: $!";
1537 my @cmd = qw(dpkg-source);
1538 push @cmd, '--no-check' if $dsc_checked;
1539 push @cmd, qw(-x --), $dscfn;
1542 my ($tree,$dir) = mktree_in_ud_from_only_subdir();
1543 check_for_vendor_patches() if madformat($dsc->{format});
1544 runcmd qw(sh -ec), 'dpkg-parsechangelog >../changelog.tmp';
1545 my $clogp = parsecontrol('../changelog.tmp',"commit's changelog");
1546 my $authline = clogp_authline $clogp;
1547 my $changes = getfield $clogp, 'Changes';
1548 open C, ">../commit.tmp" or die $!;
1549 print C <<END or die $!;
1556 # imported from the archive
1559 my $rawimport_hash = make_commit qw(../commit.tmp);
1560 my $cversion = getfield $clogp, 'Version';
1561 my $rawimport_mergeinput = {
1562 Commit => $rawimport_hash,
1563 Info => "Import of source package",
1565 my @output = ($rawimport_mergeinput);
1566 progress "synthesised git commit from .dsc $cversion";
1567 if ($lastpush_mergeinput) {
1568 my $oldclogp = mergeinfo_getclogp($lastpush_mergeinput);
1569 my $oversion = getfield $oldclogp, 'Version';
1571 version_compare($oversion, $cversion);
1573 @output = ($rawimport_mergeinput, $lastpush_mergeinput,
1574 { Message => <<END, ReverseParents => 1 });
1575 Record $package ($cversion) in archive suite $csuite
1577 } elsif ($vcmp > 0) {
1578 print STDERR <<END or die $!;
1580 Version actually in archive: $cversion (older)
1581 Last version pushed with dgit: $oversion (newer or same)
1584 @output = $lastpush_mergeinput;
1586 # Same version. Use what's in the server git branch,
1587 # discarding our own import. (This could happen if the
1588 # server automatically imports all packages into git.)
1589 @output = $lastpush_mergeinput;
1592 changedir '../../../..';
1597 sub complete_file_from_dsc ($$) {
1598 our ($dstdir, $fi) = @_;
1599 # Ensures that we have, in $dir, the file $fi, with the correct
1600 # contents. (Downloading it from alongside $dscurl if necessary.)
1602 my $f = $fi->{Filename};
1603 my $tf = "$dstdir/$f";
1606 if (stat_exists $tf) {
1607 progress "using existing $f";
1610 $furl =~ s{/[^/]+$}{};
1612 die "$f ?" unless $f =~ m/^\Q${package}\E_/;
1613 die "$f ?" if $f =~ m#/#;
1614 runcmd_ordryrun_local @curl,qw(-o),$tf,'--',"$furl";
1615 return 0 if !act_local();
1619 open F, "<", "$tf" or die "$tf: $!";
1620 $fi->{Digester}->reset();
1621 $fi->{Digester}->addfile(*F);
1622 F->error and die $!;
1623 my $got = $fi->{Digester}->hexdigest();
1624 $got eq $fi->{Hash} or
1625 fail "file $f has hash $got but .dsc".
1626 " demands hash $fi->{Hash} ".
1627 ($downloaded ? "(got wrong file from archive!)"
1628 : "(perhaps you should delete this file?)");
1633 sub ensure_we_have_orig () {
1634 foreach my $fi (dsc_files_info()) {
1635 my $f = $fi->{Filename};
1636 next unless is_orig_file($f);
1637 complete_file_from_dsc('..', $fi)
1642 sub git_fetch_us () {
1643 # Want to fetch only what we are going to use, unless
1644 # deliberately-not-ff, in which case we must fetch everything.
1646 my @specs = deliberately_not_fast_forward ? qw(tags/*) :
1648 (quiltmode_splitbrain
1649 ? (map { $_->('*',access_basedistro) }
1650 \&debiantag_new, \&debiantag_maintview)
1651 : debiantags('*',access_basedistro));
1652 push @specs, server_branch($csuite);
1653 push @specs, qw(heads/*) if deliberately_not_fast_forward;
1655 # This is rather miserable:
1656 # When git-fetch --prune is passed a fetchspec ending with a *,
1657 # it does a plausible thing. If there is no * then:
1658 # - it matches subpaths too, even if the supplied refspec
1659 # starts refs, and behaves completely madly if the source
1660 # has refs/refs/something. (See, for example, Debian #NNNN.)
1661 # - if there is no matching remote ref, it bombs out the whole
1663 # We want to fetch a fixed ref, and we don't know in advance
1664 # if it exists, so this is not suitable.
1666 # Our workaround is to use git-ls-remote. git-ls-remote has its
1667 # own qairks. Notably, it has the absurd multi-tail-matching
1668 # behaviour: git-ls-remote R refs/foo can report refs/foo AND
1669 # refs/refs/foo etc.
1671 # Also, we want an idempotent snapshot, but we have to make two
1672 # calls to the remote: one to git-ls-remote and to git-fetch. The
1673 # solution is use git-ls-remote to obtain a target state, and
1674 # git-fetch to try to generate it. If we don't manage to generate
1675 # the target state, we try again.
1677 my $specre = join '|', map {
1683 printdebug "git_fetch_us specre=$specre\n";
1684 my $wanted_rref = sub {
1686 return m/^(?:$specre)$/o;
1689 my $fetch_iteration = 0;
1692 if (++$fetch_iteration > 10) {
1693 fail "too many iterations trying to get sane fetch!";
1696 my @look = map { "refs/$_" } @specs;
1697 my @lcmd = (@git, qw(ls-remote -q --refs), access_giturl(), @look);
1701 open GITLS, "-|", @lcmd or die $!;
1703 printdebug "=> ", $_;
1704 m/^(\w+)\s+(\S+)\n/ or die "ls-remote $_ ?";
1705 my ($objid,$rrefname) = ($1,$2);
1706 if (!$wanted_rref->($rrefname)) {
1708 warning: git-ls-remote @look reported $rrefname; this is silly, ignoring it.
1712 $wantr{$rrefname} = $objid;
1715 close GITLS or failedcmd @lcmd;
1717 # OK, now %want is exactly what we want for refs in @specs
1719 return () if !m/\*$/ && !exists $wantr{"refs/$_"};
1720 "+refs/$_:".lrfetchrefs."/$_";
1723 my @fcmd = (@git, qw(fetch -p -n -q), access_giturl(), @fspecs);
1724 runcmd_ordryrun_local @git, qw(fetch -p -n -q), access_giturl(),
1727 %lrfetchrefs_f = ();
1730 git_for_each_ref(lrfetchrefs, sub {
1731 my ($objid,$objtype,$lrefname,$reftail) = @_;
1732 $lrfetchrefs_f{$lrefname} = $objid;
1733 $objgot{$objid} = 1;
1736 foreach my $lrefname (sort keys %lrfetchrefs_f) {
1737 my $rrefname = 'refs'.substr($lrefname, length lrfetchrefs);
1738 if (!exists $wantr{$rrefname}) {
1739 if ($wanted_rref->($rrefname)) {
1741 git-fetch @fspecs created $lrefname which git-ls-remote @look didn't list.
1745 warning: git-fetch @fspecs created $lrefname; this is silly, deleting it.
1748 runcmd_ordryrun_local @git, qw(update-ref -d), $lrefname;
1749 delete $lrfetchrefs_f{$lrefname};
1753 foreach my $rrefname (sort keys %wantr) {
1754 my $lrefname = lrfetchrefs.substr($rrefname, 4);
1755 my $got = $lrfetchrefs_f{$lrefname} // '<none>';
1756 my $want = $wantr{$rrefname};
1757 next if $got eq $want;
1758 if (!defined $objgot{$want}) {
1760 warning: git-ls-remote suggests we want $lrefname
1761 warning: and it should refer to $want
1762 warning: but git-fetch didn't fetch that object to any relevant ref.
1763 warning: This may be due to a race with someone updating the server.
1764 warning: Will try again...
1766 next FETCH_ITERATION;
1769 git-fetch @fspecs made $lrefname=$got but want git-ls-remote @look says $want
1771 runcmd_ordryrun_local @git, qw(update-ref -m),
1772 "dgit fetch git-fetch fixup", $lrefname, $want;
1773 $lrfetchrefs_f{$lrefname} = $want;
1777 printdebug "git_fetch_us: git-fetch --no-insane emulation complete\n",
1778 Dumper(\%lrfetchrefs_f);
1781 my @tagpats = debiantags('*',access_basedistro);
1783 git_for_each_ref([map { "refs/tags/$_" } @tagpats], sub {
1784 my ($objid,$objtype,$fullrefname,$reftail) = @_;
1785 printdebug "currently $fullrefname=$objid\n";
1786 $here{$fullrefname} = $objid;
1788 git_for_each_ref([map { lrfetchrefs."/tags/".$_ } @tagpats], sub {
1789 my ($objid,$objtype,$fullrefname,$reftail) = @_;
1790 my $lref = "refs".substr($fullrefname, length(lrfetchrefs));
1791 printdebug "offered $lref=$objid\n";
1792 if (!defined $here{$lref}) {
1793 my @upd = (@git, qw(update-ref), $lref, $objid, '');
1794 runcmd_ordryrun_local @upd;
1795 lrfetchref_used $fullrefname;
1796 } elsif ($here{$lref} eq $objid) {
1797 lrfetchref_used $fullrefname;
1800 "Not updateting $lref from $here{$lref} to $objid.\n";
1805 sub mergeinfo_getclogp ($) {
1806 # Ensures thit $mi->{Clogp} exists and returns it
1808 $mi->{Clogp} = commit_getclogp($mi->{Commit});
1811 sub mergeinfo_version ($) {
1812 return getfield( (mergeinfo_getclogp $_[0]), 'Version' );
1815 sub fetch_from_archive () {
1816 # Ensures that lrref() is what is actually in the archive, one way
1817 # or another, according to us - ie this client's
1818 # appropritaely-updated archive view. Also returns the commit id.
1819 # If there is nothing in the archive, leaves lrref alone and
1820 # returns undef. git_fetch_us must have already been called.
1824 foreach my $field (@ourdscfield) {
1825 $dsc_hash = $dsc->{$field};
1826 last if defined $dsc_hash;
1828 if (defined $dsc_hash) {
1829 $dsc_hash =~ m/\w+/ or fail "invalid hash in .dsc \`$dsc_hash'";
1831 progress "last upload to archive specified git hash";
1833 progress "last upload to archive has NO git hash";
1836 progress "no version available from the archive";
1839 # If the archive's .dsc has a Dgit field, there are three
1840 # relevant git commitids we need to choose between and/or merge
1842 # 1. $dsc_hash: the Dgit field from the archive
1843 # 2. $lastpush_hash: the suite branch on the dgit git server
1844 # 3. $lastfetch_hash: our local tracking brach for the suite
1846 # These may all be distinct and need not be in any fast forward
1849 # If the dsc was pushed to this suite, then the server suite
1850 # branch will have been updated; but it might have been pushed to
1851 # a different suite and copied by the archive. Conversely a more
1852 # recent version may have been pushed with dgit but not appeared
1853 # in the archive (yet).
1855 # $lastfetch_hash may be awkward because archive imports
1856 # (particularly, imports of Dgit-less .dscs) are performed only as
1857 # needed on individual clients, so different clients may perform a
1858 # different subset of them - and these imports are only made
1859 # public during push. So $lastfetch_hash may represent a set of
1860 # imports different to a subsequent upload by a different dgit
1863 # Our approach is as follows:
1865 # As between $dsc_hash and $lastpush_hash: if $lastpush_hash is a
1866 # descendant of $dsc_hash, then it was pushed by a dgit user who
1867 # had based their work on $dsc_hash, so we should prefer it.
1868 # Otherwise, $dsc_hash was installed into this suite in the
1869 # archive other than by a dgit push, and (necessarily) after the
1870 # last dgit push into that suite (since a dgit push would have
1871 # been descended from the dgit server git branch); thus, in that
1872 # case, we prefer the archive's version (and produce a
1873 # pseudo-merge to overwrite the dgit server git branch).
1875 # (If there is no Dgit field in the archive's .dsc then
1876 # generate_commit_from_dsc uses the version numbers to decide
1877 # whether the suite branch or the archive is newer. If the suite
1878 # branch is newer it ignores the archive's .dsc; otherwise it
1879 # generates an import of the .dsc, and produces a pseudo-merge to
1880 # overwrite the suite branch with the archive contents.)
1882 # The outcome of that part of the algorithm is the `public view',
1883 # and is same for all dgit clients: it does not depend on any
1884 # unpublished history in the local tracking branch.
1886 # As between the public view and the local tracking branch: The
1887 # local tracking branch is only updated by dgit fetch, and
1888 # whenever dgit fetch runs it includes the public view in the
1889 # local tracking branch. Therefore if the public view is not
1890 # descended from the local tracking branch, the local tracking
1891 # branch must contain history which was imported from the archive
1892 # but never pushed; and, its tip is now out of date. So, we make
1893 # a pseudo-merge to overwrite the old imports and stitch the old
1896 # Finally: we do not necessarily reify the public view (as
1897 # described above). This is so that we do not end up stacking two
1898 # pseudo-merges. So what we actually do is figure out the inputs
1899 # to any public view pseudo-merge and put them in @mergeinputs.
1902 # $mergeinputs[]{Commit}
1903 # $mergeinputs[]{Info}
1904 # $mergeinputs[0] is the one whose tree we use
1905 # @mergeinputs is in the order we use in the actual commit)
1908 # $mergeinputs[]{Message} is a commit message to use
1909 # $mergeinputs[]{ReverseParents} if def specifies that parent
1910 # list should be in opposite order
1911 # Such an entry has no Commit or Info. It applies only when found
1912 # in the last entry. (This ugliness is to support making
1913 # identical imports to previous dgit versions.)
1915 my $lastpush_hash = git_get_ref(lrfetchref());
1916 printdebug "previous reference hash=$lastpush_hash\n";
1917 $lastpush_mergeinput = $lastpush_hash && {
1918 Commit => $lastpush_hash,
1919 Info => "dgit suite branch on dgit git server",
1922 my $lastfetch_hash = git_get_ref(lrref());
1923 printdebug "fetch_from_archive: lastfetch=$lastfetch_hash\n";
1924 my $lastfetch_mergeinput = $lastfetch_hash && {
1925 Commit => $lastfetch_hash,
1926 Info => "dgit client's archive history view",
1929 my $dsc_mergeinput = $dsc_hash && {
1930 Commit => $dsc_hash,
1931 Info => "Dgit field in .dsc from archive",
1935 my $del_lrfetchrefs = sub {
1938 printdebug "del_lrfetchrefs...\n";
1939 foreach my $fullrefname (sort keys %lrfetchrefs_d) {
1940 my $objid = $lrfetchrefs_d{$fullrefname};
1941 printdebug "del_lrfetchrefs: $objid $fullrefname\n";
1943 $gur ||= new IO::Handle;
1944 open $gur, "|-", qw(git update-ref --stdin) or die $!;
1946 printf $gur "delete %s %s\n", $fullrefname, $objid;
1949 close $gur or failedcmd "git update-ref delete lrfetchrefs";
1953 if (defined $dsc_hash) {
1954 fail "missing remote git history even though dsc has hash -".
1955 " could not find ref ".rref()." at ".access_giturl()
1956 unless $lastpush_hash;
1957 ensure_we_have_orig();
1958 if ($dsc_hash eq $lastpush_hash) {
1959 @mergeinputs = $dsc_mergeinput
1960 } elsif (is_fast_fwd($dsc_hash,$lastpush_hash)) {
1961 print STDERR <<END or die $!;
1963 Git commit in archive is behind the last version allegedly pushed/uploaded.
1964 Commit referred to by archive: $dsc_hash
1965 Last version pushed with dgit: $lastpush_hash
1968 @mergeinputs = ($lastpush_mergeinput);
1970 # Archive has .dsc which is not a descendant of the last dgit
1971 # push. This can happen if the archive moves .dscs about.
1972 # Just follow its lead.
1973 if (is_fast_fwd($lastpush_hash,$dsc_hash)) {
1974 progress "archive .dsc names newer git commit";
1975 @mergeinputs = ($dsc_mergeinput);
1977 progress "archive .dsc names other git commit, fixing up";
1978 @mergeinputs = ($dsc_mergeinput, $lastpush_mergeinput);
1982 @mergeinputs = generate_commits_from_dsc();
1983 # We have just done an import. Now, our import algorithm might
1984 # have been improved. But even so we do not want to generate
1985 # a new different import of the same package. So if the
1986 # version numbers are the same, just use our existing version.
1987 # If the version numbers are different, the archive has changed
1988 # (perhaps, rewound).
1989 if ($lastfetch_mergeinput &&
1990 !version_compare( (mergeinfo_version $lastfetch_mergeinput),
1991 (mergeinfo_version $mergeinputs[0]) )) {
1992 @mergeinputs = ($lastfetch_mergeinput);
1994 } elsif ($lastpush_hash) {
1995 # only in git, not in the archive yet
1996 @mergeinputs = ($lastpush_mergeinput);
1997 print STDERR <<END or die $!;
1999 Package not found in the archive, but has allegedly been pushed using dgit.
2003 printdebug "nothing found!\n";
2004 if (defined $skew_warning_vsn) {
2005 print STDERR <<END or die $!;
2007 Warning: relevant archive skew detected.
2008 Archive allegedly contains $skew_warning_vsn
2009 But we were not able to obtain any version from the archive or git.
2013 unshift @end, $del_lrfetchrefs;
2017 if ($lastfetch_hash &&
2019 my $h = $_->{Commit};
2020 $h and is_fast_fwd($lastfetch_hash, $h);
2021 # If true, one of the existing parents of this commit
2022 # is a descendant of the $lastfetch_hash, so we'll
2023 # be ff from that automatically.
2027 push @mergeinputs, $lastfetch_mergeinput;
2030 printdebug "fetch mergeinfos:\n";
2031 foreach my $mi (@mergeinputs) {
2033 printdebug " commit $mi->{Commit} $mi->{Info}\n";
2035 printdebug sprintf " ReverseParents=%d Message=%s",
2036 $mi->{ReverseParents}, $mi->{Message};
2040 my $compat_info= pop @mergeinputs
2041 if $mergeinputs[$#mergeinputs]{Message};
2043 @mergeinputs = grep { defined $_->{Commit} } @mergeinputs;
2046 if (@mergeinputs > 1) {
2048 my $tree_commit = $mergeinputs[0]{Commit};
2050 my $tree = cmdoutput @git, qw(cat-file commit), $tree_commit;
2051 $tree =~ m/\n\n/; $tree = $`;
2052 $tree =~ m/^tree (\w+)$/m or die "$dsc_hash tree ?";
2055 # We use the changelog author of the package in question the
2056 # author of this pseudo-merge. This is (roughly) correct if
2057 # this commit is simply representing aa non-dgit upload.
2058 # (Roughly because it does not record sponsorship - but we
2059 # don't have sponsorship info because that's in the .changes,
2060 # which isn't in the archivw.)
2062 # But, it might be that we are representing archive history
2063 # updates (including in-archive copies). These are not really
2064 # the responsibility of the person who created the .dsc, but
2065 # there is no-one whose name we should better use. (The
2066 # author of the .dsc-named commit is clearly worse.)
2068 my $useclogp = mergeinfo_getclogp $mergeinputs[0];
2069 my $author = clogp_authline $useclogp;
2070 my $cversion = getfield $useclogp, 'Version';
2072 my $mcf = ".git/dgit/mergecommit";
2073 open MC, ">", $mcf or die "$mcf $!";
2074 print MC <<END or die $!;
2078 my @parents = grep { $_->{Commit} } @mergeinputs;
2079 @parents = reverse @parents if $compat_info->{ReverseParents};
2080 print MC <<END or die $! foreach @parents;
2084 print MC <<END or die $!;
2090 if (defined $compat_info->{Message}) {
2091 print MC $compat_info->{Message} or die $!;
2093 print MC <<END or die $!;
2094 Record $package ($cversion) in archive suite $csuite
2098 my $message_add_info = sub {
2100 my $mversion = mergeinfo_version $mi;
2101 printf MC " %-20s %s\n", $mversion, $mi->{Info}
2105 $message_add_info->($mergeinputs[0]);
2106 print MC <<END or die $!;
2107 should be treated as descended from
2109 $message_add_info->($_) foreach @mergeinputs[1..$#mergeinputs];
2113 $hash = make_commit $mcf;
2115 $hash = $mergeinputs[0]{Commit};
2117 progress "fetch hash=$hash\n";
2120 my ($lasth, $what) = @_;
2121 return unless $lasth;
2122 die "$lasth $hash $what ?" unless is_fast_fwd($lasth, $hash);
2125 $chkff->($lastpush_hash, 'dgit repo server tip (last push)');
2126 $chkff->($lastfetch_hash, 'local tracking tip (last fetch)');
2128 runcmd @git, qw(update-ref -m), "dgit fetch $csuite",
2129 'DGIT_ARCHIVE', $hash;
2130 cmdoutput @git, qw(log -n2), $hash;
2131 # ... gives git a chance to complain if our commit is malformed
2133 if (defined $skew_warning_vsn) {
2135 printdebug "SKEW CHECK WANT $skew_warning_vsn\n";
2136 my $gotclogp = commit_getclogp($hash);
2137 my $got_vsn = getfield $gotclogp, 'Version';
2138 printdebug "SKEW CHECK GOT $got_vsn\n";
2139 if (version_compare($got_vsn, $skew_warning_vsn) < 0) {
2140 print STDERR <<END or die $!;
2142 Warning: archive skew detected. Using the available version:
2143 Archive allegedly contains $skew_warning_vsn
2144 We were able to obtain only $got_vsn
2150 if ($lastfetch_hash ne $hash) {
2151 my @upd_cmd = (@git, qw(update-ref -m), 'dgit fetch', lrref(), $hash);
2155 dryrun_report @upd_cmd;
2159 lrfetchref_used lrfetchref();
2161 unshift @end, $del_lrfetchrefs;
2165 sub set_local_git_config ($$) {
2167 runcmd @git, qw(config), $k, $v;
2170 sub setup_mergechangelogs (;$) {
2172 return unless $always || access_cfg_bool(1, 'setup-mergechangelogs');
2174 my $driver = 'dpkg-mergechangelogs';
2175 my $cb = "merge.$driver";
2176 my $attrs = '.git/info/attributes';
2177 ensuredir '.git/info';
2179 open NATTRS, ">", "$attrs.new" or die "$attrs.new $!";
2180 if (!open ATTRS, "<", $attrs) {
2181 $!==ENOENT or die "$attrs: $!";
2185 next if m{^debian/changelog\s};
2186 print NATTRS $_, "\n" or die $!;
2188 ATTRS->error and die $!;
2191 print NATTRS "debian/changelog merge=$driver\n" or die $!;
2194 set_local_git_config "$cb.name", 'debian/changelog merge driver';
2195 set_local_git_config "$cb.driver", 'dpkg-mergechangelogs -m %O %A %B %A';
2197 rename "$attrs.new", "$attrs" or die "$attrs: $!";
2200 sub setup_useremail (;$) {
2202 return unless $always || access_cfg_bool(1, 'setup-useremail');
2205 my ($k, $envvar) = @_;
2206 my $v = access_cfg("user-$k", 'RETURN-UNDEF') // $ENV{$envvar};
2207 return unless defined $v;
2208 set_local_git_config "user.$k", $v;
2211 $setup->('email', 'DEBEMAIL');
2212 $setup->('name', 'DEBFULLNAME');
2215 sub setup_new_tree () {
2216 setup_mergechangelogs();
2222 canonicalise_suite();
2223 badusage "dry run makes no sense with clone" unless act_local();
2224 my $hasgit = check_for_git();
2225 mkdir $dstdir or fail "create \`$dstdir': $!";
2227 runcmd @git, qw(init -q);
2228 my $giturl = access_giturl(1);
2229 if (defined $giturl) {
2230 open H, "> .git/HEAD" or die $!;
2231 print H "ref: ".lref()."\n" or die $!;
2233 runcmd @git, qw(remote add), 'origin', $giturl;
2236 progress "fetching existing git history";
2238 runcmd_ordryrun_local @git, qw(fetch origin);
2240 progress "starting new git history";
2242 fetch_from_archive() or no_such_package;
2243 my $vcsgiturl = $dsc->{'Vcs-Git'};
2244 if (length $vcsgiturl) {
2245 $vcsgiturl =~ s/\s+-b\s+\S+//g;
2246 runcmd @git, qw(remote add vcs-git), $vcsgiturl;
2249 runcmd @git, qw(reset --hard), lrref();
2250 printdone "ready for work in $dstdir";
2254 if (check_for_git()) {
2257 fetch_from_archive() or no_such_package();
2258 printdone "fetched into ".lrref();
2263 runcmd_ordryrun_local @git, qw(merge -m),"Merge from $csuite [dgit]",
2265 printdone "fetched to ".lrref()." and merged into HEAD";
2268 sub check_not_dirty () {
2269 foreach my $f (qw(local-options local-patch-header)) {
2270 if (stat_exists "debian/source/$f") {
2271 fail "git tree contains debian/source/$f";
2275 return if $ignoredirty;
2277 my @cmd = (@git, qw(diff --quiet HEAD));
2279 $!=0; $?=-1; system @cmd;
2282 fail "working tree is dirty (does not match HEAD)";
2288 sub commit_admin ($) {
2291 runcmd_ordryrun_local @git, qw(commit -m), $m;
2294 sub commit_quilty_patch () {
2295 my $output = cmdoutput @git, qw(status --porcelain);
2297 foreach my $l (split /\n/, $output) {
2298 next unless $l =~ m/\S/;
2299 if ($l =~ m{^(?:\?\?| M) (.pc|debian/patches)}) {
2303 delete $adds{'.pc'}; # if there wasn't one before, don't add it
2305 progress "nothing quilty to commit, ok.";
2308 my @adds = map { s/[][*?\\]/\\$&/g; $_; } sort keys %adds;
2309 runcmd_ordryrun_local @git, qw(add -f), @adds;
2310 commit_admin "Commit Debian 3.0 (quilt) metadata";
2313 sub get_source_format () {
2315 if (open F, "debian/source/options") {
2319 s/\s+$//; # ignore missing final newline
2321 my ($k, $v) = ($`, $'); #');
2322 $v =~ s/^"(.*)"$/$1/;
2328 F->error and die $!;
2331 die $! unless $!==&ENOENT;
2334 if (!open F, "debian/source/format") {
2335 die $! unless $!==&ENOENT;
2339 F->error and die $!;
2341 return ($_, \%options);
2346 return 0 unless $format eq '3.0 (quilt)';
2347 our $quilt_mode_warned;
2348 if ($quilt_mode eq 'nocheck') {
2349 progress "Not doing any fixup of \`$format' due to".
2350 " ----no-quilt-fixup or --quilt=nocheck"
2351 unless $quilt_mode_warned++;
2354 progress "Format \`$format', need to check/update patch stack"
2355 unless $quilt_mode_warned++;
2359 # An "infopair" is a tuple [ $thing, $what ]
2360 # (often $thing is a commit hash; $what is a description)
2362 sub infopair_cond_equal ($$) {
2364 $x->[0] eq $y->[0] or fail <<END;
2365 $x->[1] ($x->[0]) not equal to $y->[1] ($y->[0])
2369 sub infopair_lrf_tag_lookup ($$) {
2370 my ($tagnames, $what) = @_;
2371 # $tagname may be an array ref
2372 my @tagnames = ref $tagnames ? @$tagnames : ($tagnames);
2373 printdebug "infopair_lrfetchref_tag_lookup $what @tagnames\n";
2374 foreach my $tagname (@tagnames) {
2375 my $lrefname = lrfetchrefs."/tags/$tagname";
2376 my $tagobj = $lrfetchrefs_f{$lrefname};
2377 next unless defined $tagobj;
2378 printdebug "infopair_lrfetchref_tag_lookup $tagobj $tagname $what\n";
2379 return [ git_rev_parse($tagobj), $what ];
2381 fail @tagnames==1 ? <<END : <<END;
2382 Wanted tag $what (@tagnames) on dgit server, but not found
2384 Wanted tag $what (one of: @tagnames) on dgit server, but not found
2388 sub infopair_cond_ff ($$) {
2389 my ($anc,$desc) = @_;
2390 is_fast_fwd($anc->[0], $desc->[0]) or fail <<END;
2391 $anc->[1] ($anc->[0]) .. $desc->[1] ($desc->[0]) is not fast forward
2395 sub splitbrain_pseudomerge ($$$$) {
2396 my ($clogp, $maintview, $dgitview, $archive_hash) = @_;
2397 # => $merged_dgitview
2398 printdebug "splitbrain_pseudomerge...\n";
2400 # We: debian/PREVIOUS HEAD($maintview)
2401 # expect: o ----------------- o
2404 # a/d/PREVIOUS $dgitview
2407 # we do: `------------------ o
2411 my $arch_clogp = commit_getclogp $archive_hash;
2412 my $i_arch_v = [ (getfield $arch_clogp, 'Version'),
2413 'version currently in archive' ];
2415 printdebug "splitbrain_pseudomerge i_arch_v @$i_arch_v\n";
2417 return $dgitview unless defined $archive_hash;
2419 if (defined $overwrite_version) {
2420 progress "Declaring that HEAD inciudes all changes in archive...";
2421 progress "Checking that $overwrite_version does so...";
2422 infopair_cond_equal([ $overwrite_version, '--overwrite= version' ],
2425 progress "Checking that HEAD inciudes all changes in archive...";
2428 return $dgitview if is_fast_fwd $archive_hash, $dgitview;
2430 my $t_dep14 = debiantag_maintview $i_arch_v->[0], access_basedistro;
2431 my $i_dep14 = infopair_lrf_tag_lookup($t_dep14, "maintainer view tag");
2432 my $t_dgit = debiantag_new $i_arch_v->[0], access_basedistro;
2433 my $i_dgit = infopair_lrf_tag_lookup($t_dgit, "dgit view tag");
2434 my $i_archive = [ $archive_hash, "current archive contents" ];
2436 printdebug "splitbrain_pseudomerge i_archive @$i_archive\n";
2438 infopair_cond_equal($i_dgit, $i_archive);
2439 infopair_cond_ff($i_dep14, $i_dgit);
2440 $overwrite_version // infopair_cond_ff($i_dep14, [ $maintview, 'HEAD' ]);
2442 my $tree = cmdoutput qw(git rev-parse), "${dgitview}:";
2443 my $authline = clogp_authline $clogp;
2446 my $pmf = ".git/dgit/pseudomerge";
2447 open MC, ">", $pmf or die "$pmf $!";
2448 print MC <<END or die $!;
2451 parent $archive_hash
2456 if (defined $overwrite_version) {
2458 Declare fast forward from $overwrite_version
2460 [dgit --quilt=$quilt_mode --overwrite-version=$overwrite_version]
2464 Make fast forward from $i_arch_v->[0]
2466 [dgit --quilt=$quilt_mode]
2471 progress "Making pseudo-merge of $i_arch_v->[0] into dgit view.";
2472 return make_commit($pmf);
2475 sub push_parse_changelog ($) {
2478 my $clogp = Dpkg::Control::Hash->new();
2479 $clogp->load($clogpfn) or die;
2481 $package = getfield $clogp, 'Source';
2482 my $cversion = getfield $clogp, 'Version';
2483 my $tag = debiantag($cversion, access_basedistro);
2484 runcmd @git, qw(check-ref-format), $tag;
2486 my $dscfn = dscfn($cversion);
2488 return ($clogp, $cversion, $dscfn);
2491 sub push_parse_dsc ($$$) {
2492 my ($dscfn,$dscfnwhat, $cversion) = @_;
2493 $dsc = parsecontrol($dscfn,$dscfnwhat);
2494 my $dversion = getfield $dsc, 'Version';
2495 my $dscpackage = getfield $dsc, 'Source';
2496 ($dscpackage eq $package && $dversion eq $cversion) or
2497 fail "$dscfn is for $dscpackage $dversion".
2498 " but debian/changelog is for $package $cversion";
2501 sub push_tagwants ($$$$) {
2502 my ($cversion, $dgithead, $maintviewhead, $tfbase) = @_;
2505 TagFn => \&debiantag,
2510 if (defined $maintviewhead) {
2512 TagFn => \&debiantag_maintview,
2513 Objid => $maintviewhead,
2514 TfSuffix => '-maintview',
2518 foreach my $tw (@tagwants) {
2519 $tw->{Tag} = $tw->{TagFn}($cversion, access_basedistro);
2520 $tw->{Tfn} = sub { $tfbase.$tw->{TfSuffix}.$_[0]; };
2522 printdebug 'push_tagwants: ', Dumper(\@_, \@tagwants);
2526 sub push_mktags ($$ $$ $) {
2528 $changesfile,$changesfilewhat,
2531 die unless $tagwants->[0]{View} eq 'dgit';
2533 $dsc->{$ourdscfield[0]} = $tagwants->[0]{Objid};
2534 $dsc->save("$dscfn.tmp") or die $!;
2536 my $changes = parsecontrol($changesfile,$changesfilewhat);
2537 foreach my $field (qw(Source Distribution Version)) {
2538 $changes->{$field} eq $clogp->{$field} or
2539 fail "changes field $field \`$changes->{$field}'".
2540 " does not match changelog \`$clogp->{$field}'";
2543 my $cversion = getfield $clogp, 'Version';
2544 my $clogsuite = getfield $clogp, 'Distribution';
2546 # We make the git tag by hand because (a) that makes it easier
2547 # to control the "tagger" (b) we can do remote signing
2548 my $authline = clogp_authline $clogp;
2549 my $delibs = join(" ", "",@deliberatelies);
2550 my $declaredistro = access_basedistro();
2554 my $tfn = $tw->{Tfn};
2555 my $head = $tw->{Objid};
2556 my $tag = $tw->{Tag};
2558 open TO, '>', $tfn->('.tmp') or die $!;
2559 print TO <<END or die $!;
2566 if ($tw->{View} eq 'dgit') {
2567 print TO <<END or die $!;
2568 $package release $cversion for $clogsuite ($csuite) [dgit]
2569 [dgit distro=$declaredistro$delibs]
2571 foreach my $ref (sort keys %previously) {
2572 print TO <<END or die $!;
2573 [dgit previously:$ref=$previously{$ref}]
2576 } elsif ($tw->{View} eq 'maint') {
2577 print TO <<END or die $!;
2578 $package release $cversion for $clogsuite ($csuite)
2579 (maintainer view tag generated by dgit --quilt=$quilt_mode)
2582 die Dumper($tw)."?";
2587 my $tagobjfn = $tfn->('.tmp');
2589 if (!defined $keyid) {
2590 $keyid = access_cfg('keyid','RETURN-UNDEF');
2592 if (!defined $keyid) {
2593 $keyid = getfield $clogp, 'Maintainer';
2595 unlink $tfn->('.tmp.asc') or $!==&ENOENT or die $!;
2596 my @sign_cmd = (@gpg, qw(--detach-sign --armor));
2597 push @sign_cmd, qw(-u),$keyid if defined $keyid;
2598 push @sign_cmd, $tfn->('.tmp');
2599 runcmd_ordryrun @sign_cmd;
2601 $tagobjfn = $tfn->('.signed.tmp');
2602 runcmd shell_cmd "exec >$tagobjfn", qw(cat --),
2603 $tfn->('.tmp'), $tfn->('.tmp.asc');
2609 my @r = map { $mktag->($_); } @$tagwants;
2613 sub sign_changes ($) {
2614 my ($changesfile) = @_;
2616 my @debsign_cmd = @debsign;
2617 push @debsign_cmd, "-k$keyid" if defined $keyid;
2618 push @debsign_cmd, "-p$gpg[0]" if $gpg[0] ne 'gpg';
2619 push @debsign_cmd, $changesfile;
2620 runcmd_ordryrun @debsign_cmd;
2625 printdebug "actually entering push\n";
2627 supplementary_message(<<'END');
2628 Push failed, while checking state of the archive.
2629 You can retry the push, after fixing the problem, if you like.
2631 if (check_for_git()) {
2634 my $archive_hash = fetch_from_archive();
2635 if (!$archive_hash) {
2637 fail "package appears to be new in this suite;".
2638 " if this is intentional, use --new";
2641 supplementary_message(<<'END');
2642 Push failed, while preparing your push.
2643 You can retry the push, after fixing the problem, if you like.
2646 need_tagformat 'new', "quilt mode $quilt_mode"
2647 if quiltmode_splitbrain;
2651 access_giturl(); # check that success is vaguely likely
2654 my $clogpfn = ".git/dgit/changelog.822.tmp";
2655 runcmd shell_cmd "exec >$clogpfn", qw(dpkg-parsechangelog);
2657 responder_send_file('parsed-changelog', $clogpfn);
2659 my ($clogp, $cversion, $dscfn) =
2660 push_parse_changelog("$clogpfn");
2662 my $dscpath = "$buildproductsdir/$dscfn";
2663 stat_exists $dscpath or
2664 fail "looked for .dsc $dscfn, but $!;".
2665 " maybe you forgot to build";
2667 responder_send_file('dsc', $dscpath);
2669 push_parse_dsc($dscpath, $dscfn, $cversion);
2671 my $format = getfield $dsc, 'Format';
2672 printdebug "format $format\n";
2674 my $actualhead = git_rev_parse('HEAD');
2675 my $dgithead = $actualhead;
2676 my $maintviewhead = undef;
2678 if (madformat($format)) {
2679 # user might have not used dgit build, so maybe do this now:
2680 if (quiltmode_splitbrain()) {
2681 my $upstreamversion = $clogp->{Version};
2682 $upstreamversion =~ s/-[^-]*$//;
2684 quilt_make_fake_dsc($upstreamversion);
2685 my ($dgitview, $cachekey) =
2686 quilt_check_splitbrain_cache($actualhead, $upstreamversion);
2688 "--quilt=$quilt_mode but no cached dgit view:
2689 perhaps tree changed since dgit build[-source] ?";
2691 $dgithead = splitbrain_pseudomerge($clogp,
2692 $actualhead, $dgitview,
2694 $maintviewhead = $actualhead;
2695 changedir '../../../..';
2696 prep_ud(); # so _only_subdir() works, below
2698 commit_quilty_patch();
2705 if ($archive_hash) {
2706 if (is_fast_fwd($archive_hash, $dgithead)) {
2708 } elsif (deliberately_not_fast_forward) {
2711 fail "dgit push: HEAD is not a descendant".
2712 " of the archive's version.\n".
2713 "dgit: To overwrite its contents,".
2714 " use git merge -s ours ".lrref().".\n".
2715 "dgit: To rewind history, if permitted by the archive,".
2716 " use --deliberately-not-fast-forward";
2721 progress "checking that $dscfn corresponds to HEAD";
2722 runcmd qw(dpkg-source -x --),
2723 $dscpath =~ m#^/# ? $dscpath : "../../../$dscpath";
2724 my ($tree,$dir) = mktree_in_ud_from_only_subdir();
2725 check_for_vendor_patches() if madformat($dsc->{format});
2726 changedir '../../../..';
2727 my $diffopt = $debuglevel>0 ? '--exit-code' : '--quiet';
2728 my @diffcmd = (@git, qw(diff), $diffopt, $tree, $dgithead);
2729 debugcmd "+",@diffcmd;
2731 my $r = system @diffcmd;
2734 fail "$dscfn specifies a different tree to your HEAD commit;".
2735 " perhaps you forgot to build".
2736 ($diffopt eq '--exit-code' ? "" :
2737 " (run with -D to see full diff output)");
2742 if (!$changesfile) {
2743 my $pat = changespat $cversion;
2744 my @cs = glob "$buildproductsdir/$pat";
2745 fail "failed to find unique changes file".
2746 " (looked for $pat in $buildproductsdir);".
2747 " perhaps you need to use dgit -C"
2749 ($changesfile) = @cs;
2751 $changesfile = "$buildproductsdir/$changesfile";
2754 # Checks complete, we're going to try and go ahead:
2756 responder_send_file('changes',$changesfile);
2757 responder_send_command("param head $dgithead");
2758 responder_send_command("param csuite $csuite");
2759 responder_send_command("param tagformat $tagformat");
2760 if (defined $maintviewhead) {
2761 die unless ($protovsn//4) >= 4;
2762 responder_send_command("param maint-view $maintviewhead");
2765 if (deliberately_not_fast_forward) {
2766 git_for_each_ref(lrfetchrefs, sub {
2767 my ($objid,$objtype,$lrfetchrefname,$reftail) = @_;
2768 my $rrefname= substr($lrfetchrefname, length(lrfetchrefs) + 1);
2769 responder_send_command("previously $rrefname=$objid");
2770 $previously{$rrefname} = $objid;
2774 my @tagwants = push_tagwants($cversion, $dgithead, $maintviewhead,
2778 supplementary_message(<<'END');
2779 Push failed, while signing the tag.
2780 You can retry the push, after fixing the problem, if you like.
2782 # If we manage to sign but fail to record it anywhere, it's fine.
2783 if ($we_are_responder) {
2784 @tagobjfns = map { $_->{Tfn}('.signed-tmp') } @tagwants;
2785 responder_receive_files('signed-tag', @tagobjfns);
2787 @tagobjfns = push_mktags($clogp,$dscpath,
2788 $changesfile,$changesfile,
2791 supplementary_message(<<'END');
2792 Push failed, *after* signing the tag.
2793 If you want to try again, you should use a new version number.
2796 pairwise { $a->{TagObjFn} = $b } @tagwants, @tagobjfns;
2798 foreach my $tw (@tagwants) {
2799 my $tag = $tw->{Tag};
2800 my $tagobjfn = $tw->{TagObjFn};
2802 cmdoutput @git, qw(hash-object -w -t tag), $tagobjfn;
2803 runcmd_ordryrun @git, qw(verify-tag), $tag_obj_hash;
2804 runcmd_ordryrun_local
2805 @git, qw(update-ref), "refs/tags/$tag", $tag_obj_hash;
2808 supplementary_message(<<'END');
2809 Push failed, while updating the remote git repository - see messages above.
2810 If you want to try again, you should use a new version number.
2812 if (!check_for_git()) {
2813 create_remote_git_repo();
2816 my @pushrefs = $forceflag.$dgithead.":".rrref();
2817 foreach my $tw (@tagwants) {
2818 push @pushrefs, $forceflag."refs/tags/$tw->{Tag}";
2821 runcmd_ordryrun @git, qw(push),access_giturl(), @pushrefs;
2822 runcmd_ordryrun @git, qw(update-ref -m), 'dgit push', lrref(), $dgithead;
2824 supplementary_message(<<'END');
2825 Push failed, after updating the remote git repository.
2826 If you want to try again, you must use a new version number.
2828 if ($we_are_responder) {
2829 my $dryrunsuffix = act_local() ? "" : ".tmp";
2830 responder_receive_files('signed-dsc-changes',
2831 "$dscpath$dryrunsuffix",
2832 "$changesfile$dryrunsuffix");
2835 rename "$dscpath.tmp",$dscpath or die "$dscfn $!";
2837 progress "[new .dsc left in $dscpath.tmp]";
2839 sign_changes $changesfile;
2842 supplementary_message(<<END);
2843 Push failed, while uploading package(s) to the archive server.
2844 You can retry the upload of exactly these same files with dput of:
2846 If that .changes file is broken, you will need to use a new version
2847 number for your next attempt at the upload.
2849 my $host = access_cfg('upload-host','RETURN-UNDEF');
2850 my @hostarg = defined($host) ? ($host,) : ();
2851 runcmd_ordryrun @dput, @hostarg, $changesfile;
2852 printdone "pushed and uploaded $cversion";
2854 supplementary_message('');
2855 responder_send_command("complete");
2862 badusage "-p is not allowed with clone; specify as argument instead"
2863 if defined $package;
2866 } elsif (@ARGV==2 && $ARGV[1] =~ m#^\w#) {
2867 ($package,$isuite) = @ARGV;
2868 } elsif (@ARGV==2 && $ARGV[1] =~ m#^[./]#) {
2869 ($package,$dstdir) = @ARGV;
2870 } elsif (@ARGV==3) {
2871 ($package,$isuite,$dstdir) = @ARGV;
2873 badusage "incorrect arguments to dgit clone";
2875 $dstdir ||= "$package";
2877 if (stat_exists $dstdir) {
2878 fail "$dstdir already exists";
2882 if ($rmonerror && !$dryrun_level) {
2883 $cwd_remove= getcwd();
2885 return unless defined $cwd_remove;
2886 if (!chdir "$cwd_remove") {
2887 return if $!==&ENOENT;
2888 die "chdir $cwd_remove: $!";
2891 rmtree($dstdir) or die "remove $dstdir: $!\n";
2892 } elsif (!grep { $! == $_ }
2893 (ENOENT, ENOTDIR, EACCES, EPERM, ELOOP)) {
2895 print STDERR "check whether to remove $dstdir: $!\n";
2901 $cwd_remove = undef;
2904 sub branchsuite () {
2905 my $branch = cmdoutput_errok @git, qw(symbolic-ref HEAD);
2906 if ($branch =~ m#$lbranch_re#o) {
2913 sub fetchpullargs () {
2915 if (!defined $package) {
2916 my $sourcep = parsecontrol('debian/control','debian/control');
2917 $package = getfield $sourcep, 'Source';
2920 # $isuite = branchsuite(); # this doesn't work because dak hates canons
2922 my $clogp = parsechangelog();
2923 $isuite = getfield $clogp, 'Distribution';
2925 canonicalise_suite();
2926 progress "fetching from suite $csuite";
2927 } elsif (@ARGV==1) {
2929 canonicalise_suite();
2931 badusage "incorrect arguments to dgit fetch or dgit pull";
2950 badusage "-p is not allowed with dgit push" if defined $package;
2952 my $clogp = parsechangelog();
2953 $package = getfield $clogp, 'Source';
2956 } elsif (@ARGV==1) {
2957 ($specsuite) = (@ARGV);
2959 badusage "incorrect arguments to dgit push";
2961 $isuite = getfield $clogp, 'Distribution';
2963 local ($package) = $existing_package; # this is a hack
2964 canonicalise_suite();
2966 canonicalise_suite();
2968 if (defined $specsuite &&
2969 $specsuite ne $isuite &&
2970 $specsuite ne $csuite) {
2971 fail "dgit push: changelog specifies $isuite ($csuite)".
2972 " but command line specifies $specsuite";
2977 #---------- remote commands' implementation ----------
2979 sub cmd_remote_push_build_host {
2980 my ($nrargs) = shift @ARGV;
2981 my (@rargs) = @ARGV[0..$nrargs-1];
2982 @ARGV = @ARGV[$nrargs..$#ARGV];
2984 my ($dir,$vsnwant) = @rargs;
2985 # vsnwant is a comma-separated list; we report which we have
2986 # chosen in our ready response (so other end can tell if they
2989 $we_are_responder = 1;
2990 $us .= " (build host)";
2994 open PI, "<&STDIN" or die $!;
2995 open STDIN, "/dev/null" or die $!;
2996 open PO, ">&STDOUT" or die $!;
2998 open STDOUT, ">&STDERR" or die $!;
3002 ($protovsn) = grep {
3003 $vsnwant =~ m{^(?:.*,)?$_(?:,.*)?$}
3004 } @rpushprotovsn_support;
3006 fail "build host has dgit rpush protocol versions ".
3007 (join ",", @rpushprotovsn_support).
3008 " but invocation host has $vsnwant"
3009 unless defined $protovsn;
3011 responder_send_command("dgit-remote-push-ready $protovsn");
3012 rpush_handle_protovsn_bothends();
3017 sub cmd_remote_push_responder { cmd_remote_push_build_host(); }
3018 # ... for compatibility with proto vsn.1 dgit (just so that user gets
3019 # a good error message)
3021 sub rpush_handle_protovsn_bothends () {
3022 if ($protovsn < 4) {
3023 need_tagformat 'old', "rpush negotiated protocol $protovsn";
3032 my $report = i_child_report();
3033 if (defined $report) {
3034 printdebug "($report)\n";
3035 } elsif ($i_child_pid) {
3036 printdebug "(killing build host child $i_child_pid)\n";
3037 kill 15, $i_child_pid;
3039 if (defined $i_tmp && !defined $initiator_tempdir) {
3041 eval { rmtree $i_tmp; };
3045 END { i_cleanup(); }
3048 my ($base,$selector,@args) = @_;
3049 $selector =~ s/\-/_/g;
3050 { no strict qw(refs); &{"${base}_${selector}"}(@args); }
3057 if ($host =~ m/^((?:[^][]|\[[^][]*\])*)\:/) {
3065 push @rargs, join ",", @rpushprotovsn_support;
3068 push @rdgit, @ropts;
3069 push @rdgit, qw(remote-push-build-host), (scalar @rargs), @rargs;
3071 my @cmd = (@ssh, $host, shellquote @rdgit);
3074 if (defined $initiator_tempdir) {
3075 rmtree $initiator_tempdir;
3076 mkdir $initiator_tempdir, 0700 or die "$initiator_tempdir: $!";
3077 $i_tmp = $initiator_tempdir;
3081 $i_child_pid = open2(\*RO, \*RI, @cmd);
3083 ($protovsn) = initiator_expect { m/^dgit-remote-push-ready (\S+)/ };
3084 die "$protovsn ?" unless grep { $_ eq $protovsn } @rpushprotovsn_support;
3085 $supplementary_message = '' unless $protovsn >= 3;
3087 fail "rpush negotiated protocol version $protovsn".
3088 " which does not support quilt mode $quilt_mode"
3089 if quiltmode_splitbrain;
3091 rpush_handle_protovsn_bothends();
3093 my ($icmd,$iargs) = initiator_expect {
3094 m/^(\S+)(?: (.*))?$/;
3097 i_method "i_resp", $icmd, $iargs;
3101 sub i_resp_progress ($) {
3103 my $msg = protocol_read_bytes \*RO, $rhs;
3107 sub i_resp_supplementary_message ($) {
3109 $supplementary_message = protocol_read_bytes \*RO, $rhs;
3112 sub i_resp_complete {
3113 my $pid = $i_child_pid;
3114 $i_child_pid = undef; # prevents killing some other process with same pid
3115 printdebug "waiting for build host child $pid...\n";
3116 my $got = waitpid $pid, 0;
3117 die $! unless $got == $pid;
3118 die "build host child failed $?" if $?;
3121 printdebug "all done\n";
3125 sub i_resp_file ($) {
3127 my $localname = i_method "i_localname", $keyword;
3128 my $localpath = "$i_tmp/$localname";
3129 stat_exists $localpath and
3130 badproto \*RO, "file $keyword ($localpath) twice";
3131 protocol_receive_file \*RO, $localpath;
3132 i_method "i_file", $keyword;
3137 sub i_resp_param ($) {
3138 $_[0] =~ m/^(\S+) (.*)$/ or badproto \*RO, "bad param spec";
3142 sub i_resp_previously ($) {
3143 $_[0] =~ m#^(refs/tags/\S+)=(\w+)$#
3144 or badproto \*RO, "bad previously spec";
3145 my $r = system qw(git check-ref-format), $1;
3146 die "bad previously ref spec ($r)" if $r;
3147 $previously{$1} = $2;
3152 sub i_resp_want ($) {
3154 die "$keyword ?" if $i_wanted{$keyword}++;
3155 my @localpaths = i_method "i_want", $keyword;
3156 printdebug "[[ $keyword @localpaths\n";
3157 foreach my $localpath (@localpaths) {
3158 protocol_send_file \*RI, $localpath;
3160 print RI "files-end\n" or die $!;
3163 our ($i_clogp, $i_version, $i_dscfn, $i_changesfn);
3165 sub i_localname_parsed_changelog {
3166 return "remote-changelog.822";
3168 sub i_file_parsed_changelog {
3169 ($i_clogp, $i_version, $i_dscfn) =
3170 push_parse_changelog "$i_tmp/remote-changelog.822";
3171 die if $i_dscfn =~ m#/|^\W#;
3174 sub i_localname_dsc {
3175 defined $i_dscfn or badproto \*RO, "dsc (before parsed-changelog)";
3180 sub i_localname_changes {
3181 defined $i_dscfn or badproto \*RO, "dsc (before parsed-changelog)";
3182 $i_changesfn = $i_dscfn;
3183 $i_changesfn =~ s/\.dsc$/_dgit.changes/ or die;
3184 return $i_changesfn;
3186 sub i_file_changes { }
3188 sub i_want_signed_tag {
3189 printdebug Dumper(\%i_param, $i_dscfn);
3190 defined $i_param{'head'} && defined $i_dscfn && defined $i_clogp
3191 && defined $i_param{'csuite'}
3192 or badproto \*RO, "premature desire for signed-tag";
3193 my $head = $i_param{'head'};
3194 die if $head =~ m/[^0-9a-f]/ || $head !~ m/^../;
3196 my $maintview = $i_param{'maint-view'};
3197 die if defined $maintview && $maintview =~ m/[^0-9a-f]/;
3200 if ($protovsn >= 4) {
3201 my $p = $i_param{'tagformat'} // '<undef>';
3203 or badproto \*RO, "tag format mismatch: $p vs. $tagformat";
3206 die unless $i_param{'csuite'} =~ m/^$suite_re$/;
3208 push_parse_dsc $i_dscfn, 'remote dsc', $i_version;
3210 my @tagwants = push_tagwants $i_version, $head, $maintview, "tag";
3213 push_mktags $i_clogp, $i_dscfn,
3214 $i_changesfn, 'remote changes',
3218 sub i_want_signed_dsc_changes {
3219 rename "$i_dscfn.tmp","$i_dscfn" or die "$i_dscfn $!";
3220 sign_changes $i_changesfn;
3221 return ($i_dscfn, $i_changesfn);
3224 #---------- building etc. ----------
3230 #----- `3.0 (quilt)' handling -----
3232 our $fakeeditorenv = 'DGIT_FAKE_EDITOR_QUILT';
3234 sub quiltify_dpkg_commit ($$$;$) {
3235 my ($patchname,$author,$msg, $xinfo) = @_;
3239 my $descfn = ".git/dgit/quilt-description.tmp";
3240 open O, '>', $descfn or die "$descfn: $!";
3243 $msg =~ s/^\s+$/ ./mg;
3244 print O <<END or die $!;
3254 local $ENV{'EDITOR'} = cmdoutput qw(realpath --), $0;
3255 local $ENV{'VISUAL'} = $ENV{'EDITOR'};
3256 local $ENV{$fakeeditorenv} = cmdoutput qw(realpath --), $descfn;
3257 runcmd @dpkgsource, qw(--commit .), $patchname;
3261 sub quiltify_trees_differ ($$;$$) {
3262 my ($x,$y,$finegrained,$ignorenamesr) = @_;
3263 # returns true iff the two tree objects differ other than in debian/
3264 # with $finegrained,
3265 # returns bitmask 01 - differ in upstream files except .gitignore
3266 # 02 - differ in .gitignore
3267 # if $ignorenamesr is defined, $ingorenamesr->{$fn}
3268 # is set for each modified .gitignore filename $fn
3270 my @cmd = (@git, qw(diff-tree --name-only -z));
3271 push @cmd, qw(-r) if $finegrained;
3273 my $diffs= cmdoutput @cmd;
3275 foreach my $f (split /\0/, $diffs) {
3276 next if $f =~ m#^debian(?:/.*)?$#s;
3277 my $isignore = $f =~ m#^(?:.*/)?.gitignore$#s;
3278 $r |= $isignore ? 02 : 01;
3279 $ignorenamesr->{$f}=1 if $ignorenamesr && $isignore;
3281 printdebug "quiltify_trees_differ $x $y => $r\n";
3285 sub quiltify_tree_sentinelfiles ($) {
3286 # lists the `sentinel' files present in the tree
3288 my $r = cmdoutput @git, qw(ls-tree --name-only), $x,
3289 qw(-- debian/rules debian/control);
3294 sub quiltify_splitbrain_needed () {
3295 if (!$split_brain) {
3296 progress "dgit view: changes are required...";
3297 runcmd @git, qw(checkout -q -b dgit-view);
3302 sub quiltify_splitbrain ($$$$$$) {
3303 my ($clogp, $unapplied, $headref, $diffbits,
3304 $editedignores, $cachekey) = @_;
3305 if ($quilt_mode !~ m/gbp|dpm/) {
3306 # treat .gitignore just like any other upstream file
3307 $diffbits = { %$diffbits };
3308 $_ = !!$_ foreach values %$diffbits;
3310 # We would like any commits we generate to be reproducible
3311 my @authline = clogp_authline($clogp);
3312 local $ENV{GIT_COMMITTER_NAME} = $authline[0];
3313 local $ENV{GIT_COMMITTER_EMAIL} = $authline[1];
3314 local $ENV{GIT_COMMITTER_DATE} = $authline[2];
3316 if ($quilt_mode =~ m/gbp|unapplied/ &&
3317 ($diffbits->{H2O} & 01)) {
3319 "--quilt=$quilt_mode specified, implying patches-unapplied git tree\n".
3320 " but git tree differs from orig in upstream files.";
3321 if (!stat_exists "debian/patches") {
3323 "\n ... debian/patches is missing; perhaps this is a patch queue branch?";
3327 if ($quilt_mode =~ m/dpm/ &&
3328 ($diffbits->{H2A} & 01)) {
3330 --quilt=$quilt_mode specified, implying patches-applied git tree
3331 but git tree differs from result of applying debian/patches to upstream
3334 if ($quilt_mode =~ m/gbp|unapplied/ &&
3335 ($diffbits->{O2A} & 01)) { # some patches
3336 quiltify_splitbrain_needed();
3337 progress "dgit view: creating patches-applied version using gbp pq";
3338 runcmd shell_cmd 'exec >/dev/null', @gbp, qw(pq import);
3339 # gbp pq import creates a fresh branch; push back to dgit-view
3340 runcmd @git, qw(update-ref refs/heads/dgit-view HEAD);
3341 runcmd @git, qw(checkout -q dgit-view);
3343 if ($quilt_mode =~ m/gbp|dpm/ &&
3344 ($diffbits->{O2A} & 02)) {
3346 --quilt=$quilt_mode specified, implying that HEAD is for use with a
3347 tool which does not create patches for changes to upstream
3348 .gitignores: but, such patches exist in debian/patches.
3351 if (($diffbits->{H2O} & 02) && # user has modified .gitignore
3352 !($diffbits->{O2A} & 02)) { # patches do not change .gitignore
3353 quiltify_splitbrain_needed();
3354 progress "dgit view: creating patch to represent .gitignore changes";
3355 ensuredir "debian/patches";
3356 my $gipatch = "debian/patches/auto-gitignore";
3357 open GIPATCH, ">>", "$gipatch" or die "$gipatch: $!";
3358 stat GIPATCH or die "$gipatch: $!";
3359 fail "$gipatch already exists; but want to create it".
3360 " to record .gitignore changes" if (stat _)[7];
3361 print GIPATCH <<END or die "$gipatch: $!";
3362 Subject: Update .gitignore from Debian packaging branch
3364 The Debian packaging git branch contains these updates to the upstream
3365 .gitignore file(s). This patch is autogenerated, to provide these
3366 updates to users of the official Debian archive view of the package.
3368 [dgit version $our_version]
3371 close GIPATCH or die "$gipatch: $!";
3372 runcmd shell_cmd "exec >>$gipatch", @git, qw(diff),
3373 $unapplied, $headref, "--", sort keys %$editedignores;
3374 open SERIES, "+>>", "debian/patches/series" or die $!;
3375 defined seek SERIES, -1, 2 or $!==EINVAL or die $!;
3377 defined read SERIES, $newline, 1 or die $!;
3378 print SERIES "\n" or die $! unless $newline eq "\n";
3379 print SERIES "auto-gitignore\n" or die $!;
3380 close SERIES or die $!;
3381 runcmd @git, qw(add -- debian/patches/series), $gipatch;
3382 commit_admin "Commit patch to update .gitignore";
3385 my $dgitview = git_rev_parse 'refs/heads/dgit-view';
3387 changedir '../../../..';
3388 ensuredir ".git/logs/refs/dgit-intern";
3389 my $makelogfh = new IO::File ".git/logs/refs/$splitbraincache", '>>'
3391 runcmd @git, qw(update-ref -m), $cachekey, "refs/$splitbraincache",
3394 progress "dgit view: created (commit id $dgitview)";
3396 changedir '.git/dgit/unpack/work';
3399 sub quiltify ($$$$) {
3400 my ($clogp,$target,$oldtiptree,$failsuggestion) = @_;
3402 # Quilt patchification algorithm
3404 # We search backwards through the history of the main tree's HEAD
3405 # (T) looking for a start commit S whose tree object is identical
3406 # to to the patch tip tree (ie the tree corresponding to the
3407 # current dpkg-committed patch series). For these purposes
3408 # `identical' disregards anything in debian/ - this wrinkle is
3409 # necessary because dpkg-source treates debian/ specially.
3411 # We can only traverse edges where at most one of the ancestors'
3412 # trees differs (in changes outside in debian/). And we cannot
3413 # handle edges which change .pc/ or debian/patches. To avoid
3414 # going down a rathole we avoid traversing edges which introduce
3415 # debian/rules or debian/control. And we set a limit on the
3416 # number of edges we are willing to look at.
3418 # If we succeed, we walk forwards again. For each traversed edge
3419 # PC (with P parent, C child) (starting with P=S and ending with
3420 # C=T) to we do this:
3422 # - dpkg-source --commit with a patch name and message derived from C
3423 # After traversing PT, we git commit the changes which
3424 # should be contained within debian/patches.
3426 # The search for the path S..T is breadth-first. We maintain a
3427 # todo list containing search nodes. A search node identifies a
3428 # commit, and looks something like this:
3430 # Commit => $git_commit_id,
3431 # Child => $c, # or undef if P=T
3432 # Whynot => $reason_edge_PC_unsuitable, # in @nots only
3433 # Nontrivial => true iff $p..$c has relevant changes
3440 my %considered; # saves being exponential on some weird graphs
3442 my $t_sentinels = quiltify_tree_sentinelfiles $target;
3445 my ($search,$whynot) = @_;
3446 printdebug " search NOT $search->{Commit} $whynot\n";
3447 $search->{Whynot} = $whynot;
3448 push @nots, $search;
3449 no warnings qw(exiting);
3458 my $c = shift @todo;
3459 next if $considered{$c->{Commit}}++;
3461 $not->($c, "maximum search space exceeded") if --$max_work <= 0;
3463 printdebug "quiltify investigate $c->{Commit}\n";
3466 if (!quiltify_trees_differ $c->{Commit}, $oldtiptree) {
3467 printdebug " search finished hooray!\n";
3472 if ($quilt_mode eq 'nofix') {
3473 fail "quilt fixup required but quilt mode is \`nofix'\n".
3474 "HEAD commit $c->{Commit} differs from tree implied by ".
3475 " debian/patches (tree object $oldtiptree)";
3477 if ($quilt_mode eq 'smash') {
3478 printdebug " search quitting smash\n";
3482 my $c_sentinels = quiltify_tree_sentinelfiles $c->{Commit};
3483 $not->($c, "has $c_sentinels not $t_sentinels")
3484 if $c_sentinels ne $t_sentinels;
3486 my $commitdata = cmdoutput @git, qw(cat-file commit), $c->{Commit};
3487 $commitdata =~ m/\n\n/;
3489 my @parents = ($commitdata =~ m/^parent (\w+)$/gm);
3490 @parents = map { { Commit => $_, Child => $c } } @parents;
3492 $not->($c, "root commit") if !@parents;
3494 foreach my $p (@parents) {
3495 $p->{Nontrivial}= quiltify_trees_differ $p->{Commit},$c->{Commit};
3497 my $ndiffers = grep { $_->{Nontrivial} } @parents;
3498 $not->($c, "merge ($ndiffers nontrivial parents)") if $ndiffers > 1;
3500 foreach my $p (@parents) {
3501 printdebug "considering C=$c->{Commit} P=$p->{Commit}\n";
3503 my @cmd= (@git, qw(diff-tree -r --name-only),
3504 $p->{Commit},$c->{Commit}, qw(-- debian/patches .pc));
3505 my $patchstackchange = cmdoutput @cmd;
3506 if (length $patchstackchange) {
3507 $patchstackchange =~ s/\n/,/g;
3508 $not->($p, "changed $patchstackchange");
3511 printdebug " search queue P=$p->{Commit} ",
3512 ($p->{Nontrivial} ? "NT" : "triv"),"\n";
3518 printdebug "quiltify want to smash\n";
3521 my $x = $_[0]{Commit};
3522 $x =~ s/(.*?[0-9a-z]{8})[0-9a-z]*$/$1/;
3525 my $reportnot = sub {
3527 my $s = $abbrev->($notp);
3528 my $c = $notp->{Child};
3529 $s .= "..".$abbrev->($c) if $c;
3530 $s .= ": ".$notp->{Whynot};
3533 if ($quilt_mode eq 'linear') {
3534 print STDERR "$us: quilt fixup cannot be linear. Stopped at:\n";
3535 foreach my $notp (@nots) {
3536 print STDERR "$us: ", $reportnot->($notp), "\n";
3538 print STDERR "$us: $_\n" foreach @$failsuggestion;
3539 fail "quilt fixup naive history linearisation failed.\n".
3540 "Use dpkg-source --commit by hand; or, --quilt=smash for one ugly patch";
3541 } elsif ($quilt_mode eq 'smash') {
3542 } elsif ($quilt_mode eq 'auto') {
3543 progress "quilt fixup cannot be linear, smashing...";
3545 die "$quilt_mode ?";
3548 my $time = $ENV{'GIT_COMMITTER_DATE'} || time;
3549 $time =~ s/\s.*//; # trim timezone from GIT_COMMITTER_DATE
3551 my $msg = cmdoutput @git, qw(log), "-n$ncommits";
3553 quiltify_dpkg_commit "auto-$version-$target-$time",
3554 (getfield $clogp, 'Maintainer'),
3555 "Automatically generated patch ($clogp->{Version})\n".
3556 "Last (up to) $ncommits git changes, FYI:\n\n". $msg;
3560 progress "quiltify linearisation planning successful, executing...";
3562 for (my $p = $sref_S;
3563 my $c = $p->{Child};
3565 printdebug "quiltify traverse $p->{Commit}..$c->{Commit}\n";
3566 next unless $p->{Nontrivial};
3568 my $cc = $c->{Commit};
3570 my $commitdata = cmdoutput @git, qw(cat-file commit), $cc;
3571 $commitdata =~ m/\n\n/ or die "$c ?";
3574 $commitdata =~ m/^author (.*) \d+ [-+0-9]+$/m or die "$cc ?";
3577 $msg =~ s/^(.*)\n*/$1\n/ or die "$cc $msg ?";
3580 my $patchname = $title;
3581 $patchname =~ s/[.:]$//;
3582 $patchname =~ y/ A-Z/-a-z/;
3583 $patchname =~ y/-a-z0-9_.+=~//cd;
3584 $patchname =~ s/^\W/x-$&/;
3585 $patchname = substr($patchname,0,40);
3588 stat "debian/patches/$patchname$index";
3590 $!==ENOENT or die "$patchname$index $!";
3592 runcmd @git, qw(checkout -q), $cc;
3594 # We use the tip's changelog so that dpkg-source doesn't
3595 # produce complaining messages from dpkg-parsechangelog. None
3596 # of the information dpkg-source gets from the changelog is
3597 # actually relevant - it gets put into the original message
3598 # which dpkg-source provides our stunt editor, and then
3600 runcmd @git, qw(checkout -q), $target, qw(debian/changelog);
3602 quiltify_dpkg_commit "$patchname$index", $author, $msg,
3603 "X-Dgit-Generated: $clogp->{Version} $cc\n";
3605 runcmd @git, qw(checkout -q), $cc, qw(debian/changelog);
3608 runcmd @git, qw(checkout -q master);
3611 sub build_maybe_quilt_fixup () {
3612 my ($format,$fopts) = get_source_format;
3613 return unless madformat $format;
3616 check_for_vendor_patches();
3618 my $clogp = parsechangelog();
3619 my $headref = git_rev_parse('HEAD');
3624 my $upstreamversion=$version;
3625 $upstreamversion =~ s/-[^-]*$//;
3627 if ($fopts->{'single-debian-patch'}) {
3628 quilt_fixup_singlepatch($clogp, $headref, $upstreamversion);
3630 quilt_fixup_multipatch($clogp, $headref, $upstreamversion);
3633 die 'bug' if $split_brain && !$need_split_build_invocation;
3635 changedir '../../../..';
3636 runcmd_ordryrun_local
3637 @git, qw(pull --ff-only -q .git/dgit/unpack/work master);
3640 sub quilt_fixup_mkwork ($) {
3643 mkdir "work" or die $!;
3645 mktree_in_ud_here();
3646 runcmd @git, qw(reset -q --hard), $headref;
3649 sub quilt_fixup_linkorigs ($$) {
3650 my ($upstreamversion, $fn) = @_;
3651 # calls $fn->($leafname);
3653 foreach my $f (<../../../../*>) { #/){
3654 my $b=$f; $b =~ s{.*/}{};
3656 local ($debuglevel) = $debuglevel-1;
3657 printdebug "QF linkorigs $b, $f ?\n";
3659 next unless is_orig_file $b, srcfn $upstreamversion,'';
3660 printdebug "QF linkorigs $b, $f Y\n";
3661 link_ltarget $f, $b or die "$b $!";
3666 sub quilt_fixup_delete_pc () {
3667 runcmd @git, qw(rm -rqf .pc);
3668 commit_admin "Commit removal of .pc (quilt series tracking data)";
3671 sub quilt_fixup_singlepatch ($$$) {
3672 my ($clogp, $headref, $upstreamversion) = @_;
3674 progress "starting quiltify (single-debian-patch)";
3676 # dpkg-source --commit generates new patches even if
3677 # single-debian-patch is in debian/source/options. In order to
3678 # get it to generate debian/patches/debian-changes, it is
3679 # necessary to build the source package.
3681 quilt_fixup_linkorigs($upstreamversion, sub { });
3682 quilt_fixup_mkwork($headref);
3684 rmtree("debian/patches");
3686 runcmd @dpkgsource, qw(-b .);
3688 runcmd @dpkgsource, qw(-x), (srcfn $version, ".dsc");
3689 rename srcfn("$upstreamversion", "/debian/patches"),
3690 "work/debian/patches";
3693 commit_quilty_patch();
3696 sub quilt_make_fake_dsc ($) {
3697 my ($upstreamversion) = @_;
3699 my $fakeversion="$upstreamversion-~~DGITFAKE";
3701 my $fakedsc=new IO::File 'fake.dsc', '>' or die $!;
3702 print $fakedsc <<END or die $!;
3705 Version: $fakeversion
3709 my $dscaddfile=sub {
3712 my $md = new Digest::MD5;
3714 my $fh = new IO::File $b, '<' or die "$b $!";
3719 print $fakedsc " ".$md->hexdigest." $size $b\n" or die $!;
3722 quilt_fixup_linkorigs($upstreamversion, $dscaddfile);
3724 my @files=qw(debian/source/format debian/rules
3725 debian/control debian/changelog);
3726 foreach my $maybe (qw(debian/patches debian/source/options
3727 debian/tests/control)) {
3728 next unless stat_exists "../../../$maybe";
3729 push @files, $maybe;
3732 my $debtar= srcfn $fakeversion,'.debian.tar.gz';
3733 runcmd qw(env GZIP=-1n tar -zcf), "./$debtar", qw(-C ../../..), @files;
3735 $dscaddfile->($debtar);
3736 close $fakedsc or die $!;
3739 sub quilt_check_splitbrain_cache ($$) {
3740 my ($headref, $upstreamversion) = @_;
3741 # Called only if we are in (potentially) split brain mode.
3743 # Computes the cache key and looks in the cache.
3744 # Returns ($dgit_view_commitid, $cachekey) or (undef, $cachekey)
3746 my $splitbrain_cachekey;
3749 "dgit: split brain (separate dgit view) may be needed (--quilt=$quilt_mode).";
3750 # we look in the reflog of dgit-intern/quilt-cache
3751 # we look for an entry whose message is the key for the cache lookup
3752 my @cachekey = (qw(dgit), $our_version);
3753 push @cachekey, $upstreamversion;
3754 push @cachekey, $quilt_mode;
3755 push @cachekey, $headref;
3757 push @cachekey, hashfile('fake.dsc');
3759 my $srcshash = Digest::SHA->new(256);
3760 my %sfs = ( %INC, '$0(dgit)' => $0 );
3761 foreach my $sfk (sort keys %sfs) {
3762 next unless m/^\$0\b/ || m{^Debian/Dgit\b};
3763 $srcshash->add($sfk," ");
3764 $srcshash->add(hashfile($sfs{$sfk}));
3765 $srcshash->add("\n");
3767 push @cachekey, $srcshash->hexdigest();
3768 $splitbrain_cachekey = "@cachekey";
3770 my @cmd = (@git, qw(reflog), '--pretty=format:%H %gs',
3772 printdebug "splitbrain cachekey $splitbrain_cachekey\n";
3773 debugcmd "|(probably)",@cmd;
3774 my $child = open GC, "-|"; defined $child or die $!;
3776 chdir '../../..' or die $!;
3777 if (!stat ".git/logs/refs/$splitbraincache") {
3778 $! == ENOENT or die $!;
3779 printdebug ">(no reflog)\n";
3786 printdebug ">| ", $_, "\n" if $debuglevel > 1;
3787 next unless m/^(\w+) (\S.*\S)$/ && $2 eq $splitbrain_cachekey;
3790 quilt_fixup_mkwork($headref);
3791 if ($cachehit ne $headref) {
3792 progress "dgit view: found cached (commit id $cachehit)";
3793 runcmd @git, qw(checkout -q -b dgit-view), $cachehit;
3795 return ($cachehit, $splitbrain_cachekey);
3797 progress "dgit view: found cached, no changes required";
3798 return ($headref, $splitbrain_cachekey);
3800 die $! if GC->error;
3801 failedcmd unless close GC;
3803 printdebug "splitbrain cache miss\n";
3804 return (undef, $splitbrain_cachekey);
3807 sub quilt_fixup_multipatch ($$$) {
3808 my ($clogp, $headref, $upstreamversion) = @_;
3810 progress "examining quilt state (multiple patches, $quilt_mode mode)";
3813 # - honour any existing .pc in case it has any strangeness
3814 # - determine the git commit corresponding to the tip of
3815 # the patch stack (if there is one)
3816 # - if there is such a git commit, convert each subsequent
3817 # git commit into a quilt patch with dpkg-source --commit
3818 # - otherwise convert all the differences in the tree into
3819 # a single git commit
3823 # Our git tree doesn't necessarily contain .pc. (Some versions of
3824 # dgit would include the .pc in the git tree.) If there isn't
3825 # one, we need to generate one by unpacking the patches that we
3828 # We first look for a .pc in the git tree. If there is one, we
3829 # will use it. (This is not the normal case.)
3831 # Otherwise need to regenerate .pc so that dpkg-source --commit
3832 # can work. We do this as follows:
3833 # 1. Collect all relevant .orig from parent directory
3834 # 2. Generate a debian.tar.gz out of
3835 # debian/{patches,rules,source/format,source/options}
3836 # 3. Generate a fake .dsc containing just these fields:
3837 # Format Source Version Files
3838 # 4. Extract the fake .dsc
3839 # Now the fake .dsc has a .pc directory.
3840 # (In fact we do this in every case, because in future we will
3841 # want to search for a good base commit for generating patches.)
3843 # Then we can actually do the dpkg-source --commit
3844 # 1. Make a new working tree with the same object
3845 # store as our main tree and check out the main
3847 # 2. Copy .pc from the fake's extraction, if necessary
3848 # 3. Run dpkg-source --commit
3849 # 4. If the result has changes to debian/, then
3850 # - git-add them them
3851 # - git-add .pc if we had a .pc in-tree
3853 # 5. If we had a .pc in-tree, delete it, and git-commit
3854 # 6. Back in the main tree, fast forward to the new HEAD
3856 # Another situation we may have to cope with is gbp-style
3857 # patches-unapplied trees.
3859 # We would want to detect these, so we know to escape into
3860 # quilt_fixup_gbp. However, this is in general not possible.
3861 # Consider a package with a one patch which the dgit user reverts
3862 # (with git-revert or the moral equivalent).
3864 # That is indistinguishable in contents from a patches-unapplied
3865 # tree. And looking at the history to distinguish them is not
3866 # useful because the user might have made a confusing-looking git
3867 # history structure (which ought to produce an error if dgit can't
3868 # cope, not a silent reintroduction of an unwanted patch).
3870 # So gbp users will have to pass an option. But we can usually
3871 # detect their failure to do so: if the tree is not a clean
3872 # patches-applied tree, quilt linearisation fails, but the tree
3873 # _is_ a clean patches-unapplied tree, we can suggest that maybe
3874 # they want --quilt=unapplied.
3876 # To help detect this, when we are extracting the fake dsc, we
3877 # first extract it with --skip-patches, and then apply the patches
3878 # afterwards with dpkg-source --before-build. That lets us save a
3879 # tree object corresponding to .origs.
3881 my $splitbrain_cachekey;
3883 quilt_make_fake_dsc($upstreamversion);
3885 if (quiltmode_splitbrain()) {
3887 ($cachehit, $splitbrain_cachekey) =
3888 quilt_check_splitbrain_cache($headref, $upstreamversion);
3889 return if $cachehit;
3893 'exec dpkg-source --no-check --skip-patches -x fake.dsc >/dev/null';
3895 my $fakexdir= $package.'-'.(stripepoch $upstreamversion);
3896 rename $fakexdir, "fake" or die "$fakexdir $!";
3900 remove_stray_gits();
3901 mktree_in_ud_here();
3905 runcmd @git, qw(add -Af .);
3906 my $unapplied=git_write_tree();
3907 printdebug "fake orig tree object $unapplied\n";
3912 'exec dpkg-source --before-build . >/dev/null';
3916 quilt_fixup_mkwork($headref);
3919 if (stat_exists ".pc") {
3921 progress "Tree already contains .pc - will use it then delete it.";
3924 rename '../fake/.pc','.pc' or die $!;
3927 changedir '../fake';
3929 runcmd @git, qw(add -Af .);
3930 my $oldtiptree=git_write_tree();
3931 printdebug "fake o+d/p tree object $unapplied\n";
3932 changedir '../work';
3935 # We calculate some guesswork now about what kind of tree this might
3936 # be. This is mostly for error reporting.
3941 # O = orig, without patches applied
3942 # A = "applied", ie orig with H's debian/patches applied
3943 H2O => quiltify_trees_differ($headref, $unapplied, 1,\%editedignores),
3944 H2A => quiltify_trees_differ($headref, $oldtiptree,1),
3945 O2A => quiltify_trees_differ($unapplied,$oldtiptree,1),
3949 foreach my $b (qw(01 02)) {
3950 foreach my $v (qw(H2O O2A H2A)) {
3951 push @dl, ($diffbits->{$v} & $b) ? '##' : '==';
3954 printdebug "differences \@dl @dl.\n";
3957 "$us: quilt differences: src: %s orig %s gitignores: %s orig %s\n".
3958 "$us: quilt differences: HEAD %s o+d/p HEAD %s o+d/p",
3959 $dl[0], $dl[1], $dl[3], $dl[4],
3963 if (!($diffbits->{H2O} & $diffbits->{O2A})) {
3964 push @failsuggestion, "This might be a patches-unapplied branch.";
3965 } elsif (!($diffbits->{H2A} & $diffbits->{O2A})) {
3966 push @failsuggestion, "This might be a patches-applied branch.";
3968 push @failsuggestion, "Maybe you need to specify one of".
3969 " --quilt=gbp --quilt=dpm --quilt=unapplied ?";
3971 if (quiltmode_splitbrain()) {
3972 quiltify_splitbrain($clogp, $unapplied, $headref,
3973 $diffbits, \%editedignores,
3974 $splitbrain_cachekey);
3978 progress "starting quiltify (multiple patches, $quilt_mode mode)";
3979 quiltify($clogp,$headref,$oldtiptree,\@failsuggestion);
3981 if (!open P, '>>', ".pc/applied-patches") {
3982 $!==&ENOENT or die $!;
3987 commit_quilty_patch();
3989 if ($mustdeletepc) {
3990 quilt_fixup_delete_pc();
3994 sub quilt_fixup_editor () {
3995 my $descfn = $ENV{$fakeeditorenv};
3996 my $editing = $ARGV[$#ARGV];
3997 open I1, '<', $descfn or die "$descfn: $!";
3998 open I2, '<', $editing or die "$editing: $!";
3999 unlink $editing or die "$editing: $!";
4000 open O, '>', $editing or die "$editing: $!";
4001 while (<I1>) { print O or die $!; } I1->error and die $!;
4004 $copying ||= m/^\-\-\- /;
4005 next unless $copying;
4008 I2->error and die $!;
4013 sub maybe_apply_patches_dirtily () {
4014 return unless $quilt_mode =~ m/gbp|unapplied/;
4015 print STDERR <<END or die $!;
4017 dgit: Building, or cleaning with rules target, in patches-unapplied tree.
4018 dgit: Have to apply the patches - making the tree dirty.
4019 dgit: (Consider specifying --clean=git and (or) using dgit sbuild.)
4022 $patches_applied_dirtily = 01;
4023 $patches_applied_dirtily |= 02 unless stat_exists '.pc';
4024 runcmd qw(dpkg-source --before-build .);
4027 sub maybe_unapply_patches_again () {
4028 progress "dgit: Unapplying patches again to tidy up the tree."
4029 if $patches_applied_dirtily;
4030 runcmd qw(dpkg-source --after-build .)
4031 if $patches_applied_dirtily & 01;
4033 if $patches_applied_dirtily & 02;
4034 $patches_applied_dirtily = 0;
4037 #----- other building -----
4039 our $clean_using_builder;
4040 # ^ tree is to be cleaned by dpkg-source's builtin idea that it should
4041 # clean the tree before building (perhaps invoked indirectly by
4042 # whatever we are using to run the build), rather than separately
4043 # and explicitly by us.
4046 return if $clean_using_builder;
4047 if ($cleanmode eq 'dpkg-source') {
4048 maybe_apply_patches_dirtily();
4049 runcmd_ordryrun_local @dpkgbuildpackage, qw(-T clean);
4050 } elsif ($cleanmode eq 'dpkg-source-d') {
4051 maybe_apply_patches_dirtily();
4052 runcmd_ordryrun_local @dpkgbuildpackage, qw(-d -T clean);
4053 } elsif ($cleanmode eq 'git') {
4054 runcmd_ordryrun_local @git, qw(clean -xdf);
4055 } elsif ($cleanmode eq 'git-ff') {
4056 runcmd_ordryrun_local @git, qw(clean -xdff);
4057 } elsif ($cleanmode eq 'check') {
4058 my $leftovers = cmdoutput @git, qw(clean -xdn);
4059 if (length $leftovers) {
4060 print STDERR $leftovers, "\n" or die $!;
4061 fail "tree contains uncommitted files and --clean=check specified";
4063 } elsif ($cleanmode eq 'none') {
4070 badusage "clean takes no additional arguments" if @ARGV;
4073 maybe_unapply_patches_again();
4078 badusage "-p is not allowed when building" if defined $package;
4081 my $clogp = parsechangelog();
4082 $isuite = getfield $clogp, 'Distribution';
4083 $package = getfield $clogp, 'Source';
4084 $version = getfield $clogp, 'Version';
4085 build_maybe_quilt_fixup();
4087 my $pat = changespat $version;
4088 foreach my $f (glob "$buildproductsdir/$pat") {
4090 unlink $f or fail "remove old changes file $f: $!";
4092 progress "would remove $f";
4098 sub changesopts_initial () {
4099 my @opts =@changesopts[1..$#changesopts];
4102 sub changesopts_version () {
4103 if (!defined $changes_since_version) {
4104 my @vsns = archive_query('archive_query');
4105 my @quirk = access_quirk();
4106 if ($quirk[0] eq 'backports') {
4107 local $isuite = $quirk[2];
4109 canonicalise_suite();
4110 push @vsns, archive_query('archive_query');
4113 @vsns = map { $_->[0] } @vsns;
4114 @vsns = sort { -version_compare($a, $b) } @vsns;
4115 $changes_since_version = $vsns[0];
4116 progress "changelog will contain changes since $vsns[0]";
4118 $changes_since_version = '_';
4119 progress "package seems new, not specifying -v<version>";
4122 if ($changes_since_version ne '_') {
4123 return ("-v$changes_since_version");
4129 sub changesopts () {
4130 return (changesopts_initial(), changesopts_version());
4133 sub massage_dbp_args ($;$) {
4134 my ($cmd,$xargs) = @_;
4137 # - if we're going to split the source build out so we can
4138 # do strange things to it, massage the arguments to dpkg-buildpackage
4139 # so that the main build doessn't build source (or add an argument
4140 # to stop it building source by default).
4142 # - add -nc to stop dpkg-source cleaning the source tree,
4143 # unless we're not doing a split build and want dpkg-source
4144 # as cleanmode, in which case we can do nothing
4147 # 0 - source will NOT need to be built separately by caller
4148 # +1 - source will need to be built separately by caller
4149 # +2 - source will need to be built separately by caller AND
4150 # dpkg-buildpackage should not in fact be run at all!
4151 debugcmd '#massaging#', @$cmd if $debuglevel>1;
4152 #print STDERR "MASS0 ",Dumper($cmd, $xargs, $need_split_build_invocation);
4153 if ($cleanmode eq 'dpkg-source' && !$need_split_build_invocation) {
4154 $clean_using_builder = 1;
4157 # -nc has the side effect of specifying -b if nothing else specified
4158 # and some combinations of -S, -b, et al, are errors, rather than
4159 # later simply overriding earlie. So we need to:
4160 # - search the command line for these options
4161 # - pick the last one
4162 # - perhaps add our own as a default
4163 # - perhaps adjust it to the corresponding non-source-building version
4165 foreach my $l ($cmd, $xargs) {
4167 @$l = grep { !(m/^-[SgGFABb]$/s and $dmode=$_) } @$l;
4170 #print STDERR "MASS1 ",Dumper($cmd, $xargs, $dmode);
4172 if ($need_split_build_invocation) {
4173 printdebug "massage split $dmode.\n";
4174 $r = $dmode =~ m/[S]/ ? +2 :
4175 $dmode =~ y/gGF/ABb/ ? +1 :
4176 $dmode =~ m/[ABb]/ ? 0 :
4179 printdebug "massage done $r $dmode.\n";
4181 #print STDERR "MASS2 ",Dumper($cmd, $xargs, $r);
4186 my @dbp = (@dpkgbuildpackage, qw(-us -uc), changesopts_initial(), @ARGV);
4187 my $wantsrc = massage_dbp_args \@dbp;
4194 push @dbp, changesopts_version();
4195 maybe_apply_patches_dirtily();
4196 runcmd_ordryrun_local @dbp;
4198 maybe_unapply_patches_again();
4199 printdone "build successful\n";
4203 my @dbp = @dpkgbuildpackage;
4205 my $wantsrc = massage_dbp_args \@dbp, \@ARGV;
4208 if (length executable_on_path('git-buildpackage')) {
4209 @cmd = qw(git-buildpackage);
4211 @cmd = qw(gbp buildpackage);
4213 push @cmd, (qw(-us -uc --git-no-sign-tags), "--git-builder=@dbp");
4218 if (!$clean_using_builder) {
4219 push @cmd, '--git-cleaner=true';
4223 maybe_unapply_patches_again();
4225 unless (grep { m/^--git-debian-branch|^--git-ignore-branch/ } @ARGV) {
4226 canonicalise_suite();
4227 push @cmd, "--git-debian-branch=".lbranch();
4229 push @cmd, changesopts();
4230 runcmd_ordryrun_local @cmd, @ARGV;
4232 printdone "build successful\n";
4234 sub cmd_git_build { cmd_gbp_build(); } # compatibility with <= 1.0
4237 my $our_cleanmode = $cleanmode;
4238 if ($need_split_build_invocation) {
4239 # Pretend that clean is being done some other way. This
4240 # forces us not to try to use dpkg-buildpackage to clean and
4241 # build source all in one go; and instead we run dpkg-source
4242 # (and build_prep() will do the clean since $clean_using_builder
4244 $our_cleanmode = 'ELSEWHERE';
4246 if ($our_cleanmode =~ m/^dpkg-source/) {
4247 # dpkg-source invocation (below) will clean, so build_prep shouldn't
4248 $clean_using_builder = 1;
4251 $sourcechanges = changespat $version,'source';
4253 unlink "../$sourcechanges" or $!==ENOENT
4254 or fail "remove $sourcechanges: $!";
4256 $dscfn = dscfn($version);
4257 if ($our_cleanmode eq 'dpkg-source') {
4258 maybe_apply_patches_dirtily();
4259 runcmd_ordryrun_local @dpkgbuildpackage, qw(-us -uc -S),
4261 } elsif ($our_cleanmode eq 'dpkg-source-d') {
4262 maybe_apply_patches_dirtily();
4263 runcmd_ordryrun_local @dpkgbuildpackage, qw(-us -uc -S -d),
4266 my @cmd = (@dpkgsource, qw(-b --));
4269 runcmd_ordryrun_local @cmd, "work";
4270 my @udfiles = <${package}_*>;
4271 changedir "../../..";
4272 foreach my $f (@udfiles) {
4273 printdebug "source copy, found $f\n";
4276 ($f =~ m/\.debian\.tar(?:\.\w+)$/ &&
4277 $f eq srcfn($version, $&));
4278 printdebug "source copy, found $f - renaming\n";
4279 rename "$ud/$f", "../$f" or $!==ENOENT
4280 or fail "put in place new source file ($f): $!";
4283 my $pwd = must_getcwd();
4284 my $leafdir = basename $pwd;
4286 runcmd_ordryrun_local @cmd, $leafdir;
4289 runcmd_ordryrun_local qw(sh -ec),
4290 'exec >$1; shift; exec "$@"','x',
4291 "../$sourcechanges",
4292 @dpkggenchanges, qw(-S), changesopts();
4296 sub cmd_build_source {
4297 badusage "build-source takes no additional arguments" if @ARGV;
4299 maybe_unapply_patches_again();
4300 printdone "source built, results in $dscfn and $sourcechanges";
4305 my $pat = changespat $version;
4307 my @unwanted = map { s#^\.\./##; $_; } glob "../$pat";
4308 @unwanted = grep { $_ ne changespat $version,'source' } @unwanted;
4309 fail "changes files other than source matching $pat".
4310 " already present (@unwanted);".
4311 " building would result in ambiguity about the intended results"
4314 my $wasdir = must_getcwd();
4317 stat_exists $dscfn or fail "$dscfn (in parent directory): $!";
4318 stat_exists $sourcechanges
4319 or fail "$sourcechanges (in parent directory): $!";
4321 runcmd_ordryrun_local @sbuild, qw(-d), $isuite, @ARGV, $dscfn;
4322 my @changesfiles = glob $pat;
4323 @changesfiles = sort {
4324 ($b =~ m/_source\.changes$/ <=> $a =~ m/_source\.changes$/)
4327 fail "wrong number of different changes files (@changesfiles)"
4328 unless @changesfiles==2;
4329 my $binchanges = parsecontrol($changesfiles[1], "binary changes file");
4330 foreach my $l (split /\n/, getfield $binchanges, 'Files') {
4331 fail "$l found in binaries changes file $binchanges"
4334 runcmd_ordryrun_local @mergechanges, @changesfiles;
4335 my $multichanges = changespat $version,'multi';
4337 stat_exists $multichanges or fail "$multichanges: $!";
4338 foreach my $cf (glob $pat) {
4339 next if $cf eq $multichanges;
4340 rename "$cf", "$cf.inmulti" or fail "$cf\{,.inmulti}: $!";
4344 maybe_unapply_patches_again();
4345 printdone "build successful, results in $multichanges\n" or die $!;
4348 sub cmd_quilt_fixup {
4349 badusage "incorrect arguments to dgit quilt-fixup" if @ARGV;
4350 my $clogp = parsechangelog();
4351 $version = getfield $clogp, 'Version';
4352 $package = getfield $clogp, 'Source';
4355 build_maybe_quilt_fixup();
4358 sub cmd_archive_api_query {
4359 badusage "need only 1 subpath argument" unless @ARGV==1;
4360 my ($subpath) = @ARGV;
4361 my @cmd = archive_api_query_cmd($subpath);
4363 exec @cmd or fail "exec curl: $!\n";
4366 sub cmd_clone_dgit_repos_server {
4367 badusage "need destination argument" unless @ARGV==1;
4368 my ($destdir) = @ARGV;
4369 $package = '_dgit-repos-server';
4370 my @cmd = (@git, qw(clone), access_giturl(), $destdir);
4372 exec @cmd or fail "exec git clone: $!\n";
4375 sub cmd_setup_mergechangelogs {
4376 badusage "no arguments allowed to dgit setup-mergechangelogs" if @ARGV;
4377 setup_mergechangelogs(1);
4380 sub cmd_setup_useremail {
4381 badusage "no arguments allowed to dgit setup-mergechangelogs" if @ARGV;
4385 sub cmd_setup_new_tree {
4386 badusage "no arguments allowed to dgit setup-tree" if @ARGV;
4390 #---------- argument parsing and main program ----------
4393 print "dgit version $our_version\n" or die $!;
4397 our (%valopts_long, %valopts_short);
4400 sub defvalopt ($$$$) {
4401 my ($long,$short,$val_re,$how) = @_;
4402 my $oi = { Long => $long, Short => $short, Re => $val_re, How => $how };
4403 $valopts_long{$long} = $oi;
4404 $valopts_short{$short} = $oi;
4405 # $how subref should:
4406 # do whatever assignemnt or thing it likes with $_[0]
4407 # if the option should not be passed on to remote, @rvalopts=()
4408 # or $how can be a scalar ref, meaning simply assign the value
4411 defvalopt '--since-version', '-v', '[^_]+|_', \$changes_since_version;
4412 defvalopt '--distro', '-d', '.+', \$idistro;
4413 defvalopt '', '-k', '.+', \$keyid;
4414 defvalopt '--existing-package','', '.*', \$existing_package;
4415 defvalopt '--build-products-dir','','.*', \$buildproductsdir;
4416 defvalopt '--clean', '', $cleanmode_re, \$cleanmode;
4417 defvalopt '--quilt', '', $quilt_modes_re, \$quilt_mode;
4419 defvalopt '', '-c', '.*=.*', sub { push @git, '-c', @_; };
4421 defvalopt '', '-C', '.+', sub {
4422 ($changesfile) = (@_);
4423 if ($changesfile =~ s#^(.*)/##) {
4424 $buildproductsdir = $1;
4428 defvalopt '--initiator-tempdir','','.*', sub {
4429 ($initiator_tempdir) = (@_);
4430 $initiator_tempdir =~ m#^/# or
4431 badusage "--initiator-tempdir must be used specify an".
4432 " absolute, not relative, directory."
4438 if (defined $ENV{'DGIT_SSH'}) {
4439 @ssh = string_to_ssh $ENV{'DGIT_SSH'};
4440 } elsif (defined $ENV{'GIT_SSH'}) {
4441 @ssh = ($ENV{'GIT_SSH'});
4449 if (!defined $val) {
4450 badusage "$what needs a value" unless @ARGV;
4452 push @rvalopts, $val;
4454 badusage "bad value \`$val' for $what" unless
4455 $val =~ m/^$oi->{Re}$(?!\n)/s;
4456 my $how = $oi->{How};
4457 if (ref($how) eq 'SCALAR') {
4462 push @ropts, @rvalopts;
4466 last unless $ARGV[0] =~ m/^-/;
4470 if (m/^--dry-run$/) {
4473 } elsif (m/^--damp-run$/) {
4476 } elsif (m/^--no-sign$/) {
4479 } elsif (m/^--help$/) {
4481 } elsif (m/^--version$/) {
4483 } elsif (m/^--new$/) {
4486 } elsif (m/^--([-0-9a-z]+)=(.+)/s &&
4487 ($om = $opts_opt_map{$1}) &&
4491 } elsif (m/^--([-0-9a-z]+):(.*)/s &&
4492 !$opts_opt_cmdonly{$1} &&
4493 ($om = $opts_opt_map{$1})) {
4496 } elsif (m/^--ignore-dirty$/s) {
4499 } elsif (m/^--no-quilt-fixup$/s) {
4501 $quilt_mode = 'nocheck';
4502 } elsif (m/^--no-rm-on-error$/s) {
4505 } elsif (m/^--(no-)?rm-old-changes$/s) {
4508 } elsif (m/^--deliberately-($deliberately_re)$/s) {
4510 push @deliberatelies, $&;
4511 } elsif (m/^--dgit-tag-format=(old|new)$/s) {
4512 # undocumented, for testing
4514 $tagformat_want = [ $1, 'command line', 1 ];
4515 # 1 menas overrides distro configuration
4516 } elsif (m/^--always-split-source-build$/s) {
4517 # undocumented, for testing
4519 $need_split_build_invocation = 1;
4520 } elsif (m/^(--[-0-9a-z]+)(=|$)/ && ($oi = $valopts_long{$1})) {
4521 $val = $2 ? $' : undef; #';
4522 $valopt->($oi->{Long});
4524 badusage "unknown long option \`$_'";
4531 } elsif (s/^-L/-/) {
4534 } elsif (s/^-h/-/) {
4536 } elsif (s/^-D/-/) {
4540 } elsif (s/^-N/-/) {
4545 push @changesopts, $_;
4547 } elsif (s/^-wn$//s) {
4549 $cleanmode = 'none';
4550 } elsif (s/^-wg$//s) {
4553 } elsif (s/^-wgf$//s) {
4555 $cleanmode = 'git-ff';
4556 } elsif (s/^-wd$//s) {
4558 $cleanmode = 'dpkg-source';
4559 } elsif (s/^-wdd$//s) {
4561 $cleanmode = 'dpkg-source-d';
4562 } elsif (s/^-wc$//s) {
4564 $cleanmode = 'check';
4565 } elsif (m/^-[a-zA-Z]/ && ($oi = $valopts_short{$&})) {
4567 $val = undef unless length $val;
4568 $valopt->($oi->{Short});
4571 badusage "unknown short option \`$_'";
4578 sub finalise_opts_opts () {
4579 foreach my $k (keys %opts_opt_map) {
4580 my $om = $opts_opt_map{$k};
4582 my $v = access_cfg("cmd-$k", 'RETURN-UNDEF');
4584 badcfg "cannot set command for $k"
4585 unless length $om->[0];
4589 foreach my $c (access_cfg_cfgs("opts-$k")) {
4590 my $vl = $gitcfg{$c};
4591 printdebug "CL $c ",
4592 ($vl ? join " ", map { shellquote } @$vl : ""),
4593 "\n" if $debuglevel >= 4;
4595 badcfg "cannot configure options for $k"
4596 if $opts_opt_cmdonly{$k};
4597 my $insertpos = $opts_cfg_insertpos{$k};
4598 @$om = ( @$om[0..$insertpos-1],
4600 @$om[$insertpos..$#$om] );
4605 if ($ENV{$fakeeditorenv}) {
4607 quilt_fixup_editor();
4613 print STDERR "DRY RUN ONLY\n" if $dryrun_level > 1;
4614 print STDERR "DAMP RUN - WILL MAKE LOCAL (UNSIGNED) CHANGES\n"
4615 if $dryrun_level == 1;
4617 print STDERR $helpmsg or die $!;
4620 my $cmd = shift @ARGV;
4623 if (!defined $rmchanges) {
4624 local $access_forpush;
4625 $rmchanges = access_cfg_bool(0, 'rm-old-changes');
4628 if (!defined $quilt_mode) {
4629 local $access_forpush;
4630 $quilt_mode = cfg('dgit.force.quilt-mode', 'RETURN-UNDEF')
4631 // access_cfg('quilt-mode', 'RETURN-UNDEF')
4633 $quilt_mode =~ m/^($quilt_modes_re)$/
4634 or badcfg "unknown quilt-mode \`$quilt_mode'";
4638 $need_split_build_invocation ||= quiltmode_splitbrain();
4640 if (!defined $cleanmode) {
4641 local $access_forpush;
4642 $cleanmode = access_cfg('clean-mode', 'RETURN-UNDEF');
4643 $cleanmode //= 'dpkg-source';
4645 badcfg "unknown clean-mode \`$cleanmode'" unless
4646 $cleanmode =~ m/^($cleanmode_re)$(?!\n)/s;
4649 my $fn = ${*::}{"cmd_$cmd"};
4650 $fn or badusage "unknown operation $cmd";