X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=dgit.git;a=blobdiff_plain;f=dgit;h=49350ee70782bd08eb10d4a005d315c3f4e58252;hp=ede7010c9ca2e5bf955841f3e8c81b0db35364fd;hb=8ac8810ed80449a437ee8bba3b9fc5457824c371;hpb=a4e7ac2715ea7077ccedb190a783c64c06a9c539 diff --git a/dgit b/dgit index ede7010c..49350ee7 100755 --- a/dgit +++ b/dgit @@ -2,7 +2,8 @@ # dgit # Integration between git and Debian-style archives # -# Copyright (C)2013-2016 Ian Jackson +# Copyright (C)2013-2017 Ian Jackson +# Copyright (C)2017 Sean Whitton # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -19,7 +20,7 @@ use strict; -use Debian::Dgit; +use Debian::Dgit qw(:DEFAULT :playground); setup_sigwarn(); use IO::Handle; @@ -30,6 +31,8 @@ use File::Path; use File::Temp qw(tempdir); use File::Basename; use Dpkg::Version; +use Dpkg::Compression; +use Dpkg::Compression::Process; use POSIX; use IPC::Open2; use Digest::SHA; @@ -47,6 +50,8 @@ our $absurdity = undef; ###substituted### our @rpushprotovsn_support = qw(4 3 2); # 4 is new tag format our $protovsn; +our $cmd; +our $subcommand; our $isuite; our $idistro; our $package; @@ -90,7 +95,7 @@ our %format_ok = map { $_=>1 } ("1.0","3.0 (native)","3.0 (quilt)"); our $suite_re = '[-+.0-9a-z]+'; our $cleanmode_re = 'dpkg-source(?:-d)?|git|git-ff|check|none'; -our $orig_f_comp_re = 'orig(?:-[-0-9a-z]+)?'; +our $orig_f_comp_re = qr{orig(?:-$extra_orig_namepart_re)?}; our $orig_f_sig_re = '\\.(?:asc|gpg|pgp)'; our $orig_f_tail_re = "$orig_f_comp_re\\.tar(?:\\.\\w+)?(?:$orig_f_sig_re)?"; @@ -98,9 +103,11 @@ our $git_authline_re = '^([^<>]+) \<(\S+)\> (\d+ [-+]\d+)$'; our $splitbraincache = 'dgit-intern/quilt-cache'; our $rewritemap = 'dgit-rewrite/map'; +our @dpkg_source_ignores = qw(-i(?:^|/)\.git(?:/|$) -I.git); + our (@git) = qw(git); our (@dget) = qw(dget); -our (@curl) = qw(curl); +our (@curl) = (qw(curl --proto-redir), '-all,http,https', qw(-L)); our (@dput) = qw(dput); our (@debsign) = qw(debsign); our (@gpg) = qw(gpg); @@ -109,8 +116,8 @@ our (@ssh) = 'ssh'; our (@dgit) = qw(dgit); our (@aptget) = qw(apt-get); our (@aptcache) = qw(apt-cache); -our (@dpkgbuildpackage) = qw(dpkg-buildpackage -i\.git/ -I.git); -our (@dpkgsource) = qw(dpkg-source -i\.git/ -I.git); +our (@dpkgbuildpackage) = (qw(dpkg-buildpackage), @dpkg_source_ignores); +our (@dpkgsource) = (qw(dpkg-source), @dpkg_source_ignores); our (@dpkggenchanges) = qw(dpkg-genchanges); our (@mergechanges) = qw(mergechanges -f); our (@gbp_build) = (''); @@ -144,7 +151,9 @@ our %opts_cfg_insertpos = map { sub parseopts_late_defaults(); sub setup_gitattrs(;$); +sub check_gitattrs($$); +our $playground; our $keyid; autoflush STDOUT 1; @@ -248,12 +257,6 @@ sub no_such_package () { exit 4; } -sub changedir ($) { - my ($newdir) = @_; - printdebug "CD $newdir\n"; - chdir $newdir or confess "chdir: $newdir: $!"; -} - sub deliberately ($) { my ($enquiry) = @_; return !!grep { $_ eq "--deliberately-$enquiry" } @deliberatelies; @@ -280,6 +283,10 @@ sub gbp_pq { return opts_opt_multi_cmd @gbp_pq; } +sub dgit_privdir () { + our $dgit_privdir_made //= ensure_a_playground 'dgit'; +} + #---------- remote protocol support, common ---------- # remote push initiator/responder protocol: @@ -315,6 +322,9 @@ sub gbp_pq { # > param tagformat old|new # > param maint-view MAINT-VIEW-HEAD # +# > param buildinfo-filename P_V_X.buildinfo # zero or more times +# > file buildinfo # for buildinfos to sign +# # > previously REFNAME=OBJNAME # if --deliberately-not-fast-forward # # goes into tag, for replay prevention # @@ -331,6 +341,9 @@ sub gbp_pq { # [etc] # < data-block NBYTES [transfer of signed changes] # [etc] +# < data-block NBYTES [transfer of each signed buildinfo +# [etc] same number and order as "file buildinfo"] +# ... # < files-end # # > complete @@ -486,12 +499,6 @@ sub url_get { our ($dscdata,$dscurl,$dsc,$dsc_checked,$skew_warning_vsn); -sub runcmd { - debugcmd "+",@_; - $!=0; $?=-1; - failedcmd @_ if system @_; -} - sub act_local () { return $dryrun_level <= 1; } sub act_scary () { return !$dryrun_level; } @@ -535,6 +542,7 @@ main usages: dgit [dgit-opts] build [dpkg-buildpackage-opts] dgit [dgit-opts] sbuild [sbuild-opts] dgit [dgit-opts] push [dgit-opts] [suite] + dgit [dgit-opts] push-source [dgit-opts] [suite] dgit [dgit-opts] rpush build-host:build-dir ... important dgit options: -k sign tag and package with instead of default @@ -559,6 +567,9 @@ sub nextarg { return scalar shift @ARGV; } +sub pre_help () { + not_necessarily_a_tree(); +} sub cmd_help () { print $helpmsg or die $!; exit 0; @@ -635,32 +646,17 @@ our %defcfg = ('dgit.default.distro' => 'debian', our %gitcfgs; our @gitcfgsources = qw(cmdline local global system); +our $invoked_in_git_tree = 1; sub git_slurp_config () { - local ($debuglevel) = $debuglevel-2; - local $/="\0"; - # This algoritm is a bit subtle, but this is needed so that for # options which we want to be single-valued, we allow the # different config sources to override properly. See #835858. foreach my $src (@gitcfgsources) { next if $src eq 'cmdline'; # we do this ourselves since git doesn't handle it - - my @cmd = (@git, qw(config -z --get-regexp), "--$src", qw(.*)); - debugcmd "|",@cmd; - open GITS, "-|", @cmd or die $!; - while () { - chomp or die; - printdebug "=> ", (messagequote $_), "\n"; - m/\n/ or die "$_ ?"; - push @{ $gitcfgs{$src}{$`} }, $'; #'; - } - $!=0; $?=0; - close GITS - or ($!==0 && $?==256) - or failedcmd @cmd; + $gitcfgs{$src} = git_slurp_config_src $src; } } @@ -668,7 +664,7 @@ sub git_get_config ($) { my ($c) = @_; foreach my $src (@gitcfgsources) { my $l = $gitcfgs{$src}{$c}; - croak "$l $c" if $l && !ref $l; + confess "internal error ($l $c)" if $l && !ref $l; printdebug"C $c ".(defined $l ? join " ", map { messagequote "'$_'" } @$l : "undef")."\n" @@ -697,6 +693,12 @@ sub cfg { "$us: distro or suite appears not to be (properly) supported"; } +sub not_necessarily_a_tree () { + # needs to be called from pre_* + @gitcfgsources = grep { $_ ne 'local' } @gitcfgsources; + $invoked_in_git_tree = 0; +} + sub access_basedistro__noalias () { if (defined $idistro) { return $idistro; @@ -989,19 +991,13 @@ sub commit_getclogp ($) { our %commit_getclogp_memo; my $memo = $commit_getclogp_memo{$objid}; return $memo if $memo; - mkpath '.git/dgit'; - my $mclog = ".git/dgit/clog-$objid"; + + my $mclog = dgit_privdir()."clog"; runcmd shell_cmd "exec >$mclog", @git, qw(cat-file blob), "$objid:debian/changelog"; $commit_getclogp_memo{$objid} = parsechangelog("-l$mclog"); } -sub must_getcwd () { - my $d = getcwd(); - defined $d or fail "getcwd failed: $!"; - return $d; -} - sub parse_dscdata () { my $dscfh = new IO::File \$dscdata, '<' or die $!; printdebug Dumper($dscdata) if $debuglevel>1; @@ -1318,6 +1314,8 @@ sub archive_query_aptget { return [ (getfield $pre_dsc, 'Version'), $uri ]; } +sub file_in_archive_aptget () { return undef; } + #---------- `dummyapicat' archive query method ---------- sub archive_query_dummycatapi { archive_query_ftpmasterapi @_; } @@ -1683,21 +1681,14 @@ sub create_remote_git_repo () { our ($dsc_hash,$lastpush_mergeinput); our ($dsc_distro, $dsc_hint_tag, $dsc_hint_url); -our $ud = '.git/dgit/unpack'; -sub prep_ud (;$) { - my ($d) = @_; - $d //= $ud; - rmtree($d); - mkpath '.git/dgit'; - mkdir $d or die $!; +sub prep_ud () { + dgit_privdir(); # ensures that $dgit_privdir_made is based on $maindir + $playground = fresh_playground 'dgit/unpack'; } sub mktree_in_ud_here () { - runcmd qw(git init -q); - runcmd qw(git config gc.auto 0); - rmtree('.git/objects'); - symlink '../../../../objects','.git/objects' or die $!; + playtree_setup $gitcfgs{local}; } sub git_write_tree () { @@ -1730,8 +1721,8 @@ sub remove_stray_gits ($) { sub mktree_in_ud_from_only_subdir ($;$) { my ($what,$raw) = @_; - # changes into the subdir + my (@dirs) = <*/.>; die "expected one subdir but found @dirs ?" unless @dirs==1; $dirs[0] =~ m#^([^/]+)/\.$# or die; @@ -1860,6 +1851,40 @@ sub is_orig_file_of_vsn ($$) { return 1; } +# This function determines whether a .changes file is source-only from +# the point of view of dak. Thus, it permits *_source.buildinfo +# files. +# +# It does not, however, permit any other buildinfo files. After a +# source-only upload, the buildds will try to upload files like +# foo_1.2.3_amd64.buildinfo. If the package maintainer included files +# named like this in their (otherwise) source-only upload, the uploads +# of the buildd can be rejected by dak. Fixing the resultant +# situation can require manual intervention. So we block such +# .buildinfo files when the user tells us to perform a source-only +# upload (such as when using the push-source subcommand with the -C +# option, which calls this function). +# +# Note, though, that when dgit is told to prepare a source-only +# upload, such as when subcommands like build-source and push-source +# without -C are used, dgit has a more restrictive notion of +# source-only .changes than dak: such uploads will never include +# *_source.buildinfo files. This is because there is no use for such +# files when using a tool like dgit to produce the source package, as +# dgit ensures the source is identical to git HEAD. +sub test_source_only_changes ($) { + my ($changes) = @_; + foreach my $l (split /\n/, getfield $changes, 'Files') { + $l =~ m/\S+$/ or next; + # \.tar\.[a-z0-9]+ covers orig.tar and the tarballs in native packages + unless ($& =~ m/(?:\.dsc|\.diff\.gz|\.tar\.[a-z0-9]+|_source\.buildinfo)$/) { + print "purportedly source-only changes polluted by $&\n"; + return 0; + } + } + return 1; +} + sub changes_update_origs_from_dsc ($$$$) { my ($dsc, $changes, $upstreamvsn, $changesfile) = @_; my %changes_f; @@ -1986,7 +2011,14 @@ sub make_commit_text ($) { sub clogp_authline ($) { my ($clogp) = @_; my $author = getfield $clogp, 'Maintainer'; - $author =~ s#,.*##ms; + if ($author =~ m/^[^"\@]+\,/) { + # single entry Maintainer field with unquoted comma + $author = ($& =~ y/,//rd).$'; # strip the comma + } + # git wants a single author; any remaining commas in $author + # are by now preceded by @ (or "). It seems safer to punt on + # "..." for now rather than attempting to dequote or something. + $author =~ s#,.*##ms unless $author =~ m/"/; my $date = cmdoutput qw(date), '+%s %z', qw(-d), getfield($clogp,'Date'); my $authline = "$author $date"; $authline =~ m/$git_authline_re/o or @@ -2065,13 +2097,13 @@ sub generate_commits_from_dsc () { # See big comment in fetch_from_archive, below. # See also README.dsc-import. prep_ud(); - changedir $ud; + changedir $playground; my @dfi = dsc_files_info(); foreach my $fi (@dfi) { my $f = $fi->{Filename}; die "$f ?" if $f =~ m#/|^\.|\.dsc$|\.tmp$#; - my $upper_f = "../../../../$f"; + my $upper_f = "$maindir/../$f"; printdebug "considering reusing $f: "; @@ -2151,7 +2183,7 @@ sub generate_commits_from_dsc () { if defined $compr_ext && !defined $cname; my $compr_proc = new Dpkg::Compression::Process compression => $cname; - my @compr_cmd = $compr_proc->get_uncompress_cmdline(); + @compr_cmd = $compr_proc->get_uncompress_cmdline(); my $compr_fh = new IO::Handle; my $compr_pid = open $compr_fh, "-|" // die $!; if (!$compr_pid) { @@ -2178,7 +2210,7 @@ sub generate_commits_from_dsc () { !$? or failedcmd @tarcmd; close $input or - (@compr_cmd ? failedcmd @compr_cmd + (@compr_cmd ? ($?==SIGPIPE || failedcmd @compr_cmd) : die $!); # finally, we have the results in "tarball", but maybe # with the wrong permissions @@ -2308,6 +2340,7 @@ sub generate_commits_from_dsc () { my $authline = clogp_authline $clogp; my $changes = getfield $clogp, 'Changes'; + $changes =~ s/^\n//; # Changes: \n my $cversion = getfield $clogp, 'Version'; if (@tartrees) { @@ -2390,6 +2423,10 @@ END my $path = $ENV{PATH} or die; + # we use ../../gbp-pq-output, which (given that we are in + # $playground/PLAYTREE, and $playground is .git/dgit/unpack, + # is .git/dgit. + foreach my $use_absurd (qw(0 1)) { runcmd @git, qw(checkout -q unpa); runcmd @git, qw(update-ref -d refs/heads/patch-queue/unpa); @@ -2474,8 +2511,8 @@ END @output = $lastpush_mergeinput; } } - changedir '../../../..'; - rmtree($ud); + changedir $maindir; + rmtree $playground; return @output; } @@ -2496,7 +2533,7 @@ sub complete_file_from_dsc ($$;$) { $fi->{Digester}->reset(); $fi->{Digester}->addfile(*F); F->error and die $!; - my $got = $fi->{Digester}->hexdigest(); + $got = $fi->{Digester}->hexdigest(); return $got eq $fi->{Hash}; }; @@ -2570,7 +2607,7 @@ sub lrfetchref_used ($) { } sub git_lrfetch_sane { - my ($supplementary, @specs) = @_; + my ($url, $supplementary, @specs) = @_; # Make a 'refs/'.lrfetchrefs.'/*' be just like on server, # at least as regards @specs. Also leave the results in # %lrfetchrefs_f, and arrange for lrfetchref_used to be @@ -2601,8 +2638,6 @@ sub git_lrfetch_sane { # git fetch to try to generate it. If we don't manage to generate # the target state, we try again. - my $url = access_giturl(); - printdebug "git_lrfetch_sane suppl=$supplementary specs @specs\n"; my $specre = join '|', map { @@ -2742,7 +2777,8 @@ sub git_fetch_us () { push @specs, $rewritemap; push @specs, qw(heads/*) if deliberately_not_fast_forward; - git_lrfetch_sane 0, @specs; + my $url = access_giturl(); + git_lrfetch_sane $url, 0, @specs; my %here; my @tagpats = debiantags('*',access_nomdistro); @@ -2871,13 +2907,13 @@ END or fail <", $mcf or die "$mcf $!"; print MC <", "$attrs.new" or die "$attrs.new $!"; if (!open ATTRS, "<", $attrs) { @@ -3316,15 +3354,16 @@ sub ensure_setup_existing_tree () { set_local_git_config $k, 'true'; } -sub open_gitattrs () { - my $gai = new IO::File ".git/info/attributes" +sub open_main_gitattrs () { + confess 'internal error no maindir' unless defined $maindir; + my $gai = new IO::File "$maindir_gitcommon/info/attributes" or $!==ENOENT - or die "open .git/info/attributes: $!"; + or die "open $maindir_gitcommon/info/attributes: $!"; return $gai; } sub is_gitattrs_setup () { - my $gai = open_gitattrs(); + my $gai = open_main_gitattrs(); return 0 unless $gai; while (<$gai>) { return 1 if m{^\[attr\]dgit-defuse-attrs\s}; @@ -3344,14 +3383,15 @@ sub setup_gitattrs (;$) { END return; } - my $af = ".git/info/attributes"; + my $af = "$maindir_gitcommon/info/attributes"; + ensuredir "$maindir_gitcommon/info"; open GAO, "> $af.new" or die $!; print GAO <) { chomp; @@ -3366,8 +3406,38 @@ END sub setup_new_tree () { setup_mergechangelogs(); setup_useremail(); + setup_gitattrs(); +} + +sub check_gitattrs ($$) { + my ($treeish, $what) = @_; + + return if is_gitattrs_setup; + + local $/="\0"; + my @cmd = (@git, qw(ls-tree -lrz --), "${treeish}:"); + debugcmd "|",@cmd; + my $gafl = new IO::File; + open $gafl, "-|", @cmd or die $!; + while (<$gafl>) { + chomp or die; + s/^\d+\s+\w+\s+\w+\s+(\d+)\t// or die; + next if $1 == 0; + next unless m{(?:^|/)\.gitattributes$}; + + # oh dear, found one + print STDERR <(), and returns undef @@ -3433,6 +3503,7 @@ sub fork_for_multisuite ($) { $before_fetch_merge->(); foreach my $tsuite (@suites[1..$#suites]) { + $tsuite =~ s/^-/$cbasesuite-/; my $csubsuite = multisuite_suite_child($tsuite, \@mergeinputs, sub { @end = (); @@ -3547,18 +3618,22 @@ END } sub clone ($) { + # in multisuite, returns twice! + # once in parent after first suite fetched, + # and then again in child after everything is finished my ($dstdir) = @_; badusage "dry run makes no sense with clone" unless act_local(); my $multi_fetched = fork_for_multisuite(sub { printdebug "multi clone before fetch merge\n"; changedir $dstdir; + record_maindir(); }); if ($multi_fetched) { printdebug "multi clone after fetch merge\n"; clone_set_head(); clone_finish($dstdir); - exit 0; + return; } printdebug "clone main body\n"; @@ -3567,6 +3642,8 @@ sub clone ($) { mkdir $dstdir or fail "create \`$dstdir': $!"; changedir $dstdir; runcmd @git, qw(init -q); + record_maindir(); + setup_new_tree(); clone_set_head(); my $giturl = access_giturl(1); if (defined $giturl) { @@ -3585,7 +3662,6 @@ sub clone ($) { $vcsgiturl =~ s/\s+-b\s+\S+//g; runcmd @git, qw(remote add vcs-git), $vcsgiturl; } - setup_new_tree(); clone_finish($dstdir); } @@ -3616,15 +3692,7 @@ sub check_not_dirty () { return if $ignoredirty; - my @cmd = (@git, qw(diff --quiet HEAD)); - debugcmd "+",@cmd; - $!=0; $?=-1; system @cmd; - return if !$?; - if ($?==256) { - fail "working tree is dirty (does not match HEAD)"; - } else { - failedcmd @cmd; - } + git_check_unmodified(); } sub commit_admin ($) { @@ -3706,7 +3774,7 @@ sub maybe_split_brain_save ($$$) { my ($headref, $dgitview, $msg) = @_; # => message fragment "$saved" describing disposition of $dgitview return "commit id $dgitview" unless defined $split_brain_save; - my @cmd = (shell_cmd "cd ../../../..", + my @cmd = (shell_cmd 'cd "$1"; shift', $maindir, @git, qw(update-ref -m), "dgit --dgit-view-save $msg HEAD=$headref", $split_brain_save, $dgitview); @@ -3764,18 +3832,28 @@ sub pseudomerge_version_check ($$) { } else { my $v = $i_arch_v->[0]; progress "Checking package changelog for archive version $v ..."; + my $cd; eval { my @xa = ("-f$v", "-t$v"); my $vclogp = parsechangelog @xa; - my $cv = [ (getfield $vclogp, 'Version'), - "Version field from dpkg-parsechangelog @xa" ]; + my $gf = sub { + my ($fn) = @_; + [ (getfield $vclogp, $fn), + "$fn field from dpkg-parsechangelog @xa" ]; + }; + my $cv = $gf->('Version'); infopair_cond_equal($i_arch_v, $cv); + $cd = $gf->('Distribution'); }; if ($@) { $@ =~ s/^dgit: //gm; fail "$@". "Perhaps debian/changelog does not mention $v ?"; } + fail <[0] =~ m/UNRELEASED/; +$cd->[1] is $cd->[0] +Your tree seems to based on earlier (not uploaded) $v. +END } } @@ -3797,8 +3875,7 @@ sub pseudomerge_make_commit ($$$$ $$) { : !length $overwrite_version ? " --overwrite" : " --overwrite=".$overwrite_version; - mkpath '.git/dgit'; - my $pmf = ".git/dgit/pseudomerge"; + my $pmf = dgit_privdir()."/pseudomerge"; open MC, ">", $pmf or die "$pmf $!"; print MC <$clogpfn", qw(dpkg-parsechangelog); responder_send_file('parsed-changelog', $clogpfn); @@ -4134,20 +4212,20 @@ END if (madformat_wantfixup($format)) { # user might have not used dgit build, so maybe do this now: if (quiltmode_splitbrain()) { - changedir $ud; + changedir $playground; quilt_make_fake_dsc($upstreamversion); my $cachekey; ($dgithead, $cachekey) = quilt_check_splitbrain_cache($actualhead, $upstreamversion); $dgithead or fail "--quilt=$quilt_mode but no cached dgit view: - perhaps tree changed since dgit build[-source] ?"; + perhaps HEAD changed since dgit build[-source] ?"; $split_brain = 1; $dgithead = splitbrain_pseudomerge($clogp, $actualhead, $dgithead, $archive_hash); $maintviewhead = $actualhead; - changedir '../../../..'; + changedir $maindir; prep_ud(); # so _only_subdir() works, below } else { commit_quilty_patch(); @@ -4178,26 +4256,55 @@ END } } - changedir $ud; + changedir $playground; progress "checking that $dscfn corresponds to HEAD"; runcmd qw(dpkg-source -x --), - $dscpath =~ m#^/# ? $dscpath : "../../../$dscpath"; + $dscpath =~ m#^/# ? $dscpath : "$maindir/$dscpath"; my ($tree,$dir) = mktree_in_ud_from_only_subdir("source package"); check_for_vendor_patches() if madformat($dsc->{format}); - changedir '../../../..'; + changedir $maindir; my @diffcmd = (@git, qw(diff --quiet), $tree, $dgithead); debugcmd "+",@diffcmd; $!=0; $?=-1; my $r = system @diffcmd; if ($r) { if ($r==256) { + my $referent = $split_brain ? $dgithead : 'HEAD'; my $diffs = cmdoutput @git, qw(diff --stat), $tree, $dgithead; - fail <{$_} or + fail "buildinfo contains $_" + foreach qw(Changes Changed-by Distribution); + } + push @i_buildinfos, $bi; + delete $i_param{'buildinfo-filename'}; +} + sub i_localname_changes { defined $i_dscfn or badproto \*RO, "dsc (before parsed-changelog)"; $i_changesfn = $i_dscfn; @@ -4714,7 +4880,7 @@ sub i_want_signed_tag { sub i_want_signed_dsc_changes { rename "$i_dscfn.tmp","$i_dscfn" or die "$i_dscfn $!"; sign_changes $i_changesfn; - return ($i_dscfn, $i_changesfn); + return ($i_dscfn, $i_changesfn, @i_buildinfos); } #---------- building etc. ---------- @@ -4731,7 +4897,7 @@ sub quiltify_dpkg_commit ($$$;$) { my ($patchname,$author,$msg, $xinfo) = @_; $xinfo //= ''; - mkpath '.git/dgit'; + mkpath '.git/dgit'; # we are in playtree my $descfn = ".git/dgit/quilt-description.tmp"; open O, '>', $descfn or die "$descfn: $!"; $msg =~ s/\n+/\n\n/; @@ -4763,7 +4929,7 @@ sub quiltify_trees_differ ($$;$$$) { # a list of unrepresentable changes (removals of upstream files # (as messages) local $/=undef; - my @cmd = (@git, qw(diff-tree -z)); + my @cmd = (@git, qw(diff-tree -z --no-renames)); push @cmd, qw(--name-only) unless $unrepres; push @cmd, qw(-r) if $finegrained || $unrepres; push @cmd, $x, $y; @@ -4782,16 +4948,23 @@ sub quiltify_trees_differ ($$;$$$) { if ($unrepres) { eval { - die "not a plain file\n" - unless $newmode =~ m/^10\d{4}$/ || - $oldmode =~ m/^10\d{4}$/; + die "not a plain file or symlink\n" + unless $newmode =~ m/^(?:10|12)\d{4}$/ || + $oldmode =~ m/^(?:10|12)\d{4}$/; if ($oldmode =~ m/[^0]/ && $newmode =~ m/[^0]/) { - die "mode changed\n" if $oldmode ne $newmode; + # both old and new files exist + die "mode or type changed\n" if $oldmode ne $newmode; + die "modified symlink\n" unless $newmode =~ m/^10/; + } elsif ($oldmode =~ m/[^0]/) { + # deletion + die "deletion of symlink\n" + unless $oldmode =~ m/^10/; } else { - die "non-default mode\n" - unless $newmode =~ m/^100644$/ || - $oldmode =~ m/^100644$/; + # creation + die "creation with non-default mode\n" + unless $newmode =~ m/^100644$/ or + $newmode =~ m/^120000$/; } }; if ($@) { @@ -4917,11 +5090,11 @@ END my $dgitview = git_rev_parse 'HEAD'; - changedir '../../../..'; + changedir $maindir; # When we no longer need to support squeeze, use --create-reflog # instead of this: - ensuredir ".git/logs/refs/dgit-intern"; - my $makelogfh = new IO::File ".git/logs/refs/$splitbraincache", '>>' + ensuredir "$maindir_gitcommon/logs/refs/dgit-intern"; + my $makelogfh = new IO::File "$maindir_gitcommon/logs/refs/$splitbraincache", '>>' or die $!; my $oldcache = git_get_ref "refs/$splitbraincache"; @@ -4942,7 +5115,7 @@ END runcmd @git, qw(update-ref -m), $cachekey, "refs/$splitbraincache", $dgitview; - changedir '.git/dgit/unpack/work'; + changedir "$playground/work"; my $saved = maybe_split_brain_save $headref, $dgitview, "converted"; progress "dgit view: created ($saved)"; @@ -5146,6 +5319,7 @@ sub quiltify ($$$$) { die "contains unexpected slashes\n" if m{//} || m{/$}; die "contains leading punctuation\n" if m{^\W} || m{/\W}; die "contains bad character(s)\n" if m{[^-a-z0-9_.+=~/]}i; + die "is series file\n" if m{$series_filename_re}o; die "too long" if length > 200; }; return $_ unless $@; @@ -5184,6 +5358,7 @@ sub quiltify ($$$$) { $patchname =~ y/-a-z0-9_.+=~//cd; $patchname =~ s/^\W/x-$&/; $patchname = substr($patchname,0,40); + $patchname .= ".patch"; } if (!defined $patchdir) { $patchdir = ''; @@ -5239,7 +5414,7 @@ END my $headref = git_rev_parse('HEAD'); prep_ud(); - changedir $ud; + changedir $playground; my $upstreamversion = upstreamversion $version; @@ -5251,9 +5426,9 @@ END die 'bug' if $split_brain && !$need_split_build_invocation; - changedir '../../../..'; + changedir $maindir; runcmd_ordryrun_local - @git, qw(pull --ff-only -q .git/dgit/unpack/work master); + @git, qw(pull --ff-only -q), "$playground/work", qw(master); } sub quilt_fixup_mkwork ($) { @@ -5269,7 +5444,7 @@ sub quilt_fixup_linkorigs ($$) { my ($upstreamversion, $fn) = @_; # calls $fn->($leafname); - foreach my $f (<../../../../*>) { #/){ + foreach my $f (<$maindir/../*>) { #/){ my $b=$f; $b =~ s{.*/}{}; { local ($debuglevel) = $debuglevel-1; @@ -5348,12 +5523,12 @@ END debian/control debian/changelog); foreach my $maybe (qw(debian/patches debian/source/options debian/tests/control)) { - next unless stat_exists "../../../$maybe"; + next unless stat_exists "$maindir/$maybe"; push @files, $maybe; } my $debtar= srcfn $fakeversion,'.debian.tar.gz'; - runcmd qw(env GZIP=-1n tar -zcf), "./$debtar", qw(-C ../../..), @files; + runcmd qw(env GZIP=-1n tar -zcf), "./$debtar", qw(-C), $maindir, @files; $dscaddfile->($debtar); close $fakedsc or die $!; @@ -5362,7 +5537,7 @@ END sub quilt_check_splitbrain_cache ($$) { my ($headref, $upstreamversion) = @_; # Called only if we are in (potentially) split brain mode. - # Called in $ud. + # Called in playground. # Computes the cache key and looks in the cache. # Returns ($dgit_view_commitid, $cachekey) or (undef, $cachekey) @@ -5396,8 +5571,8 @@ sub quilt_check_splitbrain_cache ($$) { debugcmd "|(probably)",@cmd; my $child = open GC, "-|"; defined $child or die $!; if (!$child) { - chdir '../../..' or die $!; - if (!stat ".git/logs/refs/$splitbraincache") { + chdir $maindir or die $!; + if (!stat "$maindir_gitcommon/logs/refs/$splitbraincache") { $! == ENOENT or die $!; printdebug ">(no reflog)\n"; exit 0; @@ -5526,6 +5701,7 @@ sub quilt_fixup_multipatch ($$$) { rmtree '.pc'; + rmtree 'debian'; # git checkout commitish paths does not delete! runcmd @git, qw(checkout -f), $headref, qw(-- debian); my $unapplied=git_add_write_tree(); printdebug "fake orig tree object $unapplied\n"; @@ -5718,14 +5894,18 @@ sub cmd_clean () { maybe_unapply_patches_again(); } -sub build_prep_early () { - our $build_prep_early_done //= 0; - return if $build_prep_early_done++; - badusage "-p is not allowed when building" if defined $package; +sub build_or_push_prep_early () { + our $build_or_push_prep_early_done //= 0; + return if $build_or_push_prep_early_done++; + badusage "-p is not allowed with dgit $subcommand" if defined $package; my $clogp = parsechangelog(); $isuite = getfield $clogp, 'Distribution'; $package = getfield $clogp, 'Source'; $version = getfield $clogp, 'Version'; +} + +sub build_prep_early () { + build_or_push_prep_early(); notpushing(); check_not_dirty(); } @@ -5963,15 +6143,17 @@ sub cmd_gbp_build { } my @cmd = opts_opt_multi_cmd @gbp_build; - push @cmd, (qw(-us -uc --git-no-sign-tags), "--git-builder=@dbp"); + push @cmd, (qw(-us -uc --git-no-sign-tags), + "--git-builder=".(shellquote @dbp)); if ($gbp_make_orig) { - ensuredir '.git/dgit'; - my $ok = '.git/dgit/origs-gen-ok'; + my $priv = dgit_privdir(); + my $ok = "$priv/origs-gen-ok"; unlink $ok or $!==&ENOENT or die $!; my @origs_cmd = @cmd; push @origs_cmd, qw(--git-cleaner=true); - push @origs_cmd, "--git-prebuild=touch $ok .git/dgit/no-such-dir/ok"; + push @origs_cmd, "--git-prebuild=". + "touch ".(shellquote $ok)." ".(shellquote "$priv/no-such-dir/ok"); push @origs_cmd, @ARGV; if (act_local()) { debugcmd @origs_cmd; @@ -6001,21 +6183,14 @@ sub cmd_gbp_build { } sub cmd_git_build { cmd_gbp_build(); } # compatibility with <= 1.0 +sub build_source_for_push { + build_source(); + maybe_unapply_patches_again(); + $changesfile = $sourcechanges; +} + sub build_source { build_prep_early(); - my $our_cleanmode = $cleanmode; - if ($need_split_build_invocation) { - # Pretend that clean is being done some other way. This - # forces us not to try to use dpkg-buildpackage to clean and - # build source all in one go; and instead we run dpkg-source - # (and build_prep() will do the clean since $clean_using_builder - # is false). - $our_cleanmode = 'ELSEWHERE'; - } - if ($our_cleanmode =~ m/^dpkg-source/) { - # dpkg-source invocation (below) will clean, so build_prep shouldn't - $clean_using_builder = 1; - } build_prep(); $sourcechanges = changespat $version,'source'; if (act_local()) { @@ -6023,43 +6198,33 @@ sub build_source { or fail "remove $sourcechanges: $!"; } $dscfn = dscfn($version); - if ($our_cleanmode eq 'dpkg-source') { - maybe_apply_patches_dirtily(); - runcmd_ordryrun_local @dpkgbuildpackage, qw(-us -uc -S), - changesopts(); - } elsif ($our_cleanmode eq 'dpkg-source-d') { - maybe_apply_patches_dirtily(); - runcmd_ordryrun_local @dpkgbuildpackage, qw(-us -uc -S -d), - changesopts(); + my @cmd = (@dpkgsource, qw(-b --)); + if ($split_brain) { + changedir $playground; + runcmd_ordryrun_local @cmd, "work"; + my @udfiles = <${package}_*>; + changedir $maindir; + foreach my $f (@udfiles) { + printdebug "source copy, found $f\n"; + next unless + $f eq $dscfn or + ($f =~ m/\.debian\.tar(?:\.\w+)$/ && + $f eq srcfn($version, $&)); + printdebug "source copy, found $f - renaming\n"; + rename "$playground/$f", "../$f" or $!==ENOENT + or fail "put in place new source file ($f): $!"; + } } else { - my @cmd = (@dpkgsource, qw(-b --)); - if ($split_brain) { - changedir $ud; - runcmd_ordryrun_local @cmd, "work"; - my @udfiles = <${package}_*>; - changedir "../../.."; - foreach my $f (@udfiles) { - printdebug "source copy, found $f\n"; - next unless - $f eq $dscfn or - ($f =~ m/\.debian\.tar(?:\.\w+)$/ && - $f eq srcfn($version, $&)); - printdebug "source copy, found $f - renaming\n"; - rename "$ud/$f", "../$f" or $!==ENOENT - or fail "put in place new source file ($f): $!"; - } - } else { - my $pwd = must_getcwd(); - my $leafdir = basename $pwd; - changedir ".."; - runcmd_ordryrun_local @cmd, $leafdir; - changedir $pwd; - } - runcmd_ordryrun_local qw(sh -ec), - 'exec >$1; shift; exec "$@"','x', - "../$sourcechanges", - @dpkggenchanges, qw(-S), changesopts(); + my $pwd = must_getcwd(); + my $leafdir = basename $pwd; + changedir ".."; + runcmd_ordryrun_local @cmd, $leafdir; + changedir $pwd; } + runcmd_ordryrun_local qw(sh -ec), + 'exec >$1; shift; exec "$@"','x', + "../$sourcechanges", + @dpkggenchanges, qw(-S), changesopts(); } sub cmd_build_source { @@ -6101,6 +6266,8 @@ sub import_dsc_result { my ($dstref, $newhash, $what_log, $what_msg) = @_; my @cmd = (@git, qw(update-ref -m), $what_log, $dstref, $newhash); runcmd @cmd; + check_gitattrs($newhash, "source tree"); + progress "dgit: import-dsc: $what_msg"; } @@ -6217,7 +6384,10 @@ END foreach my $fi (@dfi) { my $f = $fi->{Filename}; my $here = "../$f"; - next if lstat $here; + if (lstat $here) { + next if stat $here; + fail "lstat $here works but stat gives $! !"; + } fail "stat $here: $!" unless $! == ENOENT; my $there = $dscfn; if ($dscfn =~ m#^(?:\./+)?\.\./+#) { @@ -6228,8 +6398,10 @@ END fail "cannot import $dscfn which seems to be inside working tree!"; } $there =~ s#/+[^/]+$## or - fail "cannot import $dscfn which seems to not have a basename"; + fail "import $dscfn requires ../$f, but it does not exist"; $there .= "/$f"; + my $test = $there =~ m{^/} ? $there : "../$there"; + stat $test or fail "import $dscfn requires $test, but: $!"; symlink $there, $here or fail "symlink $there to $here: $!"; progress "made symlink $here -> $there"; # print STDERR Dumper($fi); @@ -6267,46 +6439,78 @@ END "results are in in git ref $dstbranch"; } +sub pre_archive_api_query () { + not_necessarily_a_tree(); +} sub cmd_archive_api_query { badusage "need only 1 subpath argument" unless @ARGV==1; my ($subpath) = @ARGV; + local $isuite = 'DGIT-API-QUERY-CMD'; my @cmd = archive_api_query_cmd($subpath); push @cmd, qw(-f); debugcmd ">",@cmd; exec @cmd or fail "exec curl: $!\n"; } +sub repos_server_url () { + $package = '_dgit-repos-server'; + local $access_forpush = 1; + local $isuite = 'DGIT-REPOS-SERVER'; + my $url = access_giturl(); +} + +sub pre_clone_dgit_repos_server () { + not_necessarily_a_tree(); +} sub cmd_clone_dgit_repos_server { badusage "need destination argument" unless @ARGV==1; my ($destdir) = @ARGV; - $package = '_dgit-repos-server'; - local $access_forpush = 0; - my @cmd = (@git, qw(clone), access_giturl(), $destdir); + my $url = repos_server_url(); + my @cmd = (@git, qw(clone), $url, $destdir); debugcmd ">",@cmd; exec @cmd or fail "exec git clone: $!\n"; } +sub pre_print_dgit_repos_server_source_url () { + not_necessarily_a_tree(); +} sub cmd_print_dgit_repos_server_source_url { badusage "no arguments allowed to dgit print-dgit-repos-server-source-url" if @ARGV; - $package = '_dgit-repos-server'; - local $access_forpush = 0; - my $url = access_giturl(); + my $url = repos_server_url(); print $url, "\n" or die $!; } +sub pre_print_dpkg_source_ignores { + not_necessarily_a_tree(); +} +sub cmd_print_dpkg_source_ignores { + badusage "no arguments allowed to dgit print-dpkg-source-ignores" + if @ARGV; + print "@dpkg_source_ignores\n" or die $!; +} + sub cmd_setup_mergechangelogs { badusage "no arguments allowed to dgit setup-mergechangelogs" if @ARGV; + local $isuite = 'DGIT-SETUP-TREE'; setup_mergechangelogs(1); } sub cmd_setup_useremail { badusage "no arguments allowed to dgit setup-useremail" if @ARGV; + local $isuite = 'DGIT-SETUP-TREE'; setup_useremail(1); } +sub cmd_setup_gitattributes { + badusage "no arguments allowed to dgit setup-useremail" if @ARGV; + local $isuite = 'DGIT-SETUP-TREE'; + setup_gitattrs(1); +} + sub cmd_setup_new_tree { badusage "no arguments allowed to dgit setup-tree" if @ARGV; + local $isuite = 'DGIT-SETUP-TREE'; setup_new_tree(); } @@ -6660,7 +6864,6 @@ if ($ENV{$fakeeditorenv}) { parseopts(); check_env_sanity(); -git_slurp_config(); print STDERR "DRY RUN ONLY\n" if $dryrun_level > 1; print STDERR "DAMP RUN - WILL MAKE LOCAL (UNSIGNED) CHANGES\n" @@ -6669,12 +6872,15 @@ if (!@ARGV) { print STDERR $helpmsg or die $!; exit 8; } -my $cmd = shift @ARGV; +$cmd = $subcommand = shift @ARGV; $cmd =~ y/-/_/; my $pre_fn = ${*::}{"pre_$cmd"}; $pre_fn->() if $pre_fn; +record_maindir if $invoked_in_git_tree; +git_slurp_config(); + my $fn = ${*::}{"cmd_$cmd"}; $fn or badusage "unknown operation $cmd"; $fn->();