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;
68 our %format_ok = map { $_=>1 } ("1.0","3.0 (native)","3.0 (quilt)");
70 our $suite_re = '[-+.0-9a-z]+';
71 our $cleanmode_re = 'dpkg-source(?:-d)?|git|git-ff|check|none';
73 our $git_authline_re = '^([^<>]+) \<(\S+)\> (\d+ [-+]\d+)$';
74 our $splitbraincache = 'dgit-intern/quilt-cache';
77 our (@dget) = qw(dget);
78 our (@curl) = qw(curl -f);
79 our (@dput) = qw(dput);
80 our (@debsign) = qw(debsign);
82 our (@sbuild) = qw(sbuild);
84 our (@dgit) = qw(dgit);
85 our (@dpkgbuildpackage) = qw(dpkg-buildpackage -i\.git/ -I.git);
86 our (@dpkgsource) = qw(dpkg-source -i\.git/ -I.git);
87 our (@dpkggenchanges) = qw(dpkg-genchanges);
88 our (@mergechanges) = qw(mergechanges -f);
89 our (@gbppq) = qw(gbp-pq);
90 our (@changesopts) = ('');
92 our %opts_opt_map = ('dget' => \@dget, # accept for compatibility
95 'debsign' => \@debsign,
101 'dpkg-source' => \@dpkgsource,
102 'dpkg-buildpackage' => \@dpkgbuildpackage,
103 'dpkg-genchanges' => \@dpkggenchanges,
104 'ch' => \@changesopts,
105 'mergechanges' => \@mergechanges);
107 our %opts_opt_cmdonly = ('gpg' => 1, 'git' => 1);
108 our %opts_cfg_insertpos = map {
110 scalar @{ $opts_opt_map{$_} }
111 } keys %opts_opt_map;
113 sub finalise_opts_opts();
119 our $supplementary_message = '';
120 our $need_split_build_invocation = 0;
121 our $split_brain = 0;
125 print STDERR "! $_\n" foreach $supplementary_message =~ m/^.+$/mg;
128 our $remotename = 'dgit';
129 our @ourdscfield = qw(Dgit Vcs-Dgit-Master);
133 sub lbranch () { return "$branchprefix/$csuite"; }
134 my $lbranch_re = '^refs/heads/'.$branchprefix.'/([^/.]+)$';
135 sub lref () { return "refs/heads/".lbranch(); }
136 sub lrref () { return "refs/remotes/$remotename/".server_branch($csuite); }
137 sub rrref () { return server_ref($csuite); }
139 sub lrfetchrefs () { return "refs/dgit-fetch/$csuite"; }
149 return "${package}_".(stripepoch $vsn).$sfx
154 return srcfn($vsn,".dsc");
157 sub changespat ($;$) {
158 my ($vsn, $arch) = @_;
159 return "${package}_".(stripepoch $vsn)."_".($arch//'*').".changes";
168 foreach my $f (@end) {
170 print STDERR "$us: cleanup: $@" if length $@;
174 sub badcfg { print STDERR "$us: invalid configuration: @_\n"; exit 12; }
176 sub no_such_package () {
177 print STDERR "$us: package $package does not exist in suite $isuite\n";
183 return "+".rrref().":".lrref();
188 printdebug "CD $newdir\n";
189 chdir $newdir or die "chdir: $newdir: $!";
192 sub deliberately ($) {
194 return !!grep { $_ eq "--deliberately-$enquiry" } @deliberatelies;
197 sub deliberately_not_fast_forward () {
198 foreach (qw(not-fast-forward fresh-repo)) {
199 return 1 if deliberately($_) || deliberately("TEST-dgit-only-$_");
203 #---------- remote protocol support, common ----------
205 # remote push initiator/responder protocol:
206 # $ dgit remote-push-build-host <n-rargs> <rargs>... <push-args>...
207 # where <rargs> is <push-host-dir> <supported-proto-vsn>,... ...
208 # < dgit-remote-push-ready <actual-proto-vsn>
210 # > file parsed-changelog
211 # [indicates that output of dpkg-parsechangelog follows]
212 # > data-block NBYTES
213 # > [NBYTES bytes of data (no newline)]
214 # [maybe some more blocks]
226 # [indicates that signed tag is wanted]
227 # < data-block NBYTES
228 # < [NBYTES bytes of data (no newline)]
229 # [maybe some more blocks]
233 # > want signed-dsc-changes
234 # < data-block NBYTES [transfer of signed dsc]
236 # < data-block NBYTES [transfer of signed changes]
244 sub i_child_report () {
245 # Sees if our child has died, and reap it if so. Returns a string
246 # describing how it died if it failed, or undef otherwise.
247 return undef unless $i_child_pid;
248 my $got = waitpid $i_child_pid, WNOHANG;
249 return undef if $got <= 0;
250 die unless $got == $i_child_pid;
251 $i_child_pid = undef;
252 return undef unless $?;
253 return "build host child ".waitstatusmsg();
258 fail "connection lost: $!" if $fh->error;
259 fail "protocol violation; $m not expected";
262 sub badproto_badread ($$) {
264 fail "connection lost: $!" if $!;
265 my $report = i_child_report();
266 fail $report if defined $report;
267 badproto $fh, "eof (reading $wh)";
270 sub protocol_expect (&$) {
271 my ($match, $fh) = @_;
274 defined && chomp or badproto_badread $fh, "protocol message";
282 badproto $fh, "\`$_'";
285 sub protocol_send_file ($$) {
286 my ($fh, $ourfn) = @_;
287 open PF, "<", $ourfn or die "$ourfn: $!";
290 my $got = read PF, $d, 65536;
291 die "$ourfn: $!" unless defined $got;
293 print $fh "data-block ".length($d)."\n" or die $!;
294 print $fh $d or die $!;
296 PF->error and die "$ourfn $!";
297 print $fh "data-end\n" or die $!;
301 sub protocol_read_bytes ($$) {
302 my ($fh, $nbytes) = @_;
303 $nbytes =~ m/^[1-9]\d{0,5}$|^0$/ or badproto \*RO, "bad byte count";
305 my $got = read $fh, $d, $nbytes;
306 $got==$nbytes or badproto_badread $fh, "data block";
310 sub protocol_receive_file ($$) {
311 my ($fh, $ourfn) = @_;
312 printdebug "() $ourfn\n";
313 open PF, ">", $ourfn or die "$ourfn: $!";
315 my ($y,$l) = protocol_expect {
316 m/^data-block (.*)$/ ? (1,$1) :
317 m/^data-end$/ ? (0,) :
321 my $d = protocol_read_bytes $fh, $l;
322 print PF $d or die $!;
327 #---------- remote protocol support, responder ----------
329 sub responder_send_command ($) {
331 return unless $we_are_responder;
332 # called even without $we_are_responder
333 printdebug ">> $command\n";
334 print PO $command, "\n" or die $!;
337 sub responder_send_file ($$) {
338 my ($keyword, $ourfn) = @_;
339 return unless $we_are_responder;
340 printdebug "]] $keyword $ourfn\n";
341 responder_send_command "file $keyword";
342 protocol_send_file \*PO, $ourfn;
345 sub responder_receive_files ($@) {
346 my ($keyword, @ourfns) = @_;
347 die unless $we_are_responder;
348 printdebug "[[ $keyword @ourfns\n";
349 responder_send_command "want $keyword";
350 foreach my $fn (@ourfns) {
351 protocol_receive_file \*PI, $fn;
354 protocol_expect { m/^files-end$/ } \*PI;
357 #---------- remote protocol support, initiator ----------
359 sub initiator_expect (&) {
361 protocol_expect { &$match } \*RO;
364 #---------- end remote code ----------
367 if ($we_are_responder) {
369 responder_send_command "progress ".length($m) or die $!;
370 print PO $m or die $!;
380 $ua = LWP::UserAgent->new();
384 progress "downloading $what...";
385 my $r = $ua->get(@_) or die $!;
386 return undef if $r->code == 404;
387 $r->is_success or fail "failed to fetch $what: ".$r->status_line;
388 return $r->decoded_content(charset => 'none');
391 our ($dscdata,$dscurl,$dsc,$dsc_checked,$skew_warning_vsn);
396 failedcmd @_ if system @_;
399 sub act_local () { return $dryrun_level <= 1; }
400 sub act_scary () { return !$dryrun_level; }
403 if (!$dryrun_level) {
404 progress "dgit ok: @_";
406 progress "would be ok: @_ (but dry run only)";
411 printcmd(\*STDERR,$debugprefix."#",@_);
414 sub runcmd_ordryrun {
422 sub runcmd_ordryrun_local {
431 my ($first_shell, @cmd) = @_;
432 return qw(sh -ec), $first_shell.'; exec "$@"', 'x', @cmd;
435 our $helpmsg = <<END;
437 dgit [dgit-opts] clone [dgit-opts] package [suite] [./dir|/dir]
438 dgit [dgit-opts] fetch|pull [dgit-opts] [suite]
439 dgit [dgit-opts] build [dpkg-buildpackage-opts]
440 dgit [dgit-opts] sbuild [sbuild-opts]
441 dgit [dgit-opts] push [dgit-opts] [suite]
442 dgit [dgit-opts] rpush build-host:build-dir ...
443 important dgit options:
444 -k<keyid> sign tag and package with <keyid> instead of default
445 --dry-run -n do not change anything, but go through the motions
446 --damp-run -L like --dry-run but make local changes, without signing
447 --new -N allow introducing a new package
448 --debug -D increase debug level
449 -c<name>=<value> set git config option (used directly by dgit too)
452 our $later_warning_msg = <<END;
453 Perhaps the upload is stuck in incoming. Using the version from git.
457 print STDERR "$us: @_\n", $helpmsg or die $!;
462 @ARGV or badusage "too few arguments";
463 return scalar shift @ARGV;
467 print $helpmsg or die $!;
471 our $td = $ENV{DGIT_TEST_DUMMY_DIR} || "DGIT_TEST_DUMMY_DIR-unset";
473 our %defcfg = ('dgit.default.distro' => 'debian',
474 'dgit.default.username' => '',
475 'dgit.default.archive-query-default-component' => 'main',
476 'dgit.default.ssh' => 'ssh',
477 'dgit.default.archive-query' => 'madison:',
478 'dgit.default.sshpsql-dbname' => 'service=projectb',
479 'dgit-distro.debian.archive-query' => 'ftpmasterapi:',
480 'dgit-distro.debian.git-check' => 'url',
481 'dgit-distro.debian.git-check-suffix' => '/info/refs',
482 'dgit-distro.debian.new-private-pushers' => 't',
483 'dgit-distro.debian/push.git-url' => '',
484 'dgit-distro.debian/push.git-host' => 'push.dgit.debian.org',
485 'dgit-distro.debian/push.git-user-force' => 'dgit',
486 'dgit-distro.debian/push.git-proto' => 'git+ssh://',
487 'dgit-distro.debian/push.git-path' => '/dgit/debian/repos',
488 'dgit-distro.debian/push.git-create' => 'true',
489 'dgit-distro.debian/push.git-check' => 'ssh-cmd',
490 'dgit-distro.debian.archive-query-url', 'https://api.ftp-master.debian.org/',
491 # 'dgit-distro.debian.archive-query-tls-key',
492 # '/etc/ssl/certs/%HOST%.pem:/etc/dgit/%HOST%.pem',
493 # ^ this does not work because curl is broken nowadays
494 # Fixing #790093 properly will involve providing providing the key
495 # in some pacagke and maybe updating these paths.
497 # 'dgit-distro.debian.archive-query-tls-curl-args',
498 # '--ca-path=/etc/ssl/ca-debian',
499 # ^ this is a workaround but works (only) on DSA-administered machines
500 'dgit-distro.debian.git-url' => 'https://git.dgit.debian.org',
501 'dgit-distro.debian.git-url-suffix' => '',
502 'dgit-distro.debian.upload-host' => 'ftp-master', # for dput
503 'dgit-distro.debian.mirror' => 'http://ftp.debian.org/debian/',
504 'dgit-distro.debian.backports-quirk' => '(squeeze)-backports*',
505 'dgit-distro.debian-backports.mirror' => 'http://backports.debian.org/debian-backports/',
506 'dgit-distro.ubuntu.git-check' => 'false',
507 'dgit-distro.ubuntu.mirror' => 'http://archive.ubuntu.com/ubuntu',
508 'dgit-distro.test-dummy.ssh' => "$td/ssh",
509 'dgit-distro.test-dummy.username' => "alice",
510 'dgit-distro.test-dummy.git-check' => "ssh-cmd",
511 'dgit-distro.test-dummy.git-create' => "ssh-cmd",
512 'dgit-distro.test-dummy.git-url' => "$td/git",
513 'dgit-distro.test-dummy.git-host' => "git",
514 'dgit-distro.test-dummy.git-path' => "$td/git",
515 'dgit-distro.test-dummy.archive-query' => "ftpmasterapi:",
516 'dgit-distro.test-dummy.archive-query-url' => "file://$td/aq/",
517 'dgit-distro.test-dummy.mirror' => "file://$td/mirror/",
518 'dgit-distro.test-dummy.upload-host' => 'test-dummy',
523 sub git_slurp_config () {
524 local ($debuglevel) = $debuglevel-2;
527 my @cmd = (@git, qw(config -z --get-regexp .*));
530 open GITS, "-|", @cmd or failedcmd @cmd;
533 printdebug "=> ", (messagequote $_), "\n";
535 push @{ $gitcfg{$`} }, $'; #';
539 or ($!==0 && $?==256)
543 sub git_get_config ($) {
546 printdebug"C $c ".(defined $l ? messagequote "'$l'" : "undef")."\n"
549 @$l==1 or badcfg "multiple values for $c" if @$l > 1;
555 return undef if $c =~ /RETURN-UNDEF/;
556 my $v = git_get_config($c);
557 return $v if defined $v;
558 my $dv = $defcfg{$c};
559 return $dv if defined $dv;
561 badcfg "need value for one of: @_\n".
562 "$us: distro or suite appears not to be (properly) supported";
565 sub access_basedistro () {
566 if (defined $idistro) {
569 return cfg("dgit-suite.$isuite.distro",
570 "dgit.default.distro");
574 sub access_quirk () {
575 # returns (quirk name, distro to use instead or undef, quirk-specific info)
576 my $basedistro = access_basedistro();
577 my $backports_quirk = cfg("dgit-distro.$basedistro.backports-quirk",
579 if (defined $backports_quirk) {
580 my $re = $backports_quirk;
581 $re =~ s/[^-0-9a-z_\%*()]/\\$&/ig;
583 $re =~ s/\%/([-0-9a-z_]+)/
584 or $re =~ m/[()]/ or badcfg "backports-quirk needs \% or ( )";
585 if ($isuite =~ m/^$re$/) {
586 return ('backports',"$basedistro-backports",$1);
589 return ('none',undef);
594 sub parse_cfg_bool ($$$) {
595 my ($what,$def,$v) = @_;
598 $v =~ m/^[ty1]/ ? 1 :
599 $v =~ m/^[fn0]/ ? 0 :
600 badcfg "$what needs t (true, y, 1) or f (false, n, 0) not \`$v'";
603 sub access_forpush_config () {
604 my $d = access_basedistro();
608 parse_cfg_bool('new-private-pushers', 0,
609 cfg("dgit-distro.$d.new-private-pushers",
612 my $v = cfg("dgit-distro.$d.readonly", 'RETURN-UNDEF');
615 $v =~ m/^[ty1]/ ? 0 : # force readonly, forpush = 0
616 $v =~ m/^[fn0]/ ? 1 : # force nonreadonly, forpush = 1
617 $v =~ m/^[a]/ ? '' : # auto, forpush = ''
618 badcfg "readonly needs t (true, y, 1) or f (false, n, 0) or a (auto)";
621 sub access_forpush () {
622 $access_forpush //= access_forpush_config();
623 return $access_forpush;
627 die "$access_forpush ?" if ($access_forpush // 1) ne 1;
628 badcfg "pushing but distro is configured readonly"
629 if access_forpush_config() eq '0';
631 $supplementary_message = <<'END' unless $we_are_responder;
632 Push failed, before we got started.
633 You can retry the push, after fixing the problem, if you like.
635 finalise_opts_opts();
639 finalise_opts_opts();
642 sub supplementary_message ($) {
644 if (!$we_are_responder) {
645 $supplementary_message = $msg;
647 } elsif ($protovsn >= 3) {
648 responder_send_command "supplementary-message ".length($msg)
650 print PO $msg or die $!;
654 sub access_distros () {
655 # Returns list of distros to try, in order
658 # 0. `instead of' distro name(s) we have been pointed to
659 # 1. the access_quirk distro, if any
660 # 2a. the user's specified distro, or failing that } basedistro
661 # 2b. the distro calculated from the suite }
662 my @l = access_basedistro();
664 my (undef,$quirkdistro) = access_quirk();
665 unshift @l, $quirkdistro;
666 unshift @l, $instead_distro;
667 @l = grep { defined } @l;
669 if (access_forpush()) {
670 @l = map { ("$_/push", $_) } @l;
675 sub access_cfg_cfgs (@) {
678 # The nesting of these loops determines the search order. We put
679 # the key loop on the outside so that we search all the distros
680 # for each key, before going on to the next key. That means that
681 # if access_cfg is called with a more specific, and then a less
682 # specific, key, an earlier distro can override the less specific
683 # without necessarily overriding any more specific keys. (If the
684 # distro wants to override the more specific keys it can simply do
685 # so; whereas if we did the loop the other way around, it would be
686 # impossible to for an earlier distro to override a less specific
687 # key but not the more specific ones without restating the unknown
688 # values of the more specific keys.
691 # We have to deal with RETURN-UNDEF specially, so that we don't
692 # terminate the search prematurely.
694 if (m/RETURN-UNDEF/) { push @rundef, $_; last; }
697 foreach my $d (access_distros()) {
698 push @cfgs, map { "dgit-distro.$d.$_" } @realkeys;
700 push @cfgs, map { "dgit.default.$_" } @realkeys;
707 my (@cfgs) = access_cfg_cfgs(@keys);
708 my $value = cfg(@cfgs);
712 sub access_cfg_bool ($$) {
713 my ($def, @keys) = @_;
714 parse_cfg_bool($keys[0], $def, access_cfg(@keys, 'RETURN-UNDEF'));
717 sub string_to_ssh ($) {
719 if ($spec =~ m/\s/) {
720 return qw(sh -ec), 'exec '.$spec.' "$@"', 'x';
726 sub access_cfg_ssh () {
727 my $gitssh = access_cfg('ssh', 'RETURN-UNDEF');
728 if (!defined $gitssh) {
731 return string_to_ssh $gitssh;
735 sub access_runeinfo ($) {
737 return ": dgit ".access_basedistro()." $info ;";
740 sub access_someuserhost ($) {
742 my $user = access_cfg("$some-user-force", 'RETURN-UNDEF');
743 defined($user) && length($user) or
744 $user = access_cfg("$some-user",'username');
745 my $host = access_cfg("$some-host");
746 return length($user) ? "$user\@$host" : $host;
749 sub access_gituserhost () {
750 return access_someuserhost('git');
753 sub access_giturl (;$) {
755 my $url = access_cfg('git-url','RETURN-UNDEF');
758 my $proto = access_cfg('git-proto', 'RETURN-UNDEF');
759 return undef unless defined $proto;
762 access_gituserhost().
763 access_cfg('git-path');
765 $suffix = access_cfg('git-url-suffix','RETURN-UNDEF');
768 return "$url/$package$suffix";
771 sub parsecontrolfh ($$;$) {
772 my ($fh, $desc, $allowsigned) = @_;
773 our $dpkgcontrolhash_noissigned;
776 my %opts = ('name' => $desc);
777 $opts{allow_pgp}= $allowsigned || !$dpkgcontrolhash_noissigned;
778 $c = Dpkg::Control::Hash->new(%opts);
779 $c->parse($fh,$desc) or die "parsing of $desc failed";
780 last if $allowsigned;
781 last if $dpkgcontrolhash_noissigned;
782 my $issigned= $c->get_option('is_pgp_signed');
783 if (!defined $issigned) {
784 $dpkgcontrolhash_noissigned= 1;
785 seek $fh, 0,0 or die "seek $desc: $!";
786 } elsif ($issigned) {
787 fail "control file $desc is (already) PGP-signed. ".
788 " Note that dgit push needs to modify the .dsc and then".
789 " do the signature itself";
798 my ($file, $desc) = @_;
799 my $fh = new IO::Handle;
800 open $fh, '<', $file or die "$file: $!";
801 my $c = parsecontrolfh($fh,$desc);
802 $fh->error and die $!;
808 my ($dctrl,$field) = @_;
809 my $v = $dctrl->{$field};
810 return $v if defined $v;
811 fail "missing field $field in ".$v->get_option('name');
815 my $c = Dpkg::Control::Hash->new();
816 my $p = new IO::Handle;
817 my @cmd = (qw(dpkg-parsechangelog), @_);
818 open $p, '-|', @cmd or die $!;
820 $?=0; $!=0; close $p or failedcmd @cmd;
826 defined $d or fail "getcwd failed: $!";
832 sub archive_query ($) {
834 my $query = access_cfg('archive-query','RETURN-UNDEF');
835 $query =~ s/^(\w+):// or badcfg "invalid archive-query method \`$query'";
838 { no strict qw(refs); &{"${method}_${proto}"}($proto,$data); }
841 sub pool_dsc_subpath ($$) {
842 my ($vsn,$component) = @_; # $package is implict arg
843 my $prefix = substr($package, 0, $package =~ m/^l/ ? 4 : 1);
844 return "/pool/$component/$prefix/$package/".dscfn($vsn);
847 #---------- `ftpmasterapi' archive query method (nascent) ----------
849 sub archive_api_query_cmd ($) {
851 my @cmd = qw(curl -sS);
852 my $url = access_cfg('archive-query-url');
853 if ($url =~ m#^https://([-.0-9a-z]+)/#) {
855 my $keys = access_cfg('archive-query-tls-key','RETURN-UNDEF') //'';
856 foreach my $key (split /\:/, $keys) {
857 $key =~ s/\%HOST\%/$host/g;
859 fail "for $url: stat $key: $!" unless $!==ENOENT;
862 fail "config requested specific TLS key but do not know".
863 " how to get curl to use exactly that EE key ($key)";
864 # push @cmd, "--cacert", $key, "--capath", "/dev/enoent";
865 # # Sadly the above line does not work because of changes
866 # # to gnutls. The real fix for #790093 may involve
867 # # new curl options.
870 # Fixing #790093 properly will involve providing a value
871 # for this on clients.
872 my $kargs = access_cfg('archive-query-tls-curl-ca-args','RETURN-UNDEF');
873 push @cmd, split / /, $kargs if defined $kargs;
875 push @cmd, $url.$subpath;
881 my ($data, $subpath) = @_;
882 badcfg "ftpmasterapi archive query method takes no data part"
884 my @cmd = archive_api_query_cmd($subpath);
885 my $json = cmdoutput @cmd;
886 return decode_json($json);
889 sub canonicalise_suite_ftpmasterapi () {
890 my ($proto,$data) = @_;
891 my $suites = api_query($data, 'suites');
893 foreach my $entry (@$suites) {
895 my $v = $entry->{$_};
896 defined $v && $v eq $isuite;
898 push @matched, $entry;
900 fail "unknown suite $isuite" unless @matched;
903 @matched==1 or die "multiple matches for suite $isuite\n";
904 $cn = "$matched[0]{codename}";
905 defined $cn or die "suite $isuite info has no codename\n";
906 $cn =~ m/^$suite_re$/ or die "suite $isuite maps to bad codename\n";
908 die "bad ftpmaster api response: $@\n".Dumper(\@matched)
913 sub archive_query_ftpmasterapi () {
914 my ($proto,$data) = @_;
915 my $info = api_query($data, "dsc_in_suite/$isuite/$package");
917 my $digester = Digest::SHA->new(256);
918 foreach my $entry (@$info) {
920 my $vsn = "$entry->{version}";
921 my ($ok,$msg) = version_check $vsn;
922 die "bad version: $msg\n" unless $ok;
923 my $component = "$entry->{component}";
924 $component =~ m/^$component_re$/ or die "bad component";
925 my $filename = "$entry->{filename}";
926 $filename && $filename !~ m#[^-+:._~0-9a-zA-Z/]|^[/.]|/[/.]#
927 or die "bad filename";
928 my $sha256sum = "$entry->{sha256sum}";
929 $sha256sum =~ m/^[0-9a-f]+$/ or die "bad sha256sum";
930 push @rows, [ $vsn, "/pool/$component/$filename",
931 $digester, $sha256sum ];
933 die "bad ftpmaster api response: $@\n".Dumper($entry)
936 @rows = sort { -version_compare($a->[0],$b->[0]) } @rows;
940 #---------- `madison' archive query method ----------
942 sub archive_query_madison {
943 return map { [ @$_[0..1] ] } madison_get_parse(@_);
946 sub madison_get_parse {
947 my ($proto,$data) = @_;
948 die unless $proto eq 'madison';
950 $data= access_cfg('madison-distro','RETURN-UNDEF');
951 $data //= access_basedistro();
953 $rmad{$proto,$data,$package} ||= cmdoutput
954 qw(rmadison -asource),"-s$isuite","-u$data",$package;
955 my $rmad = $rmad{$proto,$data,$package};
958 foreach my $l (split /\n/, $rmad) {
959 $l =~ m{^ \s*( [^ \t|]+ )\s* \|
960 \s*( [^ \t|]+ )\s* \|
961 \s*( [^ \t|/]+ )(?:/([^ \t|/]+))? \s* \|
962 \s*( [^ \t|]+ )\s* }x or die "$rmad ?";
963 $1 eq $package or die "$rmad $package ?";
970 $component = access_cfg('archive-query-default-component');
972 $5 eq 'source' or die "$rmad ?";
973 push @out, [$vsn,pool_dsc_subpath($vsn,$component),$newsuite];
975 return sort { -version_compare($a->[0],$b->[0]); } @out;
978 sub canonicalise_suite_madison {
979 # madison canonicalises for us
980 my @r = madison_get_parse(@_);
982 "unable to canonicalise suite using package $package".
983 " which does not appear to exist in suite $isuite;".
984 " --existing-package may help";
988 #---------- `sshpsql' archive query method ----------
991 my ($data,$runeinfo,$sql) = @_;
993 $data= access_someuserhost('sshpsql').':'.
994 access_cfg('sshpsql-dbname');
996 $data =~ m/:/ or badcfg "invalid sshpsql method string \`$data'";
997 my ($userhost,$dbname) = ($`,$'); #';
999 my @cmd = (access_cfg_ssh, $userhost,
1000 access_runeinfo("ssh-psql $runeinfo").
1001 " export LC_MESSAGES=C; export LC_CTYPE=C;".
1002 " ".shellquote qw(psql -A), $dbname, qw(-c), $sql);
1004 open P, "-|", @cmd or die $!;
1007 printdebug(">|$_|\n");
1010 $!=0; $?=0; close P or failedcmd @cmd;
1012 my $nrows = pop @rows;
1013 $nrows =~ s/^\((\d+) rows?\)$/$1/ or die "$nrows ?";
1014 @rows == $nrows+1 or die "$nrows ".(scalar @rows)." ?";
1015 @rows = map { [ split /\|/, $_ ] } @rows;
1016 my $ncols = scalar @{ shift @rows };
1017 die if grep { scalar @$_ != $ncols } @rows;
1021 sub sql_injection_check {
1022 foreach (@_) { die "$_ $& ?" if m{[^-+=:_.,/0-9a-zA-Z]}; }
1025 sub archive_query_sshpsql ($$) {
1026 my ($proto,$data) = @_;
1027 sql_injection_check $isuite, $package;
1028 my @rows = sshpsql($data, "archive-query $isuite $package", <<END);
1029 SELECT source.version, component.name, files.filename, files.sha256sum
1031 JOIN src_associations ON source.id = src_associations.source
1032 JOIN suite ON suite.id = src_associations.suite
1033 JOIN dsc_files ON dsc_files.source = source.id
1034 JOIN files_archive_map ON files_archive_map.file_id = dsc_files.file
1035 JOIN component ON component.id = files_archive_map.component_id
1036 JOIN files ON files.id = dsc_files.file
1037 WHERE ( suite.suite_name='$isuite' OR suite.codename='$isuite' )
1038 AND source.source='$package'
1039 AND files.filename LIKE '%.dsc';
1041 @rows = sort { -version_compare($a->[0],$b->[0]) } @rows;
1042 my $digester = Digest::SHA->new(256);
1044 my ($vsn,$component,$filename,$sha256sum) = @$_;
1045 [ $vsn, "/pool/$component/$filename",$digester,$sha256sum ];
1050 sub canonicalise_suite_sshpsql ($$) {
1051 my ($proto,$data) = @_;
1052 sql_injection_check $isuite;
1053 my @rows = sshpsql($data, "canonicalise-suite $isuite", <<END);
1054 SELECT suite.codename
1055 FROM suite where suite_name='$isuite' or codename='$isuite';
1057 @rows = map { $_->[0] } @rows;
1058 fail "unknown suite $isuite" unless @rows;
1059 die "ambiguous $isuite: @rows ?" if @rows>1;
1063 #---------- `dummycat' archive query method ----------
1065 sub canonicalise_suite_dummycat ($$) {
1066 my ($proto,$data) = @_;
1067 my $dpath = "$data/suite.$isuite";
1068 if (!open C, "<", $dpath) {
1069 $!==ENOENT or die "$dpath: $!";
1070 printdebug "dummycat canonicalise_suite $isuite $dpath ENOENT\n";
1074 chomp or die "$dpath: $!";
1076 printdebug "dummycat canonicalise_suite $isuite $dpath = $_\n";
1080 sub archive_query_dummycat ($$) {
1081 my ($proto,$data) = @_;
1082 canonicalise_suite();
1083 my $dpath = "$data/package.$csuite.$package";
1084 if (!open C, "<", $dpath) {
1085 $!==ENOENT or die "$dpath: $!";
1086 printdebug "dummycat query $csuite $package $dpath ENOENT\n";
1094 printdebug "dummycat query $csuite $package $dpath | $_\n";
1095 my @row = split /\s+/, $_;
1096 @row==2 or die "$dpath: $_ ?";
1099 C->error and die "$dpath: $!";
1101 return sort { -version_compare($a->[0],$b->[0]); } @rows;
1104 #---------- archive query entrypoints and rest of program ----------
1106 sub canonicalise_suite () {
1107 return if defined $csuite;
1108 fail "cannot operate on $isuite suite" if $isuite eq 'UNRELEASED';
1109 $csuite = archive_query('canonicalise_suite');
1110 if ($isuite ne $csuite) {
1111 progress "canonical suite name for $isuite is $csuite";
1115 sub get_archive_dsc () {
1116 canonicalise_suite();
1117 my @vsns = archive_query('archive_query');
1118 foreach my $vinfo (@vsns) {
1119 my ($vsn,$subpath,$digester,$digest) = @$vinfo;
1120 $dscurl = access_cfg('mirror').$subpath;
1121 $dscdata = url_get($dscurl);
1123 $skew_warning_vsn = $vsn if !defined $skew_warning_vsn;
1128 $digester->add($dscdata);
1129 my $got = $digester->hexdigest();
1131 fail "$dscurl has hash $got but".
1132 " archive told us to expect $digest";
1134 my $dscfh = new IO::File \$dscdata, '<' or die $!;
1135 printdebug Dumper($dscdata) if $debuglevel>1;
1136 $dsc = parsecontrolfh($dscfh,$dscurl,1);
1137 printdebug Dumper($dsc) if $debuglevel>1;
1138 my $fmt = getfield $dsc, 'Format';
1139 fail "unsupported source format $fmt, sorry" unless $format_ok{$fmt};
1140 $dsc_checked = !!$digester;
1146 sub check_for_git ();
1147 sub check_for_git () {
1149 my $how = access_cfg('git-check');
1150 if ($how eq 'ssh-cmd') {
1152 (access_cfg_ssh, access_gituserhost(),
1153 access_runeinfo("git-check $package").
1154 " set -e; cd ".access_cfg('git-path').";".
1155 " if test -d $package.git; then echo 1; else echo 0; fi");
1156 my $r= cmdoutput @cmd;
1157 if ($r =~ m/^divert (\w+)$/) {
1159 my ($usedistro,) = access_distros();
1160 # NB that if we are pushing, $usedistro will be $distro/push
1161 $instead_distro= cfg("dgit-distro.$usedistro.diverts.$divert");
1162 $instead_distro =~ s{^/}{ access_basedistro()."/" }e;
1163 progress "diverting to $divert (using config for $instead_distro)";
1164 return check_for_git();
1166 failedcmd @cmd unless $r =~ m/^[01]$/;
1168 } elsif ($how eq 'url') {
1169 my $prefix = access_cfg('git-check-url','git-url');
1170 my $suffix = access_cfg('git-check-suffix','git-suffix',
1171 'RETURN-UNDEF') // '.git';
1172 my $url = "$prefix/$package$suffix";
1173 my @cmd = (qw(curl -sS -I), $url);
1174 my $result = cmdoutput @cmd;
1175 $result =~ s/^\S+ 200 .*\n\r?\n//;
1176 # curl -sS -I with https_proxy prints
1177 # HTTP/1.0 200 Connection established
1178 $result =~ m/^\S+ (404|200) /s or
1179 fail "unexpected results from git check query - ".
1180 Dumper($prefix, $result);
1182 if ($code eq '404') {
1184 } elsif ($code eq '200') {
1189 } elsif ($how eq 'true') {
1191 } elsif ($how eq 'false') {
1194 badcfg "unknown git-check \`$how'";
1198 sub create_remote_git_repo () {
1199 my $how = access_cfg('git-create');
1200 if ($how eq 'ssh-cmd') {
1202 (access_cfg_ssh, access_gituserhost(),
1203 access_runeinfo("git-create $package").
1204 "set -e; cd ".access_cfg('git-path').";".
1205 " cp -a _template $package.git");
1206 } elsif ($how eq 'true') {
1209 badcfg "unknown git-create \`$how'";
1213 our ($dsc_hash,$lastpush_hash);
1215 our $ud = '.git/dgit/unpack';
1225 sub mktree_in_ud_here () {
1226 runcmd qw(git init -q);
1227 rmtree('.git/objects');
1228 symlink '../../../../objects','.git/objects' or die $!;
1231 sub git_write_tree () {
1232 my $tree = cmdoutput @git, qw(write-tree);
1233 $tree =~ m/^\w+$/ or die "$tree ?";
1237 sub remove_stray_gits () {
1238 my @gitscmd = qw(find -name .git -prune -print0);
1239 debugcmd "|",@gitscmd;
1240 open GITS, "-|", @gitscmd or failedcmd @gitscmd;
1245 print STDERR "$us: warning: removing from source package: ",
1246 (messagequote $_), "\n";
1250 $!=0; $?=0; close GITS or failedcmd @gitscmd;
1253 sub mktree_in_ud_from_only_subdir () {
1254 # changes into the subdir
1256 die unless @dirs==1;
1257 $dirs[0] =~ m#^([^/]+)/\.$# or die;
1261 remove_stray_gits();
1262 mktree_in_ud_here();
1263 my ($format, $fopts) = get_source_format();
1264 if (madformat($format)) {
1267 runcmd @git, qw(add -Af);
1268 my $tree=git_write_tree();
1269 return ($tree,$dir);
1272 sub dsc_files_info () {
1273 foreach my $csumi (['Checksums-Sha256','Digest::SHA', 'new(256)'],
1274 ['Checksums-Sha1', 'Digest::SHA', 'new(1)'],
1275 ['Files', 'Digest::MD5', 'new()']) {
1276 my ($fname, $module, $method) = @$csumi;
1277 my $field = $dsc->{$fname};
1278 next unless defined $field;
1279 eval "use $module; 1;" or die $@;
1281 foreach (split /\n/, $field) {
1283 m/^(\w+) (\d+) (\S+)$/ or
1284 fail "could not parse .dsc $fname line \`$_'";
1285 my $digester = eval "$module"."->$method;" or die $@;
1290 Digester => $digester,
1295 fail "missing any supported Checksums-* or Files field in ".
1296 $dsc->get_option('name');
1300 map { $_->{Filename} } dsc_files_info();
1303 sub is_orig_file ($;$) {
1306 m/\.orig(?:-\w+)?\.tar\.\w+$/ or return 0;
1307 defined $base or return 1;
1311 sub make_commit ($) {
1313 return cmdoutput @git, qw(hash-object -w -t commit), $file;
1316 sub clogp_authline ($) {
1318 my $author = getfield $clogp, 'Maintainer';
1319 $author =~ s#,.*##ms;
1320 my $date = cmdoutput qw(date), '+%s %z', qw(-d), getfield($clogp,'Date');
1321 my $authline = "$author $date";
1322 $authline =~ m/$git_authline_re/o or
1323 fail "unexpected commit author line format \`$authline'".
1324 " (was generated from changelog Maintainer field)";
1325 return ($1,$2,$3) if wantarray;
1329 sub vendor_patches_distro ($$) {
1330 my ($checkdistro, $what) = @_;
1331 return unless defined $checkdistro;
1333 my $series = "debian/patches/\L$checkdistro\E.series";
1334 printdebug "checking for vendor-specific $series ($what)\n";
1336 if (!open SERIES, "<", $series) {
1337 die "$series $!" unless $!==ENOENT;
1346 Unfortunately, this source package uses a feature of dpkg-source where
1347 the same source package unpacks to different source code on different
1348 distros. dgit cannot safely operate on such packages on affected
1349 distros, because the meaning of source packages is not stable.
1351 Please ask the distro/maintainer to remove the distro-specific series
1352 files and use a different technique (if necessary, uploading actually
1353 different packages, if different distros are supposed to have
1357 fail "Found active distro-specific series file for".
1358 " $checkdistro ($what): $series, cannot continue";
1360 die "$series $!" if SERIES->error;
1364 sub check_for_vendor_patches () {
1365 # This dpkg-source feature doesn't seem to be documented anywhere!
1366 # But it can be found in the changelog (reformatted):
1368 # commit 4fa01b70df1dc4458daee306cfa1f987b69da58c
1369 # Author: Raphael Hertzog <hertzog@debian.org>
1370 # Date: Sun Oct 3 09:36:48 2010 +0200
1372 # dpkg-source: correctly create .pc/.quilt_series with alternate
1375 # If you have debian/patches/ubuntu.series and you were
1376 # unpacking the source package on ubuntu, quilt was still
1377 # directed to debian/patches/series instead of
1378 # debian/patches/ubuntu.series.
1380 # debian/changelog | 3 +++
1381 # scripts/Dpkg/Source/Package/V3/quilt.pm | 4 +++-
1382 # 2 files changed, 6 insertions(+), 1 deletion(-)
1385 vendor_patches_distro($ENV{DEB_VENDOR}, "DEB_VENDOR");
1386 vendor_patches_distro(Dpkg::Vendor::get_current_vendor(),
1387 "Dpkg::Vendor \`current vendor'");
1388 vendor_patches_distro(access_basedistro(),
1389 "distro being accessed");
1392 sub generate_commit_from_dsc () {
1396 foreach my $fi (dsc_files_info()) {
1397 my $f = $fi->{Filename};
1398 die "$f ?" if $f =~ m#/|^\.|\.dsc$|\.tmp$#;
1400 link_ltarget "../../../$f", $f
1404 complete_file_from_dsc('.', $fi)
1407 if (is_orig_file($f)) {
1408 link $f, "../../../../$f"
1414 my $dscfn = "$package.dsc";
1416 open D, ">", $dscfn or die "$dscfn: $!";
1417 print D $dscdata or die "$dscfn: $!";
1418 close D or die "$dscfn: $!";
1419 my @cmd = qw(dpkg-source);
1420 push @cmd, '--no-check' if $dsc_checked;
1421 push @cmd, qw(-x --), $dscfn;
1424 my ($tree,$dir) = mktree_in_ud_from_only_subdir();
1425 check_for_vendor_patches() if madformat($dsc->{format});
1426 runcmd qw(sh -ec), 'dpkg-parsechangelog >../changelog.tmp';
1427 my $clogp = parsecontrol('../changelog.tmp',"commit's changelog");
1428 my $authline = clogp_authline $clogp;
1429 my $changes = getfield $clogp, 'Changes';
1430 open C, ">../commit.tmp" or die $!;
1431 print C <<END or die $!;
1438 # imported from the archive
1441 my $outputhash = make_commit qw(../commit.tmp);
1442 my $cversion = getfield $clogp, 'Version';
1443 progress "synthesised git commit from .dsc $cversion";
1444 if ($lastpush_hash) {
1445 runcmd @git, qw(reset --hard), $lastpush_hash;
1446 runcmd qw(sh -ec), 'dpkg-parsechangelog >>../changelogold.tmp';
1447 my $oldclogp = parsecontrol('../changelogold.tmp','previous changelog');
1448 my $oversion = getfield $oldclogp, 'Version';
1450 version_compare($oversion, $cversion);
1452 # git upload/ is earlier vsn than archive, use archive
1453 open C, ">../commit2.tmp" or die $!;
1454 print C <<END or die $!;
1456 parent $lastpush_hash
1461 Record $package ($cversion) in archive suite $csuite
1463 $outputhash = make_commit qw(../commit2.tmp);
1464 } elsif ($vcmp > 0) {
1465 print STDERR <<END or die $!;
1467 Version actually in archive: $cversion (older)
1468 Last allegedly pushed/uploaded: $oversion (newer or same)
1471 $outputhash = $lastpush_hash;
1473 $outputhash = $lastpush_hash;
1476 changedir '../../../..';
1477 runcmd @git, qw(update-ref -m),"dgit fetch import $cversion",
1478 'DGIT_ARCHIVE', $outputhash;
1479 cmdoutput @git, qw(log -n2), $outputhash;
1480 # ... gives git a chance to complain if our commit is malformed
1485 sub complete_file_from_dsc ($$) {
1486 our ($dstdir, $fi) = @_;
1487 # Ensures that we have, in $dir, the file $fi, with the correct
1488 # contents. (Downloading it from alongside $dscurl if necessary.)
1490 my $f = $fi->{Filename};
1491 my $tf = "$dstdir/$f";
1494 if (stat_exists $tf) {
1495 progress "using existing $f";
1498 $furl =~ s{/[^/]+$}{};
1500 die "$f ?" unless $f =~ m/^\Q${package}\E_/;
1501 die "$f ?" if $f =~ m#/#;
1502 runcmd_ordryrun_local @curl,qw(-o),$tf,'--',"$furl";
1503 return 0 if !act_local();
1507 open F, "<", "$tf" or die "$tf: $!";
1508 $fi->{Digester}->reset();
1509 $fi->{Digester}->addfile(*F);
1510 F->error and die $!;
1511 my $got = $fi->{Digester}->hexdigest();
1512 $got eq $fi->{Hash} or
1513 fail "file $f has hash $got but .dsc".
1514 " demands hash $fi->{Hash} ".
1515 ($downloaded ? "(got wrong file from archive!)"
1516 : "(perhaps you should delete this file?)");
1521 sub ensure_we_have_orig () {
1522 foreach my $fi (dsc_files_info()) {
1523 my $f = $fi->{Filename};
1524 next unless is_orig_file($f);
1525 complete_file_from_dsc('..', $fi)
1530 sub git_fetch_us () {
1531 my @specs = (fetchspec());
1533 map { "+refs/$_/*:".lrfetchrefs."/$_/*" }
1535 runcmd_ordryrun_local @git, qw(fetch -p -n -q), access_giturl(), @specs;
1538 my $tagpat = debiantag('*',access_basedistro);
1540 git_for_each_ref("refs/tags/".$tagpat, sub {
1541 my ($objid,$objtype,$fullrefname,$reftail) = @_;
1542 printdebug "currently $fullrefname=$objid\n";
1543 $here{$fullrefname} = $objid;
1545 git_for_each_ref(lrfetchrefs."/tags/".$tagpat, sub {
1546 my ($objid,$objtype,$fullrefname,$reftail) = @_;
1547 my $lref = "refs".substr($fullrefname, length lrfetchrefs);
1548 printdebug "offered $lref=$objid\n";
1549 if (!defined $here{$lref}) {
1550 my @upd = (@git, qw(update-ref), $lref, $objid, '');
1551 runcmd_ordryrun_local @upd;
1552 } elsif ($here{$lref} eq $objid) {
1555 "Not updateting $lref from $here{$lref} to $objid.\n";
1560 sub fetch_from_archive () {
1561 # ensures that lrref() is what is actually in the archive,
1562 # one way or another
1566 foreach my $field (@ourdscfield) {
1567 $dsc_hash = $dsc->{$field};
1568 last if defined $dsc_hash;
1570 if (defined $dsc_hash) {
1571 $dsc_hash =~ m/\w+/ or fail "invalid hash in .dsc \`$dsc_hash'";
1573 progress "last upload to archive specified git hash";
1575 progress "last upload to archive has NO git hash";
1578 progress "no version available from the archive";
1581 $lastpush_hash = git_get_ref(lrref());
1582 printdebug "previous reference hash=$lastpush_hash\n";
1584 if (defined $dsc_hash) {
1585 fail "missing remote git history even though dsc has hash -".
1586 " could not find ref ".lrref().
1587 " (should have been fetched from ".access_giturl()."#".rrref().")"
1588 unless $lastpush_hash;
1590 ensure_we_have_orig();
1591 if ($dsc_hash eq $lastpush_hash) {
1592 } elsif (is_fast_fwd($dsc_hash,$lastpush_hash)) {
1593 print STDERR <<END or die $!;
1595 Git commit in archive is behind the last version allegedly pushed/uploaded.
1596 Commit referred to by archive: $dsc_hash
1597 Last allegedly pushed/uploaded: $lastpush_hash
1600 $hash = $lastpush_hash;
1602 fail "git head (".lrref()."=$lastpush_hash) is not a ".
1603 "descendant of archive's .dsc hash ($dsc_hash)";
1606 $hash = generate_commit_from_dsc();
1607 } elsif ($lastpush_hash) {
1608 # only in git, not in the archive yet
1609 $hash = $lastpush_hash;
1610 print STDERR <<END or die $!;
1612 Package not found in the archive, but has allegedly been pushed using dgit.
1616 printdebug "nothing found!\n";
1617 if (defined $skew_warning_vsn) {
1618 print STDERR <<END or die $!;
1620 Warning: relevant archive skew detected.
1621 Archive allegedly contains $skew_warning_vsn
1622 But we were not able to obtain any version from the archive or git.
1628 printdebug "current hash=$hash\n";
1629 if ($lastpush_hash) {
1630 fail "not fast forward on last upload branch!".
1631 " (archive's version left in DGIT_ARCHIVE)"
1632 unless is_fast_fwd($lastpush_hash, $hash);
1634 if (defined $skew_warning_vsn) {
1636 printdebug "SKEW CHECK WANT $skew_warning_vsn\n";
1637 my $clogf = ".git/dgit/changelog.tmp";
1638 runcmd shell_cmd "exec >$clogf",
1639 @git, qw(cat-file blob), "$hash:debian/changelog";
1640 my $gotclogp = parsechangelog("-l$clogf");
1641 my $got_vsn = getfield $gotclogp, 'Version';
1642 printdebug "SKEW CHECK GOT $got_vsn\n";
1643 if (version_compare($got_vsn, $skew_warning_vsn) < 0) {
1644 print STDERR <<END or die $!;
1646 Warning: archive skew detected. Using the available version:
1647 Archive allegedly contains $skew_warning_vsn
1648 We were able to obtain only $got_vsn
1653 if ($lastpush_hash ne $hash) {
1654 my @upd_cmd = (@git, qw(update-ref -m), 'dgit fetch', lrref(), $hash);
1658 dryrun_report @upd_cmd;
1664 sub set_local_git_config ($$) {
1666 runcmd @git, qw(config), $k, $v;
1669 sub setup_mergechangelogs (;$) {
1671 return unless $always || access_cfg_bool(1, 'setup-mergechangelogs');
1673 my $driver = 'dpkg-mergechangelogs';
1674 my $cb = "merge.$driver";
1675 my $attrs = '.git/info/attributes';
1676 ensuredir '.git/info';
1678 open NATTRS, ">", "$attrs.new" or die "$attrs.new $!";
1679 if (!open ATTRS, "<", $attrs) {
1680 $!==ENOENT or die "$attrs: $!";
1684 next if m{^debian/changelog\s};
1685 print NATTRS $_, "\n" or die $!;
1687 ATTRS->error and die $!;
1690 print NATTRS "debian/changelog merge=$driver\n" or die $!;
1693 set_local_git_config "$cb.name", 'debian/changelog merge driver';
1694 set_local_git_config "$cb.driver", 'dpkg-mergechangelogs -m %O %A %B %A';
1696 rename "$attrs.new", "$attrs" or die "$attrs: $!";
1699 sub setup_useremail (;$) {
1701 return unless $always || access_cfg_bool(1, 'setup-useremail');
1704 my ($k, $envvar) = @_;
1705 my $v = access_cfg("user-$k", 'RETURN-UNDEF') // $ENV{$envvar};
1706 return unless defined $v;
1707 set_local_git_config "user.$k", $v;
1710 $setup->('email', 'DEBEMAIL');
1711 $setup->('name', 'DEBFULLNAME');
1714 sub setup_new_tree () {
1715 setup_mergechangelogs();
1721 canonicalise_suite();
1722 badusage "dry run makes no sense with clone" unless act_local();
1723 my $hasgit = check_for_git();
1724 mkdir $dstdir or fail "create \`$dstdir': $!";
1726 runcmd @git, qw(init -q);
1727 my $giturl = access_giturl(1);
1728 if (defined $giturl) {
1729 set_local_git_config "remote.$remotename.fetch", fetchspec();
1730 open H, "> .git/HEAD" or die $!;
1731 print H "ref: ".lref()."\n" or die $!;
1733 runcmd @git, qw(remote add), 'origin', $giturl;
1736 progress "fetching existing git history";
1738 runcmd_ordryrun_local @git, qw(fetch origin);
1740 progress "starting new git history";
1742 fetch_from_archive() or no_such_package;
1743 my $vcsgiturl = $dsc->{'Vcs-Git'};
1744 if (length $vcsgiturl) {
1745 $vcsgiturl =~ s/\s+-b\s+\S+//g;
1746 runcmd @git, qw(remote add vcs-git), $vcsgiturl;
1749 runcmd @git, qw(reset --hard), lrref();
1750 printdone "ready for work in $dstdir";
1754 if (check_for_git()) {
1757 fetch_from_archive() or no_such_package();
1758 printdone "fetched into ".lrref();
1763 runcmd_ordryrun_local @git, qw(merge -m),"Merge from $csuite [dgit]",
1765 printdone "fetched to ".lrref()." and merged into HEAD";
1768 sub check_not_dirty () {
1769 foreach my $f (qw(local-options local-patch-header)) {
1770 if (stat_exists "debian/source/$f") {
1771 fail "git tree contains debian/source/$f";
1775 return if $ignoredirty;
1777 my @cmd = (@git, qw(diff --quiet HEAD));
1779 $!=0; $?=0; system @cmd;
1780 return if !$! && !$?;
1781 if (!$! && $?==256) {
1782 fail "working tree is dirty (does not match HEAD)";
1788 sub commit_admin ($) {
1791 runcmd_ordryrun_local @git, qw(commit -m), $m;
1794 sub commit_quilty_patch () {
1795 my $output = cmdoutput @git, qw(status --porcelain);
1797 foreach my $l (split /\n/, $output) {
1798 next unless $l =~ m/\S/;
1799 if ($l =~ m{^(?:\?\?| M) (.pc|debian/patches)}) {
1803 delete $adds{'.pc'}; # if there wasn't one before, don't add it
1805 progress "nothing quilty to commit, ok.";
1808 my @adds = map { s/[][*?\\]/\\$&/g; $_; } sort keys %adds;
1809 runcmd_ordryrun_local @git, qw(add -f), @adds;
1810 commit_admin "Commit Debian 3.0 (quilt) metadata";
1813 sub get_source_format () {
1815 if (open F, "debian/source/options") {
1819 s/\s+$//; # ignore missing final newline
1821 my ($k, $v) = ($`, $'); #');
1822 $v =~ s/^"(.*)"$/$1/;
1828 F->error and die $!;
1831 die $! unless $!==&ENOENT;
1834 if (!open F, "debian/source/format") {
1835 die $! unless $!==&ENOENT;
1839 F->error and die $!;
1841 return ($_, \%options);
1846 return 0 unless $format eq '3.0 (quilt)';
1847 if ($quilt_mode eq 'nocheck') {
1848 progress "Not doing any fixup of \`$format' due to --no-quilt-fixup";
1851 progress "Format \`$format', checking/updating patch stack";
1855 sub push_parse_changelog ($) {
1858 my $clogp = Dpkg::Control::Hash->new();
1859 $clogp->load($clogpfn) or die;
1861 $package = getfield $clogp, 'Source';
1862 my $cversion = getfield $clogp, 'Version';
1863 my $tag = debiantag($cversion, access_basedistro);
1864 runcmd @git, qw(check-ref-format), $tag;
1866 my $dscfn = dscfn($cversion);
1868 return ($clogp, $cversion, $tag, $dscfn);
1871 sub push_parse_dsc ($$$) {
1872 my ($dscfn,$dscfnwhat, $cversion) = @_;
1873 $dsc = parsecontrol($dscfn,$dscfnwhat);
1874 my $dversion = getfield $dsc, 'Version';
1875 my $dscpackage = getfield $dsc, 'Source';
1876 ($dscpackage eq $package && $dversion eq $cversion) or
1877 fail "$dscfn is for $dscpackage $dversion".
1878 " but debian/changelog is for $package $cversion";
1881 sub push_mktag ($$$$$$$) {
1882 my ($head,$clogp,$tag,
1884 $changesfile,$changesfilewhat,
1887 $dsc->{$ourdscfield[0]} = $head;
1888 $dsc->save("$dscfn.tmp") or die $!;
1890 my $changes = parsecontrol($changesfile,$changesfilewhat);
1891 foreach my $field (qw(Source Distribution Version)) {
1892 $changes->{$field} eq $clogp->{$field} or
1893 fail "changes field $field \`$changes->{$field}'".
1894 " does not match changelog \`$clogp->{$field}'";
1897 my $cversion = getfield $clogp, 'Version';
1898 my $clogsuite = getfield $clogp, 'Distribution';
1900 # We make the git tag by hand because (a) that makes it easier
1901 # to control the "tagger" (b) we can do remote signing
1902 my $authline = clogp_authline $clogp;
1903 my $delibs = join(" ", "",@deliberatelies);
1904 my $declaredistro = access_basedistro();
1905 open TO, '>', $tfn->('.tmp') or die $!;
1906 print TO <<END or die $!;
1912 $package release $cversion for $clogsuite ($csuite) [dgit]
1913 [dgit distro=$declaredistro$delibs]
1915 foreach my $ref (sort keys %previously) {
1916 print TO <<END or die $!;
1917 [dgit previously:$ref=$previously{$ref}]
1923 my $tagobjfn = $tfn->('.tmp');
1925 if (!defined $keyid) {
1926 $keyid = access_cfg('keyid','RETURN-UNDEF');
1928 if (!defined $keyid) {
1929 $keyid = getfield $clogp, 'Maintainer';
1931 unlink $tfn->('.tmp.asc') or $!==&ENOENT or die $!;
1932 my @sign_cmd = (@gpg, qw(--detach-sign --armor));
1933 push @sign_cmd, qw(-u),$keyid if defined $keyid;
1934 push @sign_cmd, $tfn->('.tmp');
1935 runcmd_ordryrun @sign_cmd;
1937 $tagobjfn = $tfn->('.signed.tmp');
1938 runcmd shell_cmd "exec >$tagobjfn", qw(cat --),
1939 $tfn->('.tmp'), $tfn->('.tmp.asc');
1946 sub sign_changes ($) {
1947 my ($changesfile) = @_;
1949 my @debsign_cmd = @debsign;
1950 push @debsign_cmd, "-k$keyid" if defined $keyid;
1951 push @debsign_cmd, "-p$gpg[0]" if $gpg[0] ne 'gpg';
1952 push @debsign_cmd, $changesfile;
1953 runcmd_ordryrun @debsign_cmd;
1958 my ($forceflag) = @_;
1959 printdebug "actually entering push\n";
1960 supplementary_message(<<'END');
1961 Push failed, while preparing your push.
1962 You can retry the push, after fixing the problem, if you like.
1966 access_giturl(); # check that success is vaguely likely
1968 my $clogpfn = ".git/dgit/changelog.822.tmp";
1969 runcmd shell_cmd "exec >$clogpfn", qw(dpkg-parsechangelog);
1971 responder_send_file('parsed-changelog', $clogpfn);
1973 my ($clogp, $cversion, $tag, $dscfn) =
1974 push_parse_changelog("$clogpfn");
1976 my $dscpath = "$buildproductsdir/$dscfn";
1977 stat_exists $dscpath or
1978 fail "looked for .dsc $dscfn, but $!;".
1979 " maybe you forgot to build";
1981 responder_send_file('dsc', $dscpath);
1983 push_parse_dsc($dscpath, $dscfn, $cversion);
1985 my $format = getfield $dsc, 'Format';
1986 printdebug "format $format\n";
1987 if (madformat($format)) {
1988 # user might have not used dgit build, so maybe do this now:
1989 commit_quilty_patch();
1993 progress "checking that $dscfn corresponds to HEAD";
1994 runcmd qw(dpkg-source -x --),
1995 $dscpath =~ m#^/# ? $dscpath : "../../../$dscpath";
1996 my ($tree,$dir) = mktree_in_ud_from_only_subdir();
1997 check_for_vendor_patches() if madformat($dsc->{format});
1998 changedir '../../../..';
1999 my $diffopt = $debuglevel>0 ? '--exit-code' : '--quiet';
2000 my @diffcmd = (@git, qw(diff), $diffopt, $tree);
2001 debugcmd "+",@diffcmd;
2003 my $r = system @diffcmd;
2006 fail "$dscfn specifies a different tree to your HEAD commit;".
2007 " perhaps you forgot to build".
2008 ($diffopt eq '--exit-code' ? "" :
2009 " (run with -D to see full diff output)");
2014 my $head = git_rev_parse('HEAD');
2015 if (!$changesfile) {
2016 my $pat = changespat $cversion;
2017 my @cs = glob "$buildproductsdir/$pat";
2018 fail "failed to find unique changes file".
2019 " (looked for $pat in $buildproductsdir);".
2020 " perhaps you need to use dgit -C"
2022 ($changesfile) = @cs;
2024 $changesfile = "$buildproductsdir/$changesfile";
2027 responder_send_file('changes',$changesfile);
2028 responder_send_command("param head $head");
2029 responder_send_command("param csuite $csuite");
2031 if (deliberately_not_fast_forward) {
2032 git_for_each_ref(lrfetchrefs, sub {
2033 my ($objid,$objtype,$lrfetchrefname,$reftail) = @_;
2034 my $rrefname= substr($lrfetchrefname, length(lrfetchrefs) + 1);
2035 responder_send_command("previously $rrefname=$objid");
2036 $previously{$rrefname} = $objid;
2040 my $tfn = sub { ".git/dgit/tag$_[0]"; };
2043 supplementary_message(<<'END');
2044 Push failed, while signing the tag.
2045 You can retry the push, after fixing the problem, if you like.
2047 # If we manage to sign but fail to record it anywhere, it's fine.
2048 if ($we_are_responder) {
2049 $tagobjfn = $tfn->('.signed.tmp');
2050 responder_receive_files('signed-tag', $tagobjfn);
2053 push_mktag($head,$clogp,$tag,
2055 $changesfile,$changesfile,
2058 supplementary_message(<<'END');
2059 Push failed, *after* signing the tag.
2060 If you want to try again, you should use a new version number.
2063 my $tag_obj_hash = cmdoutput @git, qw(hash-object -w -t tag), $tagobjfn;
2064 runcmd_ordryrun @git, qw(verify-tag), $tag_obj_hash;
2065 runcmd_ordryrun_local @git, qw(update-ref), "refs/tags/$tag", $tag_obj_hash;
2067 supplementary_message(<<'END');
2068 Push failed, while updating the remote git repository - see messages above.
2069 If you want to try again, you should use a new version number.
2071 if (!check_for_git()) {
2072 create_remote_git_repo();
2074 runcmd_ordryrun @git, qw(push),access_giturl(),
2075 $forceflag."HEAD:".rrref(), $forceflag."refs/tags/$tag";
2076 runcmd_ordryrun @git, qw(update-ref -m), 'dgit push', lrref(), 'HEAD';
2078 supplementary_message(<<'END');
2079 Push failed, after updating the remote git repository.
2080 If you want to try again, you must use a new version number.
2082 if ($we_are_responder) {
2083 my $dryrunsuffix = act_local() ? "" : ".tmp";
2084 responder_receive_files('signed-dsc-changes',
2085 "$dscpath$dryrunsuffix",
2086 "$changesfile$dryrunsuffix");
2089 rename "$dscpath.tmp",$dscpath or die "$dscfn $!";
2091 progress "[new .dsc left in $dscpath.tmp]";
2093 sign_changes $changesfile;
2096 supplementary_message(<<END);
2097 Push failed, while uploading package(s) to the archive server.
2098 You can retry the upload of exactly these same files with dput of:
2100 If that .changes file is broken, you will need to use a new version
2101 number for your next attempt at the upload.
2103 my $host = access_cfg('upload-host','RETURN-UNDEF');
2104 my @hostarg = defined($host) ? ($host,) : ();
2105 runcmd_ordryrun @dput, @hostarg, $changesfile;
2106 printdone "pushed and uploaded $cversion";
2108 supplementary_message('');
2109 responder_send_command("complete");
2116 badusage "-p is not allowed with clone; specify as argument instead"
2117 if defined $package;
2120 } elsif (@ARGV==2 && $ARGV[1] =~ m#^\w#) {
2121 ($package,$isuite) = @ARGV;
2122 } elsif (@ARGV==2 && $ARGV[1] =~ m#^[./]#) {
2123 ($package,$dstdir) = @ARGV;
2124 } elsif (@ARGV==3) {
2125 ($package,$isuite,$dstdir) = @ARGV;
2127 badusage "incorrect arguments to dgit clone";
2129 $dstdir ||= "$package";
2131 if (stat_exists $dstdir) {
2132 fail "$dstdir already exists";
2136 if ($rmonerror && !$dryrun_level) {
2137 $cwd_remove= getcwd();
2139 return unless defined $cwd_remove;
2140 if (!chdir "$cwd_remove") {
2141 return if $!==&ENOENT;
2142 die "chdir $cwd_remove: $!";
2145 rmtree($dstdir) or die "remove $dstdir: $!\n";
2146 } elsif (!grep { $! == $_ }
2147 (ENOENT, ENOTDIR, EACCES, EPERM, ELOOP)) {
2149 print STDERR "check whether to remove $dstdir: $!\n";
2155 $cwd_remove = undef;
2158 sub branchsuite () {
2159 my $branch = cmdoutput_errok @git, qw(symbolic-ref HEAD);
2160 if ($branch =~ m#$lbranch_re#o) {
2167 sub fetchpullargs () {
2169 if (!defined $package) {
2170 my $sourcep = parsecontrol('debian/control','debian/control');
2171 $package = getfield $sourcep, 'Source';
2174 # $isuite = branchsuite(); # this doesn't work because dak hates canons
2176 my $clogp = parsechangelog();
2177 $isuite = getfield $clogp, 'Distribution';
2179 canonicalise_suite();
2180 progress "fetching from suite $csuite";
2181 } elsif (@ARGV==1) {
2183 canonicalise_suite();
2185 badusage "incorrect arguments to dgit fetch or dgit pull";
2204 badusage "-p is not allowed with dgit push" if defined $package;
2206 my $clogp = parsechangelog();
2207 $package = getfield $clogp, 'Source';
2210 } elsif (@ARGV==1) {
2211 ($specsuite) = (@ARGV);
2213 badusage "incorrect arguments to dgit push";
2215 $isuite = getfield $clogp, 'Distribution';
2217 local ($package) = $existing_package; # this is a hack
2218 canonicalise_suite();
2220 canonicalise_suite();
2222 if (defined $specsuite &&
2223 $specsuite ne $isuite &&
2224 $specsuite ne $csuite) {
2225 fail "dgit push: changelog specifies $isuite ($csuite)".
2226 " but command line specifies $specsuite";
2228 supplementary_message(<<'END');
2229 Push failed, while checking state of the archive.
2230 You can retry the push, after fixing the problem, if you like.
2232 if (check_for_git()) {
2236 if (fetch_from_archive()) {
2237 if (is_fast_fwd(lrref(), 'HEAD')) {
2239 } elsif (deliberately_not_fast_forward) {
2242 fail "dgit push: HEAD is not a descendant".
2243 " of the archive's version.\n".
2244 "dgit: To overwrite its contents,".
2245 " use git merge -s ours ".lrref().".\n".
2246 "dgit: To rewind history, if permitted by the archive,".
2247 " use --deliberately-not-fast-forward";
2251 fail "package appears to be new in this suite;".
2252 " if this is intentional, use --new";
2257 #---------- remote commands' implementation ----------
2259 sub cmd_remote_push_build_host {
2260 my ($nrargs) = shift @ARGV;
2261 my (@rargs) = @ARGV[0..$nrargs-1];
2262 @ARGV = @ARGV[$nrargs..$#ARGV];
2264 my ($dir,$vsnwant) = @rargs;
2265 # vsnwant is a comma-separated list; we report which we have
2266 # chosen in our ready response (so other end can tell if they
2269 $we_are_responder = 1;
2270 $us .= " (build host)";
2274 open PI, "<&STDIN" or die $!;
2275 open STDIN, "/dev/null" or die $!;
2276 open PO, ">&STDOUT" or die $!;
2278 open STDOUT, ">&STDERR" or die $!;
2282 ($protovsn) = grep {
2283 $vsnwant =~ m{^(?:.*,)?$_(?:,.*)?$}
2284 } @rpushprotovsn_support;
2286 fail "build host has dgit rpush protocol versions ".
2287 (join ",", @rpushprotovsn_support).
2288 " but invocation host has $vsnwant"
2289 unless defined $protovsn;
2291 responder_send_command("dgit-remote-push-ready $protovsn");
2297 sub cmd_remote_push_responder { cmd_remote_push_build_host(); }
2298 # ... for compatibility with proto vsn.1 dgit (just so that user gets
2299 # a good error message)
2305 my $report = i_child_report();
2306 if (defined $report) {
2307 printdebug "($report)\n";
2308 } elsif ($i_child_pid) {
2309 printdebug "(killing build host child $i_child_pid)\n";
2310 kill 15, $i_child_pid;
2312 if (defined $i_tmp && !defined $initiator_tempdir) {
2314 eval { rmtree $i_tmp; };
2318 END { i_cleanup(); }
2321 my ($base,$selector,@args) = @_;
2322 $selector =~ s/\-/_/g;
2323 { no strict qw(refs); &{"${base}_${selector}"}(@args); }
2330 if ($host =~ m/^((?:[^][]|\[[^][]*\])*)\:/) {
2338 push @rargs, join ",", @rpushprotovsn_support;
2341 push @rdgit, @ropts;
2342 push @rdgit, qw(remote-push-build-host), (scalar @rargs), @rargs;
2344 my @cmd = (@ssh, $host, shellquote @rdgit);
2347 if (defined $initiator_tempdir) {
2348 rmtree $initiator_tempdir;
2349 mkdir $initiator_tempdir, 0700 or die "$initiator_tempdir: $!";
2350 $i_tmp = $initiator_tempdir;
2354 $i_child_pid = open2(\*RO, \*RI, @cmd);
2356 ($protovsn) = initiator_expect { m/^dgit-remote-push-ready (\S+)/ };
2357 die "$protovsn ?" unless grep { $_ eq $protovsn } @rpushprotovsn_support;
2358 $supplementary_message = '' unless $protovsn >= 3;
2360 my ($icmd,$iargs) = initiator_expect {
2361 m/^(\S+)(?: (.*))?$/;
2364 i_method "i_resp", $icmd, $iargs;
2368 sub i_resp_progress ($) {
2370 my $msg = protocol_read_bytes \*RO, $rhs;
2374 sub i_resp_supplementary_message ($) {
2376 $supplementary_message = protocol_read_bytes \*RO, $rhs;
2379 sub i_resp_complete {
2380 my $pid = $i_child_pid;
2381 $i_child_pid = undef; # prevents killing some other process with same pid
2382 printdebug "waiting for build host child $pid...\n";
2383 my $got = waitpid $pid, 0;
2384 die $! unless $got == $pid;
2385 die "build host child failed $?" if $?;
2388 printdebug "all done\n";
2392 sub i_resp_file ($) {
2394 my $localname = i_method "i_localname", $keyword;
2395 my $localpath = "$i_tmp/$localname";
2396 stat_exists $localpath and
2397 badproto \*RO, "file $keyword ($localpath) twice";
2398 protocol_receive_file \*RO, $localpath;
2399 i_method "i_file", $keyword;
2404 sub i_resp_param ($) {
2405 $_[0] =~ m/^(\S+) (.*)$/ or badproto \*RO, "bad param spec";
2409 sub i_resp_previously ($) {
2410 $_[0] =~ m#^(refs/tags/\S+)=(\w+)$#
2411 or badproto \*RO, "bad previously spec";
2412 my $r = system qw(git check-ref-format), $1;
2413 die "bad previously ref spec ($r)" if $r;
2414 $previously{$1} = $2;
2419 sub i_resp_want ($) {
2421 die "$keyword ?" if $i_wanted{$keyword}++;
2422 my @localpaths = i_method "i_want", $keyword;
2423 printdebug "[[ $keyword @localpaths\n";
2424 foreach my $localpath (@localpaths) {
2425 protocol_send_file \*RI, $localpath;
2427 print RI "files-end\n" or die $!;
2430 our ($i_clogp, $i_version, $i_tag, $i_dscfn, $i_changesfn);
2432 sub i_localname_parsed_changelog {
2433 return "remote-changelog.822";
2435 sub i_file_parsed_changelog {
2436 ($i_clogp, $i_version, $i_tag, $i_dscfn) =
2437 push_parse_changelog "$i_tmp/remote-changelog.822";
2438 die if $i_dscfn =~ m#/|^\W#;
2441 sub i_localname_dsc {
2442 defined $i_dscfn or badproto \*RO, "dsc (before parsed-changelog)";
2447 sub i_localname_changes {
2448 defined $i_dscfn or badproto \*RO, "dsc (before parsed-changelog)";
2449 $i_changesfn = $i_dscfn;
2450 $i_changesfn =~ s/\.dsc$/_dgit.changes/ or die;
2451 return $i_changesfn;
2453 sub i_file_changes { }
2455 sub i_want_signed_tag {
2456 printdebug Dumper(\%i_param, $i_dscfn);
2457 defined $i_param{'head'} && defined $i_dscfn && defined $i_clogp
2458 && defined $i_param{'csuite'}
2459 or badproto \*RO, "premature desire for signed-tag";
2460 my $head = $i_param{'head'};
2461 die if $head =~ m/[^0-9a-f]/ || $head !~ m/^../;
2463 die unless $i_param{'csuite'} =~ m/^$suite_re$/;
2465 push_parse_dsc $i_dscfn, 'remote dsc', $i_version;
2468 push_mktag $head, $i_clogp, $i_tag,
2470 $i_changesfn, 'remote changes',
2471 sub { "tag$_[0]"; };
2476 sub i_want_signed_dsc_changes {
2477 rename "$i_dscfn.tmp","$i_dscfn" or die "$i_dscfn $!";
2478 sign_changes $i_changesfn;
2479 return ($i_dscfn, $i_changesfn);
2482 #---------- building etc. ----------
2488 #----- `3.0 (quilt)' handling -----
2490 our $fakeeditorenv = 'DGIT_FAKE_EDITOR_QUILT';
2492 sub quiltify_dpkg_commit ($$$;$) {
2493 my ($patchname,$author,$msg, $xinfo) = @_;
2497 my $descfn = ".git/dgit/quilt-description.tmp";
2498 open O, '>', $descfn or die "$descfn: $!";
2501 $msg =~ s/^\s+$/ ./mg;
2502 print O <<END or die $!;
2512 local $ENV{'EDITOR'} = cmdoutput qw(realpath --), $0;
2513 local $ENV{'VISUAL'} = $ENV{'EDITOR'};
2514 local $ENV{$fakeeditorenv} = cmdoutput qw(realpath --), $descfn;
2515 runcmd @dpkgsource, qw(--commit .), $patchname;
2519 sub quiltify_trees_differ ($$;$$) {
2520 my ($x,$y,$finegrained,$ignorenamesr) = @_;
2521 # returns true iff the two tree objects differ other than in debian/
2522 # with $finegrained,
2523 # returns bitmask 01 - differ in upstream files except .gitignore
2524 # 02 - differ in .gitignore
2525 # if $ignorenamesr is defined, $ingorenamesr->{$fn}
2526 # is set for each modified .gitignore filename $fn
2528 my @cmd = (@git, qw(diff-tree --name-only -z));
2529 push @cmd, qw(-r) if $finegrained;
2531 my $diffs= cmdoutput @cmd;
2533 foreach my $f (split /\0/, $diffs) {
2534 next if $f =~ m#^debian(?:/.*)?$#s;
2535 my $isignore = $f =~ m#^(?:.*/)?.gitignore$#s;
2536 $r |= $isignore ? 02 : 01;
2537 $ignorenamesr->{$f}=1 if $ignorenamesr && $isignore;
2539 printdebug "quiltify_trees_differ $x $y => $r\n";
2543 sub quiltify_tree_sentinelfiles ($) {
2544 # lists the `sentinel' files present in the tree
2546 my $r = cmdoutput @git, qw(ls-tree --name-only), $x,
2547 qw(-- debian/rules debian/control);
2552 sub quiltify_splitbrain_needed () {
2553 if (!$split_brain) {
2554 progress "creating dgit view";
2555 runcmd @git, qw(checkout -q -b dgit-view);
2560 sub quiltify_splitbrain ($$$$$$) {
2561 my ($clogp, $unapplied, $headref, $diffbits,
2562 $editedignores, $cachekey) = @_;
2563 if ($quilt_mode !~ m/gbp|dpm/) {
2564 # treat .gitignore just like any other upstream file
2565 $diffbits = { %$diffbits };
2566 $_ = !!$_ foreach values %$diffbits;
2568 # We would like any commits we generate to be reproducible
2569 my @authline = clogp_authline($clogp);
2570 local $ENV{GIT_COMMITTER_NAME} = $authline[0];
2571 local $ENV{GIT_COMMITTER_EMAIL} = $authline[1];
2572 local $ENV{GIT_COMMITTER_DATE} = $authline[2];
2573 if ($quilt_mode =~ m/gbp|unapplied/ &&
2574 ($diffbits->{O2A} & 01) && # some patches
2575 !($diffbits->{H2O} & 01)) { # but HEAD is like orig
2576 quiltify_splitbrain_needed();
2577 progress "creating patches-applied version using gbp-pq";
2578 open STDOUT, ">/dev/null" or die $!;
2579 runcmd shell_cmd 'exec >/dev/null', @gbppq, qw(import);
2580 # gbp-pq import creates a fresh branch; push back to dgit-view
2581 runcmd @git, qw(update-ref refs/heads/dgit-view HEAD);
2582 runcmd @git, qw(checkout dgit-view);
2584 if (($diffbits->{H2O} & 02) && # user has modified .gitignore
2585 !($diffbits->{O2A} & 02)) { # patches do not change .gitignore
2586 quiltify_splitbrain_needed();
2587 progress "creating patch to represent .gitignore changes";
2588 ensuredir "debian/patches";
2589 my $gipatch = "debian/patches/auto-gitignore";
2590 open GIPATCH, ">>", "$gipatch" or die "$gipatch: $!";
2591 stat GIPATCH or die "$gipatch: $!";
2592 fail "$gipatch already exists; but want to create it".
2593 " to record .gitignore changes" if (stat _)[7];
2594 print GIPATCH <<END or die "$gipatch: $!";
2595 Subject: Update .gitignore from Debian packaging branch
2597 The Debian packaging git branch contains these updates to the upstream
2598 .gitignore file(s). This patch is autogenerated, to provide these
2599 updates to users of the official Debian archive view of the package.
2601 [dgit version $our_version]
2604 close GIPATCH or die "$gipatch: $!";
2605 runcmd shell_cmd "exec >>$gipatch", @git, qw(diff),
2606 $unapplied, $headref, "--", sort keys %$editedignores;
2607 open SERIES, "+>>", "debian/patches/series" or die $!;
2608 defined seek SERIES, -1, 2 or $!==EINVAL or die $!;
2610 defined read SERIES, $newline, 1 or die $!;
2611 print SERIES "\n" or die $! unless $newline eq "\n";
2612 print SERIES "auto-gitignore\n" or die $!;
2613 close SERIES or die $!;
2614 runcmd @git, qw(add -- debian/patches/series), $gipatch;
2615 commit_admin "Commit patch to update .gitignore";
2618 my $dgitview = git_rev_parse 'refs/heads/dgit-view';
2620 changedir '../../../..';
2621 ensuredir ".git/logs/refs/dgit-intern";
2622 my $makelogfh = new IO::File ".git/logs/refs/$splitbraincache", '>>'
2624 runcmd @git, qw(update-ref -m), $cachekey, "refs/$splitbraincache",
2627 die 'xxx fast forward (should not depend on quilt mode, but will always be needed if we did $split_brain)';
2628 changedir '.git/dgit/unpack/work';
2631 sub quiltify ($$$$) {
2632 my ($clogp,$target,$oldtiptree,$failsuggestion) = @_;
2634 # Quilt patchification algorithm
2636 # We search backwards through the history of the main tree's HEAD
2637 # (T) looking for a start commit S whose tree object is identical
2638 # to to the patch tip tree (ie the tree corresponding to the
2639 # current dpkg-committed patch series). For these purposes
2640 # `identical' disregards anything in debian/ - this wrinkle is
2641 # necessary because dpkg-source treates debian/ specially.
2643 # We can only traverse edges where at most one of the ancestors'
2644 # trees differs (in changes outside in debian/). And we cannot
2645 # handle edges which change .pc/ or debian/patches. To avoid
2646 # going down a rathole we avoid traversing edges which introduce
2647 # debian/rules or debian/control. And we set a limit on the
2648 # number of edges we are willing to look at.
2650 # If we succeed, we walk forwards again. For each traversed edge
2651 # PC (with P parent, C child) (starting with P=S and ending with
2652 # C=T) to we do this:
2654 # - dpkg-source --commit with a patch name and message derived from C
2655 # After traversing PT, we git commit the changes which
2656 # should be contained within debian/patches.
2658 # The search for the path S..T is breadth-first. We maintain a
2659 # todo list containing search nodes. A search node identifies a
2660 # commit, and looks something like this:
2662 # Commit => $git_commit_id,
2663 # Child => $c, # or undef if P=T
2664 # Whynot => $reason_edge_PC_unsuitable, # in @nots only
2665 # Nontrivial => true iff $p..$c has relevant changes
2672 my %considered; # saves being exponential on some weird graphs
2674 my $t_sentinels = quiltify_tree_sentinelfiles $target;
2677 my ($search,$whynot) = @_;
2678 printdebug " search NOT $search->{Commit} $whynot\n";
2679 $search->{Whynot} = $whynot;
2680 push @nots, $search;
2681 no warnings qw(exiting);
2690 my $c = shift @todo;
2691 next if $considered{$c->{Commit}}++;
2693 $not->($c, "maximum search space exceeded") if --$max_work <= 0;
2695 printdebug "quiltify investigate $c->{Commit}\n";
2698 if (!quiltify_trees_differ $c->{Commit}, $oldtiptree) {
2699 printdebug " search finished hooray!\n";
2704 if ($quilt_mode eq 'nofix') {
2705 fail "quilt fixup required but quilt mode is \`nofix'\n".
2706 "HEAD commit $c->{Commit} differs from tree implied by ".
2707 " debian/patches (tree object $oldtiptree)";
2709 if ($quilt_mode eq 'smash') {
2710 printdebug " search quitting smash\n";
2714 my $c_sentinels = quiltify_tree_sentinelfiles $c->{Commit};
2715 $not->($c, "has $c_sentinels not $t_sentinels")
2716 if $c_sentinels ne $t_sentinels;
2718 my $commitdata = cmdoutput @git, qw(cat-file commit), $c->{Commit};
2719 $commitdata =~ m/\n\n/;
2721 my @parents = ($commitdata =~ m/^parent (\w+)$/gm);
2722 @parents = map { { Commit => $_, Child => $c } } @parents;
2724 $not->($c, "root commit") if !@parents;
2726 foreach my $p (@parents) {
2727 $p->{Nontrivial}= quiltify_trees_differ $p->{Commit},$c->{Commit};
2729 my $ndiffers = grep { $_->{Nontrivial} } @parents;
2730 $not->($c, "merge ($ndiffers nontrivial parents)") if $ndiffers > 1;
2732 foreach my $p (@parents) {
2733 printdebug "considering C=$c->{Commit} P=$p->{Commit}\n";
2735 my @cmd= (@git, qw(diff-tree -r --name-only),
2736 $p->{Commit},$c->{Commit}, qw(-- debian/patches .pc));
2737 my $patchstackchange = cmdoutput @cmd;
2738 if (length $patchstackchange) {
2739 $patchstackchange =~ s/\n/,/g;
2740 $not->($p, "changed $patchstackchange");
2743 printdebug " search queue P=$p->{Commit} ",
2744 ($p->{Nontrivial} ? "NT" : "triv"),"\n";
2750 printdebug "quiltify want to smash\n";
2753 my $x = $_[0]{Commit};
2754 $x =~ s/(.*?[0-9a-z]{8})[0-9a-z]*$/$1/;
2757 my $reportnot = sub {
2759 my $s = $abbrev->($notp);
2760 my $c = $notp->{Child};
2761 $s .= "..".$abbrev->($c) if $c;
2762 $s .= ": ".$notp->{Whynot};
2765 if ($quilt_mode eq 'linear') {
2766 print STDERR "$us: quilt fixup cannot be linear. Stopped at:\n";
2767 foreach my $notp (@nots) {
2768 print STDERR "$us: ", $reportnot->($notp), "\n";
2770 print STDERR "$us: $_\n" foreach @$failsuggestion;
2771 fail "quilt fixup naive history linearisation failed.\n".
2772 "Use dpkg-source --commit by hand; or, --quilt=smash for one ugly patch";
2773 } elsif ($quilt_mode eq 'smash') {
2774 } elsif ($quilt_mode eq 'auto') {
2775 progress "quilt fixup cannot be linear, smashing...";
2777 die "$quilt_mode ?";
2782 my $msg = cmdoutput @git, qw(log), "-n$ncommits";
2784 quiltify_dpkg_commit "auto-$version-$target-$time",
2785 (getfield $clogp, 'Maintainer'),
2786 "Automatically generated patch ($clogp->{Version})\n".
2787 "Last (up to) $ncommits git changes, FYI:\n\n". $msg;
2791 progress "quiltify linearisation planning successful, executing...";
2793 for (my $p = $sref_S;
2794 my $c = $p->{Child};
2796 printdebug "quiltify traverse $p->{Commit}..$c->{Commit}\n";
2797 next unless $p->{Nontrivial};
2799 my $cc = $c->{Commit};
2801 my $commitdata = cmdoutput @git, qw(cat-file commit), $cc;
2802 $commitdata =~ m/\n\n/ or die "$c ?";
2805 $commitdata =~ m/^author (.*) \d+ [-+0-9]+$/m or die "$cc ?";
2808 $msg =~ s/^(.*)\n*/$1\n/ or die "$cc $msg ?";
2811 my $patchname = $title;
2812 $patchname =~ s/[.:]$//;
2813 $patchname =~ y/ A-Z/-a-z/;
2814 $patchname =~ y/-a-z0-9_.+=~//cd;
2815 $patchname =~ s/^\W/x-$&/;
2816 $patchname = substr($patchname,0,40);
2819 stat "debian/patches/$patchname$index";
2821 $!==ENOENT or die "$patchname$index $!";
2823 runcmd @git, qw(checkout -q), $cc;
2825 # We use the tip's changelog so that dpkg-source doesn't
2826 # produce complaining messages from dpkg-parsechangelog. None
2827 # of the information dpkg-source gets from the changelog is
2828 # actually relevant - it gets put into the original message
2829 # which dpkg-source provides our stunt editor, and then
2831 runcmd @git, qw(checkout -q), $target, qw(debian/changelog);
2833 quiltify_dpkg_commit "$patchname$index", $author, $msg,
2834 "X-Dgit-Generated: $clogp->{Version} $cc\n";
2836 runcmd @git, qw(checkout -q), $cc, qw(debian/changelog);
2839 runcmd @git, qw(checkout -q master);
2842 sub build_maybe_quilt_fixup () {
2843 my ($format,$fopts) = get_source_format;
2844 return unless madformat $format;
2847 check_for_vendor_patches();
2849 my $clogp = parsechangelog();
2850 my $headref = git_rev_parse('HEAD');
2855 my $upstreamversion=$version;
2856 $upstreamversion =~ s/-[^-]*$//;
2858 if ($fopts->{'single-debian-patch'}) {
2859 quilt_fixup_singlepatch($clogp, $headref, $upstreamversion);
2861 quilt_fixup_multipatch($clogp, $headref, $upstreamversion);
2864 changedir '../../../..';
2865 runcmd_ordryrun_local
2866 @git, qw(pull --ff-only -q .git/dgit/unpack/work master);
2869 sub quilt_fixup_mkwork ($) {
2872 mkdir "work" or die $!;
2874 mktree_in_ud_here();
2875 runcmd @git, qw(reset -q --hard), $headref;
2878 sub quilt_fixup_linkorigs ($$) {
2879 my ($upstreamversion, $fn) = @_;
2880 # calls $fn->($leafname);
2882 foreach my $f (<../../../../*>) { #/){
2883 my $b=$f; $b =~ s{.*/}{};
2885 local ($debuglevel) = $debuglevel-1;
2886 printdebug "QF linkorigs $b, $f ?\n";
2888 next unless is_orig_file $b, srcfn $upstreamversion,'';
2889 printdebug "QF linkorigs $b, $f Y\n";
2890 link_ltarget $f, $b or die "$b $!";
2895 sub quilt_fixup_delete_pc () {
2896 runcmd @git, qw(rm -rqf .pc);
2897 commit_admin "Commit removal of .pc (quilt series tracking data)";
2900 sub quilt_fixup_singlepatch ($$$) {
2901 my ($clogp, $headref, $upstreamversion) = @_;
2903 progress "starting quiltify (single-debian-patch)";
2905 # dpkg-source --commit generates new patches even if
2906 # single-debian-patch is in debian/source/options. In order to
2907 # get it to generate debian/patches/debian-changes, it is
2908 # necessary to build the source package.
2910 quilt_fixup_linkorigs($upstreamversion, sub { });
2911 quilt_fixup_mkwork($headref);
2913 rmtree("debian/patches");
2915 runcmd @dpkgsource, qw(-b .);
2917 runcmd @dpkgsource, qw(-x), (srcfn $version, ".dsc");
2918 rename srcfn("$upstreamversion", "/debian/patches"),
2919 "work/debian/patches";
2922 commit_quilty_patch();
2927 sub quilt_fixup_multipatch ($$$) {
2928 my ($clogp, $headref, $upstreamversion) = @_;
2930 progress "starting quiltify (multiple patches, $quilt_mode mode)";
2933 # - honour any existing .pc in case it has any strangeness
2934 # - determine the git commit corresponding to the tip of
2935 # the patch stack (if there is one)
2936 # - if there is such a git commit, convert each subsequent
2937 # git commit into a quilt patch with dpkg-source --commit
2938 # - otherwise convert all the differences in the tree into
2939 # a single git commit
2943 # Our git tree doesn't necessarily contain .pc. (Some versions of
2944 # dgit would include the .pc in the git tree.) If there isn't
2945 # one, we need to generate one by unpacking the patches that we
2948 # We first look for a .pc in the git tree. If there is one, we
2949 # will use it. (This is not the normal case.)
2951 # Otherwise need to regenerate .pc so that dpkg-source --commit
2952 # can work. We do this as follows:
2953 # 1. Collect all relevant .orig from parent directory
2954 # 2. Generate a debian.tar.gz out of
2955 # debian/{patches,rules,source/format,source/options}
2956 # 3. Generate a fake .dsc containing just these fields:
2957 # Format Source Version Files
2958 # 4. Extract the fake .dsc
2959 # Now the fake .dsc has a .pc directory.
2960 # (In fact we do this in every case, because in future we will
2961 # want to search for a good base commit for generating patches.)
2963 # Then we can actually do the dpkg-source --commit
2964 # 1. Make a new working tree with the same object
2965 # store as our main tree and check out the main
2967 # 2. Copy .pc from the fake's extraction, if necessary
2968 # 3. Run dpkg-source --commit
2969 # 4. If the result has changes to debian/, then
2970 # - git-add them them
2971 # - git-add .pc if we had a .pc in-tree
2973 # 5. If we had a .pc in-tree, delete it, and git-commit
2974 # 6. Back in the main tree, fast forward to the new HEAD
2976 # Another situation we may have to cope with is gbp-style
2977 # patches-unapplied trees.
2979 # We would want to detect these, so we know to escape into
2980 # quilt_fixup_gbp. However, this is in general not possible.
2981 # Consider a package with a one patch which the dgit user reverts
2982 # (with git-revert or the moral equivalent).
2984 # That is indistinguishable in contents from a patches-unapplied
2985 # tree. And looking at the history to distinguish them is not
2986 # useful because the user might have made a confusing-looking git
2987 # history structure (which ought to produce an error if dgit can't
2988 # cope, not a silent reintroduction of an unwanted patch).
2990 # So gbp users will have to pass an option. But we can usually
2991 # detect their failure to do so: if the tree is not a clean
2992 # patches-applied tree, quilt linearisation fails, but the tree
2993 # _is_ a clean patches-unapplied tree, we can suggest that maybe
2994 # they want --quilt=unapplied.
2996 # To help detect this, when we are extracting the fake dsc, we
2997 # first extract it with --skip-patches, and then apply the patches
2998 # afterwards with dpkg-source --before-build. That lets us save a
2999 # tree object corresponding to .origs.
3001 my $fakeversion="$upstreamversion-~~DGITFAKE";
3003 my $fakedsc=new IO::File 'fake.dsc', '>' or die $!;
3004 print $fakedsc <<END or die $!;
3007 Version: $fakeversion
3011 my $dscaddfile=sub {
3014 my $md = new Digest::MD5;
3016 my $fh = new IO::File $b, '<' or die "$b $!";
3021 print $fakedsc " ".$md->hexdigest." $size $b\n" or die $!;
3024 quilt_fixup_linkorigs($upstreamversion, $dscaddfile);
3026 my @files=qw(debian/source/format debian/rules
3027 debian/control debian/changelog);
3028 foreach my $maybe (qw(debian/patches debian/source/options
3029 debian/tests/control)) {
3030 next unless stat_exists "../../../$maybe";
3031 push @files, $maybe;
3034 my $debtar= srcfn $fakeversion,'.debian.tar.gz';
3035 runcmd qw(env GZIP=-1 tar -zcf), "./$debtar", qw(-C ../../..), @files;
3037 $dscaddfile->($debtar);
3038 close $fakedsc or die $!;
3040 my $splitbrain_cachekey;
3041 if ($quilt_mode =~ m/gbp|dpm|unapplied/) {
3042 # we look in the reflog of dgit-intern/quilt-cache
3043 # we look for an entry whose message is the key for the cache lookup
3044 my @cachekey = (qw(dgit), $our_version);
3045 push @cachekey, $upstreamversion;
3046 push @cachekey, $headref;
3048 push @cachekey, hashfile('fake.dsc');
3050 my $srcshash = Digest::SHA->new(256);
3051 my %sfs = ( %INC, '$0(dgit)' => $0 );
3052 foreach my $sfk (sort keys %sfs) {
3053 $srcshash->add($sfk," ");
3054 $srcshash->add(hashfile($sfs{$sfk}));
3055 $srcshash->add("\n");
3057 push @cachekey, $srcshash->hexdigest();
3058 $splitbrain_cachekey = "@cachekey";
3060 my @cmd = (@git, qw(reflog), '--pretty=format:%H %gs',
3062 printdebug "splitbrain cachekey $splitbrain_cachekey\n";
3063 debugcmd "|(probably)",@cmd;
3064 my $child = open GC, "-|"; defined $child or die $!;
3066 chdir '../../..' or die $!;
3067 if (!stat ".git/logs/refs/$splitbraincache") {
3068 $! == ENOENT or die $!;
3069 printdebug ">(no reflog)\n";
3076 printdebug ">| ", $_, "\n" if $debuglevel > 1;
3077 next unless m/^(\w+) (\S.*\S)$/ && $2 eq $splitbrain_cachekey;
3080 quilt_fixup_mkwork($headref);
3081 if ($cachehit ne $headref) {
3082 progress "quilt fixup ($quilt_mode mode) found cached tree";
3083 runcmd @git, qw(checkout -q -b dgit-view), $cachehit;
3087 progress "quilt fixup ($quilt_mode mode)".
3088 " found cached indication that no changes needed";
3091 die $! if GC->error;
3092 failedcmd unless close GC;
3094 printdebug "splitbrain cache miss\n";
3098 'exec dpkg-source --no-check --skip-patches -x fake.dsc >/dev/null';
3100 my $fakexdir= $package.'-'.(stripepoch $upstreamversion);
3101 rename $fakexdir, "fake" or die "$fakexdir $!";
3105 remove_stray_gits();
3106 mktree_in_ud_here();
3110 runcmd @git, qw(add -Af .);
3111 my $unapplied=git_write_tree();
3112 printdebug "fake orig tree object $unapplied\n";
3117 'exec dpkg-source --before-build . >/dev/null';
3121 quilt_fixup_mkwork($headref);
3124 if (stat_exists ".pc") {
3126 progress "Tree already contains .pc - will use it then delete it.";
3129 rename '../fake/.pc','.pc' or die $!;
3132 changedir '../fake';
3134 runcmd @git, qw(add -Af .);
3135 my $oldtiptree=git_write_tree();
3136 printdebug "fake o+d/p tree object $unapplied\n";
3137 changedir '../work';
3140 # We calculate some guesswork now about what kind of tree this might
3141 # be. This is mostly for error reporting.
3146 # O = orig, without patches applied
3147 # A = "applied", ie orig with H's debian/patches applied
3148 H2O => quiltify_trees_differ($headref, $unapplied, 1,\%editedignores),
3149 H2A => quiltify_trees_differ($headref, $oldtiptree,1),
3150 O2A => quiltify_trees_differ($unapplied,$oldtiptree,1),
3154 foreach my $b (qw(01 02)) {
3155 foreach my $v (qw(H2O O2A H2A)) {
3156 push @dl, ($diffbits->{$v} & $b) ? '##' : '==';
3159 printdebug "differences \@dl @dl.\n";
3162 "$us: quilt differences: src: %s orig %s gitignores: %s orig %s\n".
3163 "$us: quilt differences: HEAD %s o+d/p HEAD %s o+d/p",
3164 $dl[0], $dl[1], $dl[3], $dl[4],
3168 if (!($diffbits->{H2O} & $diffbits->{O2A})) {
3169 push @failsuggestion, "This might be a patches-unapplied branch.";
3170 } elsif (!($diffbits->{H2A} & $diffbits->{O2A})) {
3171 push @failsuggestion, "This might be a patches-applied branch.";
3173 push @failsuggestion, "Maybe you need to specify one of".
3174 " --quilt=gbp --quilt=dpm --quilt=unapplied ?";
3176 if ($splitbrain_cachekey) {
3177 quiltify_splitbrain($clogp, $unapplied, $headref,
3178 $diffbits, \%editedignores,
3179 $splitbrain_cachekey);
3183 quiltify($clogp,$headref,$oldtiptree,\@failsuggestion);
3185 if (!open P, '>>', ".pc/applied-patches") {
3186 $!==&ENOENT or die $!;
3191 commit_quilty_patch();
3193 if ($mustdeletepc) {
3194 quilt_fixup_delete_pc();
3198 sub quilt_fixup_editor () {
3199 my $descfn = $ENV{$fakeeditorenv};
3200 my $editing = $ARGV[$#ARGV];
3201 open I1, '<', $descfn or die "$descfn: $!";
3202 open I2, '<', $editing or die "$editing: $!";
3203 unlink $editing or die "$editing: $!";
3204 open O, '>', $editing or die "$editing: $!";
3205 while (<I1>) { print O or die $!; } I1->error and die $!;
3208 $copying ||= m/^\-\-\- /;
3209 next unless $copying;
3212 I2->error and die $!;
3217 #----- other building -----
3219 our $suppress_clean;
3222 return if $suppress_clean;
3223 if ($cleanmode eq 'dpkg-source') {
3224 runcmd_ordryrun_local @dpkgbuildpackage, qw(-T clean);
3225 } elsif ($cleanmode eq 'dpkg-source-d') {
3226 runcmd_ordryrun_local @dpkgbuildpackage, qw(-d -T clean);
3227 } elsif ($cleanmode eq 'git') {
3228 runcmd_ordryrun_local @git, qw(clean -xdf);
3229 } elsif ($cleanmode eq 'git-ff') {
3230 runcmd_ordryrun_local @git, qw(clean -xdff);
3231 } elsif ($cleanmode eq 'check') {
3232 my $leftovers = cmdoutput @git, qw(clean -xdn);
3233 if (length $leftovers) {
3234 print STDERR $leftovers, "\n" or die $!;
3235 fail "tree contains uncommitted files and --clean=check specified";
3237 } elsif ($cleanmode eq 'none') {
3244 badusage "clean takes no additional arguments" if @ARGV;
3251 badusage "-p is not allowed when building" if defined $package;
3254 my $clogp = parsechangelog();
3255 $isuite = getfield $clogp, 'Distribution';
3256 $package = getfield $clogp, 'Source';
3257 $version = getfield $clogp, 'Version';
3258 build_maybe_quilt_fixup();
3260 my $pat = changespat $version;
3261 foreach my $f (glob "$buildproductsdir/$pat") {
3263 unlink $f or fail "remove old changes file $f: $!";
3265 progress "would remove $f";
3271 sub changesopts_initial () {
3272 my @opts =@changesopts[1..$#changesopts];
3275 sub changesopts_version () {
3276 if (!defined $changes_since_version) {
3277 my @vsns = archive_query('archive_query');
3278 my @quirk = access_quirk();
3279 if ($quirk[0] eq 'backports') {
3280 local $isuite = $quirk[2];
3282 canonicalise_suite();
3283 push @vsns, archive_query('archive_query');
3286 @vsns = map { $_->[0] } @vsns;
3287 @vsns = sort { -version_compare($a, $b) } @vsns;
3288 $changes_since_version = $vsns[0];
3289 progress "changelog will contain changes since $vsns[0]";
3291 $changes_since_version = '_';
3292 progress "package seems new, not specifying -v<version>";
3295 if ($changes_since_version ne '_') {
3296 return ("-v$changes_since_version");
3302 sub changesopts () {
3303 return (changesopts_initial(), changesopts_version());
3306 sub massage_dbp_args ($;$) {
3307 my ($cmd,$xargs) = @_;
3310 # - if we're going to split the source build out so we can
3311 # do strange things to it, massage the arguments to dpkg-buildpackage
3312 # so that the main build doessn't build source (or add an argument
3313 # to stop it building source by default).
3315 # - add -nc to stop dpkg-source cleaning the source tree,
3316 # unless we're not doing a split build and want dpkg-source
3317 # as cleanmode, in which case we can do nothing
3320 # 0 - source will NOT need to be built separately by caller
3321 # +1 - source will need to be built separately by caller
3322 # +2 - source will need to be built separately by caller AND
3323 # dpkg-buildpackage should not in fact be run at all!
3324 debugcmd '#massaging#', @$cmd if $debuglevel>1;
3325 #print STDERR "MASS0 ",Dumper($cmd, $xargs, $need_split_build_invocation);
3326 if ($cleanmode eq 'dpkg-source' && !$need_split_build_invocation) {
3327 $suppress_clean = 1;
3330 # -nc has the side effect of specifying -b if nothing else specified
3331 # and some combinations of -S, -b, et al, are errors, rather than
3332 # later simply overriding earlie. So we need to:
3333 # - search the command line for these options
3334 # - pick the last one
3335 # - perhaps add our own as a default
3336 # - perhaps adjust it to the corresponding non-source-building version
3338 foreach my $l ($cmd, $xargs) {
3340 @$l = grep { !(m/^-[SgGFABb]$/s and $dmode=$_) } @$l;
3343 #print STDERR "MASS1 ",Dumper($cmd, $xargs, $dmode);
3345 if ($need_split_build_invocation) {
3346 $r = $dmode =~ m/[S]/ ? +2 :
3347 $dmode =~ y/gGF/ABb/ ? +1 :
3348 $dmode =~ m/[ABb]/ ? 0 :
3352 #print STDERR "MASS2 ",Dumper($cmd, $xargs, $r);
3357 my @dbp = (@dpkgbuildpackage, qw(-us -uc), changesopts_initial(), @ARGV);
3358 my $wantsrc = massage_dbp_args \@dbp;
3365 push @dbp, changesopts_version();
3366 runcmd_ordryrun_local @dbp;
3368 printdone "build successful\n";
3372 my @dbp = @dpkgbuildpackage;
3374 my $wantsrc = massage_dbp_args \@dbp, \@ARGV;
3377 if (length executable_on_path('git-buildpackage')) {
3378 @cmd = qw(git-buildpackage);
3380 @cmd = qw(gbp buildpackage);
3382 push @cmd, (qw(-us -uc --git-no-sign-tags), "--git-builder=@dbp");
3387 if (!$suppress_clean) {
3388 push @cmd, '--git-cleaner=true';
3393 unless (grep { m/^--git-debian-branch|^--git-ignore-branch/ } @ARGV) {
3394 canonicalise_suite();
3395 push @cmd, "--git-debian-branch=".lbranch();
3397 push @cmd, changesopts();
3398 runcmd_ordryrun_local @cmd, @ARGV;
3400 printdone "build successful\n";
3402 sub cmd_git_build { cmd_gbp_build(); } # compatibility with <= 1.0
3405 if ($cleanmode =~ m/^dpkg-source/) {
3406 # dpkg-source will clean, so we shouldn't
3407 $suppress_clean = 1;
3410 $sourcechanges = changespat $version,'source';
3412 unlink "../$sourcechanges" or $!==ENOENT
3413 or fail "remove $sourcechanges: $!";
3415 $dscfn = dscfn($version);
3416 if ($cleanmode eq 'dpkg-source') {
3417 runcmd_ordryrun_local @dpkgbuildpackage, qw(-us -uc -S),
3419 } elsif ($cleanmode eq 'dpkg-source-d') {
3420 runcmd_ordryrun_local @dpkgbuildpackage, qw(-us -uc -S -d),
3423 my $pwd = must_getcwd();
3424 my $leafdir = basename $pwd;
3426 runcmd_ordryrun_local @dpkgsource, qw(-b --), $leafdir;
3428 runcmd_ordryrun_local qw(sh -ec),
3429 'exec >$1; shift; exec "$@"','x',
3430 "../$sourcechanges",
3431 @dpkggenchanges, qw(-S), changesopts();
3435 sub cmd_build_source {
3436 badusage "build-source takes no additional arguments" if @ARGV;
3438 printdone "source built, results in $dscfn and $sourcechanges";
3443 my $pat = changespat $version;
3445 my @unwanted = map { s#^\.\./##; $_; } glob "../$pat";
3446 @unwanted = grep { $_ ne changespat $version,'source' } @unwanted;
3447 fail "changes files other than source matching $pat".
3448 " already present (@unwanted);".
3449 " building would result in ambiguity about the intended results"
3454 stat_exists $dscfn or fail "$dscfn (in parent directory): $!";
3455 stat_exists $sourcechanges
3456 or fail "$sourcechanges (in parent directory): $!";
3458 runcmd_ordryrun_local @sbuild, qw(-d), $isuite, @ARGV, $dscfn;
3459 my @changesfiles = glob $pat;
3460 @changesfiles = sort {
3461 ($b =~ m/_source\.changes$/ <=> $a =~ m/_source\.changes$/)
3464 fail "wrong number of different changes files (@changesfiles)"
3465 unless @changesfiles==2;
3466 my $binchanges = parsecontrol($changesfiles[1], "binary changes file");
3467 foreach my $l (split /\n/, getfield $binchanges, 'Files') {
3468 fail "$l found in binaries changes file $binchanges"
3471 runcmd_ordryrun_local @mergechanges, @changesfiles;
3472 my $multichanges = changespat $version,'multi';
3474 stat_exists $multichanges or fail "$multichanges: $!";
3475 foreach my $cf (glob $pat) {
3476 next if $cf eq $multichanges;
3477 rename "$cf", "$cf.inmulti" or fail "$cf\{,.inmulti}: $!";
3480 printdone "build successful, results in $multichanges\n" or die $!;
3483 sub cmd_quilt_fixup {
3484 badusage "incorrect arguments to dgit quilt-fixup" if @ARGV;
3485 my $clogp = parsechangelog();
3486 $version = getfield $clogp, 'Version';
3487 $package = getfield $clogp, 'Source';
3490 build_maybe_quilt_fixup();
3493 sub cmd_archive_api_query {
3494 badusage "need only 1 subpath argument" unless @ARGV==1;
3495 my ($subpath) = @ARGV;
3496 my @cmd = archive_api_query_cmd($subpath);
3498 exec @cmd or fail "exec curl: $!\n";
3501 sub cmd_clone_dgit_repos_server {
3502 badusage "need destination argument" unless @ARGV==1;
3503 my ($destdir) = @ARGV;
3504 $package = '_dgit-repos-server';
3505 my @cmd = (@git, qw(clone), access_giturl(), $destdir);
3507 exec @cmd or fail "exec git clone: $!\n";
3510 sub cmd_setup_mergechangelogs {
3511 badusage "no arguments allowed to dgit setup-mergechangelogs" if @ARGV;
3512 setup_mergechangelogs(1);
3515 sub cmd_setup_useremail {
3516 badusage "no arguments allowed to dgit setup-mergechangelogs" if @ARGV;
3520 sub cmd_setup_new_tree {
3521 badusage "no arguments allowed to dgit setup-tree" if @ARGV;
3525 #---------- argument parsing and main program ----------
3528 print "dgit version $our_version\n" or die $!;
3532 our (%valopts_long, %valopts_short);
3535 sub defvalopt ($$$$) {
3536 my ($long,$short,$val_re,$how) = @_;
3537 my $oi = { Long => $long, Short => $short, Re => $val_re, How => $how };
3538 $valopts_long{$long} = $oi;
3539 $valopts_short{$short} = $oi;
3540 # $how subref should:
3541 # do whatever assignemnt or thing it likes with $_[0]
3542 # if the option should not be passed on to remote, @rvalopts=()
3543 # or $how can be a scalar ref, meaning simply assign the value
3546 defvalopt '--since-version', '-v', '[^_]+|_', \$changes_since_version;
3547 defvalopt '--distro', '-d', '.+', \$idistro;
3548 defvalopt '', '-k', '.+', \$keyid;
3549 defvalopt '--existing-package','', '.*', \$existing_package;
3550 defvalopt '--build-products-dir','','.*', \$buildproductsdir;
3551 defvalopt '--clean', '', $cleanmode_re, \$cleanmode;
3552 defvalopt '--quilt', '', $quilt_modes_re, \$quilt_mode;
3554 defvalopt '', '-c', '.*=.*', sub { push @git, '-c', @_; };
3556 defvalopt '', '-C', '.+', sub {
3557 ($changesfile) = (@_);
3558 if ($changesfile =~ s#^(.*)/##) {
3559 $buildproductsdir = $1;
3563 defvalopt '--initiator-tempdir','','.*', sub {
3564 ($initiator_tempdir) = (@_);
3565 $initiator_tempdir =~ m#^/# or
3566 badusage "--initiator-tempdir must be used specify an".
3567 " absolute, not relative, directory."
3573 if (defined $ENV{'DGIT_SSH'}) {
3574 @ssh = string_to_ssh $ENV{'DGIT_SSH'};
3575 } elsif (defined $ENV{'GIT_SSH'}) {
3576 @ssh = ($ENV{'GIT_SSH'});
3584 if (!defined $val) {
3585 badusage "$what needs a value" unless @ARGV;
3587 push @rvalopts, $val;
3589 badusage "bad value \`$val' for $what" unless
3590 $val =~ m/^$oi->{Re}$(?!\n)/s;
3591 my $how = $oi->{How};
3592 if (ref($how) eq 'SCALAR') {
3597 push @ropts, @rvalopts;
3601 last unless $ARGV[0] =~ m/^-/;
3605 if (m/^--dry-run$/) {
3608 } elsif (m/^--damp-run$/) {
3611 } elsif (m/^--no-sign$/) {
3614 } elsif (m/^--help$/) {
3616 } elsif (m/^--version$/) {
3618 } elsif (m/^--new$/) {
3621 } elsif (m/^--([-0-9a-z]+)=(.+)/s &&
3622 ($om = $opts_opt_map{$1}) &&
3626 } elsif (m/^--([-0-9a-z]+):(.*)/s &&
3627 !$opts_opt_cmdonly{$1} &&
3628 ($om = $opts_opt_map{$1})) {
3631 } elsif (m/^--ignore-dirty$/s) {
3634 } elsif (m/^--no-quilt-fixup$/s) {
3636 $quilt_mode = 'nocheck';
3637 } elsif (m/^--no-rm-on-error$/s) {
3640 } elsif (m/^--(no-)?rm-old-changes$/s) {
3643 } elsif (m/^--deliberately-($deliberately_re)$/s) {
3645 push @deliberatelies, $&;
3646 } elsif (m/^--always-split-source-build$/s) {
3647 # undocumented, for testing
3649 $need_split_build_invocation = 1;
3650 } elsif (m/^(--[-0-9a-z]+)(=|$)/ && ($oi = $valopts_long{$1})) {
3651 $val = $2 ? $' : undef; #';
3652 $valopt->($oi->{Long});
3654 badusage "unknown long option \`$_'";
3661 } elsif (s/^-L/-/) {
3664 } elsif (s/^-h/-/) {
3666 } elsif (s/^-D/-/) {
3670 } elsif (s/^-N/-/) {
3675 push @changesopts, $_;
3677 } elsif (s/^-wn$//s) {
3679 $cleanmode = 'none';
3680 } elsif (s/^-wg$//s) {
3683 } elsif (s/^-wgf$//s) {
3685 $cleanmode = 'git-ff';
3686 } elsif (s/^-wd$//s) {
3688 $cleanmode = 'dpkg-source';
3689 } elsif (s/^-wdd$//s) {
3691 $cleanmode = 'dpkg-source-d';
3692 } elsif (s/^-wc$//s) {
3694 $cleanmode = 'check';
3695 } elsif (m/^-[a-zA-Z]/ && ($oi = $valopts_short{$&})) {
3697 $val = undef unless length $val;
3698 $valopt->($oi->{Short});
3701 badusage "unknown short option \`$_'";
3708 sub finalise_opts_opts () {
3709 foreach my $k (keys %opts_opt_map) {
3710 my $om = $opts_opt_map{$k};
3712 my $v = access_cfg("cmd-$k", 'RETURN-UNDEF');
3714 badcfg "cannot set command for $k"
3715 unless length $om->[0];
3719 foreach my $c (access_cfg_cfgs("opts-$k")) {
3720 my $vl = $gitcfg{$c};
3721 printdebug "CL $c ",
3722 ($vl ? join " ", map { shellquote } @$vl : ""),
3723 "\n" if $debuglevel >= 4;
3725 badcfg "cannot configure options for $k"
3726 if $opts_opt_cmdonly{$k};
3727 my $insertpos = $opts_cfg_insertpos{$k};
3728 @$om = ( @$om[0..$insertpos-1],
3730 @$om[$insertpos..$#$om] );
3735 if ($ENV{$fakeeditorenv}) {
3737 quilt_fixup_editor();
3743 print STDERR "DRY RUN ONLY\n" if $dryrun_level > 1;
3744 print STDERR "DAMP RUN - WILL MAKE LOCAL (UNSIGNED) CHANGES\n"
3745 if $dryrun_level == 1;
3747 print STDERR $helpmsg or die $!;
3750 my $cmd = shift @ARGV;
3753 if (!defined $rmchanges) {
3754 local $access_forpush;
3755 $rmchanges = access_cfg_bool(0, 'rm-old-changes');
3758 if (!defined $quilt_mode) {
3759 local $access_forpush;
3760 $quilt_mode = cfg('dgit.force.quilt-mode', 'RETURN-UNDEF')
3761 // access_cfg('quilt-mode', 'RETURN-UNDEF')
3763 $quilt_mode =~ m/^($quilt_modes_re)$/
3764 or badcfg "unknown quilt-mode \`$quilt_mode'";
3768 if (!defined $cleanmode) {
3769 local $access_forpush;
3770 $cleanmode = access_cfg('clean-mode', 'RETURN-UNDEF');
3771 $cleanmode //= 'dpkg-source';
3773 badcfg "unknown clean-mode \`$cleanmode'" unless
3774 $cleanmode =~ m/^($cleanmode_re)$(?!\n)/s;
3777 my $fn = ${*::}{"cmd_$cmd"};
3778 $fn or badusage "unknown operation $cmd";