3 # Integration between git and Debian-style archives
5 # Copyright (C)2013-2015 Ian Jackson
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
28 use Dpkg::Control::Hash;
30 use File::Temp qw(tempdir);
40 our $our_version = 'UNRELEASED'; ###substituted###
42 our @rpushprotovsn_support = qw(3 2);
45 our $isuite = 'unstable';
51 our $dryrun_level = 0;
53 our $buildproductsdir = '..';
59 our $existing_package = 'dpkg';
61 our $changes_since_version;
64 our $quilt_modes_re = 'linear|smash|auto|nofix|nocheck|gbp|unapplied';
65 our $we_are_responder;
66 our $initiator_tempdir;
67 our $patches_applied_dirtily = 00;
69 our %format_ok = map { $_=>1 } ("1.0","3.0 (native)","3.0 (quilt)");
71 our $suite_re = '[-+.0-9a-z]+';
72 our $cleanmode_re = 'dpkg-source(?:-d)?|git|git-ff|check|none';
74 our $git_authline_re = '^([^<>]+) \<(\S+)\> (\d+ [-+]\d+)$';
75 our $splitbraincache = 'dgit-intern/quilt-cache';
78 our (@dget) = qw(dget);
79 our (@curl) = qw(curl -f);
80 our (@dput) = qw(dput);
81 our (@debsign) = qw(debsign);
83 our (@sbuild) = qw(sbuild);
85 our (@dgit) = qw(dgit);
86 our (@dpkgbuildpackage) = qw(dpkg-buildpackage -i\.git/ -I.git);
87 our (@dpkgsource) = qw(dpkg-source -i\.git/ -I.git);
88 our (@dpkggenchanges) = qw(dpkg-genchanges);
89 our (@mergechanges) = qw(mergechanges -f);
91 our (@changesopts) = ('');
93 our %opts_opt_map = ('dget' => \@dget, # accept for compatibility
96 'debsign' => \@debsign,
102 'dpkg-source' => \@dpkgsource,
103 'dpkg-buildpackage' => \@dpkgbuildpackage,
104 'dpkg-genchanges' => \@dpkggenchanges,
106 'ch' => \@changesopts,
107 'mergechanges' => \@mergechanges);
109 our %opts_opt_cmdonly = ('gpg' => 1, 'git' => 1);
110 our %opts_cfg_insertpos = map {
112 scalar @{ $opts_opt_map{$_} }
113 } keys %opts_opt_map;
115 sub finalise_opts_opts();
121 our $supplementary_message = '';
122 our $need_split_build_invocation = 0;
123 our $split_brain = 0;
127 print STDERR "! $_\n" foreach $supplementary_message =~ m/^.+$/mg;
130 our $remotename = 'dgit';
131 our @ourdscfield = qw(Dgit Vcs-Dgit-Master);
135 sub lbranch () { return "$branchprefix/$csuite"; }
136 my $lbranch_re = '^refs/heads/'.$branchprefix.'/([^/.]+)$';
137 sub lref () { return "refs/heads/".lbranch(); }
138 sub lrref () { return "refs/remotes/$remotename/".server_branch($csuite); }
139 sub rrref () { return server_ref($csuite); }
141 sub lrfetchrefs () { return "refs/dgit-fetch/$csuite"; }
151 return "${package}_".(stripepoch $vsn).$sfx
156 return srcfn($vsn,".dsc");
159 sub changespat ($;$) {
160 my ($vsn, $arch) = @_;
161 return "${package}_".(stripepoch $vsn)."_".($arch//'*').".changes";
170 foreach my $f (@end) {
172 print STDERR "$us: cleanup: $@" if length $@;
176 sub badcfg { print STDERR "$us: invalid configuration: @_\n"; exit 12; }
178 sub no_such_package () {
179 print STDERR "$us: package $package does not exist in suite $isuite\n";
185 return "+".rrref().":".lrref();
190 printdebug "CD $newdir\n";
191 chdir $newdir or die "chdir: $newdir: $!";
194 sub deliberately ($) {
196 return !!grep { $_ eq "--deliberately-$enquiry" } @deliberatelies;
199 sub deliberately_not_fast_forward () {
200 foreach (qw(not-fast-forward fresh-repo)) {
201 return 1 if deliberately($_) || deliberately("TEST-dgit-only-$_");
205 sub quiltmode_splitbrain () {
206 $quilt_mode =~ m/gbp|dpm|unapplied/;
209 #---------- remote protocol support, common ----------
211 # remote push initiator/responder protocol:
212 # $ dgit remote-push-build-host <n-rargs> <rargs>... <push-args>...
213 # where <rargs> is <push-host-dir> <supported-proto-vsn>,... ...
214 # < dgit-remote-push-ready <actual-proto-vsn>
221 # > supplementary-message NBYTES # $protovsn >= 3
226 # > file parsed-changelog
227 # [indicates that output of dpkg-parsechangelog follows]
228 # > data-block NBYTES
229 # > [NBYTES bytes of data (no newline)]
230 # [maybe some more blocks]
240 # > param csuite SUITE
242 # > previously REFNAME=OBJNAME # if --deliberately-not-fast-forward
243 # # goes into tag, for replay prevention
246 # [indicates that signed tag is wanted]
247 # < data-block NBYTES
248 # < [NBYTES bytes of data (no newline)]
249 # [maybe some more blocks]
253 # > want signed-dsc-changes
254 # < data-block NBYTES [transfer of signed dsc]
256 # < data-block NBYTES [transfer of signed changes]
264 sub i_child_report () {
265 # Sees if our child has died, and reap it if so. Returns a string
266 # describing how it died if it failed, or undef otherwise.
267 return undef unless $i_child_pid;
268 my $got = waitpid $i_child_pid, WNOHANG;
269 return undef if $got <= 0;
270 die unless $got == $i_child_pid;
271 $i_child_pid = undef;
272 return undef unless $?;
273 return "build host child ".waitstatusmsg();
278 fail "connection lost: $!" if $fh->error;
279 fail "protocol violation; $m not expected";
282 sub badproto_badread ($$) {
284 fail "connection lost: $!" if $!;
285 my $report = i_child_report();
286 fail $report if defined $report;
287 badproto $fh, "eof (reading $wh)";
290 sub protocol_expect (&$) {
291 my ($match, $fh) = @_;
294 defined && chomp or badproto_badread $fh, "protocol message";
302 badproto $fh, "\`$_'";
305 sub protocol_send_file ($$) {
306 my ($fh, $ourfn) = @_;
307 open PF, "<", $ourfn or die "$ourfn: $!";
310 my $got = read PF, $d, 65536;
311 die "$ourfn: $!" unless defined $got;
313 print $fh "data-block ".length($d)."\n" or die $!;
314 print $fh $d or die $!;
316 PF->error and die "$ourfn $!";
317 print $fh "data-end\n" or die $!;
321 sub protocol_read_bytes ($$) {
322 my ($fh, $nbytes) = @_;
323 $nbytes =~ m/^[1-9]\d{0,5}$|^0$/ or badproto \*RO, "bad byte count";
325 my $got = read $fh, $d, $nbytes;
326 $got==$nbytes or badproto_badread $fh, "data block";
330 sub protocol_receive_file ($$) {
331 my ($fh, $ourfn) = @_;
332 printdebug "() $ourfn\n";
333 open PF, ">", $ourfn or die "$ourfn: $!";
335 my ($y,$l) = protocol_expect {
336 m/^data-block (.*)$/ ? (1,$1) :
337 m/^data-end$/ ? (0,) :
341 my $d = protocol_read_bytes $fh, $l;
342 print PF $d or die $!;
347 #---------- remote protocol support, responder ----------
349 sub responder_send_command ($) {
351 return unless $we_are_responder;
352 # called even without $we_are_responder
353 printdebug ">> $command\n";
354 print PO $command, "\n" or die $!;
357 sub responder_send_file ($$) {
358 my ($keyword, $ourfn) = @_;
359 return unless $we_are_responder;
360 printdebug "]] $keyword $ourfn\n";
361 responder_send_command "file $keyword";
362 protocol_send_file \*PO, $ourfn;
365 sub responder_receive_files ($@) {
366 my ($keyword, @ourfns) = @_;
367 die unless $we_are_responder;
368 printdebug "[[ $keyword @ourfns\n";
369 responder_send_command "want $keyword";
370 foreach my $fn (@ourfns) {
371 protocol_receive_file \*PI, $fn;
374 protocol_expect { m/^files-end$/ } \*PI;
377 #---------- remote protocol support, initiator ----------
379 sub initiator_expect (&) {
381 protocol_expect { &$match } \*RO;
384 #---------- end remote code ----------
387 if ($we_are_responder) {
389 responder_send_command "progress ".length($m) or die $!;
390 print PO $m or die $!;
400 $ua = LWP::UserAgent->new();
404 progress "downloading $what...";
405 my $r = $ua->get(@_) or die $!;
406 return undef if $r->code == 404;
407 $r->is_success or fail "failed to fetch $what: ".$r->status_line;
408 return $r->decoded_content(charset => 'none');
411 our ($dscdata,$dscurl,$dsc,$dsc_checked,$skew_warning_vsn);
416 failedcmd @_ if system @_;
419 sub act_local () { return $dryrun_level <= 1; }
420 sub act_scary () { return !$dryrun_level; }
423 if (!$dryrun_level) {
424 progress "dgit ok: @_";
426 progress "would be ok: @_ (but dry run only)";
431 printcmd(\*STDERR,$debugprefix."#",@_);
434 sub runcmd_ordryrun {
442 sub runcmd_ordryrun_local {
451 my ($first_shell, @cmd) = @_;
452 return qw(sh -ec), $first_shell.'; exec "$@"', 'x', @cmd;
455 our $helpmsg = <<END;
457 dgit [dgit-opts] clone [dgit-opts] package [suite] [./dir|/dir]
458 dgit [dgit-opts] fetch|pull [dgit-opts] [suite]
459 dgit [dgit-opts] build [dpkg-buildpackage-opts]
460 dgit [dgit-opts] sbuild [sbuild-opts]
461 dgit [dgit-opts] push [dgit-opts] [suite]
462 dgit [dgit-opts] rpush build-host:build-dir ...
463 important dgit options:
464 -k<keyid> sign tag and package with <keyid> instead of default
465 --dry-run -n do not change anything, but go through the motions
466 --damp-run -L like --dry-run but make local changes, without signing
467 --new -N allow introducing a new package
468 --debug -D increase debug level
469 -c<name>=<value> set git config option (used directly by dgit too)
472 our $later_warning_msg = <<END;
473 Perhaps the upload is stuck in incoming. Using the version from git.
477 print STDERR "$us: @_\n", $helpmsg or die $!;
482 @ARGV or badusage "too few arguments";
483 return scalar shift @ARGV;
487 print $helpmsg or die $!;
491 our $td = $ENV{DGIT_TEST_DUMMY_DIR} || "DGIT_TEST_DUMMY_DIR-unset";
493 our %defcfg = ('dgit.default.distro' => 'debian',
494 'dgit.default.username' => '',
495 'dgit.default.archive-query-default-component' => 'main',
496 'dgit.default.ssh' => 'ssh',
497 'dgit.default.archive-query' => 'madison:',
498 'dgit.default.sshpsql-dbname' => 'service=projectb',
499 'dgit-distro.debian.archive-query' => 'ftpmasterapi:',
500 'dgit-distro.debian.git-check' => 'url',
501 'dgit-distro.debian.git-check-suffix' => '/info/refs',
502 'dgit-distro.debian.new-private-pushers' => 't',
503 'dgit-distro.debian/push.git-url' => '',
504 'dgit-distro.debian/push.git-host' => 'push.dgit.debian.org',
505 'dgit-distro.debian/push.git-user-force' => 'dgit',
506 'dgit-distro.debian/push.git-proto' => 'git+ssh://',
507 'dgit-distro.debian/push.git-path' => '/dgit/debian/repos',
508 'dgit-distro.debian/push.git-create' => 'true',
509 'dgit-distro.debian/push.git-check' => 'ssh-cmd',
510 'dgit-distro.debian.archive-query-url', 'https://api.ftp-master.debian.org/',
511 # 'dgit-distro.debian.archive-query-tls-key',
512 # '/etc/ssl/certs/%HOST%.pem:/etc/dgit/%HOST%.pem',
513 # ^ this does not work because curl is broken nowadays
514 # Fixing #790093 properly will involve providing providing the key
515 # in some pacagke and maybe updating these paths.
517 # 'dgit-distro.debian.archive-query-tls-curl-args',
518 # '--ca-path=/etc/ssl/ca-debian',
519 # ^ this is a workaround but works (only) on DSA-administered machines
520 'dgit-distro.debian.git-url' => 'https://git.dgit.debian.org',
521 'dgit-distro.debian.git-url-suffix' => '',
522 'dgit-distro.debian.upload-host' => 'ftp-master', # for dput
523 'dgit-distro.debian.mirror' => 'http://ftp.debian.org/debian/',
524 'dgit-distro.debian.backports-quirk' => '(squeeze)-backports*',
525 'dgit-distro.debian-backports.mirror' => 'http://backports.debian.org/debian-backports/',
526 'dgit-distro.ubuntu.git-check' => 'false',
527 'dgit-distro.ubuntu.mirror' => 'http://archive.ubuntu.com/ubuntu',
528 'dgit-distro.test-dummy.ssh' => "$td/ssh",
529 'dgit-distro.test-dummy.username' => "alice",
530 'dgit-distro.test-dummy.git-check' => "ssh-cmd",
531 'dgit-distro.test-dummy.git-create' => "ssh-cmd",
532 'dgit-distro.test-dummy.git-url' => "$td/git",
533 'dgit-distro.test-dummy.git-host' => "git",
534 'dgit-distro.test-dummy.git-path' => "$td/git",
535 'dgit-distro.test-dummy.archive-query' => "ftpmasterapi:",
536 'dgit-distro.test-dummy.archive-query-url' => "file://$td/aq/",
537 'dgit-distro.test-dummy.mirror' => "file://$td/mirror/",
538 'dgit-distro.test-dummy.upload-host' => 'test-dummy',
543 sub git_slurp_config () {
544 local ($debuglevel) = $debuglevel-2;
547 my @cmd = (@git, qw(config -z --get-regexp .*));
550 open GITS, "-|", @cmd or die $!;
553 printdebug "=> ", (messagequote $_), "\n";
555 push @{ $gitcfg{$`} }, $'; #';
559 or ($!==0 && $?==256)
563 sub git_get_config ($) {
566 printdebug"C $c ".(defined $l ? messagequote "'$l'" : "undef")."\n"
569 @$l==1 or badcfg "multiple values for $c" if @$l > 1;
575 return undef if $c =~ /RETURN-UNDEF/;
576 my $v = git_get_config($c);
577 return $v if defined $v;
578 my $dv = $defcfg{$c};
579 return $dv if defined $dv;
581 badcfg "need value for one of: @_\n".
582 "$us: distro or suite appears not to be (properly) supported";
585 sub access_basedistro () {
586 if (defined $idistro) {
589 return cfg("dgit-suite.$isuite.distro",
590 "dgit.default.distro");
594 sub access_quirk () {
595 # returns (quirk name, distro to use instead or undef, quirk-specific info)
596 my $basedistro = access_basedistro();
597 my $backports_quirk = cfg("dgit-distro.$basedistro.backports-quirk",
599 if (defined $backports_quirk) {
600 my $re = $backports_quirk;
601 $re =~ s/[^-0-9a-z_\%*()]/\\$&/ig;
603 $re =~ s/\%/([-0-9a-z_]+)/
604 or $re =~ m/[()]/ or badcfg "backports-quirk needs \% or ( )";
605 if ($isuite =~ m/^$re$/) {
606 return ('backports',"$basedistro-backports",$1);
609 return ('none',undef);
614 sub parse_cfg_bool ($$$) {
615 my ($what,$def,$v) = @_;
618 $v =~ m/^[ty1]/ ? 1 :
619 $v =~ m/^[fn0]/ ? 0 :
620 badcfg "$what needs t (true, y, 1) or f (false, n, 0) not \`$v'";
623 sub access_forpush_config () {
624 my $d = access_basedistro();
628 parse_cfg_bool('new-private-pushers', 0,
629 cfg("dgit-distro.$d.new-private-pushers",
632 my $v = cfg("dgit-distro.$d.readonly", 'RETURN-UNDEF');
635 $v =~ m/^[ty1]/ ? 0 : # force readonly, forpush = 0
636 $v =~ m/^[fn0]/ ? 1 : # force nonreadonly, forpush = 1
637 $v =~ m/^[a]/ ? '' : # auto, forpush = ''
638 badcfg "readonly needs t (true, y, 1) or f (false, n, 0) or a (auto)";
641 sub access_forpush () {
642 $access_forpush //= access_forpush_config();
643 return $access_forpush;
647 die "$access_forpush ?" if ($access_forpush // 1) ne 1;
648 badcfg "pushing but distro is configured readonly"
649 if access_forpush_config() eq '0';
651 $supplementary_message = <<'END' unless $we_are_responder;
652 Push failed, before we got started.
653 You can retry the push, after fixing the problem, if you like.
655 finalise_opts_opts();
659 finalise_opts_opts();
662 sub supplementary_message ($) {
664 if (!$we_are_responder) {
665 $supplementary_message = $msg;
667 } elsif ($protovsn >= 3) {
668 responder_send_command "supplementary-message ".length($msg)
670 print PO $msg or die $!;
674 sub access_distros () {
675 # Returns list of distros to try, in order
678 # 0. `instead of' distro name(s) we have been pointed to
679 # 1. the access_quirk distro, if any
680 # 2a. the user's specified distro, or failing that } basedistro
681 # 2b. the distro calculated from the suite }
682 my @l = access_basedistro();
684 my (undef,$quirkdistro) = access_quirk();
685 unshift @l, $quirkdistro;
686 unshift @l, $instead_distro;
687 @l = grep { defined } @l;
689 if (access_forpush()) {
690 @l = map { ("$_/push", $_) } @l;
695 sub access_cfg_cfgs (@) {
698 # The nesting of these loops determines the search order. We put
699 # the key loop on the outside so that we search all the distros
700 # for each key, before going on to the next key. That means that
701 # if access_cfg is called with a more specific, and then a less
702 # specific, key, an earlier distro can override the less specific
703 # without necessarily overriding any more specific keys. (If the
704 # distro wants to override the more specific keys it can simply do
705 # so; whereas if we did the loop the other way around, it would be
706 # impossible to for an earlier distro to override a less specific
707 # key but not the more specific ones without restating the unknown
708 # values of the more specific keys.
711 # We have to deal with RETURN-UNDEF specially, so that we don't
712 # terminate the search prematurely.
714 if (m/RETURN-UNDEF/) { push @rundef, $_; last; }
717 foreach my $d (access_distros()) {
718 push @cfgs, map { "dgit-distro.$d.$_" } @realkeys;
720 push @cfgs, map { "dgit.default.$_" } @realkeys;
727 my (@cfgs) = access_cfg_cfgs(@keys);
728 my $value = cfg(@cfgs);
732 sub access_cfg_bool ($$) {
733 my ($def, @keys) = @_;
734 parse_cfg_bool($keys[0], $def, access_cfg(@keys, 'RETURN-UNDEF'));
737 sub string_to_ssh ($) {
739 if ($spec =~ m/\s/) {
740 return qw(sh -ec), 'exec '.$spec.' "$@"', 'x';
746 sub access_cfg_ssh () {
747 my $gitssh = access_cfg('ssh', 'RETURN-UNDEF');
748 if (!defined $gitssh) {
751 return string_to_ssh $gitssh;
755 sub access_runeinfo ($) {
757 return ": dgit ".access_basedistro()." $info ;";
760 sub access_someuserhost ($) {
762 my $user = access_cfg("$some-user-force", 'RETURN-UNDEF');
763 defined($user) && length($user) or
764 $user = access_cfg("$some-user",'username');
765 my $host = access_cfg("$some-host");
766 return length($user) ? "$user\@$host" : $host;
769 sub access_gituserhost () {
770 return access_someuserhost('git');
773 sub access_giturl (;$) {
775 my $url = access_cfg('git-url','RETURN-UNDEF');
778 my $proto = access_cfg('git-proto', 'RETURN-UNDEF');
779 return undef unless defined $proto;
782 access_gituserhost().
783 access_cfg('git-path');
785 $suffix = access_cfg('git-url-suffix','RETURN-UNDEF');
788 return "$url/$package$suffix";
791 sub parsecontrolfh ($$;$) {
792 my ($fh, $desc, $allowsigned) = @_;
793 our $dpkgcontrolhash_noissigned;
796 my %opts = ('name' => $desc);
797 $opts{allow_pgp}= $allowsigned || !$dpkgcontrolhash_noissigned;
798 $c = Dpkg::Control::Hash->new(%opts);
799 $c->parse($fh,$desc) or die "parsing of $desc failed";
800 last if $allowsigned;
801 last if $dpkgcontrolhash_noissigned;
802 my $issigned= $c->get_option('is_pgp_signed');
803 if (!defined $issigned) {
804 $dpkgcontrolhash_noissigned= 1;
805 seek $fh, 0,0 or die "seek $desc: $!";
806 } elsif ($issigned) {
807 fail "control file $desc is (already) PGP-signed. ".
808 " Note that dgit push needs to modify the .dsc and then".
809 " do the signature itself";
818 my ($file, $desc) = @_;
819 my $fh = new IO::Handle;
820 open $fh, '<', $file or die "$file: $!";
821 my $c = parsecontrolfh($fh,$desc);
822 $fh->error and die $!;
828 my ($dctrl,$field) = @_;
829 my $v = $dctrl->{$field};
830 return $v if defined $v;
831 fail "missing field $field in ".$v->get_option('name');
835 my $c = Dpkg::Control::Hash->new();
836 my $p = new IO::Handle;
837 my @cmd = (qw(dpkg-parsechangelog), @_);
838 open $p, '-|', @cmd or die $!;
840 $?=0; $!=0; close $p or failedcmd @cmd;
846 defined $d or fail "getcwd failed: $!";
852 sub archive_query ($) {
854 my $query = access_cfg('archive-query','RETURN-UNDEF');
855 $query =~ s/^(\w+):// or badcfg "invalid archive-query method \`$query'";
858 { no strict qw(refs); &{"${method}_${proto}"}($proto,$data); }
861 sub pool_dsc_subpath ($$) {
862 my ($vsn,$component) = @_; # $package is implict arg
863 my $prefix = substr($package, 0, $package =~ m/^l/ ? 4 : 1);
864 return "/pool/$component/$prefix/$package/".dscfn($vsn);
867 #---------- `ftpmasterapi' archive query method (nascent) ----------
869 sub archive_api_query_cmd ($) {
871 my @cmd = qw(curl -sS);
872 my $url = access_cfg('archive-query-url');
873 if ($url =~ m#^https://([-.0-9a-z]+)/#) {
875 my $keys = access_cfg('archive-query-tls-key','RETURN-UNDEF') //'';
876 foreach my $key (split /\:/, $keys) {
877 $key =~ s/\%HOST\%/$host/g;
879 fail "for $url: stat $key: $!" unless $!==ENOENT;
882 fail "config requested specific TLS key but do not know".
883 " how to get curl to use exactly that EE key ($key)";
884 # push @cmd, "--cacert", $key, "--capath", "/dev/enoent";
885 # # Sadly the above line does not work because of changes
886 # # to gnutls. The real fix for #790093 may involve
887 # # new curl options.
890 # Fixing #790093 properly will involve providing a value
891 # for this on clients.
892 my $kargs = access_cfg('archive-query-tls-curl-ca-args','RETURN-UNDEF');
893 push @cmd, split / /, $kargs if defined $kargs;
895 push @cmd, $url.$subpath;
901 my ($data, $subpath) = @_;
902 badcfg "ftpmasterapi archive query method takes no data part"
904 my @cmd = archive_api_query_cmd($subpath);
905 my $json = cmdoutput @cmd;
906 return decode_json($json);
909 sub canonicalise_suite_ftpmasterapi () {
910 my ($proto,$data) = @_;
911 my $suites = api_query($data, 'suites');
913 foreach my $entry (@$suites) {
915 my $v = $entry->{$_};
916 defined $v && $v eq $isuite;
918 push @matched, $entry;
920 fail "unknown suite $isuite" unless @matched;
923 @matched==1 or die "multiple matches for suite $isuite\n";
924 $cn = "$matched[0]{codename}";
925 defined $cn or die "suite $isuite info has no codename\n";
926 $cn =~ m/^$suite_re$/ or die "suite $isuite maps to bad codename\n";
928 die "bad ftpmaster api response: $@\n".Dumper(\@matched)
933 sub archive_query_ftpmasterapi () {
934 my ($proto,$data) = @_;
935 my $info = api_query($data, "dsc_in_suite/$isuite/$package");
937 my $digester = Digest::SHA->new(256);
938 foreach my $entry (@$info) {
940 my $vsn = "$entry->{version}";
941 my ($ok,$msg) = version_check $vsn;
942 die "bad version: $msg\n" unless $ok;
943 my $component = "$entry->{component}";
944 $component =~ m/^$component_re$/ or die "bad component";
945 my $filename = "$entry->{filename}";
946 $filename && $filename !~ m#[^-+:._~0-9a-zA-Z/]|^[/.]|/[/.]#
947 or die "bad filename";
948 my $sha256sum = "$entry->{sha256sum}";
949 $sha256sum =~ m/^[0-9a-f]+$/ or die "bad sha256sum";
950 push @rows, [ $vsn, "/pool/$component/$filename",
951 $digester, $sha256sum ];
953 die "bad ftpmaster api response: $@\n".Dumper($entry)
956 @rows = sort { -version_compare($a->[0],$b->[0]) } @rows;
960 #---------- `madison' archive query method ----------
962 sub archive_query_madison {
963 return map { [ @$_[0..1] ] } madison_get_parse(@_);
966 sub madison_get_parse {
967 my ($proto,$data) = @_;
968 die unless $proto eq 'madison';
970 $data= access_cfg('madison-distro','RETURN-UNDEF');
971 $data //= access_basedistro();
973 $rmad{$proto,$data,$package} ||= cmdoutput
974 qw(rmadison -asource),"-s$isuite","-u$data",$package;
975 my $rmad = $rmad{$proto,$data,$package};
978 foreach my $l (split /\n/, $rmad) {
979 $l =~ m{^ \s*( [^ \t|]+ )\s* \|
980 \s*( [^ \t|]+ )\s* \|
981 \s*( [^ \t|/]+ )(?:/([^ \t|/]+))? \s* \|
982 \s*( [^ \t|]+ )\s* }x or die "$rmad ?";
983 $1 eq $package or die "$rmad $package ?";
990 $component = access_cfg('archive-query-default-component');
992 $5 eq 'source' or die "$rmad ?";
993 push @out, [$vsn,pool_dsc_subpath($vsn,$component),$newsuite];
995 return sort { -version_compare($a->[0],$b->[0]); } @out;
998 sub canonicalise_suite_madison {
999 # madison canonicalises for us
1000 my @r = madison_get_parse(@_);
1002 "unable to canonicalise suite using package $package".
1003 " which does not appear to exist in suite $isuite;".
1004 " --existing-package may help";
1008 #---------- `sshpsql' archive query method ----------
1011 my ($data,$runeinfo,$sql) = @_;
1012 if (!length $data) {
1013 $data= access_someuserhost('sshpsql').':'.
1014 access_cfg('sshpsql-dbname');
1016 $data =~ m/:/ or badcfg "invalid sshpsql method string \`$data'";
1017 my ($userhost,$dbname) = ($`,$'); #';
1019 my @cmd = (access_cfg_ssh, $userhost,
1020 access_runeinfo("ssh-psql $runeinfo").
1021 " export LC_MESSAGES=C; export LC_CTYPE=C;".
1022 " ".shellquote qw(psql -A), $dbname, qw(-c), $sql);
1024 open P, "-|", @cmd or die $!;
1027 printdebug(">|$_|\n");
1030 $!=0; $?=0; close P or failedcmd @cmd;
1032 my $nrows = pop @rows;
1033 $nrows =~ s/^\((\d+) rows?\)$/$1/ or die "$nrows ?";
1034 @rows == $nrows+1 or die "$nrows ".(scalar @rows)." ?";
1035 @rows = map { [ split /\|/, $_ ] } @rows;
1036 my $ncols = scalar @{ shift @rows };
1037 die if grep { scalar @$_ != $ncols } @rows;
1041 sub sql_injection_check {
1042 foreach (@_) { die "$_ $& ?" if m{[^-+=:_.,/0-9a-zA-Z]}; }
1045 sub archive_query_sshpsql ($$) {
1046 my ($proto,$data) = @_;
1047 sql_injection_check $isuite, $package;
1048 my @rows = sshpsql($data, "archive-query $isuite $package", <<END);
1049 SELECT source.version, component.name, files.filename, files.sha256sum
1051 JOIN src_associations ON source.id = src_associations.source
1052 JOIN suite ON suite.id = src_associations.suite
1053 JOIN dsc_files ON dsc_files.source = source.id
1054 JOIN files_archive_map ON files_archive_map.file_id = dsc_files.file
1055 JOIN component ON component.id = files_archive_map.component_id
1056 JOIN files ON files.id = dsc_files.file
1057 WHERE ( suite.suite_name='$isuite' OR suite.codename='$isuite' )
1058 AND source.source='$package'
1059 AND files.filename LIKE '%.dsc';
1061 @rows = sort { -version_compare($a->[0],$b->[0]) } @rows;
1062 my $digester = Digest::SHA->new(256);
1064 my ($vsn,$component,$filename,$sha256sum) = @$_;
1065 [ $vsn, "/pool/$component/$filename",$digester,$sha256sum ];
1070 sub canonicalise_suite_sshpsql ($$) {
1071 my ($proto,$data) = @_;
1072 sql_injection_check $isuite;
1073 my @rows = sshpsql($data, "canonicalise-suite $isuite", <<END);
1074 SELECT suite.codename
1075 FROM suite where suite_name='$isuite' or codename='$isuite';
1077 @rows = map { $_->[0] } @rows;
1078 fail "unknown suite $isuite" unless @rows;
1079 die "ambiguous $isuite: @rows ?" if @rows>1;
1083 #---------- `dummycat' archive query method ----------
1085 sub canonicalise_suite_dummycat ($$) {
1086 my ($proto,$data) = @_;
1087 my $dpath = "$data/suite.$isuite";
1088 if (!open C, "<", $dpath) {
1089 $!==ENOENT or die "$dpath: $!";
1090 printdebug "dummycat canonicalise_suite $isuite $dpath ENOENT\n";
1094 chomp or die "$dpath: $!";
1096 printdebug "dummycat canonicalise_suite $isuite $dpath = $_\n";
1100 sub archive_query_dummycat ($$) {
1101 my ($proto,$data) = @_;
1102 canonicalise_suite();
1103 my $dpath = "$data/package.$csuite.$package";
1104 if (!open C, "<", $dpath) {
1105 $!==ENOENT or die "$dpath: $!";
1106 printdebug "dummycat query $csuite $package $dpath ENOENT\n";
1114 printdebug "dummycat query $csuite $package $dpath | $_\n";
1115 my @row = split /\s+/, $_;
1116 @row==2 or die "$dpath: $_ ?";
1119 C->error and die "$dpath: $!";
1121 return sort { -version_compare($a->[0],$b->[0]); } @rows;
1124 #---------- archive query entrypoints and rest of program ----------
1126 sub canonicalise_suite () {
1127 return if defined $csuite;
1128 fail "cannot operate on $isuite suite" if $isuite eq 'UNRELEASED';
1129 $csuite = archive_query('canonicalise_suite');
1130 if ($isuite ne $csuite) {
1131 progress "canonical suite name for $isuite is $csuite";
1135 sub get_archive_dsc () {
1136 canonicalise_suite();
1137 my @vsns = archive_query('archive_query');
1138 foreach my $vinfo (@vsns) {
1139 my ($vsn,$subpath,$digester,$digest) = @$vinfo;
1140 $dscurl = access_cfg('mirror').$subpath;
1141 $dscdata = url_get($dscurl);
1143 $skew_warning_vsn = $vsn if !defined $skew_warning_vsn;
1148 $digester->add($dscdata);
1149 my $got = $digester->hexdigest();
1151 fail "$dscurl has hash $got but".
1152 " archive told us to expect $digest";
1154 my $dscfh = new IO::File \$dscdata, '<' or die $!;
1155 printdebug Dumper($dscdata) if $debuglevel>1;
1156 $dsc = parsecontrolfh($dscfh,$dscurl,1);
1157 printdebug Dumper($dsc) if $debuglevel>1;
1158 my $fmt = getfield $dsc, 'Format';
1159 fail "unsupported source format $fmt, sorry" unless $format_ok{$fmt};
1160 $dsc_checked = !!$digester;
1166 sub check_for_git ();
1167 sub check_for_git () {
1169 my $how = access_cfg('git-check');
1170 if ($how eq 'ssh-cmd') {
1172 (access_cfg_ssh, access_gituserhost(),
1173 access_runeinfo("git-check $package").
1174 " set -e; cd ".access_cfg('git-path').";".
1175 " if test -d $package.git; then echo 1; else echo 0; fi");
1176 my $r= cmdoutput @cmd;
1177 if (defined $r and $r =~ m/^divert (\w+)$/) {
1179 my ($usedistro,) = access_distros();
1180 # NB that if we are pushing, $usedistro will be $distro/push
1181 $instead_distro= cfg("dgit-distro.$usedistro.diverts.$divert");
1182 $instead_distro =~ s{^/}{ access_basedistro()."/" }e;
1183 progress "diverting to $divert (using config for $instead_distro)";
1184 return check_for_git();
1186 failedcmd @cmd unless defined $r and $r =~ m/^[01]$/;
1188 } elsif ($how eq 'url') {
1189 my $prefix = access_cfg('git-check-url','git-url');
1190 my $suffix = access_cfg('git-check-suffix','git-suffix',
1191 'RETURN-UNDEF') // '.git';
1192 my $url = "$prefix/$package$suffix";
1193 my @cmd = (qw(curl -sS -I), $url);
1194 my $result = cmdoutput @cmd;
1195 $result =~ s/^\S+ 200 .*\n\r?\n//;
1196 # curl -sS -I with https_proxy prints
1197 # HTTP/1.0 200 Connection established
1198 $result =~ m/^\S+ (404|200) /s or
1199 fail "unexpected results from git check query - ".
1200 Dumper($prefix, $result);
1202 if ($code eq '404') {
1204 } elsif ($code eq '200') {
1209 } elsif ($how eq 'true') {
1211 } elsif ($how eq 'false') {
1214 badcfg "unknown git-check \`$how'";
1218 sub create_remote_git_repo () {
1219 my $how = access_cfg('git-create');
1220 if ($how eq 'ssh-cmd') {
1222 (access_cfg_ssh, access_gituserhost(),
1223 access_runeinfo("git-create $package").
1224 "set -e; cd ".access_cfg('git-path').";".
1225 " cp -a _template $package.git");
1226 } elsif ($how eq 'true') {
1229 badcfg "unknown git-create \`$how'";
1233 our ($dsc_hash,$lastpush_hash);
1235 our $ud = '.git/dgit/unpack';
1245 sub mktree_in_ud_here () {
1246 runcmd qw(git init -q);
1247 rmtree('.git/objects');
1248 symlink '../../../../objects','.git/objects' or die $!;
1251 sub git_write_tree () {
1252 my $tree = cmdoutput @git, qw(write-tree);
1253 $tree =~ m/^\w+$/ or die "$tree ?";
1257 sub remove_stray_gits () {
1258 my @gitscmd = qw(find -name .git -prune -print0);
1259 debugcmd "|",@gitscmd;
1260 open GITS, "-|", @gitscmd or die $!;
1265 print STDERR "$us: warning: removing from source package: ",
1266 (messagequote $_), "\n";
1270 $!=0; $?=0; close GITS or failedcmd @gitscmd;
1273 sub mktree_in_ud_from_only_subdir () {
1274 # changes into the subdir
1276 die "@dirs ?" unless @dirs==1;
1277 $dirs[0] =~ m#^([^/]+)/\.$# or die;
1281 remove_stray_gits();
1282 mktree_in_ud_here();
1283 my ($format, $fopts) = get_source_format();
1284 if (madformat($format)) {
1287 runcmd @git, qw(add -Af);
1288 my $tree=git_write_tree();
1289 return ($tree,$dir);
1292 sub dsc_files_info () {
1293 foreach my $csumi (['Checksums-Sha256','Digest::SHA', 'new(256)'],
1294 ['Checksums-Sha1', 'Digest::SHA', 'new(1)'],
1295 ['Files', 'Digest::MD5', 'new()']) {
1296 my ($fname, $module, $method) = @$csumi;
1297 my $field = $dsc->{$fname};
1298 next unless defined $field;
1299 eval "use $module; 1;" or die $@;
1301 foreach (split /\n/, $field) {
1303 m/^(\w+) (\d+) (\S+)$/ or
1304 fail "could not parse .dsc $fname line \`$_'";
1305 my $digester = eval "$module"."->$method;" or die $@;
1310 Digester => $digester,
1315 fail "missing any supported Checksums-* or Files field in ".
1316 $dsc->get_option('name');
1320 map { $_->{Filename} } dsc_files_info();
1323 sub is_orig_file ($;$) {
1326 m/\.orig(?:-\w+)?\.tar\.\w+$/ or return 0;
1327 defined $base or return 1;
1331 sub make_commit ($) {
1333 return cmdoutput @git, qw(hash-object -w -t commit), $file;
1336 sub clogp_authline ($) {
1338 my $author = getfield $clogp, 'Maintainer';
1339 $author =~ s#,.*##ms;
1340 my $date = cmdoutput qw(date), '+%s %z', qw(-d), getfield($clogp,'Date');
1341 my $authline = "$author $date";
1342 $authline =~ m/$git_authline_re/o or
1343 fail "unexpected commit author line format \`$authline'".
1344 " (was generated from changelog Maintainer field)";
1345 return ($1,$2,$3) if wantarray;
1349 sub vendor_patches_distro ($$) {
1350 my ($checkdistro, $what) = @_;
1351 return unless defined $checkdistro;
1353 my $series = "debian/patches/\L$checkdistro\E.series";
1354 printdebug "checking for vendor-specific $series ($what)\n";
1356 if (!open SERIES, "<", $series) {
1357 die "$series $!" unless $!==ENOENT;
1366 Unfortunately, this source package uses a feature of dpkg-source where
1367 the same source package unpacks to different source code on different
1368 distros. dgit cannot safely operate on such packages on affected
1369 distros, because the meaning of source packages is not stable.
1371 Please ask the distro/maintainer to remove the distro-specific series
1372 files and use a different technique (if necessary, uploading actually
1373 different packages, if different distros are supposed to have
1377 fail "Found active distro-specific series file for".
1378 " $checkdistro ($what): $series, cannot continue";
1380 die "$series $!" if SERIES->error;
1384 sub check_for_vendor_patches () {
1385 # This dpkg-source feature doesn't seem to be documented anywhere!
1386 # But it can be found in the changelog (reformatted):
1388 # commit 4fa01b70df1dc4458daee306cfa1f987b69da58c
1389 # Author: Raphael Hertzog <hertzog@debian.org>
1390 # Date: Sun Oct 3 09:36:48 2010 +0200
1392 # dpkg-source: correctly create .pc/.quilt_series with alternate
1395 # If you have debian/patches/ubuntu.series and you were
1396 # unpacking the source package on ubuntu, quilt was still
1397 # directed to debian/patches/series instead of
1398 # debian/patches/ubuntu.series.
1400 # debian/changelog | 3 +++
1401 # scripts/Dpkg/Source/Package/V3/quilt.pm | 4 +++-
1402 # 2 files changed, 6 insertions(+), 1 deletion(-)
1405 vendor_patches_distro($ENV{DEB_VENDOR}, "DEB_VENDOR");
1406 vendor_patches_distro(Dpkg::Vendor::get_current_vendor(),
1407 "Dpkg::Vendor \`current vendor'");
1408 vendor_patches_distro(access_basedistro(),
1409 "distro being accessed");
1412 sub generate_commit_from_dsc () {
1416 foreach my $fi (dsc_files_info()) {
1417 my $f = $fi->{Filename};
1418 die "$f ?" if $f =~ m#/|^\.|\.dsc$|\.tmp$#;
1420 link_ltarget "../../../$f", $f
1424 complete_file_from_dsc('.', $fi)
1427 if (is_orig_file($f)) {
1428 link $f, "../../../../$f"
1434 my $dscfn = "$package.dsc";
1436 open D, ">", $dscfn or die "$dscfn: $!";
1437 print D $dscdata or die "$dscfn: $!";
1438 close D or die "$dscfn: $!";
1439 my @cmd = qw(dpkg-source);
1440 push @cmd, '--no-check' if $dsc_checked;
1441 push @cmd, qw(-x --), $dscfn;
1444 my ($tree,$dir) = mktree_in_ud_from_only_subdir();
1445 check_for_vendor_patches() if madformat($dsc->{format});
1446 runcmd qw(sh -ec), 'dpkg-parsechangelog >../changelog.tmp';
1447 my $clogp = parsecontrol('../changelog.tmp',"commit's changelog");
1448 my $authline = clogp_authline $clogp;
1449 my $changes = getfield $clogp, 'Changes';
1450 open C, ">../commit.tmp" or die $!;
1451 print C <<END or die $!;
1458 # imported from the archive
1461 my $outputhash = make_commit qw(../commit.tmp);
1462 my $cversion = getfield $clogp, 'Version';
1463 progress "synthesised git commit from .dsc $cversion";
1464 if ($lastpush_hash) {
1465 runcmd @git, qw(reset -q --hard), $lastpush_hash;
1466 runcmd qw(sh -ec), 'dpkg-parsechangelog >>../changelogold.tmp';
1467 my $oldclogp = parsecontrol('../changelogold.tmp','previous changelog');
1468 my $oversion = getfield $oldclogp, 'Version';
1470 version_compare($oversion, $cversion);
1472 # git upload/ is earlier vsn than archive, use archive
1473 open C, ">../commit2.tmp" or die $!;
1474 print C <<END or die $!;
1476 parent $lastpush_hash
1481 Record $package ($cversion) in archive suite $csuite
1483 $outputhash = make_commit qw(../commit2.tmp);
1484 } elsif ($vcmp > 0) {
1485 print STDERR <<END or die $!;
1487 Version actually in archive: $cversion (older)
1488 Last allegedly pushed/uploaded: $oversion (newer or same)
1491 $outputhash = $lastpush_hash;
1493 $outputhash = $lastpush_hash;
1496 changedir '../../../..';
1497 runcmd @git, qw(update-ref -m),"dgit fetch import $cversion",
1498 'DGIT_ARCHIVE', $outputhash;
1499 cmdoutput @git, qw(log -n2), $outputhash;
1500 # ... gives git a chance to complain if our commit is malformed
1505 sub complete_file_from_dsc ($$) {
1506 our ($dstdir, $fi) = @_;
1507 # Ensures that we have, in $dir, the file $fi, with the correct
1508 # contents. (Downloading it from alongside $dscurl if necessary.)
1510 my $f = $fi->{Filename};
1511 my $tf = "$dstdir/$f";
1514 if (stat_exists $tf) {
1515 progress "using existing $f";
1518 $furl =~ s{/[^/]+$}{};
1520 die "$f ?" unless $f =~ m/^\Q${package}\E_/;
1521 die "$f ?" if $f =~ m#/#;
1522 runcmd_ordryrun_local @curl,qw(-o),$tf,'--',"$furl";
1523 return 0 if !act_local();
1527 open F, "<", "$tf" or die "$tf: $!";
1528 $fi->{Digester}->reset();
1529 $fi->{Digester}->addfile(*F);
1530 F->error and die $!;
1531 my $got = $fi->{Digester}->hexdigest();
1532 $got eq $fi->{Hash} or
1533 fail "file $f has hash $got but .dsc".
1534 " demands hash $fi->{Hash} ".
1535 ($downloaded ? "(got wrong file from archive!)"
1536 : "(perhaps you should delete this file?)");
1541 sub ensure_we_have_orig () {
1542 foreach my $fi (dsc_files_info()) {
1543 my $f = $fi->{Filename};
1544 next unless is_orig_file($f);
1545 complete_file_from_dsc('..', $fi)
1550 sub git_fetch_us () {
1551 my @specs = (fetchspec());
1553 map { "+refs/$_/*:".lrfetchrefs."/$_/*" }
1555 runcmd_ordryrun_local @git, qw(fetch -p -n -q), access_giturl(), @specs;
1558 my $tagpat = debiantag('*',access_basedistro);
1560 git_for_each_ref("refs/tags/".$tagpat, sub {
1561 my ($objid,$objtype,$fullrefname,$reftail) = @_;
1562 printdebug "currently $fullrefname=$objid\n";
1563 $here{$fullrefname} = $objid;
1565 git_for_each_ref(lrfetchrefs."/tags/".$tagpat, sub {
1566 my ($objid,$objtype,$fullrefname,$reftail) = @_;
1567 my $lref = "refs".substr($fullrefname, length lrfetchrefs);
1568 printdebug "offered $lref=$objid\n";
1569 if (!defined $here{$lref}) {
1570 my @upd = (@git, qw(update-ref), $lref, $objid, '');
1571 runcmd_ordryrun_local @upd;
1572 } elsif ($here{$lref} eq $objid) {
1575 "Not updateting $lref from $here{$lref} to $objid.\n";
1580 sub fetch_from_archive () {
1581 # ensures that lrref() is what is actually in the archive,
1582 # one way or another
1586 foreach my $field (@ourdscfield) {
1587 $dsc_hash = $dsc->{$field};
1588 last if defined $dsc_hash;
1590 if (defined $dsc_hash) {
1591 $dsc_hash =~ m/\w+/ or fail "invalid hash in .dsc \`$dsc_hash'";
1593 progress "last upload to archive specified git hash";
1595 progress "last upload to archive has NO git hash";
1598 progress "no version available from the archive";
1601 $lastpush_hash = git_get_ref(lrref());
1602 printdebug "previous reference hash=$lastpush_hash\n";
1604 if (defined $dsc_hash) {
1605 fail "missing remote git history even though dsc has hash -".
1606 " could not find ref ".lrref().
1607 " (should have been fetched from ".access_giturl()."#".rrref().")"
1608 unless $lastpush_hash;
1610 ensure_we_have_orig();
1611 if ($dsc_hash eq $lastpush_hash) {
1612 } elsif (is_fast_fwd($dsc_hash,$lastpush_hash)) {
1613 print STDERR <<END or die $!;
1615 Git commit in archive is behind the last version allegedly pushed/uploaded.
1616 Commit referred to by archive: $dsc_hash
1617 Last allegedly pushed/uploaded: $lastpush_hash
1620 $hash = $lastpush_hash;
1622 fail "git head (".lrref()."=$lastpush_hash) is not a ".
1623 "descendant of archive's .dsc hash ($dsc_hash)";
1626 $hash = generate_commit_from_dsc();
1627 } elsif ($lastpush_hash) {
1628 # only in git, not in the archive yet
1629 $hash = $lastpush_hash;
1630 print STDERR <<END or die $!;
1632 Package not found in the archive, but has allegedly been pushed using dgit.
1636 printdebug "nothing found!\n";
1637 if (defined $skew_warning_vsn) {
1638 print STDERR <<END or die $!;
1640 Warning: relevant archive skew detected.
1641 Archive allegedly contains $skew_warning_vsn
1642 But we were not able to obtain any version from the archive or git.
1648 printdebug "current hash=$hash\n";
1649 if ($lastpush_hash) {
1650 fail "not fast forward on last upload branch!".
1651 " (archive's version left in DGIT_ARCHIVE)"
1652 unless is_fast_fwd($lastpush_hash, $hash);
1654 if (defined $skew_warning_vsn) {
1656 printdebug "SKEW CHECK WANT $skew_warning_vsn\n";
1657 my $clogf = ".git/dgit/changelog.tmp";
1658 runcmd shell_cmd "exec >$clogf",
1659 @git, qw(cat-file blob), "$hash:debian/changelog";
1660 my $gotclogp = parsechangelog("-l$clogf");
1661 my $got_vsn = getfield $gotclogp, 'Version';
1662 printdebug "SKEW CHECK GOT $got_vsn\n";
1663 if (version_compare($got_vsn, $skew_warning_vsn) < 0) {
1664 print STDERR <<END or die $!;
1666 Warning: archive skew detected. Using the available version:
1667 Archive allegedly contains $skew_warning_vsn
1668 We were able to obtain only $got_vsn
1673 if ($lastpush_hash ne $hash) {
1674 my @upd_cmd = (@git, qw(update-ref -m), 'dgit fetch', lrref(), $hash);
1678 dryrun_report @upd_cmd;
1684 sub set_local_git_config ($$) {
1686 runcmd @git, qw(config), $k, $v;
1689 sub setup_mergechangelogs (;$) {
1691 return unless $always || access_cfg_bool(1, 'setup-mergechangelogs');
1693 my $driver = 'dpkg-mergechangelogs';
1694 my $cb = "merge.$driver";
1695 my $attrs = '.git/info/attributes';
1696 ensuredir '.git/info';
1698 open NATTRS, ">", "$attrs.new" or die "$attrs.new $!";
1699 if (!open ATTRS, "<", $attrs) {
1700 $!==ENOENT or die "$attrs: $!";
1704 next if m{^debian/changelog\s};
1705 print NATTRS $_, "\n" or die $!;
1707 ATTRS->error and die $!;
1710 print NATTRS "debian/changelog merge=$driver\n" or die $!;
1713 set_local_git_config "$cb.name", 'debian/changelog merge driver';
1714 set_local_git_config "$cb.driver", 'dpkg-mergechangelogs -m %O %A %B %A';
1716 rename "$attrs.new", "$attrs" or die "$attrs: $!";
1719 sub setup_useremail (;$) {
1721 return unless $always || access_cfg_bool(1, 'setup-useremail');
1724 my ($k, $envvar) = @_;
1725 my $v = access_cfg("user-$k", 'RETURN-UNDEF') // $ENV{$envvar};
1726 return unless defined $v;
1727 set_local_git_config "user.$k", $v;
1730 $setup->('email', 'DEBEMAIL');
1731 $setup->('name', 'DEBFULLNAME');
1734 sub setup_new_tree () {
1735 setup_mergechangelogs();
1741 canonicalise_suite();
1742 badusage "dry run makes no sense with clone" unless act_local();
1743 my $hasgit = check_for_git();
1744 mkdir $dstdir or fail "create \`$dstdir': $!";
1746 runcmd @git, qw(init -q);
1747 my $giturl = access_giturl(1);
1748 if (defined $giturl) {
1749 set_local_git_config "remote.$remotename.fetch", fetchspec();
1750 open H, "> .git/HEAD" or die $!;
1751 print H "ref: ".lref()."\n" or die $!;
1753 runcmd @git, qw(remote add), 'origin', $giturl;
1756 progress "fetching existing git history";
1758 runcmd_ordryrun_local @git, qw(fetch origin);
1760 progress "starting new git history";
1762 fetch_from_archive() or no_such_package;
1763 my $vcsgiturl = $dsc->{'Vcs-Git'};
1764 if (length $vcsgiturl) {
1765 $vcsgiturl =~ s/\s+-b\s+\S+//g;
1766 runcmd @git, qw(remote add vcs-git), $vcsgiturl;
1769 runcmd @git, qw(reset --hard), lrref();
1770 printdone "ready for work in $dstdir";
1774 if (check_for_git()) {
1777 fetch_from_archive() or no_such_package();
1778 printdone "fetched into ".lrref();
1783 runcmd_ordryrun_local @git, qw(merge -m),"Merge from $csuite [dgit]",
1785 printdone "fetched to ".lrref()." and merged into HEAD";
1788 sub check_not_dirty () {
1789 foreach my $f (qw(local-options local-patch-header)) {
1790 if (stat_exists "debian/source/$f") {
1791 fail "git tree contains debian/source/$f";
1795 return if $ignoredirty;
1797 my @cmd = (@git, qw(diff --quiet HEAD));
1799 $!=0; $?=-1; system @cmd;
1802 fail "working tree is dirty (does not match HEAD)";
1808 sub commit_admin ($) {
1811 runcmd_ordryrun_local @git, qw(commit -m), $m;
1814 sub commit_quilty_patch () {
1815 my $output = cmdoutput @git, qw(status --porcelain);
1817 foreach my $l (split /\n/, $output) {
1818 next unless $l =~ m/\S/;
1819 if ($l =~ m{^(?:\?\?| M) (.pc|debian/patches)}) {
1823 delete $adds{'.pc'}; # if there wasn't one before, don't add it
1825 progress "nothing quilty to commit, ok.";
1828 my @adds = map { s/[][*?\\]/\\$&/g; $_; } sort keys %adds;
1829 runcmd_ordryrun_local @git, qw(add -f), @adds;
1830 commit_admin "Commit Debian 3.0 (quilt) metadata";
1833 sub get_source_format () {
1835 if (open F, "debian/source/options") {
1839 s/\s+$//; # ignore missing final newline
1841 my ($k, $v) = ($`, $'); #');
1842 $v =~ s/^"(.*)"$/$1/;
1848 F->error and die $!;
1851 die $! unless $!==&ENOENT;
1854 if (!open F, "debian/source/format") {
1855 die $! unless $!==&ENOENT;
1859 F->error and die $!;
1861 return ($_, \%options);
1866 return 0 unless $format eq '3.0 (quilt)';
1867 our $quilt_mode_warned;
1868 if ($quilt_mode eq 'nocheck') {
1869 progress "Not doing any fixup of \`$format' due to".
1870 " ----no-quilt-fixup or --quilt=nocheck"
1871 unless $quilt_mode_warned++;
1874 progress "Format \`$format', need to check/update patch stack"
1875 unless $quilt_mode_warned++;
1879 sub push_parse_changelog ($) {
1882 my $clogp = Dpkg::Control::Hash->new();
1883 $clogp->load($clogpfn) or die;
1885 $package = getfield $clogp, 'Source';
1886 my $cversion = getfield $clogp, 'Version';
1887 my $tag = debiantag($cversion, access_basedistro);
1888 runcmd @git, qw(check-ref-format), $tag;
1890 my $dscfn = dscfn($cversion);
1892 return ($clogp, $cversion, $tag, $dscfn);
1895 sub push_parse_dsc ($$$) {
1896 my ($dscfn,$dscfnwhat, $cversion) = @_;
1897 $dsc = parsecontrol($dscfn,$dscfnwhat);
1898 my $dversion = getfield $dsc, 'Version';
1899 my $dscpackage = getfield $dsc, 'Source';
1900 ($dscpackage eq $package && $dversion eq $cversion) or
1901 fail "$dscfn is for $dscpackage $dversion".
1902 " but debian/changelog is for $package $cversion";
1905 sub push_mktag ($$$$$$$) {
1906 my ($head,$clogp,$tag,
1908 $changesfile,$changesfilewhat,
1911 $dsc->{$ourdscfield[0]} = $head;
1912 $dsc->save("$dscfn.tmp") or die $!;
1914 my $changes = parsecontrol($changesfile,$changesfilewhat);
1915 foreach my $field (qw(Source Distribution Version)) {
1916 $changes->{$field} eq $clogp->{$field} or
1917 fail "changes field $field \`$changes->{$field}'".
1918 " does not match changelog \`$clogp->{$field}'";
1921 my $cversion = getfield $clogp, 'Version';
1922 my $clogsuite = getfield $clogp, 'Distribution';
1924 # We make the git tag by hand because (a) that makes it easier
1925 # to control the "tagger" (b) we can do remote signing
1926 my $authline = clogp_authline $clogp;
1927 my $delibs = join(" ", "",@deliberatelies);
1928 my $declaredistro = access_basedistro();
1929 open TO, '>', $tfn->('.tmp') or die $!;
1930 print TO <<END or die $!;
1936 $package release $cversion for $clogsuite ($csuite) [dgit]
1937 [dgit distro=$declaredistro$delibs]
1939 foreach my $ref (sort keys %previously) {
1940 print TO <<END or die $!;
1941 [dgit previously:$ref=$previously{$ref}]
1947 my $tagobjfn = $tfn->('.tmp');
1949 if (!defined $keyid) {
1950 $keyid = access_cfg('keyid','RETURN-UNDEF');
1952 if (!defined $keyid) {
1953 $keyid = getfield $clogp, 'Maintainer';
1955 unlink $tfn->('.tmp.asc') or $!==&ENOENT or die $!;
1956 my @sign_cmd = (@gpg, qw(--detach-sign --armor));
1957 push @sign_cmd, qw(-u),$keyid if defined $keyid;
1958 push @sign_cmd, $tfn->('.tmp');
1959 runcmd_ordryrun @sign_cmd;
1961 $tagobjfn = $tfn->('.signed.tmp');
1962 runcmd shell_cmd "exec >$tagobjfn", qw(cat --),
1963 $tfn->('.tmp'), $tfn->('.tmp.asc');
1970 sub sign_changes ($) {
1971 my ($changesfile) = @_;
1973 my @debsign_cmd = @debsign;
1974 push @debsign_cmd, "-k$keyid" if defined $keyid;
1975 push @debsign_cmd, "-p$gpg[0]" if $gpg[0] ne 'gpg';
1976 push @debsign_cmd, $changesfile;
1977 runcmd_ordryrun @debsign_cmd;
1982 my ($forceflag) = @_;
1983 printdebug "actually entering push\n";
1984 supplementary_message(<<'END');
1985 Push failed, while preparing your push.
1986 You can retry the push, after fixing the problem, if you like.
1990 access_giturl(); # check that success is vaguely likely
1992 my $clogpfn = ".git/dgit/changelog.822.tmp";
1993 runcmd shell_cmd "exec >$clogpfn", qw(dpkg-parsechangelog);
1995 responder_send_file('parsed-changelog', $clogpfn);
1997 my ($clogp, $cversion, $tag, $dscfn) =
1998 push_parse_changelog("$clogpfn");
2000 my $dscpath = "$buildproductsdir/$dscfn";
2001 stat_exists $dscpath or
2002 fail "looked for .dsc $dscfn, but $!;".
2003 " maybe you forgot to build";
2005 responder_send_file('dsc', $dscpath);
2007 push_parse_dsc($dscpath, $dscfn, $cversion);
2009 my $format = getfield $dsc, 'Format';
2010 printdebug "format $format\n";
2012 my $head = git_rev_parse('HEAD');
2014 if (madformat($format)) {
2015 # user might have not used dgit build, so maybe do this now:
2016 if (quiltmode_splitbrain()) {
2017 my $upstreamversion = $clogp->{Version};
2018 $upstreamversion =~ s/-[^-]*$//;
2020 quilt_make_fake_dsc($upstreamversion);
2021 my ($dgitview, $cachekey) =
2022 quilt_check_splitbrain_cache($head, $upstreamversion);
2024 "--quilt=$quilt_mode but no cached dgit view:
2025 perhaps tree changed since dgit build[-source] ?";
2027 changedir '../../../..';
2028 prep_ud(); # so _only_subdir() works, below
2030 commit_quilty_patch();
2034 die 'xxx fast forward (should not depend on quilt mode, but will always be needed if we did $split_brain)' if $split_brain;
2038 progress "checking that $dscfn corresponds to HEAD";
2039 runcmd qw(dpkg-source -x --),
2040 $dscpath =~ m#^/# ? $dscpath : "../../../$dscpath";
2041 my ($tree,$dir) = mktree_in_ud_from_only_subdir();
2042 check_for_vendor_patches() if madformat($dsc->{format});
2043 changedir '../../../..';
2044 my $diffopt = $debuglevel>0 ? '--exit-code' : '--quiet';
2045 my @diffcmd = (@git, qw(diff), $diffopt, $tree);
2046 debugcmd "+",@diffcmd;
2048 my $r = system @diffcmd;
2051 fail "$dscfn specifies a different tree to your HEAD commit;".
2052 " perhaps you forgot to build".
2053 ($diffopt eq '--exit-code' ? "" :
2054 " (run with -D to see full diff output)");
2059 if (!$changesfile) {
2060 my $pat = changespat $cversion;
2061 my @cs = glob "$buildproductsdir/$pat";
2062 fail "failed to find unique changes file".
2063 " (looked for $pat in $buildproductsdir);".
2064 " perhaps you need to use dgit -C"
2066 ($changesfile) = @cs;
2068 $changesfile = "$buildproductsdir/$changesfile";
2071 responder_send_file('changes',$changesfile);
2072 responder_send_command("param head $head");
2073 responder_send_command("param csuite $csuite");
2075 if (deliberately_not_fast_forward) {
2076 git_for_each_ref(lrfetchrefs, sub {
2077 my ($objid,$objtype,$lrfetchrefname,$reftail) = @_;
2078 my $rrefname= substr($lrfetchrefname, length(lrfetchrefs) + 1);
2079 responder_send_command("previously $rrefname=$objid");
2080 $previously{$rrefname} = $objid;
2084 my $tfn = sub { ".git/dgit/tag$_[0]"; };
2087 supplementary_message(<<'END');
2088 Push failed, while signing the tag.
2089 You can retry the push, after fixing the problem, if you like.
2091 # If we manage to sign but fail to record it anywhere, it's fine.
2092 if ($we_are_responder) {
2093 $tagobjfn = $tfn->('.signed.tmp');
2094 responder_receive_files('signed-tag', $tagobjfn);
2097 push_mktag($head,$clogp,$tag,
2099 $changesfile,$changesfile,
2102 supplementary_message(<<'END');
2103 Push failed, *after* signing the tag.
2104 If you want to try again, you should use a new version number.
2107 my $tag_obj_hash = cmdoutput @git, qw(hash-object -w -t tag), $tagobjfn;
2108 runcmd_ordryrun @git, qw(verify-tag), $tag_obj_hash;
2109 runcmd_ordryrun_local @git, qw(update-ref), "refs/tags/$tag", $tag_obj_hash;
2111 supplementary_message(<<'END');
2112 Push failed, while updating the remote git repository - see messages above.
2113 If you want to try again, you should use a new version number.
2115 if (!check_for_git()) {
2116 create_remote_git_repo();
2118 runcmd_ordryrun @git, qw(push),access_giturl(),
2119 $forceflag."HEAD:".rrref(), $forceflag."refs/tags/$tag";
2120 runcmd_ordryrun @git, qw(update-ref -m), 'dgit push', lrref(), 'HEAD';
2122 supplementary_message(<<'END');
2123 Push failed, after updating the remote git repository.
2124 If you want to try again, you must use a new version number.
2126 if ($we_are_responder) {
2127 my $dryrunsuffix = act_local() ? "" : ".tmp";
2128 responder_receive_files('signed-dsc-changes',
2129 "$dscpath$dryrunsuffix",
2130 "$changesfile$dryrunsuffix");
2133 rename "$dscpath.tmp",$dscpath or die "$dscfn $!";
2135 progress "[new .dsc left in $dscpath.tmp]";
2137 sign_changes $changesfile;
2140 supplementary_message(<<END);
2141 Push failed, while uploading package(s) to the archive server.
2142 You can retry the upload of exactly these same files with dput of:
2144 If that .changes file is broken, you will need to use a new version
2145 number for your next attempt at the upload.
2147 my $host = access_cfg('upload-host','RETURN-UNDEF');
2148 my @hostarg = defined($host) ? ($host,) : ();
2149 runcmd_ordryrun @dput, @hostarg, $changesfile;
2150 printdone "pushed and uploaded $cversion";
2152 supplementary_message('');
2153 responder_send_command("complete");
2160 badusage "-p is not allowed with clone; specify as argument instead"
2161 if defined $package;
2164 } elsif (@ARGV==2 && $ARGV[1] =~ m#^\w#) {
2165 ($package,$isuite) = @ARGV;
2166 } elsif (@ARGV==2 && $ARGV[1] =~ m#^[./]#) {
2167 ($package,$dstdir) = @ARGV;
2168 } elsif (@ARGV==3) {
2169 ($package,$isuite,$dstdir) = @ARGV;
2171 badusage "incorrect arguments to dgit clone";
2173 $dstdir ||= "$package";
2175 if (stat_exists $dstdir) {
2176 fail "$dstdir already exists";
2180 if ($rmonerror && !$dryrun_level) {
2181 $cwd_remove= getcwd();
2183 return unless defined $cwd_remove;
2184 if (!chdir "$cwd_remove") {
2185 return if $!==&ENOENT;
2186 die "chdir $cwd_remove: $!";
2189 rmtree($dstdir) or die "remove $dstdir: $!\n";
2190 } elsif (!grep { $! == $_ }
2191 (ENOENT, ENOTDIR, EACCES, EPERM, ELOOP)) {
2193 print STDERR "check whether to remove $dstdir: $!\n";
2199 $cwd_remove = undef;
2202 sub branchsuite () {
2203 my $branch = cmdoutput_errok @git, qw(symbolic-ref HEAD);
2204 if ($branch =~ m#$lbranch_re#o) {
2211 sub fetchpullargs () {
2213 if (!defined $package) {
2214 my $sourcep = parsecontrol('debian/control','debian/control');
2215 $package = getfield $sourcep, 'Source';
2218 # $isuite = branchsuite(); # this doesn't work because dak hates canons
2220 my $clogp = parsechangelog();
2221 $isuite = getfield $clogp, 'Distribution';
2223 canonicalise_suite();
2224 progress "fetching from suite $csuite";
2225 } elsif (@ARGV==1) {
2227 canonicalise_suite();
2229 badusage "incorrect arguments to dgit fetch or dgit pull";
2248 badusage "-p is not allowed with dgit push" if defined $package;
2250 my $clogp = parsechangelog();
2251 $package = getfield $clogp, 'Source';
2254 } elsif (@ARGV==1) {
2255 ($specsuite) = (@ARGV);
2257 badusage "incorrect arguments to dgit push";
2259 $isuite = getfield $clogp, 'Distribution';
2261 local ($package) = $existing_package; # this is a hack
2262 canonicalise_suite();
2264 canonicalise_suite();
2266 if (defined $specsuite &&
2267 $specsuite ne $isuite &&
2268 $specsuite ne $csuite) {
2269 fail "dgit push: changelog specifies $isuite ($csuite)".
2270 " but command line specifies $specsuite";
2272 supplementary_message(<<'END');
2273 Push failed, while checking state of the archive.
2274 You can retry the push, after fixing the problem, if you like.
2276 if (check_for_git()) {
2280 if (fetch_from_archive()) {
2281 if (is_fast_fwd(lrref(), 'HEAD')) {
2283 } elsif (deliberately_not_fast_forward) {
2286 fail "dgit push: HEAD is not a descendant".
2287 " of the archive's version.\n".
2288 "dgit: To overwrite its contents,".
2289 " use git merge -s ours ".lrref().".\n".
2290 "dgit: To rewind history, if permitted by the archive,".
2291 " use --deliberately-not-fast-forward";
2295 fail "package appears to be new in this suite;".
2296 " if this is intentional, use --new";
2301 #---------- remote commands' implementation ----------
2303 sub cmd_remote_push_build_host {
2304 my ($nrargs) = shift @ARGV;
2305 my (@rargs) = @ARGV[0..$nrargs-1];
2306 @ARGV = @ARGV[$nrargs..$#ARGV];
2308 my ($dir,$vsnwant) = @rargs;
2309 # vsnwant is a comma-separated list; we report which we have
2310 # chosen in our ready response (so other end can tell if they
2313 $we_are_responder = 1;
2314 $us .= " (build host)";
2318 open PI, "<&STDIN" or die $!;
2319 open STDIN, "/dev/null" or die $!;
2320 open PO, ">&STDOUT" or die $!;
2322 open STDOUT, ">&STDERR" or die $!;
2326 ($protovsn) = grep {
2327 $vsnwant =~ m{^(?:.*,)?$_(?:,.*)?$}
2328 } @rpushprotovsn_support;
2330 fail "build host has dgit rpush protocol versions ".
2331 (join ",", @rpushprotovsn_support).
2332 " but invocation host has $vsnwant"
2333 unless defined $protovsn;
2335 responder_send_command("dgit-remote-push-ready $protovsn");
2341 sub cmd_remote_push_responder { cmd_remote_push_build_host(); }
2342 # ... for compatibility with proto vsn.1 dgit (just so that user gets
2343 # a good error message)
2349 my $report = i_child_report();
2350 if (defined $report) {
2351 printdebug "($report)\n";
2352 } elsif ($i_child_pid) {
2353 printdebug "(killing build host child $i_child_pid)\n";
2354 kill 15, $i_child_pid;
2356 if (defined $i_tmp && !defined $initiator_tempdir) {
2358 eval { rmtree $i_tmp; };
2362 END { i_cleanup(); }
2365 my ($base,$selector,@args) = @_;
2366 $selector =~ s/\-/_/g;
2367 { no strict qw(refs); &{"${base}_${selector}"}(@args); }
2374 if ($host =~ m/^((?:[^][]|\[[^][]*\])*)\:/) {
2382 push @rargs, join ",", @rpushprotovsn_support;
2385 push @rdgit, @ropts;
2386 push @rdgit, qw(remote-push-build-host), (scalar @rargs), @rargs;
2388 my @cmd = (@ssh, $host, shellquote @rdgit);
2391 if (defined $initiator_tempdir) {
2392 rmtree $initiator_tempdir;
2393 mkdir $initiator_tempdir, 0700 or die "$initiator_tempdir: $!";
2394 $i_tmp = $initiator_tempdir;
2398 $i_child_pid = open2(\*RO, \*RI, @cmd);
2400 ($protovsn) = initiator_expect { m/^dgit-remote-push-ready (\S+)/ };
2401 die "$protovsn ?" unless grep { $_ eq $protovsn } @rpushprotovsn_support;
2402 $supplementary_message = '' unless $protovsn >= 3;
2404 my ($icmd,$iargs) = initiator_expect {
2405 m/^(\S+)(?: (.*))?$/;
2408 i_method "i_resp", $icmd, $iargs;
2412 sub i_resp_progress ($) {
2414 my $msg = protocol_read_bytes \*RO, $rhs;
2418 sub i_resp_supplementary_message ($) {
2420 $supplementary_message = protocol_read_bytes \*RO, $rhs;
2423 sub i_resp_complete {
2424 my $pid = $i_child_pid;
2425 $i_child_pid = undef; # prevents killing some other process with same pid
2426 printdebug "waiting for build host child $pid...\n";
2427 my $got = waitpid $pid, 0;
2428 die $! unless $got == $pid;
2429 die "build host child failed $?" if $?;
2432 printdebug "all done\n";
2436 sub i_resp_file ($) {
2438 my $localname = i_method "i_localname", $keyword;
2439 my $localpath = "$i_tmp/$localname";
2440 stat_exists $localpath and
2441 badproto \*RO, "file $keyword ($localpath) twice";
2442 protocol_receive_file \*RO, $localpath;
2443 i_method "i_file", $keyword;
2448 sub i_resp_param ($) {
2449 $_[0] =~ m/^(\S+) (.*)$/ or badproto \*RO, "bad param spec";
2453 sub i_resp_previously ($) {
2454 $_[0] =~ m#^(refs/tags/\S+)=(\w+)$#
2455 or badproto \*RO, "bad previously spec";
2456 my $r = system qw(git check-ref-format), $1;
2457 die "bad previously ref spec ($r)" if $r;
2458 $previously{$1} = $2;
2463 sub i_resp_want ($) {
2465 die "$keyword ?" if $i_wanted{$keyword}++;
2466 my @localpaths = i_method "i_want", $keyword;
2467 printdebug "[[ $keyword @localpaths\n";
2468 foreach my $localpath (@localpaths) {
2469 protocol_send_file \*RI, $localpath;
2471 print RI "files-end\n" or die $!;
2474 our ($i_clogp, $i_version, $i_tag, $i_dscfn, $i_changesfn);
2476 sub i_localname_parsed_changelog {
2477 return "remote-changelog.822";
2479 sub i_file_parsed_changelog {
2480 ($i_clogp, $i_version, $i_tag, $i_dscfn) =
2481 push_parse_changelog "$i_tmp/remote-changelog.822";
2482 die if $i_dscfn =~ m#/|^\W#;
2485 sub i_localname_dsc {
2486 defined $i_dscfn or badproto \*RO, "dsc (before parsed-changelog)";
2491 sub i_localname_changes {
2492 defined $i_dscfn or badproto \*RO, "dsc (before parsed-changelog)";
2493 $i_changesfn = $i_dscfn;
2494 $i_changesfn =~ s/\.dsc$/_dgit.changes/ or die;
2495 return $i_changesfn;
2497 sub i_file_changes { }
2499 sub i_want_signed_tag {
2500 printdebug Dumper(\%i_param, $i_dscfn);
2501 defined $i_param{'head'} && defined $i_dscfn && defined $i_clogp
2502 && defined $i_param{'csuite'}
2503 or badproto \*RO, "premature desire for signed-tag";
2504 my $head = $i_param{'head'};
2505 die if $head =~ m/[^0-9a-f]/ || $head !~ m/^../;
2507 die unless $i_param{'csuite'} =~ m/^$suite_re$/;
2509 push_parse_dsc $i_dscfn, 'remote dsc', $i_version;
2512 push_mktag $head, $i_clogp, $i_tag,
2514 $i_changesfn, 'remote changes',
2515 sub { "tag$_[0]"; };
2520 sub i_want_signed_dsc_changes {
2521 rename "$i_dscfn.tmp","$i_dscfn" or die "$i_dscfn $!";
2522 sign_changes $i_changesfn;
2523 return ($i_dscfn, $i_changesfn);
2526 #---------- building etc. ----------
2532 #----- `3.0 (quilt)' handling -----
2534 our $fakeeditorenv = 'DGIT_FAKE_EDITOR_QUILT';
2536 sub quiltify_dpkg_commit ($$$;$) {
2537 my ($patchname,$author,$msg, $xinfo) = @_;
2541 my $descfn = ".git/dgit/quilt-description.tmp";
2542 open O, '>', $descfn or die "$descfn: $!";
2545 $msg =~ s/^\s+$/ ./mg;
2546 print O <<END or die $!;
2556 local $ENV{'EDITOR'} = cmdoutput qw(realpath --), $0;
2557 local $ENV{'VISUAL'} = $ENV{'EDITOR'};
2558 local $ENV{$fakeeditorenv} = cmdoutput qw(realpath --), $descfn;
2559 runcmd @dpkgsource, qw(--commit .), $patchname;
2563 sub quiltify_trees_differ ($$;$$) {
2564 my ($x,$y,$finegrained,$ignorenamesr) = @_;
2565 # returns true iff the two tree objects differ other than in debian/
2566 # with $finegrained,
2567 # returns bitmask 01 - differ in upstream files except .gitignore
2568 # 02 - differ in .gitignore
2569 # if $ignorenamesr is defined, $ingorenamesr->{$fn}
2570 # is set for each modified .gitignore filename $fn
2572 my @cmd = (@git, qw(diff-tree --name-only -z));
2573 push @cmd, qw(-r) if $finegrained;
2575 my $diffs= cmdoutput @cmd;
2577 foreach my $f (split /\0/, $diffs) {
2578 next if $f =~ m#^debian(?:/.*)?$#s;
2579 my $isignore = $f =~ m#^(?:.*/)?.gitignore$#s;
2580 $r |= $isignore ? 02 : 01;
2581 $ignorenamesr->{$f}=1 if $ignorenamesr && $isignore;
2583 printdebug "quiltify_trees_differ $x $y => $r\n";
2587 sub quiltify_tree_sentinelfiles ($) {
2588 # lists the `sentinel' files present in the tree
2590 my $r = cmdoutput @git, qw(ls-tree --name-only), $x,
2591 qw(-- debian/rules debian/control);
2596 sub quiltify_splitbrain_needed () {
2597 if (!$split_brain) {
2598 progress "dgit view: changes are required...";
2599 runcmd @git, qw(checkout -q -b dgit-view);
2604 sub quiltify_splitbrain ($$$$$$) {
2605 my ($clogp, $unapplied, $headref, $diffbits,
2606 $editedignores, $cachekey) = @_;
2607 if ($quilt_mode !~ m/gbp|dpm/) {
2608 # treat .gitignore just like any other upstream file
2609 $diffbits = { %$diffbits };
2610 $_ = !!$_ foreach values %$diffbits;
2612 # We would like any commits we generate to be reproducible
2613 my @authline = clogp_authline($clogp);
2614 local $ENV{GIT_COMMITTER_NAME} = $authline[0];
2615 local $ENV{GIT_COMMITTER_EMAIL} = $authline[1];
2616 local $ENV{GIT_COMMITTER_DATE} = $authline[2];
2618 if ($quilt_mode =~ m/gbp|unapplied/ &&
2619 ($diffbits->{H2O} & 01)) {
2621 "--quilt=$quilt_mode specified, implying patches-unapplied git tree\n".
2622 " but git tree differs from orig in upstream files.";
2623 if (!stat_exists "debian/patches") {
2625 "\n ... debian/patches is missing; perhaps this is a patch queue branch?";
2629 if ($quilt_mode =~ m/gbp|unapplied/ &&
2630 ($diffbits->{O2A} & 01)) { # some patches
2631 quiltify_splitbrain_needed();
2632 progress "dgit view: creating patches-applied version using gbp pq";
2633 runcmd shell_cmd 'exec >/dev/null', @gbp, qw(pq import);
2634 # gbp pq import creates a fresh branch; push back to dgit-view
2635 runcmd @git, qw(update-ref refs/heads/dgit-view HEAD);
2636 runcmd @git, qw(checkout -q dgit-view);
2638 if (($diffbits->{H2O} & 02) && # user has modified .gitignore
2639 !($diffbits->{O2A} & 02)) { # patches do not change .gitignore
2640 quiltify_splitbrain_needed();
2641 progress "dgit view: creating patch to represent .gitignore changes";
2642 ensuredir "debian/patches";
2643 my $gipatch = "debian/patches/auto-gitignore";
2644 open GIPATCH, ">>", "$gipatch" or die "$gipatch: $!";
2645 stat GIPATCH or die "$gipatch: $!";
2646 fail "$gipatch already exists; but want to create it".
2647 " to record .gitignore changes" if (stat _)[7];
2648 print GIPATCH <<END or die "$gipatch: $!";
2649 Subject: Update .gitignore from Debian packaging branch
2651 The Debian packaging git branch contains these updates to the upstream
2652 .gitignore file(s). This patch is autogenerated, to provide these
2653 updates to users of the official Debian archive view of the package.
2655 [dgit version $our_version]
2658 close GIPATCH or die "$gipatch: $!";
2659 runcmd shell_cmd "exec >>$gipatch", @git, qw(diff),
2660 $unapplied, $headref, "--", sort keys %$editedignores;
2661 open SERIES, "+>>", "debian/patches/series" or die $!;
2662 defined seek SERIES, -1, 2 or $!==EINVAL or die $!;
2664 defined read SERIES, $newline, 1 or die $!;
2665 print SERIES "\n" or die $! unless $newline eq "\n";
2666 print SERIES "auto-gitignore\n" or die $!;
2667 close SERIES or die $!;
2668 runcmd @git, qw(add -- debian/patches/series), $gipatch;
2669 commit_admin "Commit patch to update .gitignore";
2672 my $dgitview = git_rev_parse 'refs/heads/dgit-view';
2674 changedir '../../../..';
2675 ensuredir ".git/logs/refs/dgit-intern";
2676 my $makelogfh = new IO::File ".git/logs/refs/$splitbraincache", '>>'
2678 runcmd @git, qw(update-ref -m), $cachekey, "refs/$splitbraincache",
2681 progress "dgit view: created (commit id $dgitview)";
2683 changedir '.git/dgit/unpack/work';
2686 sub quiltify ($$$$) {
2687 my ($clogp,$target,$oldtiptree,$failsuggestion) = @_;
2689 # Quilt patchification algorithm
2691 # We search backwards through the history of the main tree's HEAD
2692 # (T) looking for a start commit S whose tree object is identical
2693 # to to the patch tip tree (ie the tree corresponding to the
2694 # current dpkg-committed patch series). For these purposes
2695 # `identical' disregards anything in debian/ - this wrinkle is
2696 # necessary because dpkg-source treates debian/ specially.
2698 # We can only traverse edges where at most one of the ancestors'
2699 # trees differs (in changes outside in debian/). And we cannot
2700 # handle edges which change .pc/ or debian/patches. To avoid
2701 # going down a rathole we avoid traversing edges which introduce
2702 # debian/rules or debian/control. And we set a limit on the
2703 # number of edges we are willing to look at.
2705 # If we succeed, we walk forwards again. For each traversed edge
2706 # PC (with P parent, C child) (starting with P=S and ending with
2707 # C=T) to we do this:
2709 # - dpkg-source --commit with a patch name and message derived from C
2710 # After traversing PT, we git commit the changes which
2711 # should be contained within debian/patches.
2713 # The search for the path S..T is breadth-first. We maintain a
2714 # todo list containing search nodes. A search node identifies a
2715 # commit, and looks something like this:
2717 # Commit => $git_commit_id,
2718 # Child => $c, # or undef if P=T
2719 # Whynot => $reason_edge_PC_unsuitable, # in @nots only
2720 # Nontrivial => true iff $p..$c has relevant changes
2727 my %considered; # saves being exponential on some weird graphs
2729 my $t_sentinels = quiltify_tree_sentinelfiles $target;
2732 my ($search,$whynot) = @_;
2733 printdebug " search NOT $search->{Commit} $whynot\n";
2734 $search->{Whynot} = $whynot;
2735 push @nots, $search;
2736 no warnings qw(exiting);
2745 my $c = shift @todo;
2746 next if $considered{$c->{Commit}}++;
2748 $not->($c, "maximum search space exceeded") if --$max_work <= 0;
2750 printdebug "quiltify investigate $c->{Commit}\n";
2753 if (!quiltify_trees_differ $c->{Commit}, $oldtiptree) {
2754 printdebug " search finished hooray!\n";
2759 if ($quilt_mode eq 'nofix') {
2760 fail "quilt fixup required but quilt mode is \`nofix'\n".
2761 "HEAD commit $c->{Commit} differs from tree implied by ".
2762 " debian/patches (tree object $oldtiptree)";
2764 if ($quilt_mode eq 'smash') {
2765 printdebug " search quitting smash\n";
2769 my $c_sentinels = quiltify_tree_sentinelfiles $c->{Commit};
2770 $not->($c, "has $c_sentinels not $t_sentinels")
2771 if $c_sentinels ne $t_sentinels;
2773 my $commitdata = cmdoutput @git, qw(cat-file commit), $c->{Commit};
2774 $commitdata =~ m/\n\n/;
2776 my @parents = ($commitdata =~ m/^parent (\w+)$/gm);
2777 @parents = map { { Commit => $_, Child => $c } } @parents;
2779 $not->($c, "root commit") if !@parents;
2781 foreach my $p (@parents) {
2782 $p->{Nontrivial}= quiltify_trees_differ $p->{Commit},$c->{Commit};
2784 my $ndiffers = grep { $_->{Nontrivial} } @parents;
2785 $not->($c, "merge ($ndiffers nontrivial parents)") if $ndiffers > 1;
2787 foreach my $p (@parents) {
2788 printdebug "considering C=$c->{Commit} P=$p->{Commit}\n";
2790 my @cmd= (@git, qw(diff-tree -r --name-only),
2791 $p->{Commit},$c->{Commit}, qw(-- debian/patches .pc));
2792 my $patchstackchange = cmdoutput @cmd;
2793 if (length $patchstackchange) {
2794 $patchstackchange =~ s/\n/,/g;
2795 $not->($p, "changed $patchstackchange");
2798 printdebug " search queue P=$p->{Commit} ",
2799 ($p->{Nontrivial} ? "NT" : "triv"),"\n";
2805 printdebug "quiltify want to smash\n";
2808 my $x = $_[0]{Commit};
2809 $x =~ s/(.*?[0-9a-z]{8})[0-9a-z]*$/$1/;
2812 my $reportnot = sub {
2814 my $s = $abbrev->($notp);
2815 my $c = $notp->{Child};
2816 $s .= "..".$abbrev->($c) if $c;
2817 $s .= ": ".$notp->{Whynot};
2820 if ($quilt_mode eq 'linear') {
2821 print STDERR "$us: quilt fixup cannot be linear. Stopped at:\n";
2822 foreach my $notp (@nots) {
2823 print STDERR "$us: ", $reportnot->($notp), "\n";
2825 print STDERR "$us: $_\n" foreach @$failsuggestion;
2826 fail "quilt fixup naive history linearisation failed.\n".
2827 "Use dpkg-source --commit by hand; or, --quilt=smash for one ugly patch";
2828 } elsif ($quilt_mode eq 'smash') {
2829 } elsif ($quilt_mode eq 'auto') {
2830 progress "quilt fixup cannot be linear, smashing...";
2832 die "$quilt_mode ?";
2837 my $msg = cmdoutput @git, qw(log), "-n$ncommits";
2839 quiltify_dpkg_commit "auto-$version-$target-$time",
2840 (getfield $clogp, 'Maintainer'),
2841 "Automatically generated patch ($clogp->{Version})\n".
2842 "Last (up to) $ncommits git changes, FYI:\n\n". $msg;
2846 progress "quiltify linearisation planning successful, executing...";
2848 for (my $p = $sref_S;
2849 my $c = $p->{Child};
2851 printdebug "quiltify traverse $p->{Commit}..$c->{Commit}\n";
2852 next unless $p->{Nontrivial};
2854 my $cc = $c->{Commit};
2856 my $commitdata = cmdoutput @git, qw(cat-file commit), $cc;
2857 $commitdata =~ m/\n\n/ or die "$c ?";
2860 $commitdata =~ m/^author (.*) \d+ [-+0-9]+$/m or die "$cc ?";
2863 $msg =~ s/^(.*)\n*/$1\n/ or die "$cc $msg ?";
2866 my $patchname = $title;
2867 $patchname =~ s/[.:]$//;
2868 $patchname =~ y/ A-Z/-a-z/;
2869 $patchname =~ y/-a-z0-9_.+=~//cd;
2870 $patchname =~ s/^\W/x-$&/;
2871 $patchname = substr($patchname,0,40);
2874 stat "debian/patches/$patchname$index";
2876 $!==ENOENT or die "$patchname$index $!";
2878 runcmd @git, qw(checkout -q), $cc;
2880 # We use the tip's changelog so that dpkg-source doesn't
2881 # produce complaining messages from dpkg-parsechangelog. None
2882 # of the information dpkg-source gets from the changelog is
2883 # actually relevant - it gets put into the original message
2884 # which dpkg-source provides our stunt editor, and then
2886 runcmd @git, qw(checkout -q), $target, qw(debian/changelog);
2888 quiltify_dpkg_commit "$patchname$index", $author, $msg,
2889 "X-Dgit-Generated: $clogp->{Version} $cc\n";
2891 runcmd @git, qw(checkout -q), $cc, qw(debian/changelog);
2894 runcmd @git, qw(checkout -q master);
2897 sub build_maybe_quilt_fixup () {
2898 my ($format,$fopts) = get_source_format;
2899 return unless madformat $format;
2902 check_for_vendor_patches();
2904 my $clogp = parsechangelog();
2905 my $headref = git_rev_parse('HEAD');
2910 my $upstreamversion=$version;
2911 $upstreamversion =~ s/-[^-]*$//;
2913 if ($fopts->{'single-debian-patch'}) {
2914 quilt_fixup_singlepatch($clogp, $headref, $upstreamversion);
2916 quilt_fixup_multipatch($clogp, $headref, $upstreamversion);
2919 die 'bug' if $split_brain && !$need_split_build_invocation;
2921 changedir '../../../..';
2922 runcmd_ordryrun_local
2923 @git, qw(pull --ff-only -q .git/dgit/unpack/work master);
2926 sub quilt_fixup_mkwork ($) {
2929 mkdir "work" or die $!;
2931 mktree_in_ud_here();
2932 runcmd @git, qw(reset -q --hard), $headref;
2935 sub quilt_fixup_linkorigs ($$) {
2936 my ($upstreamversion, $fn) = @_;
2937 # calls $fn->($leafname);
2939 foreach my $f (<../../../../*>) { #/){
2940 my $b=$f; $b =~ s{.*/}{};
2942 local ($debuglevel) = $debuglevel-1;
2943 printdebug "QF linkorigs $b, $f ?\n";
2945 next unless is_orig_file $b, srcfn $upstreamversion,'';
2946 printdebug "QF linkorigs $b, $f Y\n";
2947 link_ltarget $f, $b or die "$b $!";
2952 sub quilt_fixup_delete_pc () {
2953 runcmd @git, qw(rm -rqf .pc);
2954 commit_admin "Commit removal of .pc (quilt series tracking data)";
2957 sub quilt_fixup_singlepatch ($$$) {
2958 my ($clogp, $headref, $upstreamversion) = @_;
2960 progress "starting quiltify (single-debian-patch)";
2962 # dpkg-source --commit generates new patches even if
2963 # single-debian-patch is in debian/source/options. In order to
2964 # get it to generate debian/patches/debian-changes, it is
2965 # necessary to build the source package.
2967 quilt_fixup_linkorigs($upstreamversion, sub { });
2968 quilt_fixup_mkwork($headref);
2970 rmtree("debian/patches");
2972 runcmd @dpkgsource, qw(-b .);
2974 runcmd @dpkgsource, qw(-x), (srcfn $version, ".dsc");
2975 rename srcfn("$upstreamversion", "/debian/patches"),
2976 "work/debian/patches";
2979 commit_quilty_patch();
2982 sub quilt_make_fake_dsc ($) {
2983 my ($upstreamversion) = @_;
2985 my $fakeversion="$upstreamversion-~~DGITFAKE";
2987 my $fakedsc=new IO::File 'fake.dsc', '>' or die $!;
2988 print $fakedsc <<END or die $!;
2991 Version: $fakeversion
2995 my $dscaddfile=sub {
2998 my $md = new Digest::MD5;
3000 my $fh = new IO::File $b, '<' or die "$b $!";
3005 print $fakedsc " ".$md->hexdigest." $size $b\n" or die $!;
3008 quilt_fixup_linkorigs($upstreamversion, $dscaddfile);
3010 my @files=qw(debian/source/format debian/rules
3011 debian/control debian/changelog);
3012 foreach my $maybe (qw(debian/patches debian/source/options
3013 debian/tests/control)) {
3014 next unless stat_exists "../../../$maybe";
3015 push @files, $maybe;
3018 my $debtar= srcfn $fakeversion,'.debian.tar.gz';
3019 runcmd qw(env GZIP=-1n tar -zcf), "./$debtar", qw(-C ../../..), @files;
3021 $dscaddfile->($debtar);
3022 close $fakedsc or die $!;
3025 sub quilt_check_splitbrain_cache ($$) {
3026 my ($headref, $upstreamversion) = @_;
3027 # Called only if we are in (potentially) split brain mode.
3029 # Computes the cache key and looks in the cache.
3030 # Returns ($dgit_view_commitid, $cachekey) or (undef, $cachekey)
3032 my $splitbrain_cachekey;
3035 "dgit: split brain (separate dgit view) may be needed (--quilt=$quilt_mode).";
3036 # we look in the reflog of dgit-intern/quilt-cache
3037 # we look for an entry whose message is the key for the cache lookup
3038 my @cachekey = (qw(dgit), $our_version);
3039 push @cachekey, $upstreamversion;
3040 push @cachekey, $quilt_mode;
3041 push @cachekey, $headref;
3043 push @cachekey, hashfile('fake.dsc');
3045 my $srcshash = Digest::SHA->new(256);
3046 my %sfs = ( %INC, '$0(dgit)' => $0 );
3047 foreach my $sfk (sort keys %sfs) {
3048 next unless m/^\$0\b/ || m{^Debian/Dgit\b};
3049 $srcshash->add($sfk," ");
3050 $srcshash->add(hashfile($sfs{$sfk}));
3051 $srcshash->add("\n");
3053 push @cachekey, $srcshash->hexdigest();
3054 $splitbrain_cachekey = "@cachekey";
3056 my @cmd = (@git, qw(reflog), '--pretty=format:%H %gs',
3058 printdebug "splitbrain cachekey $splitbrain_cachekey\n";
3059 debugcmd "|(probably)",@cmd;
3060 my $child = open GC, "-|"; defined $child or die $!;
3062 chdir '../../..' or die $!;
3063 if (!stat ".git/logs/refs/$splitbraincache") {
3064 $! == ENOENT or die $!;
3065 printdebug ">(no reflog)\n";
3072 printdebug ">| ", $_, "\n" if $debuglevel > 1;
3073 next unless m/^(\w+) (\S.*\S)$/ && $2 eq $splitbrain_cachekey;
3076 quilt_fixup_mkwork($headref);
3077 if ($cachehit ne $headref) {
3078 progress "dgit view: found cached (commit id $cachehit)";
3079 runcmd @git, qw(checkout -q -b dgit-view), $cachehit;
3081 return ($cachehit, $splitbrain_cachekey);
3083 progress "dgit view: found cached, no changes required";
3084 return ($headref, $splitbrain_cachekey);
3086 die $! if GC->error;
3087 failedcmd unless close GC;
3089 printdebug "splitbrain cache miss\n";
3090 return (undef, $splitbrain_cachekey);
3093 sub quilt_fixup_multipatch ($$$) {
3094 my ($clogp, $headref, $upstreamversion) = @_;
3096 progress "examining quilt state (multiple patches, $quilt_mode mode)";
3099 # - honour any existing .pc in case it has any strangeness
3100 # - determine the git commit corresponding to the tip of
3101 # the patch stack (if there is one)
3102 # - if there is such a git commit, convert each subsequent
3103 # git commit into a quilt patch with dpkg-source --commit
3104 # - otherwise convert all the differences in the tree into
3105 # a single git commit
3109 # Our git tree doesn't necessarily contain .pc. (Some versions of
3110 # dgit would include the .pc in the git tree.) If there isn't
3111 # one, we need to generate one by unpacking the patches that we
3114 # We first look for a .pc in the git tree. If there is one, we
3115 # will use it. (This is not the normal case.)
3117 # Otherwise need to regenerate .pc so that dpkg-source --commit
3118 # can work. We do this as follows:
3119 # 1. Collect all relevant .orig from parent directory
3120 # 2. Generate a debian.tar.gz out of
3121 # debian/{patches,rules,source/format,source/options}
3122 # 3. Generate a fake .dsc containing just these fields:
3123 # Format Source Version Files
3124 # 4. Extract the fake .dsc
3125 # Now the fake .dsc has a .pc directory.
3126 # (In fact we do this in every case, because in future we will
3127 # want to search for a good base commit for generating patches.)
3129 # Then we can actually do the dpkg-source --commit
3130 # 1. Make a new working tree with the same object
3131 # store as our main tree and check out the main
3133 # 2. Copy .pc from the fake's extraction, if necessary
3134 # 3. Run dpkg-source --commit
3135 # 4. If the result has changes to debian/, then
3136 # - git-add them them
3137 # - git-add .pc if we had a .pc in-tree
3139 # 5. If we had a .pc in-tree, delete it, and git-commit
3140 # 6. Back in the main tree, fast forward to the new HEAD
3142 # Another situation we may have to cope with is gbp-style
3143 # patches-unapplied trees.
3145 # We would want to detect these, so we know to escape into
3146 # quilt_fixup_gbp. However, this is in general not possible.
3147 # Consider a package with a one patch which the dgit user reverts
3148 # (with git-revert or the moral equivalent).
3150 # That is indistinguishable in contents from a patches-unapplied
3151 # tree. And looking at the history to distinguish them is not
3152 # useful because the user might have made a confusing-looking git
3153 # history structure (which ought to produce an error if dgit can't
3154 # cope, not a silent reintroduction of an unwanted patch).
3156 # So gbp users will have to pass an option. But we can usually
3157 # detect their failure to do so: if the tree is not a clean
3158 # patches-applied tree, quilt linearisation fails, but the tree
3159 # _is_ a clean patches-unapplied tree, we can suggest that maybe
3160 # they want --quilt=unapplied.
3162 # To help detect this, when we are extracting the fake dsc, we
3163 # first extract it with --skip-patches, and then apply the patches
3164 # afterwards with dpkg-source --before-build. That lets us save a
3165 # tree object corresponding to .origs.
3167 my $splitbrain_cachekey;
3169 quilt_make_fake_dsc($upstreamversion);
3171 if (quiltmode_splitbrain()) {
3173 ($cachehit, $splitbrain_cachekey) =
3174 quilt_check_splitbrain_cache($headref, $upstreamversion);
3175 return if $cachehit;
3179 'exec dpkg-source --no-check --skip-patches -x fake.dsc >/dev/null';
3181 my $fakexdir= $package.'-'.(stripepoch $upstreamversion);
3182 rename $fakexdir, "fake" or die "$fakexdir $!";
3186 remove_stray_gits();
3187 mktree_in_ud_here();
3191 runcmd @git, qw(add -Af .);
3192 my $unapplied=git_write_tree();
3193 printdebug "fake orig tree object $unapplied\n";
3198 'exec dpkg-source --before-build . >/dev/null';
3202 quilt_fixup_mkwork($headref);
3205 if (stat_exists ".pc") {
3207 progress "Tree already contains .pc - will use it then delete it.";
3210 rename '../fake/.pc','.pc' or die $!;
3213 changedir '../fake';
3215 runcmd @git, qw(add -Af .);
3216 my $oldtiptree=git_write_tree();
3217 printdebug "fake o+d/p tree object $unapplied\n";
3218 changedir '../work';
3221 # We calculate some guesswork now about what kind of tree this might
3222 # be. This is mostly for error reporting.
3227 # O = orig, without patches applied
3228 # A = "applied", ie orig with H's debian/patches applied
3229 H2O => quiltify_trees_differ($headref, $unapplied, 1,\%editedignores),
3230 H2A => quiltify_trees_differ($headref, $oldtiptree,1),
3231 O2A => quiltify_trees_differ($unapplied,$oldtiptree,1),
3235 foreach my $b (qw(01 02)) {
3236 foreach my $v (qw(H2O O2A H2A)) {
3237 push @dl, ($diffbits->{$v} & $b) ? '##' : '==';
3240 printdebug "differences \@dl @dl.\n";
3243 "$us: quilt differences: src: %s orig %s gitignores: %s orig %s\n".
3244 "$us: quilt differences: HEAD %s o+d/p HEAD %s o+d/p",
3245 $dl[0], $dl[1], $dl[3], $dl[4],
3249 if (!($diffbits->{H2O} & $diffbits->{O2A})) {
3250 push @failsuggestion, "This might be a patches-unapplied branch.";
3251 } elsif (!($diffbits->{H2A} & $diffbits->{O2A})) {
3252 push @failsuggestion, "This might be a patches-applied branch.";
3254 push @failsuggestion, "Maybe you need to specify one of".
3255 " --quilt=gbp --quilt=dpm --quilt=unapplied ?";
3257 if (quiltmode_splitbrain()) {
3258 quiltify_splitbrain($clogp, $unapplied, $headref,
3259 $diffbits, \%editedignores,
3260 $splitbrain_cachekey);
3264 progress "starting quiltify (multiple patches, $quilt_mode mode)";
3265 quiltify($clogp,$headref,$oldtiptree,\@failsuggestion);
3267 if (!open P, '>>', ".pc/applied-patches") {
3268 $!==&ENOENT or die $!;
3273 commit_quilty_patch();
3275 if ($mustdeletepc) {
3276 quilt_fixup_delete_pc();
3280 sub quilt_fixup_editor () {
3281 my $descfn = $ENV{$fakeeditorenv};
3282 my $editing = $ARGV[$#ARGV];
3283 open I1, '<', $descfn or die "$descfn: $!";
3284 open I2, '<', $editing or die "$editing: $!";
3285 unlink $editing or die "$editing: $!";
3286 open O, '>', $editing or die "$editing: $!";
3287 while (<I1>) { print O or die $!; } I1->error and die $!;
3290 $copying ||= m/^\-\-\- /;
3291 next unless $copying;
3294 I2->error and die $!;
3299 sub maybe_apply_patches_dirtily () {
3300 return unless $quilt_mode =~ m/gbp|unapplied/;
3301 print STDERR <<END or die $!;
3303 dgit: Building, or cleaning with rules target, in patches-unapplied tree.
3304 dgit: Have to apply the patches - making the tree dirty.
3305 dgit: (Consider specifying --clean=git and (or) using dgit sbuild.)
3308 $patches_applied_dirtily = 01;
3309 $patches_applied_dirtily |= 02 unless stat_exists '.pc';
3310 runcmd qw(dpkg-source --before-build .);
3313 sub maybe_unapply_patches_again () {
3314 progress "dgit: Unapplying patches again to tidy up the tree."
3315 if $patches_applied_dirtily;
3316 runcmd qw(dpkg-source --after-build .)
3317 if $patches_applied_dirtily & 01;
3319 if $patches_applied_dirtily & 02;
3322 #----- other building -----
3324 our $clean_using_builder;
3325 # ^ tree is to be cleaned by dpkg-source's builtin idea that it should
3326 # clean the tree before building (perhaps invoked indirectly by
3327 # whatever we are using to run the build), rather than separately
3328 # and explicitly by us.
3331 return if $clean_using_builder;
3332 if ($cleanmode eq 'dpkg-source') {
3333 maybe_apply_patches_dirtily();
3334 runcmd_ordryrun_local @dpkgbuildpackage, qw(-T clean);
3335 } elsif ($cleanmode eq 'dpkg-source-d') {
3336 maybe_apply_patches_dirtily();
3337 runcmd_ordryrun_local @dpkgbuildpackage, qw(-d -T clean);
3338 } elsif ($cleanmode eq 'git') {
3339 runcmd_ordryrun_local @git, qw(clean -xdf);
3340 } elsif ($cleanmode eq 'git-ff') {
3341 runcmd_ordryrun_local @git, qw(clean -xdff);
3342 } elsif ($cleanmode eq 'check') {
3343 my $leftovers = cmdoutput @git, qw(clean -xdn);
3344 if (length $leftovers) {
3345 print STDERR $leftovers, "\n" or die $!;
3346 fail "tree contains uncommitted files and --clean=check specified";
3348 } elsif ($cleanmode eq 'none') {
3355 badusage "clean takes no additional arguments" if @ARGV;
3358 maybe_unapply_patches_again();
3363 badusage "-p is not allowed when building" if defined $package;
3366 my $clogp = parsechangelog();
3367 $isuite = getfield $clogp, 'Distribution';
3368 $package = getfield $clogp, 'Source';
3369 $version = getfield $clogp, 'Version';
3370 build_maybe_quilt_fixup();
3372 my $pat = changespat $version;
3373 foreach my $f (glob "$buildproductsdir/$pat") {
3375 unlink $f or fail "remove old changes file $f: $!";
3377 progress "would remove $f";
3383 sub changesopts_initial () {
3384 my @opts =@changesopts[1..$#changesopts];
3387 sub changesopts_version () {
3388 if (!defined $changes_since_version) {
3389 my @vsns = archive_query('archive_query');
3390 my @quirk = access_quirk();
3391 if ($quirk[0] eq 'backports') {
3392 local $isuite = $quirk[2];
3394 canonicalise_suite();
3395 push @vsns, archive_query('archive_query');
3398 @vsns = map { $_->[0] } @vsns;
3399 @vsns = sort { -version_compare($a, $b) } @vsns;
3400 $changes_since_version = $vsns[0];
3401 progress "changelog will contain changes since $vsns[0]";
3403 $changes_since_version = '_';
3404 progress "package seems new, not specifying -v<version>";
3407 if ($changes_since_version ne '_') {
3408 return ("-v$changes_since_version");
3414 sub changesopts () {
3415 return (changesopts_initial(), changesopts_version());
3418 sub massage_dbp_args ($;$) {
3419 my ($cmd,$xargs) = @_;
3422 # - if we're going to split the source build out so we can
3423 # do strange things to it, massage the arguments to dpkg-buildpackage
3424 # so that the main build doessn't build source (or add an argument
3425 # to stop it building source by default).
3427 # - add -nc to stop dpkg-source cleaning the source tree,
3428 # unless we're not doing a split build and want dpkg-source
3429 # as cleanmode, in which case we can do nothing
3432 # 0 - source will NOT need to be built separately by caller
3433 # +1 - source will need to be built separately by caller
3434 # +2 - source will need to be built separately by caller AND
3435 # dpkg-buildpackage should not in fact be run at all!
3436 debugcmd '#massaging#', @$cmd if $debuglevel>1;
3437 #print STDERR "MASS0 ",Dumper($cmd, $xargs, $need_split_build_invocation);
3438 if ($cleanmode eq 'dpkg-source' && !$need_split_build_invocation) {
3439 $clean_using_builder = 1;
3442 # -nc has the side effect of specifying -b if nothing else specified
3443 # and some combinations of -S, -b, et al, are errors, rather than
3444 # later simply overriding earlie. So we need to:
3445 # - search the command line for these options
3446 # - pick the last one
3447 # - perhaps add our own as a default
3448 # - perhaps adjust it to the corresponding non-source-building version
3450 foreach my $l ($cmd, $xargs) {
3452 @$l = grep { !(m/^-[SgGFABb]$/s and $dmode=$_) } @$l;
3455 #print STDERR "MASS1 ",Dumper($cmd, $xargs, $dmode);
3457 if ($need_split_build_invocation) {
3458 printdebug "massage split $dmode.\n";
3459 $r = $dmode =~ m/[S]/ ? +2 :
3460 $dmode =~ y/gGF/ABb/ ? +1 :
3461 $dmode =~ m/[ABb]/ ? 0 :
3464 printdebug "massage done $r $dmode.\n";
3466 #print STDERR "MASS2 ",Dumper($cmd, $xargs, $r);
3471 my @dbp = (@dpkgbuildpackage, qw(-us -uc), changesopts_initial(), @ARGV);
3472 my $wantsrc = massage_dbp_args \@dbp;
3479 push @dbp, changesopts_version();
3480 maybe_apply_patches_dirtily();
3481 runcmd_ordryrun_local @dbp;
3483 maybe_unapply_patches_again();
3484 printdone "build successful\n";
3488 my @dbp = @dpkgbuildpackage;
3490 my $wantsrc = massage_dbp_args \@dbp, \@ARGV;
3493 if (length executable_on_path('git-buildpackage')) {
3494 @cmd = qw(git-buildpackage);
3496 @cmd = qw(gbp buildpackage);
3498 push @cmd, (qw(-us -uc --git-no-sign-tags), "--git-builder=@dbp");
3503 if (!$clean_using_builder) {
3504 push @cmd, '--git-cleaner=true';
3509 unless (grep { m/^--git-debian-branch|^--git-ignore-branch/ } @ARGV) {
3510 canonicalise_suite();
3511 push @cmd, "--git-debian-branch=".lbranch();
3513 push @cmd, changesopts();
3514 maybe_apply_patches_dirtily();
3515 runcmd_ordryrun_local @cmd, @ARGV;
3517 maybe_unapply_patches_again();
3518 printdone "build successful\n";
3520 sub cmd_git_build { cmd_gbp_build(); } # compatibility with <= 1.0
3523 my $our_cleanmode = $cleanmode;
3524 if ($need_split_build_invocation) {
3525 # Pretend that clean is being done some other way. This
3526 # forces us not to try to use dpkg-buildpackage to clean and
3527 # build source all in one go; and instead we run dpkg-source
3528 # (and build_prep() will do the clean since $clean_using_builder
3530 $our_cleanmode = 'ELSEWHERE';
3532 if ($our_cleanmode =~ m/^dpkg-source/) {
3533 # dpkg-source invocation (below) will clean, so build_prep shouldn't
3534 $clean_using_builder = 1;
3537 $sourcechanges = changespat $version,'source';
3539 unlink "../$sourcechanges" or $!==ENOENT
3540 or fail "remove $sourcechanges: $!";
3542 $dscfn = dscfn($version);
3543 if ($our_cleanmode eq 'dpkg-source') {
3544 maybe_apply_patches_dirtily();
3545 runcmd_ordryrun_local @dpkgbuildpackage, qw(-us -uc -S),
3547 } elsif ($our_cleanmode eq 'dpkg-source-d') {
3548 maybe_apply_patches_dirtily();
3549 runcmd_ordryrun_local @dpkgbuildpackage, qw(-us -uc -S -d),
3552 my @cmd = (@dpkgsource, qw(-b --));
3555 runcmd_ordryrun_local @cmd, "work";
3556 my @udfiles = <${package}_*>;
3557 changedir "../../..";
3558 foreach my $f (@udfiles) {
3559 printdebug "source copy, found $f\n";
3562 ($f =~ m/\.debian\.tar(?:\.\w+)$/ &&
3563 $f eq srcfn($version, $&));
3564 printdebug "source copy, found $f - renaming\n";
3565 rename "$ud/$f", "../$f" or $!==ENOENT
3566 or fail "put in place new source file ($f): $!";
3569 my $pwd = must_getcwd();
3570 my $leafdir = basename $pwd;
3572 runcmd_ordryrun_local @cmd, $leafdir;
3575 runcmd_ordryrun_local qw(sh -ec),
3576 'exec >$1; shift; exec "$@"','x',
3577 "../$sourcechanges",
3578 @dpkggenchanges, qw(-S), changesopts();
3582 sub cmd_build_source {
3583 badusage "build-source takes no additional arguments" if @ARGV;
3585 maybe_unapply_patches_again();
3586 printdone "source built, results in $dscfn and $sourcechanges";
3591 my $pat = changespat $version;
3593 my @unwanted = map { s#^\.\./##; $_; } glob "../$pat";
3594 @unwanted = grep { $_ ne changespat $version,'source' } @unwanted;
3595 fail "changes files other than source matching $pat".
3596 " already present (@unwanted);".
3597 " building would result in ambiguity about the intended results"
3602 stat_exists $dscfn or fail "$dscfn (in parent directory): $!";
3603 stat_exists $sourcechanges
3604 or fail "$sourcechanges (in parent directory): $!";
3606 runcmd_ordryrun_local @sbuild, qw(-d), $isuite, @ARGV, $dscfn;
3607 my @changesfiles = glob $pat;
3608 @changesfiles = sort {
3609 ($b =~ m/_source\.changes$/ <=> $a =~ m/_source\.changes$/)
3612 fail "wrong number of different changes files (@changesfiles)"
3613 unless @changesfiles==2;
3614 my $binchanges = parsecontrol($changesfiles[1], "binary changes file");
3615 foreach my $l (split /\n/, getfield $binchanges, 'Files') {
3616 fail "$l found in binaries changes file $binchanges"
3619 runcmd_ordryrun_local @mergechanges, @changesfiles;
3620 my $multichanges = changespat $version,'multi';
3622 stat_exists $multichanges or fail "$multichanges: $!";
3623 foreach my $cf (glob $pat) {
3624 next if $cf eq $multichanges;
3625 rename "$cf", "$cf.inmulti" or fail "$cf\{,.inmulti}: $!";
3628 maybe_unapply_patches_again();
3629 printdone "build successful, results in $multichanges\n" or die $!;
3632 sub cmd_quilt_fixup {
3633 badusage "incorrect arguments to dgit quilt-fixup" if @ARGV;
3634 my $clogp = parsechangelog();
3635 $version = getfield $clogp, 'Version';
3636 $package = getfield $clogp, 'Source';
3639 build_maybe_quilt_fixup();
3642 sub cmd_archive_api_query {
3643 badusage "need only 1 subpath argument" unless @ARGV==1;
3644 my ($subpath) = @ARGV;
3645 my @cmd = archive_api_query_cmd($subpath);
3647 exec @cmd or fail "exec curl: $!\n";
3650 sub cmd_clone_dgit_repos_server {
3651 badusage "need destination argument" unless @ARGV==1;
3652 my ($destdir) = @ARGV;
3653 $package = '_dgit-repos-server';
3654 my @cmd = (@git, qw(clone), access_giturl(), $destdir);
3656 exec @cmd or fail "exec git clone: $!\n";
3659 sub cmd_setup_mergechangelogs {
3660 badusage "no arguments allowed to dgit setup-mergechangelogs" if @ARGV;
3661 setup_mergechangelogs(1);
3664 sub cmd_setup_useremail {
3665 badusage "no arguments allowed to dgit setup-mergechangelogs" if @ARGV;
3669 sub cmd_setup_new_tree {
3670 badusage "no arguments allowed to dgit setup-tree" if @ARGV;
3674 #---------- argument parsing and main program ----------
3677 print "dgit version $our_version\n" or die $!;
3681 our (%valopts_long, %valopts_short);
3684 sub defvalopt ($$$$) {
3685 my ($long,$short,$val_re,$how) = @_;
3686 my $oi = { Long => $long, Short => $short, Re => $val_re, How => $how };
3687 $valopts_long{$long} = $oi;
3688 $valopts_short{$short} = $oi;
3689 # $how subref should:
3690 # do whatever assignemnt or thing it likes with $_[0]
3691 # if the option should not be passed on to remote, @rvalopts=()
3692 # or $how can be a scalar ref, meaning simply assign the value
3695 defvalopt '--since-version', '-v', '[^_]+|_', \$changes_since_version;
3696 defvalopt '--distro', '-d', '.+', \$idistro;
3697 defvalopt '', '-k', '.+', \$keyid;
3698 defvalopt '--existing-package','', '.*', \$existing_package;
3699 defvalopt '--build-products-dir','','.*', \$buildproductsdir;
3700 defvalopt '--clean', '', $cleanmode_re, \$cleanmode;
3701 defvalopt '--quilt', '', $quilt_modes_re, \$quilt_mode;
3703 defvalopt '', '-c', '.*=.*', sub { push @git, '-c', @_; };
3705 defvalopt '', '-C', '.+', sub {
3706 ($changesfile) = (@_);
3707 if ($changesfile =~ s#^(.*)/##) {
3708 $buildproductsdir = $1;
3712 defvalopt '--initiator-tempdir','','.*', sub {
3713 ($initiator_tempdir) = (@_);
3714 $initiator_tempdir =~ m#^/# or
3715 badusage "--initiator-tempdir must be used specify an".
3716 " absolute, not relative, directory."
3722 if (defined $ENV{'DGIT_SSH'}) {
3723 @ssh = string_to_ssh $ENV{'DGIT_SSH'};
3724 } elsif (defined $ENV{'GIT_SSH'}) {
3725 @ssh = ($ENV{'GIT_SSH'});
3733 if (!defined $val) {
3734 badusage "$what needs a value" unless @ARGV;
3736 push @rvalopts, $val;
3738 badusage "bad value \`$val' for $what" unless
3739 $val =~ m/^$oi->{Re}$(?!\n)/s;
3740 my $how = $oi->{How};
3741 if (ref($how) eq 'SCALAR') {
3746 push @ropts, @rvalopts;
3750 last unless $ARGV[0] =~ m/^-/;
3754 if (m/^--dry-run$/) {
3757 } elsif (m/^--damp-run$/) {
3760 } elsif (m/^--no-sign$/) {
3763 } elsif (m/^--help$/) {
3765 } elsif (m/^--version$/) {
3767 } elsif (m/^--new$/) {
3770 } elsif (m/^--([-0-9a-z]+)=(.+)/s &&
3771 ($om = $opts_opt_map{$1}) &&
3775 } elsif (m/^--([-0-9a-z]+):(.*)/s &&
3776 !$opts_opt_cmdonly{$1} &&
3777 ($om = $opts_opt_map{$1})) {
3780 } elsif (m/^--ignore-dirty$/s) {
3783 } elsif (m/^--no-quilt-fixup$/s) {
3785 $quilt_mode = 'nocheck';
3786 } elsif (m/^--no-rm-on-error$/s) {
3789 } elsif (m/^--(no-)?rm-old-changes$/s) {
3792 } elsif (m/^--deliberately-($deliberately_re)$/s) {
3794 push @deliberatelies, $&;
3795 } elsif (m/^--always-split-source-build$/s) {
3796 # undocumented, for testing
3798 $need_split_build_invocation = 1;
3799 } elsif (m/^(--[-0-9a-z]+)(=|$)/ && ($oi = $valopts_long{$1})) {
3800 $val = $2 ? $' : undef; #';
3801 $valopt->($oi->{Long});
3803 badusage "unknown long option \`$_'";
3810 } elsif (s/^-L/-/) {
3813 } elsif (s/^-h/-/) {
3815 } elsif (s/^-D/-/) {
3819 } elsif (s/^-N/-/) {
3824 push @changesopts, $_;
3826 } elsif (s/^-wn$//s) {
3828 $cleanmode = 'none';
3829 } elsif (s/^-wg$//s) {
3832 } elsif (s/^-wgf$//s) {
3834 $cleanmode = 'git-ff';
3835 } elsif (s/^-wd$//s) {
3837 $cleanmode = 'dpkg-source';
3838 } elsif (s/^-wdd$//s) {
3840 $cleanmode = 'dpkg-source-d';
3841 } elsif (s/^-wc$//s) {
3843 $cleanmode = 'check';
3844 } elsif (m/^-[a-zA-Z]/ && ($oi = $valopts_short{$&})) {
3846 $val = undef unless length $val;
3847 $valopt->($oi->{Short});
3850 badusage "unknown short option \`$_'";
3857 sub finalise_opts_opts () {
3858 foreach my $k (keys %opts_opt_map) {
3859 my $om = $opts_opt_map{$k};
3861 my $v = access_cfg("cmd-$k", 'RETURN-UNDEF');
3863 badcfg "cannot set command for $k"
3864 unless length $om->[0];
3868 foreach my $c (access_cfg_cfgs("opts-$k")) {
3869 my $vl = $gitcfg{$c};
3870 printdebug "CL $c ",
3871 ($vl ? join " ", map { shellquote } @$vl : ""),
3872 "\n" if $debuglevel >= 4;
3874 badcfg "cannot configure options for $k"
3875 if $opts_opt_cmdonly{$k};
3876 my $insertpos = $opts_cfg_insertpos{$k};
3877 @$om = ( @$om[0..$insertpos-1],
3879 @$om[$insertpos..$#$om] );
3884 if ($ENV{$fakeeditorenv}) {
3886 quilt_fixup_editor();
3892 print STDERR "DRY RUN ONLY\n" if $dryrun_level > 1;
3893 print STDERR "DAMP RUN - WILL MAKE LOCAL (UNSIGNED) CHANGES\n"
3894 if $dryrun_level == 1;
3896 print STDERR $helpmsg or die $!;
3899 my $cmd = shift @ARGV;
3902 if (!defined $rmchanges) {
3903 local $access_forpush;
3904 $rmchanges = access_cfg_bool(0, 'rm-old-changes');
3907 if (!defined $quilt_mode) {
3908 local $access_forpush;
3909 $quilt_mode = cfg('dgit.force.quilt-mode', 'RETURN-UNDEF')
3910 // access_cfg('quilt-mode', 'RETURN-UNDEF')
3912 $quilt_mode =~ m/^($quilt_modes_re)$/
3913 or badcfg "unknown quilt-mode \`$quilt_mode'";
3917 $need_split_build_invocation ||= quiltmode_splitbrain();
3919 if (!defined $cleanmode) {
3920 local $access_forpush;
3921 $cleanmode = access_cfg('clean-mode', 'RETURN-UNDEF');
3922 $cleanmode //= 'dpkg-source';
3924 badcfg "unknown clean-mode \`$cleanmode'" unless
3925 $cleanmode =~ m/^($cleanmode_re)$(?!\n)/s;
3928 my $fn = ${*::}{"cmd_$cmd"};
3929 $fn or badusage "unknown operation $cmd";