X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=dgit.git;a=blobdiff_plain;f=dgit;h=31e6bd581b7453d23519d9aa01983e5b21061e4a;hp=b8d6826928af835898ef5ea85a032ce7682a4758;hb=88c8440dc1ac4e6de35615ac18fdb3ec281e1d96;hpb=18413810f983efbd083a2fd0cbc2c2cbbadc558b diff --git a/dgit b/dgit index b8d68269..31e6bd58 100755 --- a/dgit +++ b/dgit @@ -19,7 +19,7 @@ use strict; -use Debian::Dgit; +use Debian::Dgit qw(:DEFAULT :playground); setup_sigwarn(); use IO::Handle; @@ -30,6 +30,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 +49,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; @@ -98,6 +102,8 @@ 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 --proto-redir), '-all,http,https', qw(-L)); @@ -109,8 +115,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) = (''); @@ -249,12 +255,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; @@ -281,6 +281,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: @@ -493,12 +497,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; } @@ -567,7 +565,7 @@ sub nextarg { } sub pre_help () { - no_local_git_cfg(); + not_necessarily_a_tree(); } sub cmd_help () { print $helpmsg or die $!; @@ -645,32 +643,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; } } @@ -707,9 +690,10 @@ sub cfg { "$us: distro or suite appears not to be (properly) supported"; } -sub no_local_git_cfg () { +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 () { @@ -1004,19 +988,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; @@ -1700,30 +1678,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 + fresh_playground 'dgit/unpack'; } sub mktree_in_ud_here () { - runcmd qw(git init -q); - runcmd qw(git config gc.auto 0); - foreach my $copy (qw(user.email user.name user.useConfigOnly - core.sharedRepository - core.compression core.looseCompression - core.bigFileThreshold core.fsyncObjectFiles)) { - my $v = $gitcfgs{local}{$copy}; - next unless $v; - runcmd qw(git config), $copy, $_ foreach @$v; - } - rmtree('.git/objects'); - symlink '../../../../objects','.git/objects' or die $!; - setup_gitattrs(1); + playtree_setup $gitcfgs{local}; } sub git_write_tree () { @@ -1756,8 +1718,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; @@ -2098,13 +2060,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: "; @@ -2424,6 +2386,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); @@ -2508,8 +2474,8 @@ END @output = $lastpush_mergeinput; } } - changedir '../../../..'; - rmtree($ud); + changedir $maindir; + rmtree $playground; return @output; } @@ -3206,7 +3172,7 @@ END my $author = clogp_authline $useclogp; my $cversion = getfield $useclogp, 'Version'; - my $mcf = ".git/dgit/mergecommit"; + my $mcf = dgit_privdir()."/mergecommit"; open MC, ">", $mcf or die "$mcf $!"; print MC <", "$attrs.new" or die "$attrs.new $!"; if (!open ATTRS, "<", $attrs) { @@ -3351,15 +3317,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}; @@ -3379,15 +3346,15 @@ sub setup_gitattrs (;$) { END return; } - my $af = ".git/info/attributes"; - ensuredir '.git/info'; + my $af = "$maindir_gitcommon/info/attributes"; + ensuredir "$maindir_gitcommon/info"; open GAO, "> $af.new" or die $!; print GAO <) { chomp; @@ -3623,6 +3590,7 @@ sub clone ($) { 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"; @@ -3637,6 +3605,7 @@ 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); @@ -3776,7 +3745,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); @@ -3877,8 +3846,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); @@ -4215,20 +4183,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(); @@ -4259,26 +4227,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 <', $descfn or die "$descfn: $!"; $msg =~ s/\n+/\n\n/; @@ -4911,16 +4909,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 ($@) { @@ -5046,11 +5051,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"; @@ -5071,7 +5076,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)"; @@ -5370,7 +5375,7 @@ END my $headref = git_rev_parse('HEAD'); prep_ud(); - changedir $ud; + changedir $playground; my $upstreamversion = upstreamversion $version; @@ -5382,9 +5387,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 ($) { @@ -5400,7 +5405,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; @@ -5479,12 +5484,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 $!; @@ -5493,7 +5498,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) @@ -5527,8 +5532,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; @@ -5849,14 +5854,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(); } @@ -6094,15 +6103,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; @@ -6165,10 +6176,10 @@ sub build_source { } else { my @cmd = (@dpkgsource, qw(-b --)); if ($split_brain) { - changedir $ud; + changedir $playground; runcmd_ordryrun_local @cmd, "work"; my @udfiles = <${package}_*>; - changedir "../../.."; + changedir $maindir; foreach my $f (@udfiles) { printdebug "source copy, found $f\n"; next unless @@ -6176,7 +6187,7 @@ sub build_source { ($f =~ m/\.debian\.tar(?:\.\w+)$/ && $f eq srcfn($version, $&)); printdebug "source copy, found $f - renaming\n"; - rename "$ud/$f", "../$f" or $!==ENOENT + rename "$playground/$f", "../$f" or $!==ENOENT or fail "put in place new source file ($f): $!"; } } else { @@ -6350,7 +6361,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#^(?:\./+)?\.\./+#) { @@ -6361,8 +6375,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); @@ -6401,7 +6417,7 @@ END } sub pre_archive_api_query () { - no_local_git_cfg(); + not_necessarily_a_tree(); } sub cmd_archive_api_query { badusage "need only 1 subpath argument" unless @ARGV==1; @@ -6420,7 +6436,7 @@ sub repos_server_url () { } sub pre_clone_dgit_repos_server () { - no_local_git_cfg(); + not_necessarily_a_tree(); } sub cmd_clone_dgit_repos_server { badusage "need destination argument" unless @ARGV==1; @@ -6432,7 +6448,7 @@ sub cmd_clone_dgit_repos_server { } sub pre_print_dgit_repos_server_source_url () { - no_local_git_cfg(); + not_necessarily_a_tree(); } sub cmd_print_dgit_repos_server_source_url { badusage "no arguments allowed to dgit print-dgit-repos-server-source-url" @@ -6441,6 +6457,15 @@ sub cmd_print_dgit_repos_server_source_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'; @@ -6823,12 +6848,13 @@ 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"};