X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=dgit.git;a=blobdiff_plain;f=dgit;h=51975965deb49aad24c205848e0ca5170983185b;hp=af9b182fcf5e7952479d8778f517f6da53e4f272;hb=166121d66d38a3f6584222a03558493c1a23de72;hpb=2b9520f48d257c2a1d2c5a66d47ac711d1d45908 diff --git a/dgit b/dgit index af9b182f..51975965 100755 --- a/dgit +++ b/dgit @@ -34,7 +34,6 @@ use POSIX; use IPC::Open2; use Digest::SHA; use Digest::MD5; -use List::Util qw(any); use List::MoreUtils qw(pairwise); use Text::Glob qw(match_glob); use Fcntl qw(:DEFAULT :flock); @@ -48,7 +47,7 @@ our $absurdity = undef; ###substituted### our @rpushprotovsn_support = qw(4 3 2); # 4 is new tag format our $protovsn; -our $isuite = 'unstable'; +our $isuite; our $idistro; our $package; our @ropts; @@ -70,15 +69,15 @@ our $overwrite_version; # undef: not specified; '': check changelog our $quilt_mode; our $quilt_modes_re = 'linear|smash|auto|nofix|nocheck|gbp|dpm|unapplied'; our $dodep14tag; -our $dodep14tag_re = 'want|no|always'; our $split_brain_save; our $we_are_responder; +our $we_are_initiator; our $initiator_tempdir; our $patches_applied_dirtily = 00; our $tagformat_want; our $tagformat; our $tagformatfn; -our $chase_dsc_distro=1; #xxx configurable +our $chase_dsc_distro=1; our %forceopts = map { $_=>0 } qw(unrepresentable unsupported-source-format @@ -101,7 +100,7 @@ our $rewritemap = 'dgit-rewrite/map'; 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); @@ -144,6 +143,8 @@ our %opts_cfg_insertpos = map { } keys %opts_opt_map; sub parseopts_late_defaults(); +sub setup_gitattrs(;$); +sub check_gitattrs($$); our $keyid; @@ -155,6 +156,7 @@ our $split_brain = 0; END { local ($@, $?); + return unless forkcheck_mainprocess(); print STDERR "! $_\n" foreach $supplementary_message =~ m/^.+$/mg; } @@ -219,6 +221,7 @@ initdebug(''); our @end; END { local ($?); + return unless forkcheck_mainprocess(); foreach my $f (@end) { eval { $f->(); }; print STDERR "$us: cleanup: $@" if length $@; @@ -313,6 +316,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 # @@ -329,6 +335,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 @@ -557,6 +566,9 @@ sub nextarg { return scalar shift @ARGV; } +sub pre_help () { + no_local_git_cfg(); +} sub cmd_help () { print $helpmsg or die $!; exit 0; @@ -565,6 +577,8 @@ sub cmd_help () { our $td = $ENV{DGIT_TEST_DUMMY_DIR} || "DGIT_TEST_DUMMY_DIR-unset"; our %defcfg = ('dgit.default.distro' => 'debian', + 'dgit.default.default-suite' => 'unstable', + 'dgit.default.old-dsc-distro' => 'debian', 'dgit-suite.*-security.distro' => 'debian-security', 'dgit.default.username' => '', 'dgit.default.archive-query-default-component' => 'main', @@ -664,6 +678,7 @@ sub git_get_config ($) { my ($c) = @_; foreach my $src (@gitcfgsources) { my $l = $gitcfgs{$src}{$c}; + confess "internal error ($l $c)" if $l && !ref $l; printdebug"C $c ".(defined $l ? join " ", map { messagequote "'$_'" } @$l : "undef")."\n" @@ -679,6 +694,7 @@ sub git_get_config ($) { sub cfg { foreach my $c (@_) { return undef if $c =~ /RETURN-UNDEF/; + printdebug "C? $c\n" if $debuglevel >= 5; my $v = git_get_config($c); return $v if defined $v; my $dv = $defcfg{$c}; @@ -691,7 +707,12 @@ sub cfg { "$us: distro or suite appears not to be (properly) supported"; } -sub access_basedistro () { +sub no_local_git_cfg () { + # needs to be called from pre_* + @gitcfgsources = grep { $_ ne 'local' } @gitcfgsources; +} + +sub access_basedistro__noalias () { if (defined $idistro) { return $idistro; } else { @@ -711,6 +732,12 @@ sub access_basedistro () { } } +sub access_basedistro () { + my $noalias = access_basedistro__noalias(); + my $canon = cfg("dgit-distro.$noalias.alias-canon",'RETURN-UNDEF'); + return $canon // $noalias; +} + sub access_nomdistro () { my $base = access_basedistro(); my $r = cfg("dgit-distro.$base.nominal-distro",'RETURN-UNDEF') // $base; @@ -1306,6 +1333,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 @_; } @@ -1684,8 +1713,17 @@ sub prep_ud (;$) { 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); } sub git_write_tree () { @@ -1974,7 +2012,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 @@ -2059,23 +2104,44 @@ sub generate_commits_from_dsc () { foreach my $fi (@dfi) { my $f = $fi->{Filename}; die "$f ?" if $f =~ m#/|^\.|\.dsc$|\.tmp$#; + my $upper_f = "../../../../$f"; + + printdebug "considering reusing $f: "; + + if (link_ltarget "$upper_f,fetch", $f) { + printdebug "linked (using ...,fetch).\n"; + } elsif ((printdebug "($!) "), + $! != ENOENT) { + fail "accessing ../$f,fetch: $!"; + } elsif (link_ltarget $upper_f, $f) { + printdebug "linked.\n"; + } elsif ((printdebug "($!) "), + $! != ENOENT) { + fail "accessing ../$f: $!"; + } else { + printdebug "absent.\n"; + } - printdebug "considering linking $f: "; - - link_ltarget "../../../../$f", $f - or ((printdebug "($!) "), 0) - or $!==&ENOENT - or die "$f $!"; - - printdebug "linked.\n"; - - complete_file_from_dsc('.', $fi) + my $refetched; + complete_file_from_dsc('.', $fi, \$refetched) or next; - if (is_orig_file_in_dsc($f, \@dfi)) { - link $f, "../../../../$f" - or $!==&EEXIST - or die "$f $!"; + printdebug "considering saving $f: "; + + if (link $f, $upper_f) { + printdebug "linked.\n"; + } elsif ((printdebug "($!) "), + $! != EEXIST) { + fail "saving ../$f: $!"; + } elsif (!$refetched) { + printdebug "no need.\n"; + } elsif (link $f, "$upper_f,fetch") { + printdebug "linked (using ...,fetch).\n"; + } elsif ((printdebug "($!) "), + $! != EEXIST) { + fail "saving ../$f,fetch: $!"; + } else { + printdebug "cannot.\n"; } } @@ -2118,7 +2184,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) { @@ -2145,7 +2211,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 @@ -2275,6 +2341,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) { @@ -2446,39 +2513,56 @@ END return @output; } -sub complete_file_from_dsc ($$) { - our ($dstdir, $fi) = @_; - # Ensures that we have, in $dir, the file $fi, with the correct +sub complete_file_from_dsc ($$;$) { + our ($dstdir, $fi, $refetched) = @_; + # Ensures that we have, in $dstdir, the file $fi, with the correct # contents. (Downloading it from alongside $dscurl if necessary.) + # If $refetched is defined, can overwrite "$dstdir/$fi->{Filename}" + # and will set $$refetched=1 if it did so (or tried to). my $f = $fi->{Filename}; my $tf = "$dstdir/$f"; my $downloaded = 0; + my $got; + my $checkhash = sub { + open F, "<", "$tf" or die "$tf: $!"; + $fi->{Digester}->reset(); + $fi->{Digester}->addfile(*F); + F->error and die $!; + $got = $fi->{Digester}->hexdigest(); + return $got eq $fi->{Hash}; + }; + if (stat_exists $tf) { - progress "using existing $f"; + if ($checkhash->()) { + progress "using existing $f"; + return 1; + } + if (!$refetched) { + fail "file $f has hash $got but .dsc". + " demands hash $fi->{Hash} ". + "(perhaps you should delete this file?)"; + } + progress "need to fetch correct version of $f"; + unlink $tf or die "$tf $!"; + $$refetched = 1; } else { printdebug "$tf does not exist, need to fetch\n"; - my $furl = $dscurl; - $furl =~ s{/[^/]+$}{}; - $furl .= "/$f"; - die "$f ?" unless $f =~ m/^\Q${package}\E_/; - die "$f ?" if $f =~ m#/#; - runcmd_ordryrun_local @curl,qw(-f -o),$tf,'--',"$furl"; - return 0 if !act_local(); - $downloaded = 1; - } - - open F, "<", "$tf" or die "$tf: $!"; - $fi->{Digester}->reset(); - $fi->{Digester}->addfile(*F); - F->error and die $!; - my $got = $fi->{Digester}->hexdigest(); - $got eq $fi->{Hash} or + } + + my $furl = $dscurl; + $furl =~ s{/[^/]+$}{}; + $furl .= "/$f"; + die "$f ?" unless $f =~ m/^\Q${package}\E_/; + die "$f ?" if $f =~ m#/#; + runcmd_ordryrun_local @curl,qw(-f -o),$tf,'--',"$furl"; + return 0 if !act_local(); + + $checkhash->() or fail "file $f has hash $got but .dsc". " demands hash $fi->{Hash} ". - ($downloaded ? "(got wrong file from archive!)" - : "(perhaps you should delete this file?)"); + "(got wrong file from archive!)"; return 1; } @@ -2506,7 +2590,7 @@ sub lrfetchref () { return lrfetchrefs.'/'.server_branch($csuite); } # (If we deleted them unconditionally, then we might end up # re-fetching the same git objects each time dgit fetch was run.) # -# So, leach use of lrfetchrefs needs to be accompanied by arrangements +# So, each use of lrfetchrefs needs to be accompanied by arrangements # in git_fetch_us to fetch the refs in question, and possibly a call # to lrfetchref_used. @@ -2520,7 +2604,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 @@ -2551,8 +2635,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 { @@ -2664,6 +2746,16 @@ END } last; } + + if (defined $csuite) { + printdebug "git_lrfetch_sane: tidying any old suite lrfetchrefs\n"; + git_for_each_ref("refs/dgit-fetch/$csuite", sub { + my ($objid,$objtype,$lrefname,$reftail) = @_; + next if $lrfetchrefs_f{$lrefname}; # $csuite eq $distro ? + runcmd_ordryrun_local @git, qw(update-ref -d), $lrefname; + }); + } + printdebug "git_lrfetch_sane: git fetch --no-insane emulation complete\n", Dumper(\%lrfetchrefs_f); } @@ -2682,7 +2774,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); @@ -2703,8 +2796,8 @@ sub git_fetch_us () { } elsif ($here{$lref} eq $objid) { lrfetchref_used $fullrefname; } else { - print STDERR \ - "Not updateting $lref from $here{$lref} to $objid.\n"; + print STDERR + "Not updating $lref from $here{$lref} to $objid.\n"; } }); } @@ -2739,6 +2832,11 @@ sub fetch_from_archive_record_2 ($) { } } +sub parse_dsc_field_def_dsc_distro () { + $dsc_distro //= cfg qw(dgit.default.old-dsc-distro + dgit.default.distro); +} + sub parse_dsc_field ($$) { my ($dsc, $what) = @_; my $f; @@ -2746,15 +2844,17 @@ sub parse_dsc_field ($$) { $f = $dsc->{$field}; last if defined $f; } + if (!defined $f) { progress "$what: NO git hash"; + parse_dsc_field_def_dsc_distro(); } elsif (($dsc_hash, $dsc_distro, $dsc_hint_tag, $dsc_hint_url) - = $f =~ m/^(\w+) ($distro_re) ($versiontag_re) (\S+)(?:\s|$)/) { + = $f =~ m/^(\w+)\s+($distro_re)\s+($versiontag_re)\s+(\S+)(?:\s|$)/) { progress "$what: specified git info ($dsc_distro)"; $dsc_hint_tag = [ $dsc_hint_tag ]; } elsif ($f =~ m/^\w+\s*$/) { $dsc_hash = $&; - $dsc_distro //= 'debian'; + parse_dsc_field_def_dsc_distro(); $dsc_hint_tag = [ debiantags +(getfield $dsc, 'Version'), $dsc_distro ]; progress "$what: specified git hash"; @@ -2769,7 +2869,8 @@ sub resolve_dsc_field_commit ($$) { return unless defined $dsc_hash; my $mapref = - $already_distro eq $dsc_distro || !$chase_dsc_distro + defined $already_mapref && + ($already_distro eq $dsc_distro || !$chase_dsc_distro) ? $already_mapref : undef; my $do_fetch; @@ -2803,21 +2904,27 @@ END or fail <("rewrite map", $rewritemap) or return; - $mapref = $lrf.'/'.$rewritemap; + my $rewrite_enable = do { + local $idistro = $dsc_distro; + access_cfg('rewrite-map-enable', 'RETURN-UNDEF'); + }; + + if (parse_cfg_bool 'rewrite-map-enable', 'true', $rewrite_enable) { + if (!defined $mapref) { + my $lrf = $do_fetch->("rewrite map", $rewritemap) or return; + $mapref = $lrf.'/'.$rewritemap; + } my $rewritemapdata = git_cat_file $mapref.':map'; if (defined $rewritemapdata && $rewritemapdata =~ m/^$dsc_hash(?:[ \t](\w+))/m) { @@ -2843,7 +2950,7 @@ but we could not obtain that object anywhere. END foreach my $t (@tags) { my $fullrefname = $lrf.'/'.$t; - print STDERR "CHK $t $fullrefname ".Dumper(\%lrfetchrefs_f); +# print STDERR "CHK $t $fullrefname ".Dumper(\%lrfetchrefs_f); next unless $lrfetchrefs_f{$fullrefname}; next unless is_fast_fwd "$fullrefname~0", $dsc_hash; lrfetchref_used $fullrefname; @@ -3181,6 +3288,8 @@ END lrfetchref_used lrfetchref(); + check_gitattrs($hash, "fetched source tree"); + unshift @end, $del_lrfetchrefs; return $hash; } @@ -3242,11 +3351,89 @@ sub ensure_setup_existing_tree () { set_local_git_config $k, 'true'; } +sub open_gitattrs () { + my $gai = new IO::File ".git/info/attributes" + or $!==ENOENT + or die "open .git/info/attributes: $!"; + return $gai; +} + +sub is_gitattrs_setup () { + my $gai = open_gitattrs(); + return 0 unless $gai; + while (<$gai>) { + return 1 if m{^\[attr\]dgit-defuse-attrs\s}; + } + $gai->error and die $!; + return 0; +} + +sub setup_gitattrs (;$) { + my ($always) = @_; + return unless $always || access_cfg_bool(1, 'setup-gitattributes'); + + if (is_gitattrs_setup()) { + progress < $af.new" or die $!; + print GAO <) { + chomp; + print GAO $_, "\n" or die $!; + } + $gai->error and die $!; + } + close GAO or die $!; + rename "$af.new", "$af" or die "install $af: $!"; +} + 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 @@ -3254,6 +3441,7 @@ sub multisuite_suite_child ($$$) { my $canonsuitefh = IO::File::new_tmpfile; my $pid = fork // die $!; if (!$pid) { + forkcheck_setup(); $isuite = $tsuite; $us .= " [$isuite]"; $debugprefix .= " "; @@ -3311,6 +3499,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 = (); @@ -3425,6 +3614,9 @@ 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(); @@ -3436,7 +3628,7 @@ sub clone ($) { printdebug "multi clone after fetch merge\n"; clone_set_head(); clone_finish($dstdir); - exit 0; + return; } printdebug "clone main body\n"; @@ -3445,6 +3637,7 @@ sub clone ($) { mkdir $dstdir or fail "create \`$dstdir': $!"; changedir $dstdir; runcmd @git, qw(init -q); + setup_new_tree(); clone_set_head(); my $giturl = access_giturl(1); if (defined $giturl) { @@ -3463,7 +3656,6 @@ sub clone ($) { $vcsgiturl =~ s/\s+-b\s+\S+//g; runcmd @git, qw(remote add vcs-git), $vcsgiturl; } - setup_new_tree(); clone_finish($dstdir); } @@ -3642,18 +3834,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 } } @@ -3790,8 +3992,12 @@ sub push_parse_changelog ($) { fail "-p specified $package but changelog specified $clogpackage" unless $package eq $clogpackage; my $cversion = getfield $clogp, 'Version'; - my $tag = debiantag($cversion, access_nomdistro); - runcmd @git, qw(check-ref-format), $tag; + + if (!$we_are_initiator) { + # rpush initiator can't do this because it doesn't have $isuite yet + my $tag = debiantag($cversion, access_nomdistro); + runcmd @git, qw(check-ref-format), $tag; + } my $dscfn = dscfn($cversion); @@ -3976,6 +4182,7 @@ END prep_ud(); access_giturl(); # check that success is vaguely likely + rpush_handle_protovsn_bothends() if $we_are_initiator; select_tagformat(); my $clogpfn = ".git/dgit/changelog.822.tmp"; @@ -4104,12 +4311,21 @@ END responder_send_file('changes',$changesfile); responder_send_command("param head $dgithead"); responder_send_command("param csuite $csuite"); + responder_send_command("param isuite $isuite"); responder_send_command("param tagformat $tagformat"); if (defined $maintviewhead) { die unless ($protovsn//4) >= 4; responder_send_command("param maint-view $maintviewhead"); } + # Perhaps send buildinfo(s) for signing + my $changes_files = getfield $changes, 'Files'; + my @buildinfos = ($changes_files =~ m/ .* (\S+\.buildinfo)$/mg); + foreach my $bi (@buildinfos) { + responder_send_command("param buildinfo-filename $bi"); + responder_send_file('buildinfo', "$buildproductsdir/$bi"); + } + if (deliberately_not_fast_forward) { git_for_each_ref(lrfetchrefs, sub { my ($objid,$objtype,$lrfetchrefname,$reftail) = @_; @@ -4180,9 +4396,10 @@ If you need to change the package, you must use a new version number. END if ($we_are_responder) { my $dryrunsuffix = act_local() ? "" : ".tmp"; + my @rfiles = ($dscpath, $changesfile); + push @rfiles, map { "$buildproductsdir/$_" } @buildinfos; responder_receive_files('signed-dsc-changes', - "$dscpath$dryrunsuffix", - "$changesfile$dryrunsuffix"); + map { "$_$dryrunsuffix" } @rfiles); } else { if (act_local()) { rename "$dscpath.tmp",$dscpath or die "$dscfn $!"; @@ -4208,6 +4425,9 @@ END responder_send_command("complete"); } +sub pre_clone () { + no_local_git_cfg(); +} sub cmd_clone { parseopts(); my $dstdir; @@ -4256,7 +4476,12 @@ sub cmd_clone { } sub branchsuite () { - my $branch = cmdoutput_errok @git, qw(symbolic-ref HEAD); + my @cmd = (@git, qw(symbolic-ref -q HEAD)); + my $branch = cmdoutput_errok @cmd; + if (!defined $branch) { + $?==256 or failedcmd @cmd; + return undef; + } if ($branch =~ m#$lbranch_re#o) { return $1; } else { @@ -4273,7 +4498,8 @@ sub fetchpullargs () { $isuite = branchsuite(); if (!$isuite) { my $clogp = parsechangelog(); - $isuite = getfield $clogp, 'Distribution'; + my $clogsuite = getfield $clogp, 'Distribution'; + $isuite= $clogsuite if $clogsuite ne 'UNRELEASED'; } } elsif (@ARGV==1) { ($isuite) = @ARGV; @@ -4305,7 +4531,6 @@ END sub cmd_push { parseopts(); - pushing(); badusage "-p is not allowed with dgit push" if defined $package; check_not_dirty(); my $clogp = parsechangelog(); @@ -4318,6 +4543,7 @@ sub cmd_push { badusage "incorrect arguments to dgit push"; } $isuite = getfield $clogp, 'Distribution'; + pushing(); if ($new_package) { local ($package) = $existing_package; # this is a hack canonicalise_suite(); @@ -4335,7 +4561,7 @@ sub cmd_push { #---------- remote commands' implementation ---------- -sub cmd_remote_push_build_host { +sub pre_remote_push_build_host { my ($nrargs) = shift @ARGV; my (@rargs) = @ARGV[0..$nrargs-1]; @ARGV = @ARGV[$nrargs..$#ARGV]; @@ -4348,8 +4574,6 @@ sub cmd_remote_push_build_host { $we_are_responder = 1; $us .= " (build host)"; - pushing(); - open PI, "<&STDIN" or die $!; open STDIN, "/dev/null" or die $!; open PO, ">&STDOUT" or die $!; @@ -4367,12 +4591,14 @@ sub cmd_remote_push_build_host { " but invocation host has $vsnwant" unless defined $protovsn; - responder_send_command("dgit-remote-push-ready $protovsn"); - rpush_handle_protovsn_bothends(); changedir $dir; +} +sub cmd_remote_push_build_host { + responder_send_command("dgit-remote-push-ready $protovsn"); &cmd_push; } +sub pre_remote_push_responder { pre_remote_push_build_host(); } sub cmd_remote_push_responder { cmd_remote_push_build_host(); } # ... for compatibility with proto vsn.1 dgit (just so that user gets # a good error message) @@ -4401,7 +4627,10 @@ sub i_cleanup { } } -END { i_cleanup(); } +END { + return unless forkcheck_mainprocess(); + i_cleanup(); +} sub i_method { my ($base,$selector,@args) = @_; @@ -4409,8 +4638,10 @@ sub i_method { { no strict qw(refs); &{"${base}_${selector}"}(@args); } } +sub pre_rpush () { + no_local_git_cfg(); +} sub cmd_rpush { - pushing(); my $host = nextarg; my $dir; if ($host =~ m/^((?:[^][]|\[[^][]*\])*)\:/) { @@ -4430,6 +4661,8 @@ sub cmd_rpush { my @cmd = (@ssh, $host, shellquote @rdgit); debugcmd "+",@cmd; + $we_are_initiator=1; + if (defined $initiator_tempdir) { rmtree $initiator_tempdir; mkdir $initiator_tempdir, 0700 or die "$initiator_tempdir: $!"; @@ -4443,11 +4676,6 @@ sub cmd_rpush { die "$protovsn ?" unless grep { $_ eq $protovsn } @rpushprotovsn_support; $supplementary_message = '' unless $protovsn >= 3; - fail "rpush negotiated protocol version $protovsn". - " which does not support quilt mode $quilt_mode" - if quiltmode_splitbrain; - - rpush_handle_protovsn_bothends(); for (;;) { my ($icmd,$iargs) = initiator_expect { m/^(\S+)(?: (.*))?$/; @@ -4511,6 +4739,18 @@ our %i_wanted; sub i_resp_want ($) { my ($keyword) = @_; die "$keyword ?" if $i_wanted{$keyword}++; + + defined $i_param{'csuite'} or badproto \*RO, "premature desire, no csuite"; + $isuite = $i_param{'isuite'} // $i_param{'csuite'}; + die unless $isuite =~ m/^$suite_re$/; + + pushing(); + rpush_handle_protovsn_bothends(); + + fail "rpush negotiated protocol version $protovsn". + " which does not support quilt mode $quilt_mode" + if quiltmode_splitbrain; + my @localpaths = i_method "i_want", $keyword; printdebug "[[ $keyword @localpaths\n"; foreach my $localpath (@localpaths) { @@ -4519,7 +4759,7 @@ sub i_resp_want ($) { print RI "files-end\n" or die $!; } -our ($i_clogp, $i_version, $i_dscfn, $i_changesfn); +our ($i_clogp, $i_version, $i_dscfn, $i_changesfn, @i_buildinfos); sub i_localname_parsed_changelog { return "remote-changelog.822"; @@ -4536,6 +4776,31 @@ sub i_localname_dsc { } sub i_file_dsc { } +sub i_localname_buildinfo ($) { + my $bi = $i_param{'buildinfo-filename'}; + defined $bi or badproto \*RO, "buildinfo before filename"; + defined $i_changesfn or badproto \*RO, "buildinfo before changes"; + $bi =~ m{^\Q$package\E_[!-.0-~]*\.buildinfo$}s + or badproto \*RO, "improper buildinfo filename"; + return $&; +} +sub i_file_buildinfo { + my $bi = $i_param{'buildinfo-filename'}; + my $bd = parsecontrol "$i_tmp/$bi", $bi; + my $ch = parsecontrol "$i_tmp/$i_changesfn", 'changes'; + if (!forceing [qw(buildinfo-changes-mismatch)]) { + files_compare_inputs($bd, $ch); + (getfield $bd, $_) eq (getfield $ch, $_) or + fail "buildinfo mismatch $_" + foreach qw(Source Version); + !defined $bd->{$_} 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; @@ -4577,7 +4842,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. ---------- @@ -4626,7 +4891,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; @@ -5009,6 +5274,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 $@; @@ -5047,6 +5313,7 @@ sub quiltify ($$$$) { $patchname =~ y/-a-z0-9_.+=~//cd; $patchname =~ s/^\W/x-$&/; $patchname = substr($patchname,0,40); + $patchname .= ".patch"; } if (!defined $patchdir) { $patchdir = ''; @@ -5389,6 +5656,7 @@ sub quilt_fixup_multipatch ($$$) { rmtree '.pc'; + runcmd @git, qw(checkout -f), $headref, qw(-- debian); my $unapplied=git_add_write_tree(); printdebug "fake orig tree object $unapplied\n"; @@ -5583,12 +5851,12 @@ sub cmd_clean () { sub build_prep_early () { our $build_prep_early_done //= 0; return if $build_prep_early_done++; - notpushing(); badusage "-p is not allowed when building" if defined $package; my $clogp = parsechangelog(); $isuite = getfield $clogp, 'Distribution'; $package = getfield $clogp, 'Source'; $version = getfield $clogp, 'Version'; + notpushing(); check_not_dirty(); } @@ -5959,6 +6227,15 @@ sub cmd_quilt_fixup { build_maybe_quilt_fixup(); } +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"; +} + sub cmd_import_dsc { my $needsig = 0; @@ -6026,9 +6303,18 @@ sub cmd_import_dsc { parse_dsc_field($dsc, "Dgit metadata in .dsc") unless forceing [qw(import-dsc-with-dgit-field)]; + parse_dsc_field_def_dsc_distro(); + + $isuite = 'DGIT-IMPORT-DSC'; + $idistro //= $dsc_distro; + + notpushing(); if (defined $dsc_hash) { progress "dgit: import-dsc of .dsc with Dgit field, using git hash"; + resolve_dsc_field_commit undef, undef; + } + if (defined $dsc_hash) { my @cmd = (qw(sh -ec), "echo $dsc_hash | git cat-file --batch-check"); my $objgot = cmdoutput @cmd; @@ -6046,10 +6332,9 @@ END fail "Not fast forward to $dsc_hash"; } } - @cmd = (@git, qw(update-ref -m), "dgit import-dsc (Dgit): $info", - $dstbranch, $dsc_hash); - runcmd @cmd; - progress "dgit: import-dsc updated git ref $dstbranch"; + import_dsc_result $dstbranch, $dsc_hash, + "dgit import-dsc (Dgit): $info", + "updated git ref $dstbranch"; return 0; } @@ -6109,12 +6394,14 @@ END } } - my @cmd = (@git, qw(update-ref -m), "dgit import-dsc: $info", - $dstbranch, $newhash); - runcmd @cmd; - progress "dgit: import-dsc results are in in git ref $dstbranch"; + import_dsc_result $dstbranch, $newhash, + "dgit import-dsc: $info", + "results are in in git ref $dstbranch"; } +sub pre_archive_api_query () { + no_local_git_cfg(); +} sub cmd_archive_api_query { badusage "need only 1 subpath argument" unless @ARGV==1; my ($subpath) = @ARGV; @@ -6124,27 +6411,56 @@ sub cmd_archive_api_query { 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 () { + no_local_git_cfg(); +} sub cmd_clone_dgit_repos_server { badusage "need destination argument" unless @ARGV==1; my ($destdir) = @ARGV; - $package = '_dgit-repos-server'; - 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 () { + no_local_git_cfg(); +} +sub cmd_print_dgit_repos_server_source_url { + badusage "no arguments allowed to dgit print-dgit-repos-server-source-url" + if @ARGV; + my $url = repos_server_url(); + print $url, "\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-mergechangelogs" if @ARGV; + 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(); } @@ -6156,7 +6472,9 @@ sub cmd_version { } our (%valopts_long, %valopts_short); +our (%funcopts_long); our @rvalopts; +our (@modeopt_cfgs); sub defvalopt ($$$$) { my ($long,$short,$val_re,$how) = @_; @@ -6192,6 +6510,26 @@ defvalopt '--initiator-tempdir','','.*', sub { " absolute, not relative, directory." }; +sub defoptmodes ($@) { + my ($varref, $cfgkey, $default, %optmap) = @_; + my %permit; + while (my ($opt,$val) = each %optmap) { + $funcopts_long{$opt} = sub { $$varref = $val; }; + $permit{$val} = $val; + } + push @modeopt_cfgs, { + Var => $varref, + Key => $cfgkey, + Default => $default, + Vals => \%permit + }; +} + +defoptmodes \$dodep14tag, qw( dep14tag want + --dep14tag want + --no-dep14tag no + --always-dep14tag always ); + sub parseopts () { my $om; @@ -6265,21 +6603,15 @@ sub parseopts () { } elsif (m/^--no-rm-on-error$/s) { push @ropts, $_; $rmonerror = 0; + } elsif (m/^--no-chase-dsc-distro$/s) { + push @ropts, $_; + $chase_dsc_distro = 0; } elsif (m/^--overwrite$/s) { push @ropts, $_; $overwrite_version = ''; } elsif (m/^--overwrite=(.+)$/s) { push @ropts, $_; $overwrite_version = $1; - } elsif (m/^--dep14tag$/s) { - push @ropts, $_; - $dodep14tag= 'want'; - } elsif (m/^--no-dep14tag$/s) { - push @ropts, $_; - $dodep14tag= 'no'; - } elsif (m/^--always-dep14tag$/s) { - push @ropts, $_; - $dodep14tag= 'always'; } elsif (m/^--delayed=(\d+)$/s) { push @ropts, $_; push @dput, $_; @@ -6310,9 +6642,17 @@ sub parseopts () { # undocumented, for testing push @ropts, $_; $need_split_build_invocation = 1; + } elsif (m/^--config-lookup-explode=(.+)$/s) { + # undocumented, for testing + push @ropts, $_; + $gitcfgs{cmdline}{$1} = 'CONFIG-LOOKUP-EXPLODE'; + # ^ it's supposed to be an array ref } elsif (m/^(--[-0-9a-z]+)(=|$)/ && ($oi = $valopts_long{$1})) { $val = $2 ? $' : undef; #'; $valopt->($oi->{Long}); + } elsif ($funcopts_long{$_}) { + push @ropts, $_; + $funcopts_long{$_}(); } else { badusage "unknown long option \`$_'"; } @@ -6399,6 +6739,10 @@ END sub parseopts_late_defaults () { + $isuite //= cfg("dgit-distro.$idistro.default-suite", 'RETURN-UNDEF') + if defined $idistro; + $isuite //= cfg('dgit.default.default-suite'); + foreach my $k (keys %opts_opt_map) { my $om = $opts_opt_map{$k}; @@ -6441,12 +6785,14 @@ sub parseopts_late_defaults () { $quilt_mode = $1; } - if (!defined $dodep14tag) { + foreach my $moc (@modeopt_cfgs) { local $access_forpush; - $dodep14tag = access_cfg('dep14tag', 'RETURN-UNDEF') // 'want'; - $dodep14tag =~ m/^($dodep14tag_re)$/ - or badcfg "unknown dep14tag setting \`$dodep14tag'"; - $dodep14tag = $1; + my $vr = $moc->{Var}; + next if defined $$vr; + $$vr = access_cfg($moc->{Key}, 'RETURN-UNDEF') // $moc->{Default}; + my $v = $moc->{Vals}{$$vr}; + badcfg "unknown $moc->{Key} setting \`$$vr'" unless defined $v; + $$vr = $v; } $need_split_build_invocation ||= quiltmode_splitbrain(); @@ -6468,7 +6814,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" @@ -6483,6 +6828,8 @@ $cmd =~ y/-/_/; my $pre_fn = ${*::}{"pre_$cmd"}; $pre_fn->() if $pre_fn; +git_slurp_config(); + my $fn = ${*::}{"cmd_$cmd"}; $fn or badusage "unknown operation $cmd"; $fn->();