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);
40 our $our_version = 'UNRELEASED'; ###substituted###
42 our @rpushprotovsn_support = qw(3 2);
45 our $isuite = 'unstable';
51 our $dryrun_level = 0;
53 our $buildproductsdir = '..';
59 our $existing_package = 'dpkg';
61 our $changes_since_version;
64 our $quilt_modes_re = 'linear|smash|auto|nofix|nocheck|gbp|unapplied';
65 our $we_are_responder;
66 our $initiator_tempdir;
67 our $patches_applied_dirtily = 00;
69 our %format_ok = map { $_=>1 } ("1.0","3.0 (native)","3.0 (quilt)");
71 our $suite_re = '[-+.0-9a-z]+';
72 our $cleanmode_re = 'dpkg-source(?:-d)?|git|git-ff|check|none';
74 our $git_authline_re = '^([^<>]+) \<(\S+)\> (\d+ [-+]\d+)$';
75 our $splitbraincache = 'dgit-intern/quilt-cache';
78 our (@dget) = qw(dget);
79 our (@curl) = qw(curl -f);
80 our (@dput) = qw(dput);
81 our (@debsign) = qw(debsign);
83 our (@sbuild) = qw(sbuild);
85 our (@dgit) = qw(dgit);
86 our (@dpkgbuildpackage) = qw(dpkg-buildpackage -i\.git/ -I.git);
87 our (@dpkgsource) = qw(dpkg-source -i\.git/ -I.git);
88 our (@dpkggenchanges) = qw(dpkg-genchanges);
89 our (@mergechanges) = qw(mergechanges -f);
91 our (@changesopts) = ('');
93 our %opts_opt_map = ('dget' => \@dget, # accept for compatibility
96 'debsign' => \@debsign,
102 'dpkg-source' => \@dpkgsource,
103 'dpkg-buildpackage' => \@dpkgbuildpackage,
104 'dpkg-genchanges' => \@dpkggenchanges,
105 'ch' => \@changesopts,
106 'mergechanges' => \@mergechanges);
108 our %opts_opt_cmdonly = ('gpg' => 1, 'git' => 1);
109 our %opts_cfg_insertpos = map {
111 scalar @{ $opts_opt_map{$_} }
112 } keys %opts_opt_map;
114 sub finalise_opts_opts();
120 our $supplementary_message = '';
121 our $need_split_build_invocation = 0;
122 our $split_brain = 0;
126 print STDERR "! $_\n" foreach $supplementary_message =~ m/^.+$/mg;
129 our $remotename = 'dgit';
130 our @ourdscfield = qw(Dgit Vcs-Dgit-Master);
134 sub lbranch () { return "$branchprefix/$csuite"; }
135 my $lbranch_re = '^refs/heads/'.$branchprefix.'/([^/.]+)$';
136 sub lref () { return "refs/heads/".lbranch(); }
137 sub lrref () { return "refs/remotes/$remotename/".server_branch($csuite); }
138 sub rrref () { return server_ref($csuite); }
140 sub lrfetchrefs () { return "refs/dgit-fetch/$csuite"; }
150 return "${package}_".(stripepoch $vsn).$sfx
155 return srcfn($vsn,".dsc");
158 sub changespat ($;$) {
159 my ($vsn, $arch) = @_;
160 return "${package}_".(stripepoch $vsn)."_".($arch//'*').".changes";
169 foreach my $f (@end) {
171 print STDERR "$us: cleanup: $@" if length $@;
175 sub badcfg { print STDERR "$us: invalid configuration: @_\n"; exit 12; }
177 sub no_such_package () {
178 print STDERR "$us: package $package does not exist in suite $isuite\n";
184 return "+".rrref().":".lrref();
189 printdebug "CD $newdir\n";
190 chdir $newdir or die "chdir: $newdir: $!";
193 sub deliberately ($) {
195 return !!grep { $_ eq "--deliberately-$enquiry" } @deliberatelies;
198 sub deliberately_not_fast_forward () {
199 foreach (qw(not-fast-forward fresh-repo)) {
200 return 1 if deliberately($_) || deliberately("TEST-dgit-only-$_");
204 sub quiltmode_splitbrain () {
205 $quilt_mode =~ m/gbp|dpm|unapplied/;
208 #---------- remote protocol support, common ----------
210 # remote push initiator/responder protocol:
211 # $ dgit remote-push-build-host <n-rargs> <rargs>... <push-args>...
212 # where <rargs> is <push-host-dir> <supported-proto-vsn>,... ...
213 # < dgit-remote-push-ready <actual-proto-vsn>
215 # > file parsed-changelog
216 # [indicates that output of dpkg-parsechangelog follows]
217 # > data-block NBYTES
218 # > [NBYTES bytes of data (no newline)]
219 # [maybe some more blocks]
231 # [indicates that signed tag is wanted]
232 # < data-block NBYTES
233 # < [NBYTES bytes of data (no newline)]
234 # [maybe some more blocks]
238 # > want signed-dsc-changes
239 # < data-block NBYTES [transfer of signed dsc]
241 # < data-block NBYTES [transfer of signed changes]
249 sub i_child_report () {
250 # Sees if our child has died, and reap it if so. Returns a string
251 # describing how it died if it failed, or undef otherwise.
252 return undef unless $i_child_pid;
253 my $got = waitpid $i_child_pid, WNOHANG;
254 return undef if $got <= 0;
255 die unless $got == $i_child_pid;
256 $i_child_pid = undef;
257 return undef unless $?;
258 return "build host child ".waitstatusmsg();
263 fail "connection lost: $!" if $fh->error;
264 fail "protocol violation; $m not expected";
267 sub badproto_badread ($$) {
269 fail "connection lost: $!" if $!;
270 my $report = i_child_report();
271 fail $report if defined $report;
272 badproto $fh, "eof (reading $wh)";
275 sub protocol_expect (&$) {
276 my ($match, $fh) = @_;
279 defined && chomp or badproto_badread $fh, "protocol message";
287 badproto $fh, "\`$_'";
290 sub protocol_send_file ($$) {
291 my ($fh, $ourfn) = @_;
292 open PF, "<", $ourfn or die "$ourfn: $!";
295 my $got = read PF, $d, 65536;
296 die "$ourfn: $!" unless defined $got;
298 print $fh "data-block ".length($d)."\n" or die $!;
299 print $fh $d or die $!;
301 PF->error and die "$ourfn $!";
302 print $fh "data-end\n" or die $!;
306 sub protocol_read_bytes ($$) {
307 my ($fh, $nbytes) = @_;
308 $nbytes =~ m/^[1-9]\d{0,5}$|^0$/ or badproto \*RO, "bad byte count";
310 my $got = read $fh, $d, $nbytes;
311 $got==$nbytes or badproto_badread $fh, "data block";
315 sub protocol_receive_file ($$) {
316 my ($fh, $ourfn) = @_;
317 printdebug "() $ourfn\n";
318 open PF, ">", $ourfn or die "$ourfn: $!";
320 my ($y,$l) = protocol_expect {
321 m/^data-block (.*)$/ ? (1,$1) :
322 m/^data-end$/ ? (0,) :
326 my $d = protocol_read_bytes $fh, $l;
327 print PF $d or die $!;
332 #---------- remote protocol support, responder ----------
334 sub responder_send_command ($) {
336 return unless $we_are_responder;
337 # called even without $we_are_responder
338 printdebug ">> $command\n";
339 print PO $command, "\n" or die $!;
342 sub responder_send_file ($$) {
343 my ($keyword, $ourfn) = @_;
344 return unless $we_are_responder;
345 printdebug "]] $keyword $ourfn\n";
346 responder_send_command "file $keyword";
347 protocol_send_file \*PO, $ourfn;
350 sub responder_receive_files ($@) {
351 my ($keyword, @ourfns) = @_;
352 die unless $we_are_responder;
353 printdebug "[[ $keyword @ourfns\n";
354 responder_send_command "want $keyword";
355 foreach my $fn (@ourfns) {
356 protocol_receive_file \*PI, $fn;
359 protocol_expect { m/^files-end$/ } \*PI;
362 #---------- remote protocol support, initiator ----------
364 sub initiator_expect (&) {
366 protocol_expect { &$match } \*RO;
369 #---------- end remote code ----------
372 if ($we_are_responder) {
374 responder_send_command "progress ".length($m) or die $!;
375 print PO $m or die $!;
385 $ua = LWP::UserAgent->new();
389 progress "downloading $what...";
390 my $r = $ua->get(@_) or die $!;
391 return undef if $r->code == 404;
392 $r->is_success or fail "failed to fetch $what: ".$r->status_line;
393 return $r->decoded_content(charset => 'none');
396 our ($dscdata,$dscurl,$dsc,$dsc_checked,$skew_warning_vsn);
401 failedcmd @_ if system @_;
404 sub act_local () { return $dryrun_level <= 1; }
405 sub act_scary () { return !$dryrun_level; }
408 if (!$dryrun_level) {
409 progress "dgit ok: @_";
411 progress "would be ok: @_ (but dry run only)";
416 printcmd(\*STDERR,$debugprefix."#",@_);
419 sub runcmd_ordryrun {
427 sub runcmd_ordryrun_local {
436 my ($first_shell, @cmd) = @_;
437 return qw(sh -ec), $first_shell.'; exec "$@"', 'x', @cmd;
440 our $helpmsg = <<END;
442 dgit [dgit-opts] clone [dgit-opts] package [suite] [./dir|/dir]
443 dgit [dgit-opts] fetch|pull [dgit-opts] [suite]
444 dgit [dgit-opts] build [dpkg-buildpackage-opts]
445 dgit [dgit-opts] sbuild [sbuild-opts]
446 dgit [dgit-opts] push [dgit-opts] [suite]
447 dgit [dgit-opts] rpush build-host:build-dir ...
448 important dgit options:
449 -k<keyid> sign tag and package with <keyid> instead of default
450 --dry-run -n do not change anything, but go through the motions
451 --damp-run -L like --dry-run but make local changes, without signing
452 --new -N allow introducing a new package
453 --debug -D increase debug level
454 -c<name>=<value> set git config option (used directly by dgit too)
457 our $later_warning_msg = <<END;
458 Perhaps the upload is stuck in incoming. Using the version from git.
462 print STDERR "$us: @_\n", $helpmsg or die $!;
467 @ARGV or badusage "too few arguments";
468 return scalar shift @ARGV;
472 print $helpmsg or die $!;
476 our $td = $ENV{DGIT_TEST_DUMMY_DIR} || "DGIT_TEST_DUMMY_DIR-unset";
478 our %defcfg = ('dgit.default.distro' => 'debian',
479 'dgit.default.username' => '',
480 'dgit.default.archive-query-default-component' => 'main',
481 'dgit.default.ssh' => 'ssh',
482 'dgit.default.archive-query' => 'madison:',
483 'dgit.default.sshpsql-dbname' => 'service=projectb',
484 'dgit-distro.debian.archive-query' => 'ftpmasterapi:',
485 'dgit-distro.debian.git-check' => 'url',
486 'dgit-distro.debian.git-check-suffix' => '/info/refs',
487 'dgit-distro.debian.new-private-pushers' => 't',
488 'dgit-distro.debian/push.git-url' => '',
489 'dgit-distro.debian/push.git-host' => 'push.dgit.debian.org',
490 'dgit-distro.debian/push.git-user-force' => 'dgit',
491 'dgit-distro.debian/push.git-proto' => 'git+ssh://',
492 'dgit-distro.debian/push.git-path' => '/dgit/debian/repos',
493 'dgit-distro.debian/push.git-create' => 'true',
494 'dgit-distro.debian/push.git-check' => 'ssh-cmd',
495 'dgit-distro.debian.archive-query-url', 'https://api.ftp-master.debian.org/',
496 # 'dgit-distro.debian.archive-query-tls-key',
497 # '/etc/ssl/certs/%HOST%.pem:/etc/dgit/%HOST%.pem',
498 # ^ this does not work because curl is broken nowadays
499 # Fixing #790093 properly will involve providing providing the key
500 # in some pacagke and maybe updating these paths.
502 # 'dgit-distro.debian.archive-query-tls-curl-args',
503 # '--ca-path=/etc/ssl/ca-debian',
504 # ^ this is a workaround but works (only) on DSA-administered machines
505 'dgit-distro.debian.git-url' => 'https://git.dgit.debian.org',
506 'dgit-distro.debian.git-url-suffix' => '',
507 'dgit-distro.debian.upload-host' => 'ftp-master', # for dput
508 'dgit-distro.debian.mirror' => 'http://ftp.debian.org/debian/',
509 'dgit-distro.debian.backports-quirk' => '(squeeze)-backports*',
510 'dgit-distro.debian-backports.mirror' => 'http://backports.debian.org/debian-backports/',
511 'dgit-distro.ubuntu.git-check' => 'false',
512 'dgit-distro.ubuntu.mirror' => 'http://archive.ubuntu.com/ubuntu',
513 'dgit-distro.test-dummy.ssh' => "$td/ssh",
514 'dgit-distro.test-dummy.username' => "alice",
515 'dgit-distro.test-dummy.git-check' => "ssh-cmd",
516 'dgit-distro.test-dummy.git-create' => "ssh-cmd",
517 'dgit-distro.test-dummy.git-url' => "$td/git",
518 'dgit-distro.test-dummy.git-host' => "git",
519 'dgit-distro.test-dummy.git-path' => "$td/git",
520 'dgit-distro.test-dummy.archive-query' => "ftpmasterapi:",
521 'dgit-distro.test-dummy.archive-query-url' => "file://$td/aq/",
522 'dgit-distro.test-dummy.mirror' => "file://$td/mirror/",
523 'dgit-distro.test-dummy.upload-host' => 'test-dummy',
528 sub git_slurp_config () {
529 local ($debuglevel) = $debuglevel-2;
532 my @cmd = (@git, qw(config -z --get-regexp .*));
535 open GITS, "-|", @cmd or failedcmd @cmd;
538 printdebug "=> ", (messagequote $_), "\n";
540 push @{ $gitcfg{$`} }, $'; #';
544 or ($!==0 && $?==256)
548 sub git_get_config ($) {
551 printdebug"C $c ".(defined $l ? messagequote "'$l'" : "undef")."\n"
554 @$l==1 or badcfg "multiple values for $c" if @$l > 1;
560 return undef if $c =~ /RETURN-UNDEF/;
561 my $v = git_get_config($c);
562 return $v if defined $v;
563 my $dv = $defcfg{$c};
564 return $dv if defined $dv;
566 badcfg "need value for one of: @_\n".
567 "$us: distro or suite appears not to be (properly) supported";
570 sub access_basedistro () {
571 if (defined $idistro) {
574 return cfg("dgit-suite.$isuite.distro",
575 "dgit.default.distro");
579 sub access_quirk () {
580 # returns (quirk name, distro to use instead or undef, quirk-specific info)
581 my $basedistro = access_basedistro();
582 my $backports_quirk = cfg("dgit-distro.$basedistro.backports-quirk",
584 if (defined $backports_quirk) {
585 my $re = $backports_quirk;
586 $re =~ s/[^-0-9a-z_\%*()]/\\$&/ig;
588 $re =~ s/\%/([-0-9a-z_]+)/
589 or $re =~ m/[()]/ or badcfg "backports-quirk needs \% or ( )";
590 if ($isuite =~ m/^$re$/) {
591 return ('backports',"$basedistro-backports",$1);
594 return ('none',undef);
599 sub parse_cfg_bool ($$$) {
600 my ($what,$def,$v) = @_;
603 $v =~ m/^[ty1]/ ? 1 :
604 $v =~ m/^[fn0]/ ? 0 :
605 badcfg "$what needs t (true, y, 1) or f (false, n, 0) not \`$v'";
608 sub access_forpush_config () {
609 my $d = access_basedistro();
613 parse_cfg_bool('new-private-pushers', 0,
614 cfg("dgit-distro.$d.new-private-pushers",
617 my $v = cfg("dgit-distro.$d.readonly", 'RETURN-UNDEF');
620 $v =~ m/^[ty1]/ ? 0 : # force readonly, forpush = 0
621 $v =~ m/^[fn0]/ ? 1 : # force nonreadonly, forpush = 1
622 $v =~ m/^[a]/ ? '' : # auto, forpush = ''
623 badcfg "readonly needs t (true, y, 1) or f (false, n, 0) or a (auto)";
626 sub access_forpush () {
627 $access_forpush //= access_forpush_config();
628 return $access_forpush;
632 die "$access_forpush ?" if ($access_forpush // 1) ne 1;
633 badcfg "pushing but distro is configured readonly"
634 if access_forpush_config() eq '0';
636 $supplementary_message = <<'END' unless $we_are_responder;
637 Push failed, before we got started.
638 You can retry the push, after fixing the problem, if you like.
640 finalise_opts_opts();
644 finalise_opts_opts();
647 sub supplementary_message ($) {
649 if (!$we_are_responder) {
650 $supplementary_message = $msg;
652 } elsif ($protovsn >= 3) {
653 responder_send_command "supplementary-message ".length($msg)
655 print PO $msg or die $!;
659 sub access_distros () {
660 # Returns list of distros to try, in order
663 # 0. `instead of' distro name(s) we have been pointed to
664 # 1. the access_quirk distro, if any
665 # 2a. the user's specified distro, or failing that } basedistro
666 # 2b. the distro calculated from the suite }
667 my @l = access_basedistro();
669 my (undef,$quirkdistro) = access_quirk();
670 unshift @l, $quirkdistro;
671 unshift @l, $instead_distro;
672 @l = grep { defined } @l;
674 if (access_forpush()) {
675 @l = map { ("$_/push", $_) } @l;
680 sub access_cfg_cfgs (@) {
683 # The nesting of these loops determines the search order. We put
684 # the key loop on the outside so that we search all the distros
685 # for each key, before going on to the next key. That means that
686 # if access_cfg is called with a more specific, and then a less
687 # specific, key, an earlier distro can override the less specific
688 # without necessarily overriding any more specific keys. (If the
689 # distro wants to override the more specific keys it can simply do
690 # so; whereas if we did the loop the other way around, it would be
691 # impossible to for an earlier distro to override a less specific
692 # key but not the more specific ones without restating the unknown
693 # values of the more specific keys.
696 # We have to deal with RETURN-UNDEF specially, so that we don't
697 # terminate the search prematurely.
699 if (m/RETURN-UNDEF/) { push @rundef, $_; last; }
702 foreach my $d (access_distros()) {
703 push @cfgs, map { "dgit-distro.$d.$_" } @realkeys;
705 push @cfgs, map { "dgit.default.$_" } @realkeys;
712 my (@cfgs) = access_cfg_cfgs(@keys);
713 my $value = cfg(@cfgs);
717 sub access_cfg_bool ($$) {
718 my ($def, @keys) = @_;
719 parse_cfg_bool($keys[0], $def, access_cfg(@keys, 'RETURN-UNDEF'));
722 sub string_to_ssh ($) {
724 if ($spec =~ m/\s/) {
725 return qw(sh -ec), 'exec '.$spec.' "$@"', 'x';
731 sub access_cfg_ssh () {
732 my $gitssh = access_cfg('ssh', 'RETURN-UNDEF');
733 if (!defined $gitssh) {
736 return string_to_ssh $gitssh;
740 sub access_runeinfo ($) {
742 return ": dgit ".access_basedistro()." $info ;";
745 sub access_someuserhost ($) {
747 my $user = access_cfg("$some-user-force", 'RETURN-UNDEF');
748 defined($user) && length($user) or
749 $user = access_cfg("$some-user",'username');
750 my $host = access_cfg("$some-host");
751 return length($user) ? "$user\@$host" : $host;
754 sub access_gituserhost () {
755 return access_someuserhost('git');
758 sub access_giturl (;$) {
760 my $url = access_cfg('git-url','RETURN-UNDEF');
763 my $proto = access_cfg('git-proto', 'RETURN-UNDEF');
764 return undef unless defined $proto;
767 access_gituserhost().
768 access_cfg('git-path');
770 $suffix = access_cfg('git-url-suffix','RETURN-UNDEF');
773 return "$url/$package$suffix";
776 sub parsecontrolfh ($$;$) {
777 my ($fh, $desc, $allowsigned) = @_;
778 our $dpkgcontrolhash_noissigned;
781 my %opts = ('name' => $desc);
782 $opts{allow_pgp}= $allowsigned || !$dpkgcontrolhash_noissigned;
783 $c = Dpkg::Control::Hash->new(%opts);
784 $c->parse($fh,$desc) or die "parsing of $desc failed";
785 last if $allowsigned;
786 last if $dpkgcontrolhash_noissigned;
787 my $issigned= $c->get_option('is_pgp_signed');
788 if (!defined $issigned) {
789 $dpkgcontrolhash_noissigned= 1;
790 seek $fh, 0,0 or die "seek $desc: $!";
791 } elsif ($issigned) {
792 fail "control file $desc is (already) PGP-signed. ".
793 " Note that dgit push needs to modify the .dsc and then".
794 " do the signature itself";
803 my ($file, $desc) = @_;
804 my $fh = new IO::Handle;
805 open $fh, '<', $file or die "$file: $!";
806 my $c = parsecontrolfh($fh,$desc);
807 $fh->error and die $!;
813 my ($dctrl,$field) = @_;
814 my $v = $dctrl->{$field};
815 return $v if defined $v;
816 fail "missing field $field in ".$v->get_option('name');
820 my $c = Dpkg::Control::Hash->new();
821 my $p = new IO::Handle;
822 my @cmd = (qw(dpkg-parsechangelog), @_);
823 open $p, '-|', @cmd or die $!;
825 $?=0; $!=0; close $p or failedcmd @cmd;
831 defined $d or fail "getcwd failed: $!";
837 sub archive_query ($) {
839 my $query = access_cfg('archive-query','RETURN-UNDEF');
840 $query =~ s/^(\w+):// or badcfg "invalid archive-query method \`$query'";
843 { no strict qw(refs); &{"${method}_${proto}"}($proto,$data); }
846 sub pool_dsc_subpath ($$) {
847 my ($vsn,$component) = @_; # $package is implict arg
848 my $prefix = substr($package, 0, $package =~ m/^l/ ? 4 : 1);
849 return "/pool/$component/$prefix/$package/".dscfn($vsn);
852 #---------- `ftpmasterapi' archive query method (nascent) ----------
854 sub archive_api_query_cmd ($) {
856 my @cmd = qw(curl -sS);
857 my $url = access_cfg('archive-query-url');
858 if ($url =~ m#^https://([-.0-9a-z]+)/#) {
860 my $keys = access_cfg('archive-query-tls-key','RETURN-UNDEF') //'';
861 foreach my $key (split /\:/, $keys) {
862 $key =~ s/\%HOST\%/$host/g;
864 fail "for $url: stat $key: $!" unless $!==ENOENT;
867 fail "config requested specific TLS key but do not know".
868 " how to get curl to use exactly that EE key ($key)";
869 # push @cmd, "--cacert", $key, "--capath", "/dev/enoent";
870 # # Sadly the above line does not work because of changes
871 # # to gnutls. The real fix for #790093 may involve
872 # # new curl options.
875 # Fixing #790093 properly will involve providing a value
876 # for this on clients.
877 my $kargs = access_cfg('archive-query-tls-curl-ca-args','RETURN-UNDEF');
878 push @cmd, split / /, $kargs if defined $kargs;
880 push @cmd, $url.$subpath;
886 my ($data, $subpath) = @_;
887 badcfg "ftpmasterapi archive query method takes no data part"
889 my @cmd = archive_api_query_cmd($subpath);
890 my $json = cmdoutput @cmd;
891 return decode_json($json);
894 sub canonicalise_suite_ftpmasterapi () {
895 my ($proto,$data) = @_;
896 my $suites = api_query($data, 'suites');
898 foreach my $entry (@$suites) {
900 my $v = $entry->{$_};
901 defined $v && $v eq $isuite;
903 push @matched, $entry;
905 fail "unknown suite $isuite" unless @matched;
908 @matched==1 or die "multiple matches for suite $isuite\n";
909 $cn = "$matched[0]{codename}";
910 defined $cn or die "suite $isuite info has no codename\n";
911 $cn =~ m/^$suite_re$/ or die "suite $isuite maps to bad codename\n";
913 die "bad ftpmaster api response: $@\n".Dumper(\@matched)
918 sub archive_query_ftpmasterapi () {
919 my ($proto,$data) = @_;
920 my $info = api_query($data, "dsc_in_suite/$isuite/$package");
922 my $digester = Digest::SHA->new(256);
923 foreach my $entry (@$info) {
925 my $vsn = "$entry->{version}";
926 my ($ok,$msg) = version_check $vsn;
927 die "bad version: $msg\n" unless $ok;
928 my $component = "$entry->{component}";
929 $component =~ m/^$component_re$/ or die "bad component";
930 my $filename = "$entry->{filename}";
931 $filename && $filename !~ m#[^-+:._~0-9a-zA-Z/]|^[/.]|/[/.]#
932 or die "bad filename";
933 my $sha256sum = "$entry->{sha256sum}";
934 $sha256sum =~ m/^[0-9a-f]+$/ or die "bad sha256sum";
935 push @rows, [ $vsn, "/pool/$component/$filename",
936 $digester, $sha256sum ];
938 die "bad ftpmaster api response: $@\n".Dumper($entry)
941 @rows = sort { -version_compare($a->[0],$b->[0]) } @rows;
945 #---------- `madison' archive query method ----------
947 sub archive_query_madison {
948 return map { [ @$_[0..1] ] } madison_get_parse(@_);
951 sub madison_get_parse {
952 my ($proto,$data) = @_;
953 die unless $proto eq 'madison';
955 $data= access_cfg('madison-distro','RETURN-UNDEF');
956 $data //= access_basedistro();
958 $rmad{$proto,$data,$package} ||= cmdoutput
959 qw(rmadison -asource),"-s$isuite","-u$data",$package;
960 my $rmad = $rmad{$proto,$data,$package};
963 foreach my $l (split /\n/, $rmad) {
964 $l =~ m{^ \s*( [^ \t|]+ )\s* \|
965 \s*( [^ \t|]+ )\s* \|
966 \s*( [^ \t|/]+ )(?:/([^ \t|/]+))? \s* \|
967 \s*( [^ \t|]+ )\s* }x or die "$rmad ?";
968 $1 eq $package or die "$rmad $package ?";
975 $component = access_cfg('archive-query-default-component');
977 $5 eq 'source' or die "$rmad ?";
978 push @out, [$vsn,pool_dsc_subpath($vsn,$component),$newsuite];
980 return sort { -version_compare($a->[0],$b->[0]); } @out;
983 sub canonicalise_suite_madison {
984 # madison canonicalises for us
985 my @r = madison_get_parse(@_);
987 "unable to canonicalise suite using package $package".
988 " which does not appear to exist in suite $isuite;".
989 " --existing-package may help";
993 #---------- `sshpsql' archive query method ----------
996 my ($data,$runeinfo,$sql) = @_;
998 $data= access_someuserhost('sshpsql').':'.
999 access_cfg('sshpsql-dbname');
1001 $data =~ m/:/ or badcfg "invalid sshpsql method string \`$data'";
1002 my ($userhost,$dbname) = ($`,$'); #';
1004 my @cmd = (access_cfg_ssh, $userhost,
1005 access_runeinfo("ssh-psql $runeinfo").
1006 " export LC_MESSAGES=C; export LC_CTYPE=C;".
1007 " ".shellquote qw(psql -A), $dbname, qw(-c), $sql);
1009 open P, "-|", @cmd or die $!;
1012 printdebug(">|$_|\n");
1015 $!=0; $?=0; close P or failedcmd @cmd;
1017 my $nrows = pop @rows;
1018 $nrows =~ s/^\((\d+) rows?\)$/$1/ or die "$nrows ?";
1019 @rows == $nrows+1 or die "$nrows ".(scalar @rows)." ?";
1020 @rows = map { [ split /\|/, $_ ] } @rows;
1021 my $ncols = scalar @{ shift @rows };
1022 die if grep { scalar @$_ != $ncols } @rows;
1026 sub sql_injection_check {
1027 foreach (@_) { die "$_ $& ?" if m{[^-+=:_.,/0-9a-zA-Z]}; }
1030 sub archive_query_sshpsql ($$) {
1031 my ($proto,$data) = @_;
1032 sql_injection_check $isuite, $package;
1033 my @rows = sshpsql($data, "archive-query $isuite $package", <<END);
1034 SELECT source.version, component.name, files.filename, files.sha256sum
1036 JOIN src_associations ON source.id = src_associations.source
1037 JOIN suite ON suite.id = src_associations.suite
1038 JOIN dsc_files ON dsc_files.source = source.id
1039 JOIN files_archive_map ON files_archive_map.file_id = dsc_files.file
1040 JOIN component ON component.id = files_archive_map.component_id
1041 JOIN files ON files.id = dsc_files.file
1042 WHERE ( suite.suite_name='$isuite' OR suite.codename='$isuite' )
1043 AND source.source='$package'
1044 AND files.filename LIKE '%.dsc';
1046 @rows = sort { -version_compare($a->[0],$b->[0]) } @rows;
1047 my $digester = Digest::SHA->new(256);
1049 my ($vsn,$component,$filename,$sha256sum) = @$_;
1050 [ $vsn, "/pool/$component/$filename",$digester,$sha256sum ];
1055 sub canonicalise_suite_sshpsql ($$) {
1056 my ($proto,$data) = @_;
1057 sql_injection_check $isuite;
1058 my @rows = sshpsql($data, "canonicalise-suite $isuite", <<END);
1059 SELECT suite.codename
1060 FROM suite where suite_name='$isuite' or codename='$isuite';
1062 @rows = map { $_->[0] } @rows;
1063 fail "unknown suite $isuite" unless @rows;
1064 die "ambiguous $isuite: @rows ?" if @rows>1;
1068 #---------- `dummycat' archive query method ----------
1070 sub canonicalise_suite_dummycat ($$) {
1071 my ($proto,$data) = @_;
1072 my $dpath = "$data/suite.$isuite";
1073 if (!open C, "<", $dpath) {
1074 $!==ENOENT or die "$dpath: $!";
1075 printdebug "dummycat canonicalise_suite $isuite $dpath ENOENT\n";
1079 chomp or die "$dpath: $!";
1081 printdebug "dummycat canonicalise_suite $isuite $dpath = $_\n";
1085 sub archive_query_dummycat ($$) {
1086 my ($proto,$data) = @_;
1087 canonicalise_suite();
1088 my $dpath = "$data/package.$csuite.$package";
1089 if (!open C, "<", $dpath) {
1090 $!==ENOENT or die "$dpath: $!";
1091 printdebug "dummycat query $csuite $package $dpath ENOENT\n";
1099 printdebug "dummycat query $csuite $package $dpath | $_\n";
1100 my @row = split /\s+/, $_;
1101 @row==2 or die "$dpath: $_ ?";
1104 C->error and die "$dpath: $!";
1106 return sort { -version_compare($a->[0],$b->[0]); } @rows;
1109 #---------- archive query entrypoints and rest of program ----------
1111 sub canonicalise_suite () {
1112 return if defined $csuite;
1113 fail "cannot operate on $isuite suite" if $isuite eq 'UNRELEASED';
1114 $csuite = archive_query('canonicalise_suite');
1115 if ($isuite ne $csuite) {
1116 progress "canonical suite name for $isuite is $csuite";
1120 sub get_archive_dsc () {
1121 canonicalise_suite();
1122 my @vsns = archive_query('archive_query');
1123 foreach my $vinfo (@vsns) {
1124 my ($vsn,$subpath,$digester,$digest) = @$vinfo;
1125 $dscurl = access_cfg('mirror').$subpath;
1126 $dscdata = url_get($dscurl);
1128 $skew_warning_vsn = $vsn if !defined $skew_warning_vsn;
1133 $digester->add($dscdata);
1134 my $got = $digester->hexdigest();
1136 fail "$dscurl has hash $got but".
1137 " archive told us to expect $digest";
1139 my $dscfh = new IO::File \$dscdata, '<' or die $!;
1140 printdebug Dumper($dscdata) if $debuglevel>1;
1141 $dsc = parsecontrolfh($dscfh,$dscurl,1);
1142 printdebug Dumper($dsc) if $debuglevel>1;
1143 my $fmt = getfield $dsc, 'Format';
1144 fail "unsupported source format $fmt, sorry" unless $format_ok{$fmt};
1145 $dsc_checked = !!$digester;
1151 sub check_for_git ();
1152 sub check_for_git () {
1154 my $how = access_cfg('git-check');
1155 if ($how eq 'ssh-cmd') {
1157 (access_cfg_ssh, access_gituserhost(),
1158 access_runeinfo("git-check $package").
1159 " set -e; cd ".access_cfg('git-path').";".
1160 " if test -d $package.git; then echo 1; else echo 0; fi");
1161 my $r= cmdoutput @cmd;
1162 if ($r =~ m/^divert (\w+)$/) {
1164 my ($usedistro,) = access_distros();
1165 # NB that if we are pushing, $usedistro will be $distro/push
1166 $instead_distro= cfg("dgit-distro.$usedistro.diverts.$divert");
1167 $instead_distro =~ s{^/}{ access_basedistro()."/" }e;
1168 progress "diverting to $divert (using config for $instead_distro)";
1169 return check_for_git();
1171 failedcmd @cmd unless $r =~ m/^[01]$/;
1173 } elsif ($how eq 'url') {
1174 my $prefix = access_cfg('git-check-url','git-url');
1175 my $suffix = access_cfg('git-check-suffix','git-suffix',
1176 'RETURN-UNDEF') // '.git';
1177 my $url = "$prefix/$package$suffix";
1178 my @cmd = (qw(curl -sS -I), $url);
1179 my $result = cmdoutput @cmd;
1180 $result =~ s/^\S+ 200 .*\n\r?\n//;
1181 # curl -sS -I with https_proxy prints
1182 # HTTP/1.0 200 Connection established
1183 $result =~ m/^\S+ (404|200) /s or
1184 fail "unexpected results from git check query - ".
1185 Dumper($prefix, $result);
1187 if ($code eq '404') {
1189 } elsif ($code eq '200') {
1194 } elsif ($how eq 'true') {
1196 } elsif ($how eq 'false') {
1199 badcfg "unknown git-check \`$how'";
1203 sub create_remote_git_repo () {
1204 my $how = access_cfg('git-create');
1205 if ($how eq 'ssh-cmd') {
1207 (access_cfg_ssh, access_gituserhost(),
1208 access_runeinfo("git-create $package").
1209 "set -e; cd ".access_cfg('git-path').";".
1210 " cp -a _template $package.git");
1211 } elsif ($how eq 'true') {
1214 badcfg "unknown git-create \`$how'";
1218 our ($dsc_hash,$lastpush_hash);
1220 our $ud = '.git/dgit/unpack';
1230 sub mktree_in_ud_here () {
1231 runcmd qw(git init -q);
1232 rmtree('.git/objects');
1233 symlink '../../../../objects','.git/objects' or die $!;
1236 sub git_write_tree () {
1237 my $tree = cmdoutput @git, qw(write-tree);
1238 $tree =~ m/^\w+$/ or die "$tree ?";
1242 sub remove_stray_gits () {
1243 my @gitscmd = qw(find -name .git -prune -print0);
1244 debugcmd "|",@gitscmd;
1245 open GITS, "-|", @gitscmd or failedcmd @gitscmd;
1250 print STDERR "$us: warning: removing from source package: ",
1251 (messagequote $_), "\n";
1255 $!=0; $?=0; close GITS or failedcmd @gitscmd;
1258 sub mktree_in_ud_from_only_subdir () {
1259 # changes into the subdir
1261 die unless @dirs==1;
1262 $dirs[0] =~ m#^([^/]+)/\.$# or die;
1266 remove_stray_gits();
1267 mktree_in_ud_here();
1268 my ($format, $fopts) = get_source_format();
1269 if (madformat($format)) {
1272 runcmd @git, qw(add -Af);
1273 my $tree=git_write_tree();
1274 return ($tree,$dir);
1277 sub dsc_files_info () {
1278 foreach my $csumi (['Checksums-Sha256','Digest::SHA', 'new(256)'],
1279 ['Checksums-Sha1', 'Digest::SHA', 'new(1)'],
1280 ['Files', 'Digest::MD5', 'new()']) {
1281 my ($fname, $module, $method) = @$csumi;
1282 my $field = $dsc->{$fname};
1283 next unless defined $field;
1284 eval "use $module; 1;" or die $@;
1286 foreach (split /\n/, $field) {
1288 m/^(\w+) (\d+) (\S+)$/ or
1289 fail "could not parse .dsc $fname line \`$_'";
1290 my $digester = eval "$module"."->$method;" or die $@;
1295 Digester => $digester,
1300 fail "missing any supported Checksums-* or Files field in ".
1301 $dsc->get_option('name');
1305 map { $_->{Filename} } dsc_files_info();
1308 sub is_orig_file ($;$) {
1311 m/\.orig(?:-\w+)?\.tar\.\w+$/ or return 0;
1312 defined $base or return 1;
1316 sub make_commit ($) {
1318 return cmdoutput @git, qw(hash-object -w -t commit), $file;
1321 sub clogp_authline ($) {
1323 my $author = getfield $clogp, 'Maintainer';
1324 $author =~ s#,.*##ms;
1325 my $date = cmdoutput qw(date), '+%s %z', qw(-d), getfield($clogp,'Date');
1326 my $authline = "$author $date";
1327 $authline =~ m/$git_authline_re/o or
1328 fail "unexpected commit author line format \`$authline'".
1329 " (was generated from changelog Maintainer field)";
1330 return ($1,$2,$3) if wantarray;
1334 sub vendor_patches_distro ($$) {
1335 my ($checkdistro, $what) = @_;
1336 return unless defined $checkdistro;
1338 my $series = "debian/patches/\L$checkdistro\E.series";
1339 printdebug "checking for vendor-specific $series ($what)\n";
1341 if (!open SERIES, "<", $series) {
1342 die "$series $!" unless $!==ENOENT;
1351 Unfortunately, this source package uses a feature of dpkg-source where
1352 the same source package unpacks to different source code on different
1353 distros. dgit cannot safely operate on such packages on affected
1354 distros, because the meaning of source packages is not stable.
1356 Please ask the distro/maintainer to remove the distro-specific series
1357 files and use a different technique (if necessary, uploading actually
1358 different packages, if different distros are supposed to have
1362 fail "Found active distro-specific series file for".
1363 " $checkdistro ($what): $series, cannot continue";
1365 die "$series $!" if SERIES->error;
1369 sub check_for_vendor_patches () {
1370 # This dpkg-source feature doesn't seem to be documented anywhere!
1371 # But it can be found in the changelog (reformatted):
1373 # commit 4fa01b70df1dc4458daee306cfa1f987b69da58c
1374 # Author: Raphael Hertzog <hertzog@debian.org>
1375 # Date: Sun Oct 3 09:36:48 2010 +0200
1377 # dpkg-source: correctly create .pc/.quilt_series with alternate
1380 # If you have debian/patches/ubuntu.series and you were
1381 # unpacking the source package on ubuntu, quilt was still
1382 # directed to debian/patches/series instead of
1383 # debian/patches/ubuntu.series.
1385 # debian/changelog | 3 +++
1386 # scripts/Dpkg/Source/Package/V3/quilt.pm | 4 +++-
1387 # 2 files changed, 6 insertions(+), 1 deletion(-)
1390 vendor_patches_distro($ENV{DEB_VENDOR}, "DEB_VENDOR");
1391 vendor_patches_distro(Dpkg::Vendor::get_current_vendor(),
1392 "Dpkg::Vendor \`current vendor'");
1393 vendor_patches_distro(access_basedistro(),
1394 "distro being accessed");
1397 sub generate_commit_from_dsc () {
1401 foreach my $fi (dsc_files_info()) {
1402 my $f = $fi->{Filename};
1403 die "$f ?" if $f =~ m#/|^\.|\.dsc$|\.tmp$#;
1405 link_ltarget "../../../$f", $f
1409 complete_file_from_dsc('.', $fi)
1412 if (is_orig_file($f)) {
1413 link $f, "../../../../$f"
1419 my $dscfn = "$package.dsc";
1421 open D, ">", $dscfn or die "$dscfn: $!";
1422 print D $dscdata or die "$dscfn: $!";
1423 close D or die "$dscfn: $!";
1424 my @cmd = qw(dpkg-source);
1425 push @cmd, '--no-check' if $dsc_checked;
1426 push @cmd, qw(-x --), $dscfn;
1429 my ($tree,$dir) = mktree_in_ud_from_only_subdir();
1430 check_for_vendor_patches() if madformat($dsc->{format});
1431 runcmd qw(sh -ec), 'dpkg-parsechangelog >../changelog.tmp';
1432 my $clogp = parsecontrol('../changelog.tmp',"commit's changelog");
1433 my $authline = clogp_authline $clogp;
1434 my $changes = getfield $clogp, 'Changes';
1435 open C, ">../commit.tmp" or die $!;
1436 print C <<END or die $!;
1443 # imported from the archive
1446 my $outputhash = make_commit qw(../commit.tmp);
1447 my $cversion = getfield $clogp, 'Version';
1448 progress "synthesised git commit from .dsc $cversion";
1449 if ($lastpush_hash) {
1450 runcmd @git, qw(reset --hard), $lastpush_hash;
1451 runcmd qw(sh -ec), 'dpkg-parsechangelog >>../changelogold.tmp';
1452 my $oldclogp = parsecontrol('../changelogold.tmp','previous changelog');
1453 my $oversion = getfield $oldclogp, 'Version';
1455 version_compare($oversion, $cversion);
1457 # git upload/ is earlier vsn than archive, use archive
1458 open C, ">../commit2.tmp" or die $!;
1459 print C <<END or die $!;
1461 parent $lastpush_hash
1466 Record $package ($cversion) in archive suite $csuite
1468 $outputhash = make_commit qw(../commit2.tmp);
1469 } elsif ($vcmp > 0) {
1470 print STDERR <<END or die $!;
1472 Version actually in archive: $cversion (older)
1473 Last allegedly pushed/uploaded: $oversion (newer or same)
1476 $outputhash = $lastpush_hash;
1478 $outputhash = $lastpush_hash;
1481 changedir '../../../..';
1482 runcmd @git, qw(update-ref -m),"dgit fetch import $cversion",
1483 'DGIT_ARCHIVE', $outputhash;
1484 cmdoutput @git, qw(log -n2), $outputhash;
1485 # ... gives git a chance to complain if our commit is malformed
1490 sub complete_file_from_dsc ($$) {
1491 our ($dstdir, $fi) = @_;
1492 # Ensures that we have, in $dir, the file $fi, with the correct
1493 # contents. (Downloading it from alongside $dscurl if necessary.)
1495 my $f = $fi->{Filename};
1496 my $tf = "$dstdir/$f";
1499 if (stat_exists $tf) {
1500 progress "using existing $f";
1503 $furl =~ s{/[^/]+$}{};
1505 die "$f ?" unless $f =~ m/^\Q${package}\E_/;
1506 die "$f ?" if $f =~ m#/#;
1507 runcmd_ordryrun_local @curl,qw(-o),$tf,'--',"$furl";
1508 return 0 if !act_local();
1512 open F, "<", "$tf" or die "$tf: $!";
1513 $fi->{Digester}->reset();
1514 $fi->{Digester}->addfile(*F);
1515 F->error and die $!;
1516 my $got = $fi->{Digester}->hexdigest();
1517 $got eq $fi->{Hash} or
1518 fail "file $f has hash $got but .dsc".
1519 " demands hash $fi->{Hash} ".
1520 ($downloaded ? "(got wrong file from archive!)"
1521 : "(perhaps you should delete this file?)");
1526 sub ensure_we_have_orig () {
1527 foreach my $fi (dsc_files_info()) {
1528 my $f = $fi->{Filename};
1529 next unless is_orig_file($f);
1530 complete_file_from_dsc('..', $fi)
1535 sub git_fetch_us () {
1536 my @specs = (fetchspec());
1538 map { "+refs/$_/*:".lrfetchrefs."/$_/*" }
1540 runcmd_ordryrun_local @git, qw(fetch -p -n -q), access_giturl(), @specs;
1543 my $tagpat = debiantag('*',access_basedistro);
1545 git_for_each_ref("refs/tags/".$tagpat, sub {
1546 my ($objid,$objtype,$fullrefname,$reftail) = @_;
1547 printdebug "currently $fullrefname=$objid\n";
1548 $here{$fullrefname} = $objid;
1550 git_for_each_ref(lrfetchrefs."/tags/".$tagpat, sub {
1551 my ($objid,$objtype,$fullrefname,$reftail) = @_;
1552 my $lref = "refs".substr($fullrefname, length lrfetchrefs);
1553 printdebug "offered $lref=$objid\n";
1554 if (!defined $here{$lref}) {
1555 my @upd = (@git, qw(update-ref), $lref, $objid, '');
1556 runcmd_ordryrun_local @upd;
1557 } elsif ($here{$lref} eq $objid) {
1560 "Not updateting $lref from $here{$lref} to $objid.\n";
1565 sub fetch_from_archive () {
1566 # ensures that lrref() is what is actually in the archive,
1567 # one way or another
1571 foreach my $field (@ourdscfield) {
1572 $dsc_hash = $dsc->{$field};
1573 last if defined $dsc_hash;
1575 if (defined $dsc_hash) {
1576 $dsc_hash =~ m/\w+/ or fail "invalid hash in .dsc \`$dsc_hash'";
1578 progress "last upload to archive specified git hash";
1580 progress "last upload to archive has NO git hash";
1583 progress "no version available from the archive";
1586 $lastpush_hash = git_get_ref(lrref());
1587 printdebug "previous reference hash=$lastpush_hash\n";
1589 if (defined $dsc_hash) {
1590 fail "missing remote git history even though dsc has hash -".
1591 " could not find ref ".lrref().
1592 " (should have been fetched from ".access_giturl()."#".rrref().")"
1593 unless $lastpush_hash;
1595 ensure_we_have_orig();
1596 if ($dsc_hash eq $lastpush_hash) {
1597 } elsif (is_fast_fwd($dsc_hash,$lastpush_hash)) {
1598 print STDERR <<END or die $!;
1600 Git commit in archive is behind the last version allegedly pushed/uploaded.
1601 Commit referred to by archive: $dsc_hash
1602 Last allegedly pushed/uploaded: $lastpush_hash
1605 $hash = $lastpush_hash;
1607 fail "git head (".lrref()."=$lastpush_hash) is not a ".
1608 "descendant of archive's .dsc hash ($dsc_hash)";
1611 $hash = generate_commit_from_dsc();
1612 } elsif ($lastpush_hash) {
1613 # only in git, not in the archive yet
1614 $hash = $lastpush_hash;
1615 print STDERR <<END or die $!;
1617 Package not found in the archive, but has allegedly been pushed using dgit.
1621 printdebug "nothing found!\n";
1622 if (defined $skew_warning_vsn) {
1623 print STDERR <<END or die $!;
1625 Warning: relevant archive skew detected.
1626 Archive allegedly contains $skew_warning_vsn
1627 But we were not able to obtain any version from the archive or git.
1633 printdebug "current hash=$hash\n";
1634 if ($lastpush_hash) {
1635 fail "not fast forward on last upload branch!".
1636 " (archive's version left in DGIT_ARCHIVE)"
1637 unless is_fast_fwd($lastpush_hash, $hash);
1639 if (defined $skew_warning_vsn) {
1641 printdebug "SKEW CHECK WANT $skew_warning_vsn\n";
1642 my $clogf = ".git/dgit/changelog.tmp";
1643 runcmd shell_cmd "exec >$clogf",
1644 @git, qw(cat-file blob), "$hash:debian/changelog";
1645 my $gotclogp = parsechangelog("-l$clogf");
1646 my $got_vsn = getfield $gotclogp, 'Version';
1647 printdebug "SKEW CHECK GOT $got_vsn\n";
1648 if (version_compare($got_vsn, $skew_warning_vsn) < 0) {
1649 print STDERR <<END or die $!;
1651 Warning: archive skew detected. Using the available version:
1652 Archive allegedly contains $skew_warning_vsn
1653 We were able to obtain only $got_vsn
1658 if ($lastpush_hash ne $hash) {
1659 my @upd_cmd = (@git, qw(update-ref -m), 'dgit fetch', lrref(), $hash);
1663 dryrun_report @upd_cmd;
1669 sub set_local_git_config ($$) {
1671 runcmd @git, qw(config), $k, $v;
1674 sub setup_mergechangelogs (;$) {
1676 return unless $always || access_cfg_bool(1, 'setup-mergechangelogs');
1678 my $driver = 'dpkg-mergechangelogs';
1679 my $cb = "merge.$driver";
1680 my $attrs = '.git/info/attributes';
1681 ensuredir '.git/info';
1683 open NATTRS, ">", "$attrs.new" or die "$attrs.new $!";
1684 if (!open ATTRS, "<", $attrs) {
1685 $!==ENOENT or die "$attrs: $!";
1689 next if m{^debian/changelog\s};
1690 print NATTRS $_, "\n" or die $!;
1692 ATTRS->error and die $!;
1695 print NATTRS "debian/changelog merge=$driver\n" or die $!;
1698 set_local_git_config "$cb.name", 'debian/changelog merge driver';
1699 set_local_git_config "$cb.driver", 'dpkg-mergechangelogs -m %O %A %B %A';
1701 rename "$attrs.new", "$attrs" or die "$attrs: $!";
1704 sub setup_useremail (;$) {
1706 return unless $always || access_cfg_bool(1, 'setup-useremail');
1709 my ($k, $envvar) = @_;
1710 my $v = access_cfg("user-$k", 'RETURN-UNDEF') // $ENV{$envvar};
1711 return unless defined $v;
1712 set_local_git_config "user.$k", $v;
1715 $setup->('email', 'DEBEMAIL');
1716 $setup->('name', 'DEBFULLNAME');
1719 sub setup_new_tree () {
1720 setup_mergechangelogs();
1726 canonicalise_suite();
1727 badusage "dry run makes no sense with clone" unless act_local();
1728 my $hasgit = check_for_git();
1729 mkdir $dstdir or fail "create \`$dstdir': $!";
1731 runcmd @git, qw(init -q);
1732 my $giturl = access_giturl(1);
1733 if (defined $giturl) {
1734 set_local_git_config "remote.$remotename.fetch", fetchspec();
1735 open H, "> .git/HEAD" or die $!;
1736 print H "ref: ".lref()."\n" or die $!;
1738 runcmd @git, qw(remote add), 'origin', $giturl;
1741 progress "fetching existing git history";
1743 runcmd_ordryrun_local @git, qw(fetch origin);
1745 progress "starting new git history";
1747 fetch_from_archive() or no_such_package;
1748 my $vcsgiturl = $dsc->{'Vcs-Git'};
1749 if (length $vcsgiturl) {
1750 $vcsgiturl =~ s/\s+-b\s+\S+//g;
1751 runcmd @git, qw(remote add vcs-git), $vcsgiturl;
1754 runcmd @git, qw(reset --hard), lrref();
1755 printdone "ready for work in $dstdir";
1759 if (check_for_git()) {
1762 fetch_from_archive() or no_such_package();
1763 printdone "fetched into ".lrref();
1768 runcmd_ordryrun_local @git, qw(merge -m),"Merge from $csuite [dgit]",
1770 printdone "fetched to ".lrref()." and merged into HEAD";
1773 sub check_not_dirty () {
1774 foreach my $f (qw(local-options local-patch-header)) {
1775 if (stat_exists "debian/source/$f") {
1776 fail "git tree contains debian/source/$f";
1780 return if $ignoredirty;
1782 my @cmd = (@git, qw(diff --quiet HEAD));
1784 $!=0; $?=0; system @cmd;
1785 return if !$! && !$?;
1786 if (!$! && $?==256) {
1787 fail "working tree is dirty (does not match HEAD)";
1793 sub commit_admin ($) {
1796 runcmd_ordryrun_local @git, qw(commit -m), $m;
1799 sub commit_quilty_patch () {
1800 my $output = cmdoutput @git, qw(status --porcelain);
1802 foreach my $l (split /\n/, $output) {
1803 next unless $l =~ m/\S/;
1804 if ($l =~ m{^(?:\?\?| M) (.pc|debian/patches)}) {
1808 delete $adds{'.pc'}; # if there wasn't one before, don't add it
1810 progress "nothing quilty to commit, ok.";
1813 my @adds = map { s/[][*?\\]/\\$&/g; $_; } sort keys %adds;
1814 runcmd_ordryrun_local @git, qw(add -f), @adds;
1815 commit_admin "Commit Debian 3.0 (quilt) metadata";
1818 sub get_source_format () {
1820 if (open F, "debian/source/options") {
1824 s/\s+$//; # ignore missing final newline
1826 my ($k, $v) = ($`, $'); #');
1827 $v =~ s/^"(.*)"$/$1/;
1833 F->error and die $!;
1836 die $! unless $!==&ENOENT;
1839 if (!open F, "debian/source/format") {
1840 die $! unless $!==&ENOENT;
1844 F->error and die $!;
1846 return ($_, \%options);
1851 return 0 unless $format eq '3.0 (quilt)';
1852 if ($quilt_mode eq 'nocheck') {
1853 progress "Not doing any fixup of \`$format' due to --no-quilt-fixup";
1856 progress "Format \`$format', checking/updating patch stack";
1860 sub push_parse_changelog ($) {
1863 my $clogp = Dpkg::Control::Hash->new();
1864 $clogp->load($clogpfn) or die;
1866 $package = getfield $clogp, 'Source';
1867 my $cversion = getfield $clogp, 'Version';
1868 my $tag = debiantag($cversion, access_basedistro);
1869 runcmd @git, qw(check-ref-format), $tag;
1871 my $dscfn = dscfn($cversion);
1873 return ($clogp, $cversion, $tag, $dscfn);
1876 sub push_parse_dsc ($$$) {
1877 my ($dscfn,$dscfnwhat, $cversion) = @_;
1878 $dsc = parsecontrol($dscfn,$dscfnwhat);
1879 my $dversion = getfield $dsc, 'Version';
1880 my $dscpackage = getfield $dsc, 'Source';
1881 ($dscpackage eq $package && $dversion eq $cversion) or
1882 fail "$dscfn is for $dscpackage $dversion".
1883 " but debian/changelog is for $package $cversion";
1886 sub push_mktag ($$$$$$$) {
1887 my ($head,$clogp,$tag,
1889 $changesfile,$changesfilewhat,
1892 $dsc->{$ourdscfield[0]} = $head;
1893 $dsc->save("$dscfn.tmp") or die $!;
1895 my $changes = parsecontrol($changesfile,$changesfilewhat);
1896 foreach my $field (qw(Source Distribution Version)) {
1897 $changes->{$field} eq $clogp->{$field} or
1898 fail "changes field $field \`$changes->{$field}'".
1899 " does not match changelog \`$clogp->{$field}'";
1902 my $cversion = getfield $clogp, 'Version';
1903 my $clogsuite = getfield $clogp, 'Distribution';
1905 # We make the git tag by hand because (a) that makes it easier
1906 # to control the "tagger" (b) we can do remote signing
1907 my $authline = clogp_authline $clogp;
1908 my $delibs = join(" ", "",@deliberatelies);
1909 my $declaredistro = access_basedistro();
1910 open TO, '>', $tfn->('.tmp') or die $!;
1911 print TO <<END or die $!;
1917 $package release $cversion for $clogsuite ($csuite) [dgit]
1918 [dgit distro=$declaredistro$delibs]
1920 foreach my $ref (sort keys %previously) {
1921 print TO <<END or die $!;
1922 [dgit previously:$ref=$previously{$ref}]
1928 my $tagobjfn = $tfn->('.tmp');
1930 if (!defined $keyid) {
1931 $keyid = access_cfg('keyid','RETURN-UNDEF');
1933 if (!defined $keyid) {
1934 $keyid = getfield $clogp, 'Maintainer';
1936 unlink $tfn->('.tmp.asc') or $!==&ENOENT or die $!;
1937 my @sign_cmd = (@gpg, qw(--detach-sign --armor));
1938 push @sign_cmd, qw(-u),$keyid if defined $keyid;
1939 push @sign_cmd, $tfn->('.tmp');
1940 runcmd_ordryrun @sign_cmd;
1942 $tagobjfn = $tfn->('.signed.tmp');
1943 runcmd shell_cmd "exec >$tagobjfn", qw(cat --),
1944 $tfn->('.tmp'), $tfn->('.tmp.asc');
1951 sub sign_changes ($) {
1952 my ($changesfile) = @_;
1954 my @debsign_cmd = @debsign;
1955 push @debsign_cmd, "-k$keyid" if defined $keyid;
1956 push @debsign_cmd, "-p$gpg[0]" if $gpg[0] ne 'gpg';
1957 push @debsign_cmd, $changesfile;
1958 runcmd_ordryrun @debsign_cmd;
1963 my ($forceflag) = @_;
1964 printdebug "actually entering push\n";
1965 supplementary_message(<<'END');
1966 Push failed, while preparing your push.
1967 You can retry the push, after fixing the problem, if you like.
1971 access_giturl(); # check that success is vaguely likely
1973 my $clogpfn = ".git/dgit/changelog.822.tmp";
1974 runcmd shell_cmd "exec >$clogpfn", qw(dpkg-parsechangelog);
1976 responder_send_file('parsed-changelog', $clogpfn);
1978 my ($clogp, $cversion, $tag, $dscfn) =
1979 push_parse_changelog("$clogpfn");
1981 my $dscpath = "$buildproductsdir/$dscfn";
1982 stat_exists $dscpath or
1983 fail "looked for .dsc $dscfn, but $!;".
1984 " maybe you forgot to build";
1986 responder_send_file('dsc', $dscpath);
1988 push_parse_dsc($dscpath, $dscfn, $cversion);
1990 my $format = getfield $dsc, 'Format';
1991 printdebug "format $format\n";
1993 if (madformat($format)) {
1994 # user might have not used dgit build, so maybe do this now:
1995 commit_quilty_patch();
1998 die 'xxx fast forward (should not depend on quilt mode, but will always be needed if we did $split_brain)' if $split_brain;
2002 progress "checking that $dscfn corresponds to HEAD";
2003 runcmd qw(dpkg-source -x --),
2004 $dscpath =~ m#^/# ? $dscpath : "../../../$dscpath";
2005 my ($tree,$dir) = mktree_in_ud_from_only_subdir();
2006 check_for_vendor_patches() if madformat($dsc->{format});
2007 changedir '../../../..';
2008 my $diffopt = $debuglevel>0 ? '--exit-code' : '--quiet';
2009 my @diffcmd = (@git, qw(diff), $diffopt, $tree);
2010 debugcmd "+",@diffcmd;
2012 my $r = system @diffcmd;
2015 fail "$dscfn specifies a different tree to your HEAD commit;".
2016 " perhaps you forgot to build".
2017 ($diffopt eq '--exit-code' ? "" :
2018 " (run with -D to see full diff output)");
2023 my $head = git_rev_parse('HEAD');
2024 if (!$changesfile) {
2025 my $pat = changespat $cversion;
2026 my @cs = glob "$buildproductsdir/$pat";
2027 fail "failed to find unique changes file".
2028 " (looked for $pat in $buildproductsdir);".
2029 " perhaps you need to use dgit -C"
2031 ($changesfile) = @cs;
2033 $changesfile = "$buildproductsdir/$changesfile";
2036 responder_send_file('changes',$changesfile);
2037 responder_send_command("param head $head");
2038 responder_send_command("param csuite $csuite");
2040 if (deliberately_not_fast_forward) {
2041 git_for_each_ref(lrfetchrefs, sub {
2042 my ($objid,$objtype,$lrfetchrefname,$reftail) = @_;
2043 my $rrefname= substr($lrfetchrefname, length(lrfetchrefs) + 1);
2044 responder_send_command("previously $rrefname=$objid");
2045 $previously{$rrefname} = $objid;
2049 my $tfn = sub { ".git/dgit/tag$_[0]"; };
2052 supplementary_message(<<'END');
2053 Push failed, while signing the tag.
2054 You can retry the push, after fixing the problem, if you like.
2056 # If we manage to sign but fail to record it anywhere, it's fine.
2057 if ($we_are_responder) {
2058 $tagobjfn = $tfn->('.signed.tmp');
2059 responder_receive_files('signed-tag', $tagobjfn);
2062 push_mktag($head,$clogp,$tag,
2064 $changesfile,$changesfile,
2067 supplementary_message(<<'END');
2068 Push failed, *after* signing the tag.
2069 If you want to try again, you should use a new version number.
2072 my $tag_obj_hash = cmdoutput @git, qw(hash-object -w -t tag), $tagobjfn;
2073 runcmd_ordryrun @git, qw(verify-tag), $tag_obj_hash;
2074 runcmd_ordryrun_local @git, qw(update-ref), "refs/tags/$tag", $tag_obj_hash;
2076 supplementary_message(<<'END');
2077 Push failed, while updating the remote git repository - see messages above.
2078 If you want to try again, you should use a new version number.
2080 if (!check_for_git()) {
2081 create_remote_git_repo();
2083 runcmd_ordryrun @git, qw(push),access_giturl(),
2084 $forceflag."HEAD:".rrref(), $forceflag."refs/tags/$tag";
2085 runcmd_ordryrun @git, qw(update-ref -m), 'dgit push', lrref(), 'HEAD';
2087 supplementary_message(<<'END');
2088 Push failed, after updating the remote git repository.
2089 If you want to try again, you must use a new version number.
2091 if ($we_are_responder) {
2092 my $dryrunsuffix = act_local() ? "" : ".tmp";
2093 responder_receive_files('signed-dsc-changes',
2094 "$dscpath$dryrunsuffix",
2095 "$changesfile$dryrunsuffix");
2098 rename "$dscpath.tmp",$dscpath or die "$dscfn $!";
2100 progress "[new .dsc left in $dscpath.tmp]";
2102 sign_changes $changesfile;
2105 supplementary_message(<<END);
2106 Push failed, while uploading package(s) to the archive server.
2107 You can retry the upload of exactly these same files with dput of:
2109 If that .changes file is broken, you will need to use a new version
2110 number for your next attempt at the upload.
2112 my $host = access_cfg('upload-host','RETURN-UNDEF');
2113 my @hostarg = defined($host) ? ($host,) : ();
2114 runcmd_ordryrun @dput, @hostarg, $changesfile;
2115 printdone "pushed and uploaded $cversion";
2117 supplementary_message('');
2118 responder_send_command("complete");
2125 badusage "-p is not allowed with clone; specify as argument instead"
2126 if defined $package;
2129 } elsif (@ARGV==2 && $ARGV[1] =~ m#^\w#) {
2130 ($package,$isuite) = @ARGV;
2131 } elsif (@ARGV==2 && $ARGV[1] =~ m#^[./]#) {
2132 ($package,$dstdir) = @ARGV;
2133 } elsif (@ARGV==3) {
2134 ($package,$isuite,$dstdir) = @ARGV;
2136 badusage "incorrect arguments to dgit clone";
2138 $dstdir ||= "$package";
2140 if (stat_exists $dstdir) {
2141 fail "$dstdir already exists";
2145 if ($rmonerror && !$dryrun_level) {
2146 $cwd_remove= getcwd();
2148 return unless defined $cwd_remove;
2149 if (!chdir "$cwd_remove") {
2150 return if $!==&ENOENT;
2151 die "chdir $cwd_remove: $!";
2154 rmtree($dstdir) or die "remove $dstdir: $!\n";
2155 } elsif (!grep { $! == $_ }
2156 (ENOENT, ENOTDIR, EACCES, EPERM, ELOOP)) {
2158 print STDERR "check whether to remove $dstdir: $!\n";
2164 $cwd_remove = undef;
2167 sub branchsuite () {
2168 my $branch = cmdoutput_errok @git, qw(symbolic-ref HEAD);
2169 if ($branch =~ m#$lbranch_re#o) {
2176 sub fetchpullargs () {
2178 if (!defined $package) {
2179 my $sourcep = parsecontrol('debian/control','debian/control');
2180 $package = getfield $sourcep, 'Source';
2183 # $isuite = branchsuite(); # this doesn't work because dak hates canons
2185 my $clogp = parsechangelog();
2186 $isuite = getfield $clogp, 'Distribution';
2188 canonicalise_suite();
2189 progress "fetching from suite $csuite";
2190 } elsif (@ARGV==1) {
2192 canonicalise_suite();
2194 badusage "incorrect arguments to dgit fetch or dgit pull";
2213 badusage "-p is not allowed with dgit push" if defined $package;
2215 my $clogp = parsechangelog();
2216 $package = getfield $clogp, 'Source';
2219 } elsif (@ARGV==1) {
2220 ($specsuite) = (@ARGV);
2222 badusage "incorrect arguments to dgit push";
2224 $isuite = getfield $clogp, 'Distribution';
2226 local ($package) = $existing_package; # this is a hack
2227 canonicalise_suite();
2229 canonicalise_suite();
2231 if (defined $specsuite &&
2232 $specsuite ne $isuite &&
2233 $specsuite ne $csuite) {
2234 fail "dgit push: changelog specifies $isuite ($csuite)".
2235 " but command line specifies $specsuite";
2237 supplementary_message(<<'END');
2238 Push failed, while checking state of the archive.
2239 You can retry the push, after fixing the problem, if you like.
2241 if (check_for_git()) {
2245 if (fetch_from_archive()) {
2246 if (is_fast_fwd(lrref(), 'HEAD')) {
2248 } elsif (deliberately_not_fast_forward) {
2251 fail "dgit push: HEAD is not a descendant".
2252 " of the archive's version.\n".
2253 "dgit: To overwrite its contents,".
2254 " use git merge -s ours ".lrref().".\n".
2255 "dgit: To rewind history, if permitted by the archive,".
2256 " use --deliberately-not-fast-forward";
2260 fail "package appears to be new in this suite;".
2261 " if this is intentional, use --new";
2266 #---------- remote commands' implementation ----------
2268 sub cmd_remote_push_build_host {
2269 my ($nrargs) = shift @ARGV;
2270 my (@rargs) = @ARGV[0..$nrargs-1];
2271 @ARGV = @ARGV[$nrargs..$#ARGV];
2273 my ($dir,$vsnwant) = @rargs;
2274 # vsnwant is a comma-separated list; we report which we have
2275 # chosen in our ready response (so other end can tell if they
2278 $we_are_responder = 1;
2279 $us .= " (build host)";
2283 open PI, "<&STDIN" or die $!;
2284 open STDIN, "/dev/null" or die $!;
2285 open PO, ">&STDOUT" or die $!;
2287 open STDOUT, ">&STDERR" or die $!;
2291 ($protovsn) = grep {
2292 $vsnwant =~ m{^(?:.*,)?$_(?:,.*)?$}
2293 } @rpushprotovsn_support;
2295 fail "build host has dgit rpush protocol versions ".
2296 (join ",", @rpushprotovsn_support).
2297 " but invocation host has $vsnwant"
2298 unless defined $protovsn;
2300 responder_send_command("dgit-remote-push-ready $protovsn");
2306 sub cmd_remote_push_responder { cmd_remote_push_build_host(); }
2307 # ... for compatibility with proto vsn.1 dgit (just so that user gets
2308 # a good error message)
2314 my $report = i_child_report();
2315 if (defined $report) {
2316 printdebug "($report)\n";
2317 } elsif ($i_child_pid) {
2318 printdebug "(killing build host child $i_child_pid)\n";
2319 kill 15, $i_child_pid;
2321 if (defined $i_tmp && !defined $initiator_tempdir) {
2323 eval { rmtree $i_tmp; };
2327 END { i_cleanup(); }
2330 my ($base,$selector,@args) = @_;
2331 $selector =~ s/\-/_/g;
2332 { no strict qw(refs); &{"${base}_${selector}"}(@args); }
2339 if ($host =~ m/^((?:[^][]|\[[^][]*\])*)\:/) {
2347 push @rargs, join ",", @rpushprotovsn_support;
2350 push @rdgit, @ropts;
2351 push @rdgit, qw(remote-push-build-host), (scalar @rargs), @rargs;
2353 my @cmd = (@ssh, $host, shellquote @rdgit);
2356 if (defined $initiator_tempdir) {
2357 rmtree $initiator_tempdir;
2358 mkdir $initiator_tempdir, 0700 or die "$initiator_tempdir: $!";
2359 $i_tmp = $initiator_tempdir;
2363 $i_child_pid = open2(\*RO, \*RI, @cmd);
2365 ($protovsn) = initiator_expect { m/^dgit-remote-push-ready (\S+)/ };
2366 die "$protovsn ?" unless grep { $_ eq $protovsn } @rpushprotovsn_support;
2367 $supplementary_message = '' unless $protovsn >= 3;
2369 my ($icmd,$iargs) = initiator_expect {
2370 m/^(\S+)(?: (.*))?$/;
2373 i_method "i_resp", $icmd, $iargs;
2377 sub i_resp_progress ($) {
2379 my $msg = protocol_read_bytes \*RO, $rhs;
2383 sub i_resp_supplementary_message ($) {
2385 $supplementary_message = protocol_read_bytes \*RO, $rhs;
2388 sub i_resp_complete {
2389 my $pid = $i_child_pid;
2390 $i_child_pid = undef; # prevents killing some other process with same pid
2391 printdebug "waiting for build host child $pid...\n";
2392 my $got = waitpid $pid, 0;
2393 die $! unless $got == $pid;
2394 die "build host child failed $?" if $?;
2397 printdebug "all done\n";
2401 sub i_resp_file ($) {
2403 my $localname = i_method "i_localname", $keyword;
2404 my $localpath = "$i_tmp/$localname";
2405 stat_exists $localpath and
2406 badproto \*RO, "file $keyword ($localpath) twice";
2407 protocol_receive_file \*RO, $localpath;
2408 i_method "i_file", $keyword;
2413 sub i_resp_param ($) {
2414 $_[0] =~ m/^(\S+) (.*)$/ or badproto \*RO, "bad param spec";
2418 sub i_resp_previously ($) {
2419 $_[0] =~ m#^(refs/tags/\S+)=(\w+)$#
2420 or badproto \*RO, "bad previously spec";
2421 my $r = system qw(git check-ref-format), $1;
2422 die "bad previously ref spec ($r)" if $r;
2423 $previously{$1} = $2;
2428 sub i_resp_want ($) {
2430 die "$keyword ?" if $i_wanted{$keyword}++;
2431 my @localpaths = i_method "i_want", $keyword;
2432 printdebug "[[ $keyword @localpaths\n";
2433 foreach my $localpath (@localpaths) {
2434 protocol_send_file \*RI, $localpath;
2436 print RI "files-end\n" or die $!;
2439 our ($i_clogp, $i_version, $i_tag, $i_dscfn, $i_changesfn);
2441 sub i_localname_parsed_changelog {
2442 return "remote-changelog.822";
2444 sub i_file_parsed_changelog {
2445 ($i_clogp, $i_version, $i_tag, $i_dscfn) =
2446 push_parse_changelog "$i_tmp/remote-changelog.822";
2447 die if $i_dscfn =~ m#/|^\W#;
2450 sub i_localname_dsc {
2451 defined $i_dscfn or badproto \*RO, "dsc (before parsed-changelog)";
2456 sub i_localname_changes {
2457 defined $i_dscfn or badproto \*RO, "dsc (before parsed-changelog)";
2458 $i_changesfn = $i_dscfn;
2459 $i_changesfn =~ s/\.dsc$/_dgit.changes/ or die;
2460 return $i_changesfn;
2462 sub i_file_changes { }
2464 sub i_want_signed_tag {
2465 printdebug Dumper(\%i_param, $i_dscfn);
2466 defined $i_param{'head'} && defined $i_dscfn && defined $i_clogp
2467 && defined $i_param{'csuite'}
2468 or badproto \*RO, "premature desire for signed-tag";
2469 my $head = $i_param{'head'};
2470 die if $head =~ m/[^0-9a-f]/ || $head !~ m/^../;
2472 die unless $i_param{'csuite'} =~ m/^$suite_re$/;
2474 push_parse_dsc $i_dscfn, 'remote dsc', $i_version;
2477 push_mktag $head, $i_clogp, $i_tag,
2479 $i_changesfn, 'remote changes',
2480 sub { "tag$_[0]"; };
2485 sub i_want_signed_dsc_changes {
2486 rename "$i_dscfn.tmp","$i_dscfn" or die "$i_dscfn $!";
2487 sign_changes $i_changesfn;
2488 return ($i_dscfn, $i_changesfn);
2491 #---------- building etc. ----------
2497 #----- `3.0 (quilt)' handling -----
2499 our $fakeeditorenv = 'DGIT_FAKE_EDITOR_QUILT';
2501 sub quiltify_dpkg_commit ($$$;$) {
2502 my ($patchname,$author,$msg, $xinfo) = @_;
2506 my $descfn = ".git/dgit/quilt-description.tmp";
2507 open O, '>', $descfn or die "$descfn: $!";
2510 $msg =~ s/^\s+$/ ./mg;
2511 print O <<END or die $!;
2521 local $ENV{'EDITOR'} = cmdoutput qw(realpath --), $0;
2522 local $ENV{'VISUAL'} = $ENV{'EDITOR'};
2523 local $ENV{$fakeeditorenv} = cmdoutput qw(realpath --), $descfn;
2524 runcmd @dpkgsource, qw(--commit .), $patchname;
2528 sub quiltify_trees_differ ($$;$$) {
2529 my ($x,$y,$finegrained,$ignorenamesr) = @_;
2530 # returns true iff the two tree objects differ other than in debian/
2531 # with $finegrained,
2532 # returns bitmask 01 - differ in upstream files except .gitignore
2533 # 02 - differ in .gitignore
2534 # if $ignorenamesr is defined, $ingorenamesr->{$fn}
2535 # is set for each modified .gitignore filename $fn
2537 my @cmd = (@git, qw(diff-tree --name-only -z));
2538 push @cmd, qw(-r) if $finegrained;
2540 my $diffs= cmdoutput @cmd;
2542 foreach my $f (split /\0/, $diffs) {
2543 next if $f =~ m#^debian(?:/.*)?$#s;
2544 my $isignore = $f =~ m#^(?:.*/)?.gitignore$#s;
2545 $r |= $isignore ? 02 : 01;
2546 $ignorenamesr->{$f}=1 if $ignorenamesr && $isignore;
2548 printdebug "quiltify_trees_differ $x $y => $r\n";
2552 sub quiltify_tree_sentinelfiles ($) {
2553 # lists the `sentinel' files present in the tree
2555 my $r = cmdoutput @git, qw(ls-tree --name-only), $x,
2556 qw(-- debian/rules debian/control);
2561 sub quiltify_splitbrain_needed () {
2562 if (!$split_brain) {
2563 progress "creating dgit view";
2564 runcmd @git, qw(checkout -q -b dgit-view);
2569 sub quiltify_splitbrain ($$$$$$) {
2570 my ($clogp, $unapplied, $headref, $diffbits,
2571 $editedignores, $cachekey) = @_;
2572 if ($quilt_mode !~ m/gbp|dpm/) {
2573 # treat .gitignore just like any other upstream file
2574 $diffbits = { %$diffbits };
2575 $_ = !!$_ foreach values %$diffbits;
2577 # We would like any commits we generate to be reproducible
2578 my @authline = clogp_authline($clogp);
2579 local $ENV{GIT_COMMITTER_NAME} = $authline[0];
2580 local $ENV{GIT_COMMITTER_EMAIL} = $authline[1];
2581 local $ENV{GIT_COMMITTER_DATE} = $authline[2];
2583 if ($quilt_mode =~ m/gbp|unapplied/ &&
2584 ($diffbits->{H2O} & 01)) {
2586 "--quilt=$quilt_mode specified, implying patches-unapplied git tree\n".
2587 " but git tree differs from orig in upstream files.";
2588 if (!stat_exists "debian/patches") {
2590 "\n ... debian/patches is missing; perhaps this is a patch queue branch?";
2594 if ($quilt_mode =~ m/gbp|unapplied/ &&
2595 ($diffbits->{O2A} & 01)) { # some patches
2596 quiltify_splitbrain_needed();
2597 progress "creating patches-applied version using gbp pq";
2598 runcmd shell_cmd 'exec >/dev/null', @gbp, qw(pq import);
2599 # gbp pq import creates a fresh branch; push back to dgit-view
2600 runcmd @git, qw(update-ref refs/heads/dgit-view HEAD);
2601 runcmd @git, qw(checkout -q dgit-view);
2603 if (($diffbits->{H2O} & 02) && # user has modified .gitignore
2604 !($diffbits->{O2A} & 02)) { # patches do not change .gitignore
2605 quiltify_splitbrain_needed();
2606 progress "creating patch to represent .gitignore changes";
2607 ensuredir "debian/patches";
2608 my $gipatch = "debian/patches/auto-gitignore";
2609 open GIPATCH, ">>", "$gipatch" or die "$gipatch: $!";
2610 stat GIPATCH or die "$gipatch: $!";
2611 fail "$gipatch already exists; but want to create it".
2612 " to record .gitignore changes" if (stat _)[7];
2613 print GIPATCH <<END or die "$gipatch: $!";
2614 Subject: Update .gitignore from Debian packaging branch
2616 The Debian packaging git branch contains these updates to the upstream
2617 .gitignore file(s). This patch is autogenerated, to provide these
2618 updates to users of the official Debian archive view of the package.
2620 [dgit version $our_version]
2623 close GIPATCH or die "$gipatch: $!";
2624 runcmd shell_cmd "exec >>$gipatch", @git, qw(diff),
2625 $unapplied, $headref, "--", sort keys %$editedignores;
2626 open SERIES, "+>>", "debian/patches/series" or die $!;
2627 defined seek SERIES, -1, 2 or $!==EINVAL or die $!;
2629 defined read SERIES, $newline, 1 or die $!;
2630 print SERIES "\n" or die $! unless $newline eq "\n";
2631 print SERIES "auto-gitignore\n" or die $!;
2632 close SERIES or die $!;
2633 runcmd @git, qw(add -- debian/patches/series), $gipatch;
2634 commit_admin "Commit patch to update .gitignore";
2637 my $dgitview = git_rev_parse 'refs/heads/dgit-view';
2639 changedir '../../../..';
2640 ensuredir ".git/logs/refs/dgit-intern";
2641 my $makelogfh = new IO::File ".git/logs/refs/$splitbraincache", '>>'
2643 runcmd @git, qw(update-ref -m), $cachekey, "refs/$splitbraincache",
2646 progress "created dgit view (commit id $dgitview)";
2648 changedir '.git/dgit/unpack/work';
2651 sub quiltify ($$$$) {
2652 my ($clogp,$target,$oldtiptree,$failsuggestion) = @_;
2654 # Quilt patchification algorithm
2656 # We search backwards through the history of the main tree's HEAD
2657 # (T) looking for a start commit S whose tree object is identical
2658 # to to the patch tip tree (ie the tree corresponding to the
2659 # current dpkg-committed patch series). For these purposes
2660 # `identical' disregards anything in debian/ - this wrinkle is
2661 # necessary because dpkg-source treates debian/ specially.
2663 # We can only traverse edges where at most one of the ancestors'
2664 # trees differs (in changes outside in debian/). And we cannot
2665 # handle edges which change .pc/ or debian/patches. To avoid
2666 # going down a rathole we avoid traversing edges which introduce
2667 # debian/rules or debian/control. And we set a limit on the
2668 # number of edges we are willing to look at.
2670 # If we succeed, we walk forwards again. For each traversed edge
2671 # PC (with P parent, C child) (starting with P=S and ending with
2672 # C=T) to we do this:
2674 # - dpkg-source --commit with a patch name and message derived from C
2675 # After traversing PT, we git commit the changes which
2676 # should be contained within debian/patches.
2678 # The search for the path S..T is breadth-first. We maintain a
2679 # todo list containing search nodes. A search node identifies a
2680 # commit, and looks something like this:
2682 # Commit => $git_commit_id,
2683 # Child => $c, # or undef if P=T
2684 # Whynot => $reason_edge_PC_unsuitable, # in @nots only
2685 # Nontrivial => true iff $p..$c has relevant changes
2692 my %considered; # saves being exponential on some weird graphs
2694 my $t_sentinels = quiltify_tree_sentinelfiles $target;
2697 my ($search,$whynot) = @_;
2698 printdebug " search NOT $search->{Commit} $whynot\n";
2699 $search->{Whynot} = $whynot;
2700 push @nots, $search;
2701 no warnings qw(exiting);
2710 my $c = shift @todo;
2711 next if $considered{$c->{Commit}}++;
2713 $not->($c, "maximum search space exceeded") if --$max_work <= 0;
2715 printdebug "quiltify investigate $c->{Commit}\n";
2718 if (!quiltify_trees_differ $c->{Commit}, $oldtiptree) {
2719 printdebug " search finished hooray!\n";
2724 if ($quilt_mode eq 'nofix') {
2725 fail "quilt fixup required but quilt mode is \`nofix'\n".
2726 "HEAD commit $c->{Commit} differs from tree implied by ".
2727 " debian/patches (tree object $oldtiptree)";
2729 if ($quilt_mode eq 'smash') {
2730 printdebug " search quitting smash\n";
2734 my $c_sentinels = quiltify_tree_sentinelfiles $c->{Commit};
2735 $not->($c, "has $c_sentinels not $t_sentinels")
2736 if $c_sentinels ne $t_sentinels;
2738 my $commitdata = cmdoutput @git, qw(cat-file commit), $c->{Commit};
2739 $commitdata =~ m/\n\n/;
2741 my @parents = ($commitdata =~ m/^parent (\w+)$/gm);
2742 @parents = map { { Commit => $_, Child => $c } } @parents;
2744 $not->($c, "root commit") if !@parents;
2746 foreach my $p (@parents) {
2747 $p->{Nontrivial}= quiltify_trees_differ $p->{Commit},$c->{Commit};
2749 my $ndiffers = grep { $_->{Nontrivial} } @parents;
2750 $not->($c, "merge ($ndiffers nontrivial parents)") if $ndiffers > 1;
2752 foreach my $p (@parents) {
2753 printdebug "considering C=$c->{Commit} P=$p->{Commit}\n";
2755 my @cmd= (@git, qw(diff-tree -r --name-only),
2756 $p->{Commit},$c->{Commit}, qw(-- debian/patches .pc));
2757 my $patchstackchange = cmdoutput @cmd;
2758 if (length $patchstackchange) {
2759 $patchstackchange =~ s/\n/,/g;
2760 $not->($p, "changed $patchstackchange");
2763 printdebug " search queue P=$p->{Commit} ",
2764 ($p->{Nontrivial} ? "NT" : "triv"),"\n";
2770 printdebug "quiltify want to smash\n";
2773 my $x = $_[0]{Commit};
2774 $x =~ s/(.*?[0-9a-z]{8})[0-9a-z]*$/$1/;
2777 my $reportnot = sub {
2779 my $s = $abbrev->($notp);
2780 my $c = $notp->{Child};
2781 $s .= "..".$abbrev->($c) if $c;
2782 $s .= ": ".$notp->{Whynot};
2785 if ($quilt_mode eq 'linear') {
2786 print STDERR "$us: quilt fixup cannot be linear. Stopped at:\n";
2787 foreach my $notp (@nots) {
2788 print STDERR "$us: ", $reportnot->($notp), "\n";
2790 print STDERR "$us: $_\n" foreach @$failsuggestion;
2791 fail "quilt fixup naive history linearisation failed.\n".
2792 "Use dpkg-source --commit by hand; or, --quilt=smash for one ugly patch";
2793 } elsif ($quilt_mode eq 'smash') {
2794 } elsif ($quilt_mode eq 'auto') {
2795 progress "quilt fixup cannot be linear, smashing...";
2797 die "$quilt_mode ?";
2802 my $msg = cmdoutput @git, qw(log), "-n$ncommits";
2804 quiltify_dpkg_commit "auto-$version-$target-$time",
2805 (getfield $clogp, 'Maintainer'),
2806 "Automatically generated patch ($clogp->{Version})\n".
2807 "Last (up to) $ncommits git changes, FYI:\n\n". $msg;
2811 progress "quiltify linearisation planning successful, executing...";
2813 for (my $p = $sref_S;
2814 my $c = $p->{Child};
2816 printdebug "quiltify traverse $p->{Commit}..$c->{Commit}\n";
2817 next unless $p->{Nontrivial};
2819 my $cc = $c->{Commit};
2821 my $commitdata = cmdoutput @git, qw(cat-file commit), $cc;
2822 $commitdata =~ m/\n\n/ or die "$c ?";
2825 $commitdata =~ m/^author (.*) \d+ [-+0-9]+$/m or die "$cc ?";
2828 $msg =~ s/^(.*)\n*/$1\n/ or die "$cc $msg ?";
2831 my $patchname = $title;
2832 $patchname =~ s/[.:]$//;
2833 $patchname =~ y/ A-Z/-a-z/;
2834 $patchname =~ y/-a-z0-9_.+=~//cd;
2835 $patchname =~ s/^\W/x-$&/;
2836 $patchname = substr($patchname,0,40);
2839 stat "debian/patches/$patchname$index";
2841 $!==ENOENT or die "$patchname$index $!";
2843 runcmd @git, qw(checkout -q), $cc;
2845 # We use the tip's changelog so that dpkg-source doesn't
2846 # produce complaining messages from dpkg-parsechangelog. None
2847 # of the information dpkg-source gets from the changelog is
2848 # actually relevant - it gets put into the original message
2849 # which dpkg-source provides our stunt editor, and then
2851 runcmd @git, qw(checkout -q), $target, qw(debian/changelog);
2853 quiltify_dpkg_commit "$patchname$index", $author, $msg,
2854 "X-Dgit-Generated: $clogp->{Version} $cc\n";
2856 runcmd @git, qw(checkout -q), $cc, qw(debian/changelog);
2859 runcmd @git, qw(checkout -q master);
2862 sub build_maybe_quilt_fixup () {
2863 my ($format,$fopts) = get_source_format;
2864 return unless madformat $format;
2867 check_for_vendor_patches();
2869 my $clogp = parsechangelog();
2870 my $headref = git_rev_parse('HEAD');
2875 my $upstreamversion=$version;
2876 $upstreamversion =~ s/-[^-]*$//;
2878 if ($fopts->{'single-debian-patch'}) {
2879 quilt_fixup_singlepatch($clogp, $headref, $upstreamversion);
2881 quilt_fixup_multipatch($clogp, $headref, $upstreamversion);
2884 die 'bug' if $split_brain && !$need_split_build_invocation;
2886 changedir '../../../..';
2887 runcmd_ordryrun_local
2888 @git, qw(pull --ff-only -q .git/dgit/unpack/work master);
2891 sub quilt_fixup_mkwork ($) {
2894 mkdir "work" or die $!;
2896 mktree_in_ud_here();
2897 runcmd @git, qw(reset -q --hard), $headref;
2900 sub quilt_fixup_linkorigs ($$) {
2901 my ($upstreamversion, $fn) = @_;
2902 # calls $fn->($leafname);
2904 foreach my $f (<../../../../*>) { #/){
2905 my $b=$f; $b =~ s{.*/}{};
2907 local ($debuglevel) = $debuglevel-1;
2908 printdebug "QF linkorigs $b, $f ?\n";
2910 next unless is_orig_file $b, srcfn $upstreamversion,'';
2911 printdebug "QF linkorigs $b, $f Y\n";
2912 link_ltarget $f, $b or die "$b $!";
2917 sub quilt_fixup_delete_pc () {
2918 runcmd @git, qw(rm -rqf .pc);
2919 commit_admin "Commit removal of .pc (quilt series tracking data)";
2922 sub quilt_fixup_singlepatch ($$$) {
2923 my ($clogp, $headref, $upstreamversion) = @_;
2925 progress "starting quiltify (single-debian-patch)";
2927 # dpkg-source --commit generates new patches even if
2928 # single-debian-patch is in debian/source/options. In order to
2929 # get it to generate debian/patches/debian-changes, it is
2930 # necessary to build the source package.
2932 quilt_fixup_linkorigs($upstreamversion, sub { });
2933 quilt_fixup_mkwork($headref);
2935 rmtree("debian/patches");
2937 runcmd @dpkgsource, qw(-b .);
2939 runcmd @dpkgsource, qw(-x), (srcfn $version, ".dsc");
2940 rename srcfn("$upstreamversion", "/debian/patches"),
2941 "work/debian/patches";
2944 commit_quilty_patch();
2949 sub quilt_fixup_multipatch ($$$) {
2950 my ($clogp, $headref, $upstreamversion) = @_;
2952 progress "starting quiltify (multiple patches, $quilt_mode mode)";
2955 # - honour any existing .pc in case it has any strangeness
2956 # - determine the git commit corresponding to the tip of
2957 # the patch stack (if there is one)
2958 # - if there is such a git commit, convert each subsequent
2959 # git commit into a quilt patch with dpkg-source --commit
2960 # - otherwise convert all the differences in the tree into
2961 # a single git commit
2965 # Our git tree doesn't necessarily contain .pc. (Some versions of
2966 # dgit would include the .pc in the git tree.) If there isn't
2967 # one, we need to generate one by unpacking the patches that we
2970 # We first look for a .pc in the git tree. If there is one, we
2971 # will use it. (This is not the normal case.)
2973 # Otherwise need to regenerate .pc so that dpkg-source --commit
2974 # can work. We do this as follows:
2975 # 1. Collect all relevant .orig from parent directory
2976 # 2. Generate a debian.tar.gz out of
2977 # debian/{patches,rules,source/format,source/options}
2978 # 3. Generate a fake .dsc containing just these fields:
2979 # Format Source Version Files
2980 # 4. Extract the fake .dsc
2981 # Now the fake .dsc has a .pc directory.
2982 # (In fact we do this in every case, because in future we will
2983 # want to search for a good base commit for generating patches.)
2985 # Then we can actually do the dpkg-source --commit
2986 # 1. Make a new working tree with the same object
2987 # store as our main tree and check out the main
2989 # 2. Copy .pc from the fake's extraction, if necessary
2990 # 3. Run dpkg-source --commit
2991 # 4. If the result has changes to debian/, then
2992 # - git-add them them
2993 # - git-add .pc if we had a .pc in-tree
2995 # 5. If we had a .pc in-tree, delete it, and git-commit
2996 # 6. Back in the main tree, fast forward to the new HEAD
2998 # Another situation we may have to cope with is gbp-style
2999 # patches-unapplied trees.
3001 # We would want to detect these, so we know to escape into
3002 # quilt_fixup_gbp. However, this is in general not possible.
3003 # Consider a package with a one patch which the dgit user reverts
3004 # (with git-revert or the moral equivalent).
3006 # That is indistinguishable in contents from a patches-unapplied
3007 # tree. And looking at the history to distinguish them is not
3008 # useful because the user might have made a confusing-looking git
3009 # history structure (which ought to produce an error if dgit can't
3010 # cope, not a silent reintroduction of an unwanted patch).
3012 # So gbp users will have to pass an option. But we can usually
3013 # detect their failure to do so: if the tree is not a clean
3014 # patches-applied tree, quilt linearisation fails, but the tree
3015 # _is_ a clean patches-unapplied tree, we can suggest that maybe
3016 # they want --quilt=unapplied.
3018 # To help detect this, when we are extracting the fake dsc, we
3019 # first extract it with --skip-patches, and then apply the patches
3020 # afterwards with dpkg-source --before-build. That lets us save a
3021 # tree object corresponding to .origs.
3023 my $fakeversion="$upstreamversion-~~DGITFAKE";
3025 my $fakedsc=new IO::File 'fake.dsc', '>' or die $!;
3026 print $fakedsc <<END or die $!;
3029 Version: $fakeversion
3033 my $dscaddfile=sub {
3036 my $md = new Digest::MD5;
3038 my $fh = new IO::File $b, '<' or die "$b $!";
3043 print $fakedsc " ".$md->hexdigest." $size $b\n" or die $!;
3046 quilt_fixup_linkorigs($upstreamversion, $dscaddfile);
3048 my @files=qw(debian/source/format debian/rules
3049 debian/control debian/changelog);
3050 foreach my $maybe (qw(debian/patches debian/source/options
3051 debian/tests/control)) {
3052 next unless stat_exists "../../../$maybe";
3053 push @files, $maybe;
3056 my $debtar= srcfn $fakeversion,'.debian.tar.gz';
3057 runcmd qw(env GZIP=-1 tar -zcf), "./$debtar", qw(-C ../../..), @files;
3059 $dscaddfile->($debtar);
3060 close $fakedsc or die $!;
3062 my $splitbrain_cachekey;
3063 if (quiltmode_splitbrain()) {
3064 # we look in the reflog of dgit-intern/quilt-cache
3065 # we look for an entry whose message is the key for the cache lookup
3066 my @cachekey = (qw(dgit), $our_version);
3067 push @cachekey, $upstreamversion;
3068 push @cachekey, $quilt_mode;
3069 push @cachekey, $headref;
3071 push @cachekey, hashfile('fake.dsc');
3073 my $srcshash = Digest::SHA->new(256);
3074 my %sfs = ( %INC, '$0(dgit)' => $0 );
3075 foreach my $sfk (sort keys %sfs) {
3076 $srcshash->add($sfk," ");
3077 $srcshash->add(hashfile($sfs{$sfk}));
3078 $srcshash->add("\n");
3080 push @cachekey, $srcshash->hexdigest();
3081 $splitbrain_cachekey = "@cachekey";
3083 my @cmd = (@git, qw(reflog), '--pretty=format:%H %gs',
3085 printdebug "splitbrain cachekey $splitbrain_cachekey\n";
3086 debugcmd "|(probably)",@cmd;
3087 my $child = open GC, "-|"; defined $child or die $!;
3089 chdir '../../..' or die $!;
3090 if (!stat ".git/logs/refs/$splitbraincache") {
3091 $! == ENOENT or die $!;
3092 printdebug ">(no reflog)\n";
3099 printdebug ">| ", $_, "\n" if $debuglevel > 1;
3100 next unless m/^(\w+) (\S.*\S)$/ && $2 eq $splitbrain_cachekey;
3103 quilt_fixup_mkwork($headref);
3104 if ($cachehit ne $headref) {
3105 progress "quilt fixup ($quilt_mode mode) found cached tree";
3106 runcmd @git, qw(checkout -q -b dgit-view), $cachehit;
3110 progress "quilt fixup ($quilt_mode mode)".
3111 " found cached indication that no changes needed";
3114 die $! if GC->error;
3115 failedcmd unless close GC;
3117 printdebug "splitbrain cache miss\n";
3121 'exec dpkg-source --no-check --skip-patches -x fake.dsc >/dev/null';
3123 my $fakexdir= $package.'-'.(stripepoch $upstreamversion);
3124 rename $fakexdir, "fake" or die "$fakexdir $!";
3128 remove_stray_gits();
3129 mktree_in_ud_here();
3133 runcmd @git, qw(add -Af .);
3134 my $unapplied=git_write_tree();
3135 printdebug "fake orig tree object $unapplied\n";
3140 'exec dpkg-source --before-build . >/dev/null';
3144 quilt_fixup_mkwork($headref);
3147 if (stat_exists ".pc") {
3149 progress "Tree already contains .pc - will use it then delete it.";
3152 rename '../fake/.pc','.pc' or die $!;
3155 changedir '../fake';
3157 runcmd @git, qw(add -Af .);
3158 my $oldtiptree=git_write_tree();
3159 printdebug "fake o+d/p tree object $unapplied\n";
3160 changedir '../work';
3163 # We calculate some guesswork now about what kind of tree this might
3164 # be. This is mostly for error reporting.
3169 # O = orig, without patches applied
3170 # A = "applied", ie orig with H's debian/patches applied
3171 H2O => quiltify_trees_differ($headref, $unapplied, 1,\%editedignores),
3172 H2A => quiltify_trees_differ($headref, $oldtiptree,1),
3173 O2A => quiltify_trees_differ($unapplied,$oldtiptree,1),
3177 foreach my $b (qw(01 02)) {
3178 foreach my $v (qw(H2O O2A H2A)) {
3179 push @dl, ($diffbits->{$v} & $b) ? '##' : '==';
3182 printdebug "differences \@dl @dl.\n";
3185 "$us: quilt differences: src: %s orig %s gitignores: %s orig %s\n".
3186 "$us: quilt differences: HEAD %s o+d/p HEAD %s o+d/p",
3187 $dl[0], $dl[1], $dl[3], $dl[4],
3191 if (!($diffbits->{H2O} & $diffbits->{O2A})) {
3192 push @failsuggestion, "This might be a patches-unapplied branch.";
3193 } elsif (!($diffbits->{H2A} & $diffbits->{O2A})) {
3194 push @failsuggestion, "This might be a patches-applied branch.";
3196 push @failsuggestion, "Maybe you need to specify one of".
3197 " --quilt=gbp --quilt=dpm --quilt=unapplied ?";
3199 if (quiltmode_splitbrain()) {
3200 quiltify_splitbrain($clogp, $unapplied, $headref,
3201 $diffbits, \%editedignores,
3202 $splitbrain_cachekey);
3206 quiltify($clogp,$headref,$oldtiptree,\@failsuggestion);
3208 if (!open P, '>>', ".pc/applied-patches") {
3209 $!==&ENOENT or die $!;
3214 commit_quilty_patch();
3216 if ($mustdeletepc) {
3217 quilt_fixup_delete_pc();
3221 sub quilt_fixup_editor () {
3222 my $descfn = $ENV{$fakeeditorenv};
3223 my $editing = $ARGV[$#ARGV];
3224 open I1, '<', $descfn or die "$descfn: $!";
3225 open I2, '<', $editing or die "$editing: $!";
3226 unlink $editing or die "$editing: $!";
3227 open O, '>', $editing or die "$editing: $!";
3228 while (<I1>) { print O or die $!; } I1->error and die $!;
3231 $copying ||= m/^\-\-\- /;
3232 next unless $copying;
3235 I2->error and die $!;
3240 sub maybe_apply_patches_dirtily () {
3241 return unless $quilt_mode =~ m/gbp|unapplied/;
3242 print STDERR <<END or die $!;
3244 dgit: Building, or cleaning with rules target, in patches-unapplied tree.
3245 dgit: Have to apply the patches - making the tree dirty.
3246 dgit: (Consider specifying --clean=git and (or) using dgit sbuild.)
3249 $patches_applied_dirtily = 01;
3250 $patches_applied_dirtily |= 02 unless stat_exists '.pc';
3251 runcmd qw(dpkg-source --before-build .);
3254 sub maybe_unapply_patches_again () {
3255 progress "dgit: Unapplying patches again to tidy up the tree."
3256 if $patches_applied_dirtily;
3257 runcmd qw(dpkg-source --after-build .)
3258 if $patches_applied_dirtily & 01;
3260 if $patches_applied_dirtily & 02;
3263 #----- other building -----
3265 our $clean_using_builder;
3266 # ^ tree is to be cleaned by dpkg-source's builtin idea that it should
3267 # clean the tree before building (perhaps invoked indirectly by
3268 # whatever we are using to run the build), rather than separately
3269 # and explicitly by us.
3272 return if $clean_using_builder;
3273 if ($cleanmode eq 'dpkg-source') {
3274 maybe_apply_patches_dirtily();
3275 runcmd_ordryrun_local @dpkgbuildpackage, qw(-T clean);
3276 } elsif ($cleanmode eq 'dpkg-source-d') {
3277 maybe_apply_patches_dirtily();
3278 runcmd_ordryrun_local @dpkgbuildpackage, qw(-d -T clean);
3279 } elsif ($cleanmode eq 'git') {
3280 runcmd_ordryrun_local @git, qw(clean -xdf);
3281 } elsif ($cleanmode eq 'git-ff') {
3282 runcmd_ordryrun_local @git, qw(clean -xdff);
3283 } elsif ($cleanmode eq 'check') {
3284 my $leftovers = cmdoutput @git, qw(clean -xdn);
3285 if (length $leftovers) {
3286 print STDERR $leftovers, "\n" or die $!;
3287 fail "tree contains uncommitted files and --clean=check specified";
3289 } elsif ($cleanmode eq 'none') {
3296 badusage "clean takes no additional arguments" if @ARGV;
3299 maybe_unapply_patches_again();
3304 badusage "-p is not allowed when building" if defined $package;
3307 my $clogp = parsechangelog();
3308 $isuite = getfield $clogp, 'Distribution';
3309 $package = getfield $clogp, 'Source';
3310 $version = getfield $clogp, 'Version';
3311 build_maybe_quilt_fixup();
3313 my $pat = changespat $version;
3314 foreach my $f (glob "$buildproductsdir/$pat") {
3316 unlink $f or fail "remove old changes file $f: $!";
3318 progress "would remove $f";
3324 sub changesopts_initial () {
3325 my @opts =@changesopts[1..$#changesopts];
3328 sub changesopts_version () {
3329 if (!defined $changes_since_version) {
3330 my @vsns = archive_query('archive_query');
3331 my @quirk = access_quirk();
3332 if ($quirk[0] eq 'backports') {
3333 local $isuite = $quirk[2];
3335 canonicalise_suite();
3336 push @vsns, archive_query('archive_query');
3339 @vsns = map { $_->[0] } @vsns;
3340 @vsns = sort { -version_compare($a, $b) } @vsns;
3341 $changes_since_version = $vsns[0];
3342 progress "changelog will contain changes since $vsns[0]";
3344 $changes_since_version = '_';
3345 progress "package seems new, not specifying -v<version>";
3348 if ($changes_since_version ne '_') {
3349 return ("-v$changes_since_version");
3355 sub changesopts () {
3356 return (changesopts_initial(), changesopts_version());
3359 sub massage_dbp_args ($;$) {
3360 my ($cmd,$xargs) = @_;
3363 # - if we're going to split the source build out so we can
3364 # do strange things to it, massage the arguments to dpkg-buildpackage
3365 # so that the main build doessn't build source (or add an argument
3366 # to stop it building source by default).
3368 # - add -nc to stop dpkg-source cleaning the source tree,
3369 # unless we're not doing a split build and want dpkg-source
3370 # as cleanmode, in which case we can do nothing
3373 # 0 - source will NOT need to be built separately by caller
3374 # +1 - source will need to be built separately by caller
3375 # +2 - source will need to be built separately by caller AND
3376 # dpkg-buildpackage should not in fact be run at all!
3377 debugcmd '#massaging#', @$cmd if $debuglevel>1;
3378 #print STDERR "MASS0 ",Dumper($cmd, $xargs, $need_split_build_invocation);
3379 if ($cleanmode eq 'dpkg-source' && !$need_split_build_invocation) {
3380 $clean_using_builder = 1;
3383 # -nc has the side effect of specifying -b if nothing else specified
3384 # and some combinations of -S, -b, et al, are errors, rather than
3385 # later simply overriding earlie. So we need to:
3386 # - search the command line for these options
3387 # - pick the last one
3388 # - perhaps add our own as a default
3389 # - perhaps adjust it to the corresponding non-source-building version
3391 foreach my $l ($cmd, $xargs) {
3393 @$l = grep { !(m/^-[SgGFABb]$/s and $dmode=$_) } @$l;
3396 #print STDERR "MASS1 ",Dumper($cmd, $xargs, $dmode);
3398 if ($need_split_build_invocation) {
3399 printdebug "massage split $dmode.\n";
3400 $r = $dmode =~ m/[S]/ ? +2 :
3401 $dmode =~ y/gGF/ABb/ ? +1 :
3402 $dmode =~ m/[ABb]/ ? 0 :
3405 printdebug "massage done $r $dmode.\n";
3407 #print STDERR "MASS2 ",Dumper($cmd, $xargs, $r);
3412 my @dbp = (@dpkgbuildpackage, qw(-us -uc), changesopts_initial(), @ARGV);
3413 my $wantsrc = massage_dbp_args \@dbp;
3420 push @dbp, changesopts_version();
3421 maybe_apply_patches_dirtily();
3422 runcmd_ordryrun_local @dbp;
3424 maybe_unapply_patches_again();
3425 printdone "build successful\n";
3429 my @dbp = @dpkgbuildpackage;
3431 my $wantsrc = massage_dbp_args \@dbp, \@ARGV;
3434 if (length executable_on_path('git-buildpackage')) {
3435 @cmd = qw(git-buildpackage);
3437 @cmd = qw(gbp buildpackage);
3439 push @cmd, (qw(-us -uc --git-no-sign-tags), "--git-builder=@dbp");
3444 if (!$clean_using_builder) {
3445 push @cmd, '--git-cleaner=true';
3450 unless (grep { m/^--git-debian-branch|^--git-ignore-branch/ } @ARGV) {
3451 canonicalise_suite();
3452 push @cmd, "--git-debian-branch=".lbranch();
3454 push @cmd, changesopts();
3455 maybe_apply_patches_dirtily();
3456 runcmd_ordryrun_local @cmd, @ARGV;
3458 maybe_unapply_patches_again();
3459 printdone "build successful\n";
3461 sub cmd_git_build { cmd_gbp_build(); } # compatibility with <= 1.0
3464 my $our_cleanmode = $cleanmode;
3465 if ($need_split_build_invocation) {
3466 # Pretend that clean is being done some other way. This
3467 # forces us not to try to use dpkg-buildpackage to clean and
3468 # build source all in one go; and instead we run dpkg-source
3469 # (and build_prep() will do the clean since $clean_using_builder
3471 $our_cleanmode = 'ELSEWHERE';
3473 if ($our_cleanmode =~ m/^dpkg-source/) {
3474 # dpkg-source invocation (below) will clean, so build_prep shouldn't
3475 $clean_using_builder = 1;
3478 $sourcechanges = changespat $version,'source';
3480 unlink "../$sourcechanges" or $!==ENOENT
3481 or fail "remove $sourcechanges: $!";
3483 $dscfn = dscfn($version);
3484 if ($our_cleanmode eq 'dpkg-source') {
3485 maybe_apply_patches_dirtily();
3486 runcmd_ordryrun_local @dpkgbuildpackage, qw(-us -uc -S),
3488 } elsif ($our_cleanmode eq 'dpkg-source-d') {
3489 maybe_apply_patches_dirtily();
3490 runcmd_ordryrun_local @dpkgbuildpackage, qw(-us -uc -S -d),
3493 my @cmd = (@dpkgsource, qw(-b --));
3496 runcmd_ordryrun_local @cmd, "work";
3497 my @udfiles = <${package}_*>;
3498 changedir "../../..";
3499 foreach my $f (@udfiles) {
3500 printdebug "source copy, found $f\n";
3503 ($f =~ m/\.debian\.tar(?:\.\w+)$/ &&
3504 $f eq srcfn($version, $&));
3505 printdebug "source copy, found $f - renaming\n";
3506 rename "$ud/$f", "../$f" or $!==ENOENT
3507 or fail "put in place new source file ($f): $!";
3510 my $pwd = must_getcwd();
3511 my $leafdir = basename $pwd;
3513 runcmd_ordryrun_local @cmd, $leafdir;
3516 runcmd_ordryrun_local qw(sh -ec),
3517 'exec >$1; shift; exec "$@"','x',
3518 "../$sourcechanges",
3519 @dpkggenchanges, qw(-S), changesopts();
3523 sub cmd_build_source {
3524 badusage "build-source takes no additional arguments" if @ARGV;
3526 maybe_unapply_patches_again();
3527 printdone "source built, results in $dscfn and $sourcechanges";
3532 my $pat = changespat $version;
3534 my @unwanted = map { s#^\.\./##; $_; } glob "../$pat";
3535 @unwanted = grep { $_ ne changespat $version,'source' } @unwanted;
3536 fail "changes files other than source matching $pat".
3537 " already present (@unwanted);".
3538 " building would result in ambiguity about the intended results"
3543 stat_exists $dscfn or fail "$dscfn (in parent directory): $!";
3544 stat_exists $sourcechanges
3545 or fail "$sourcechanges (in parent directory): $!";
3547 runcmd_ordryrun_local @sbuild, qw(-d), $isuite, @ARGV, $dscfn;
3548 my @changesfiles = glob $pat;
3549 @changesfiles = sort {
3550 ($b =~ m/_source\.changes$/ <=> $a =~ m/_source\.changes$/)
3553 fail "wrong number of different changes files (@changesfiles)"
3554 unless @changesfiles==2;
3555 my $binchanges = parsecontrol($changesfiles[1], "binary changes file");
3556 foreach my $l (split /\n/, getfield $binchanges, 'Files') {
3557 fail "$l found in binaries changes file $binchanges"
3560 runcmd_ordryrun_local @mergechanges, @changesfiles;
3561 my $multichanges = changespat $version,'multi';
3563 stat_exists $multichanges or fail "$multichanges: $!";
3564 foreach my $cf (glob $pat) {
3565 next if $cf eq $multichanges;
3566 rename "$cf", "$cf.inmulti" or fail "$cf\{,.inmulti}: $!";
3569 maybe_unapply_patches_again();
3570 printdone "build successful, results in $multichanges\n" or die $!;
3573 sub cmd_quilt_fixup {
3574 badusage "incorrect arguments to dgit quilt-fixup" if @ARGV;
3575 my $clogp = parsechangelog();
3576 $version = getfield $clogp, 'Version';
3577 $package = getfield $clogp, 'Source';
3580 build_maybe_quilt_fixup();
3583 sub cmd_archive_api_query {
3584 badusage "need only 1 subpath argument" unless @ARGV==1;
3585 my ($subpath) = @ARGV;
3586 my @cmd = archive_api_query_cmd($subpath);
3588 exec @cmd or fail "exec curl: $!\n";
3591 sub cmd_clone_dgit_repos_server {
3592 badusage "need destination argument" unless @ARGV==1;
3593 my ($destdir) = @ARGV;
3594 $package = '_dgit-repos-server';
3595 my @cmd = (@git, qw(clone), access_giturl(), $destdir);
3597 exec @cmd or fail "exec git clone: $!\n";
3600 sub cmd_setup_mergechangelogs {
3601 badusage "no arguments allowed to dgit setup-mergechangelogs" if @ARGV;
3602 setup_mergechangelogs(1);
3605 sub cmd_setup_useremail {
3606 badusage "no arguments allowed to dgit setup-mergechangelogs" if @ARGV;
3610 sub cmd_setup_new_tree {
3611 badusage "no arguments allowed to dgit setup-tree" if @ARGV;
3615 #---------- argument parsing and main program ----------
3618 print "dgit version $our_version\n" or die $!;
3622 our (%valopts_long, %valopts_short);
3625 sub defvalopt ($$$$) {
3626 my ($long,$short,$val_re,$how) = @_;
3627 my $oi = { Long => $long, Short => $short, Re => $val_re, How => $how };
3628 $valopts_long{$long} = $oi;
3629 $valopts_short{$short} = $oi;
3630 # $how subref should:
3631 # do whatever assignemnt or thing it likes with $_[0]
3632 # if the option should not be passed on to remote, @rvalopts=()
3633 # or $how can be a scalar ref, meaning simply assign the value
3636 defvalopt '--since-version', '-v', '[^_]+|_', \$changes_since_version;
3637 defvalopt '--distro', '-d', '.+', \$idistro;
3638 defvalopt '', '-k', '.+', \$keyid;
3639 defvalopt '--existing-package','', '.*', \$existing_package;
3640 defvalopt '--build-products-dir','','.*', \$buildproductsdir;
3641 defvalopt '--clean', '', $cleanmode_re, \$cleanmode;
3642 defvalopt '--quilt', '', $quilt_modes_re, \$quilt_mode;
3644 defvalopt '', '-c', '.*=.*', sub { push @git, '-c', @_; };
3646 defvalopt '', '-C', '.+', sub {
3647 ($changesfile) = (@_);
3648 if ($changesfile =~ s#^(.*)/##) {
3649 $buildproductsdir = $1;
3653 defvalopt '--initiator-tempdir','','.*', sub {
3654 ($initiator_tempdir) = (@_);
3655 $initiator_tempdir =~ m#^/# or
3656 badusage "--initiator-tempdir must be used specify an".
3657 " absolute, not relative, directory."
3663 if (defined $ENV{'DGIT_SSH'}) {
3664 @ssh = string_to_ssh $ENV{'DGIT_SSH'};
3665 } elsif (defined $ENV{'GIT_SSH'}) {
3666 @ssh = ($ENV{'GIT_SSH'});
3674 if (!defined $val) {
3675 badusage "$what needs a value" unless @ARGV;
3677 push @rvalopts, $val;
3679 badusage "bad value \`$val' for $what" unless
3680 $val =~ m/^$oi->{Re}$(?!\n)/s;
3681 my $how = $oi->{How};
3682 if (ref($how) eq 'SCALAR') {
3687 push @ropts, @rvalopts;
3691 last unless $ARGV[0] =~ m/^-/;
3695 if (m/^--dry-run$/) {
3698 } elsif (m/^--damp-run$/) {
3701 } elsif (m/^--no-sign$/) {
3704 } elsif (m/^--help$/) {
3706 } elsif (m/^--version$/) {
3708 } elsif (m/^--new$/) {
3711 } elsif (m/^--([-0-9a-z]+)=(.+)/s &&
3712 ($om = $opts_opt_map{$1}) &&
3716 } elsif (m/^--([-0-9a-z]+):(.*)/s &&
3717 !$opts_opt_cmdonly{$1} &&
3718 ($om = $opts_opt_map{$1})) {
3721 } elsif (m/^--ignore-dirty$/s) {
3724 } elsif (m/^--no-quilt-fixup$/s) {
3726 $quilt_mode = 'nocheck';
3727 } elsif (m/^--no-rm-on-error$/s) {
3730 } elsif (m/^--(no-)?rm-old-changes$/s) {
3733 } elsif (m/^--deliberately-($deliberately_re)$/s) {
3735 push @deliberatelies, $&;
3736 } elsif (m/^--always-split-source-build$/s) {
3737 # undocumented, for testing
3739 $need_split_build_invocation = 1;
3740 } elsif (m/^(--[-0-9a-z]+)(=|$)/ && ($oi = $valopts_long{$1})) {
3741 $val = $2 ? $' : undef; #';
3742 $valopt->($oi->{Long});
3744 badusage "unknown long option \`$_'";
3751 } elsif (s/^-L/-/) {
3754 } elsif (s/^-h/-/) {
3756 } elsif (s/^-D/-/) {
3760 } elsif (s/^-N/-/) {
3765 push @changesopts, $_;
3767 } elsif (s/^-wn$//s) {
3769 $cleanmode = 'none';
3770 } elsif (s/^-wg$//s) {
3773 } elsif (s/^-wgf$//s) {
3775 $cleanmode = 'git-ff';
3776 } elsif (s/^-wd$//s) {
3778 $cleanmode = 'dpkg-source';
3779 } elsif (s/^-wdd$//s) {
3781 $cleanmode = 'dpkg-source-d';
3782 } elsif (s/^-wc$//s) {
3784 $cleanmode = 'check';
3785 } elsif (m/^-[a-zA-Z]/ && ($oi = $valopts_short{$&})) {
3787 $val = undef unless length $val;
3788 $valopt->($oi->{Short});
3791 badusage "unknown short option \`$_'";
3798 sub finalise_opts_opts () {
3799 foreach my $k (keys %opts_opt_map) {
3800 my $om = $opts_opt_map{$k};
3802 my $v = access_cfg("cmd-$k", 'RETURN-UNDEF');
3804 badcfg "cannot set command for $k"
3805 unless length $om->[0];
3809 foreach my $c (access_cfg_cfgs("opts-$k")) {
3810 my $vl = $gitcfg{$c};
3811 printdebug "CL $c ",
3812 ($vl ? join " ", map { shellquote } @$vl : ""),
3813 "\n" if $debuglevel >= 4;
3815 badcfg "cannot configure options for $k"
3816 if $opts_opt_cmdonly{$k};
3817 my $insertpos = $opts_cfg_insertpos{$k};
3818 @$om = ( @$om[0..$insertpos-1],
3820 @$om[$insertpos..$#$om] );
3825 if ($ENV{$fakeeditorenv}) {
3827 quilt_fixup_editor();
3833 print STDERR "DRY RUN ONLY\n" if $dryrun_level > 1;
3834 print STDERR "DAMP RUN - WILL MAKE LOCAL (UNSIGNED) CHANGES\n"
3835 if $dryrun_level == 1;
3837 print STDERR $helpmsg or die $!;
3840 my $cmd = shift @ARGV;
3843 if (!defined $rmchanges) {
3844 local $access_forpush;
3845 $rmchanges = access_cfg_bool(0, 'rm-old-changes');
3848 if (!defined $quilt_mode) {
3849 local $access_forpush;
3850 $quilt_mode = cfg('dgit.force.quilt-mode', 'RETURN-UNDEF')
3851 // access_cfg('quilt-mode', 'RETURN-UNDEF')
3853 $quilt_mode =~ m/^($quilt_modes_re)$/
3854 or badcfg "unknown quilt-mode \`$quilt_mode'";
3858 $need_split_build_invocation ||= quiltmode_splitbrain();
3860 if (!defined $cleanmode) {
3861 local $access_forpush;
3862 $cleanmode = access_cfg('clean-mode', 'RETURN-UNDEF');
3863 $cleanmode //= 'dpkg-source';
3865 badcfg "unknown clean-mode \`$cleanmode'" unless
3866 $cleanmode =~ m/^($cleanmode_re)$(?!\n)/s;
3869 my $fn = ${*::}{"cmd_$cmd"};
3870 $fn or badusage "unknown operation $cmd";