X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=dgit.git;a=blobdiff_plain;f=dgit;h=ac394f0648a2adb5c2e8aa1bb1a45fead3996656;hp=65bc10f01f5c369d29b7d7a069999a8cac2fd78b;hb=0e6a55238ffdee98d5b23c70fe4105c41ea28f34;hpb=ed13a339560efb554f9afc9419ffd8157e357727 diff --git a/dgit b/dgit index 65bc10f0..ac394f06 100755 --- a/dgit +++ b/dgit @@ -2,7 +2,7 @@ # dgit # Integration between git and Debian-style archives # -# Copyright (C)2013 Ian Jackson +# Copyright (C)2013-2015 Ian Jackson # # 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 @@ -52,7 +52,7 @@ our $new_package = 0; our $ignoredirty = 0; our $rmonerror = 1; our @deliberatelies; -our %supersedes; +our %previously; our $existing_package = 'dpkg'; our $cleanmode = 'dpkg-source'; our $changes_since_version; @@ -111,7 +111,7 @@ sub lref () { return "refs/heads/".lbranch(); } sub lrref () { return "refs/remotes/$remotename/".server_branch($csuite); } sub rrref () { return server_ref($csuite); } -sub lrfetchrefs () { return "refs/dgit-fetch/$isuite"; } +sub lrfetchrefs () { return "refs/dgit-fetch/$csuite"; } sub stripepoch ($) { my ($vsn) = @_; @@ -444,15 +444,29 @@ our %defcfg = ('dgit.default.distro' => 'debian', 'dgit.default.archive-query' => 'madison:', 'dgit.default.sshpsql-dbname' => 'service=projectb', 'dgit-distro.debian.archive-query' => 'ftpmasterapi:', - 'dgit-distro.debian.git-host' => 'dgit-git.debian.net', - 'dgit-distro.debian.git-user-force' => 'dgit', - 'dgit-distro.debian.git-proto' => 'git+ssh://', - 'dgit-distro.debian.git-path' => '/dgit/debian/repos', - 'dgit-distro.debian.git-check' => 'ssh-cmd', + 'dgit-distro.debian.git-check' => 'url', + 'dgit-distro.debian.git-check-suffix' => '/info/refs', + 'dgit-distro.debian/push.git-url' => '', + 'dgit-distro.debian/push.git-host' => 'dgit-git.debian.net', + 'dgit-distro.debian/push.git-user-force' => 'dgit', + 'dgit-distro.debian/push.git-proto' => 'git+ssh://', + 'dgit-distro.debian/push.git-path' => '/dgit/debian/repos', + 'dgit-distro.debian/push.git-create' => 'true', + 'dgit-distro.debian/push.git-check' => 'ssh-cmd', 'dgit-distro.debian.archive-query-url', 'https://api.ftp-master.debian.org/', - 'dgit-distro.debian.archive-query-tls-key', - '/etc/ssl/certs/%HOST%.pem:/etc/dgit/%HOST%.pem', +# 'dgit-distro.debian.archive-query-tls-key', +# '/etc/ssl/certs/%HOST%.pem:/etc/dgit/%HOST%.pem', +# ^ this does not work because curl is broken nowadays +# Fixing #790093 properly will involve providing providing the key +# in some pacagke and maybe updating these paths. +# +# 'dgit-distro.debian.archive-query-tls-curl-args', +# '--ca-path=/etc/ssl/ca-debian', +# ^ this is a workaround but works (only) on DSA-administered machines 'dgit-distro.debian.diverts.alioth' => '/alioth', + 'dgit-distro.debian.git-url' => 'https://git.dgit.debian.org', + 'dgit-distro.debian.git-url-suffix' => '', + 'dgit-distro.debian/push.diverts.alioth' => '/alioth', 'dgit-distro.debian/alioth.git-host' => 'git.debian.org', 'dgit-distro.debian/alioth.git-user-force' => '', 'dgit-distro.debian/alioth.git-proto' => 'git+ssh://', @@ -477,20 +491,35 @@ our %defcfg = ('dgit.default.distro' => 'debian', 'dgit-distro.test-dummy.upload-host' => 'test-dummy', ); +sub git_get_config ($) { + my ($c) = @_; + + our %git_get_config_memo; + if (exists $git_get_config_memo{$c}) { + return $git_get_config_memo{$c}; + } + + my $v; + my @cmd = (@git, qw(config --), $c); + { + local ($debuglevel) = $debuglevel-2; + $v = cmdoutput_errok @cmd; + }; + if ($?==0) { + } elsif ($?==256) { + $v = undef; + } else { + failedcmd @cmd; + } + $git_get_config_memo{$c} = $v; + return $v; +} + sub cfg { foreach my $c (@_) { return undef if $c =~ /RETURN-UNDEF/; - my @cmd = (@git, qw(config --), $c); - my $v; - { - local ($debuglevel) = $debuglevel-2; - $v = cmdoutput_errok @cmd; - }; - if ($?==0) { - return $v; - } elsif ($?!=256) { - failedcmd @cmd; - } + my $v = git_get_config($c); + return $v if defined $v; my $dv = $defcfg{$c}; return $dv if defined $dv; } @@ -525,6 +554,12 @@ sub access_quirk () { return ('none',undef); } +our $access_pushing = 0; + +sub pushing () { + $access_pushing = 1; +} + sub access_distros () { # Returns list of distros to try, in order # @@ -538,7 +573,12 @@ sub access_distros () { my (undef,$quirkdistro) = access_quirk(); unshift @l, $quirkdistro; unshift @l, $instead_distro; - return grep { defined } @l; + @l = grep { defined } @l; + + if ($access_pushing) { + @l = map { ("$_/push", $_) } @l; + } + @l; } sub access_cfg (@) { @@ -611,6 +651,7 @@ sub access_gituserhost () { sub access_giturl (;$) { my ($optional) = @_; my $url = access_cfg('git-url','RETURN-UNDEF'); + my $suffix; if (!defined $url) { my $proto = access_cfg('git-proto', 'RETURN-UNDEF'); return undef unless defined $proto; @@ -618,8 +659,11 @@ sub access_giturl (;$) { $proto. access_gituserhost(). access_cfg('git-path'); + } else { + $suffix = access_cfg('git-url-suffix','RETURN-UNDEF'); } - return "$url/$package.git"; + $suffix //= '.git'; + return "$url/$package$suffix"; } sub parsecontrolfh ($$;$) { @@ -706,16 +750,25 @@ sub archive_api_query_cmd ($) { my $url = access_cfg('archive-query-url'); if ($url =~ m#^https://([-.0-9a-z]+)/#) { my $host = $1; - my $keys = access_cfg('archive-query-tls-key','RETURN-UNDEF'); + my $keys = access_cfg('archive-query-tls-key','RETURN-UNDEF') //''; foreach my $key (split /\:/, $keys) { $key =~ s/\%HOST\%/$host/g; if (!stat $key) { fail "for $url: stat $key: $!" unless $!==ENOENT; next; } - push @cmd, "--ca-certificate=$key", "--ca-directory=/dev/enoent"; + fail "config requested specific TLS key but do not know". + " how to get curl to use exactly that EE key ($key)"; +# push @cmd, "--cacert", $key, "--capath", "/dev/enoent"; +# # Sadly the above line does not work because of changes +# # to gnutls. The real fix for #790093 may involve +# # new curl options. last; } + # Fixing #790093 properly will involve providing a value + # for this on clients. + my $kargs = access_cfg('archive-query-tls-curl-ca-args','RETURN-UNDEF'); + push @cmd, split / /, $kargs if defined $kargs; } push @cmd, $url.$subpath; return @cmd; @@ -1002,13 +1055,32 @@ sub check_for_git () { if ($r =~ m/^divert (\w+)$/) { my $divert=$1; my ($usedistro,) = access_distros(); + # NB that if we are pushing, $usedistro will be $distro/push $instead_distro= cfg("dgit-distro.$usedistro.diverts.$divert"); $instead_distro =~ s{^/}{ access_basedistro()."/" }e; - printdebug "diverting $divert so using distro $instead_distro\n"; + progress "diverting to $divert (using config for $instead_distro)"; return check_for_git(); } failedcmd @cmd unless $r =~ m/^[01]$/; return $r+0; + } elsif ($how eq 'url') { + my $prefix = access_cfg('git-check-url','git-url'); + my $suffix = access_cfg('git-check-suffix','git-suffix', + 'RETURN-UNDEF') // '.git'; + my $url = "$prefix/$package$suffix"; + my @cmd = (qw(curl -sS -I), $url); + my $result = cmdoutput @cmd; + $result =~ m/^\S+ (404|200) /s or + fail "unexpected results from git check query - ". + Dumper($prefix, $result); + my $code = $1; + if ($code eq '404') { + return 0; + } elsif ($code eq '200') { + return 1; + } else { + die; + } } elsif ($how eq 'true') { return 1; } elsif ($how eq 'false') { @@ -1129,6 +1201,69 @@ sub clogp_authline ($) { return $authline; } +sub vendor_patches_distro ($$) { + my ($checkdistro, $what) = @_; + return unless defined $checkdistro; + + my $series = "debian/patches/\L$checkdistro\E.series"; + printdebug "checking for vendor-specific $series ($what)\n"; + + if (!open SERIES, "<", $series) { + die "$series $!" unless $!==ENOENT; + return; + } + while () { + next unless m/\S/; + next if m/^\s+\#/; + + print STDERR <error; + close SERIES; +} + +sub check_for_vendor_patches () { + # This dpkg-source feature doesn't seem to be documented anywhere! + # But it can be found in the changelog (reformatted): + + # commit 4fa01b70df1dc4458daee306cfa1f987b69da58c + # Author: Raphael Hertzog + # Date: Sun Oct 3 09:36:48 2010 +0200 + + # dpkg-source: correctly create .pc/.quilt_series with alternate + # series files + # + # If you have debian/patches/ubuntu.series and you were + # unpacking the source package on ubuntu, quilt was still + # directed to debian/patches/series instead of + # debian/patches/ubuntu.series. + # + # debian/changelog | 3 +++ + # scripts/Dpkg/Source/Package/V3/quilt.pm | 4 +++- + # 2 files changed, 6 insertions(+), 1 deletion(-) + + use Dpkg::Vendor; + vendor_patches_distro($ENV{DEB_VENDOR}, "DEB_VENDOR"); + vendor_patches_distro(Dpkg::Vendor::get_current_vendor(), + "Dpkg::Vendor \`current vendor'"); + vendor_patches_distro(access_basedistro(), + "distro being accessed"); +} + sub generate_commit_from_dsc () { prep_ud(); changedir $ud; @@ -1161,6 +1296,7 @@ sub generate_commit_from_dsc () { runcmd @cmd; my ($tree,$dir) = mktree_in_ud_from_only_subdir(); + check_for_vendor_patches() if madformat($dsc->{format}); runcmd qw(sh -ec), 'dpkg-parsechangelog >../changelog.tmp'; my $clogp = parsecontrol('../changelog.tmp',"commit's changelog"); my $authline = clogp_authline $clogp; @@ -1263,12 +1399,33 @@ sub ensure_we_have_orig () { } sub git_fetch_us () { - runcmd_ordryrun_local @git, qw(fetch),access_giturl(),fetchspec(); - if (deliberately_not_fast_forward) { - runcmd_ordryrun_local @git, qw(fetch -p), access_giturl(), - map { "+refs/$_/*:".lrfetchrefs."/$_/*" } - qw(tags heads); - } + my @specs = (fetchspec()); + push @specs, + map { "+refs/$_/*:".lrfetchrefs."/$_/*" } + qw(tags heads); + runcmd_ordryrun_local @git, qw(fetch -p -n -q), access_giturl(), @specs; + + my %here; + my $tagpat = debiantag('*',access_basedistro); + + git_for_each_ref("refs/tags/".$tagpat, sub { + my ($objid,$objtype,$fullrefname,$reftail) = @_; + printdebug "currently $fullrefname=$objid\n"; + $here{$fullrefname} = $objid; + }); + git_for_each_ref(lrfetchrefs."/tags/".$tagpat, sub { + my ($objid,$objtype,$fullrefname,$reftail) = @_; + my $lref = "refs".substr($fullrefname, length lrfetchrefs); + printdebug "offered $lref=$objid\n"; + if (!defined $here{$lref}) { + my @upd = (@git, qw(update-ref), $lref, $objid, ''); + runcmd_ordryrun_local @upd; + } elsif ($here{$lref} eq $objid) { + } else { + print STDERR \ + "Not updateting $lref from $here{$lref} to $objid.\n"; + } + }); } sub fetch_from_archive () { @@ -1375,6 +1532,38 @@ END return 1; } +sub set_local_git_config ($$) { + my ($k, $v) = @_; + runcmd @git, qw(config), $k, $v; +} + +sub setup_mergechangelogs () { + my $driver = 'dpkg-mergechangelogs'; + my $cb = "merge.$driver"; + my $attrs = '.git/info/attributes'; + ensuredir '.git/info'; + + open NATTRS, ">", "$attrs.new" or die "$attrs.new $!"; + if (!open ATTRS, "<", $attrs) { + $!==ENOENT or die "$attrs: $!"; + } else { + while () { + chomp; + next if m{^debian/changelog\s}; + print NATTRS $_, "\n" or die $!; + } + ATTRS->error and die $!; + close ATTRS; + } + print NATTRS "debian/changelog merge=$driver\n" or die $!; + close NATTRS; + + set_local_git_config "$cb.name", 'debian/changelog merge driver'; + set_local_git_config "$cb.driver", 'dpkg-mergechangelogs -m %O %A %B %A'; + + rename "$attrs.new", "$attrs" or die "$attrs: $!"; +} + sub clone ($) { my ($dstdir) = @_; canonicalise_suite(); @@ -1385,7 +1574,7 @@ sub clone ($) { runcmd @git, qw(init -q); my $giturl = access_giturl(1); if (defined $giturl) { - runcmd @git, qw(config), "remote.$remotename.fetch", fetchspec(); + set_local_git_config "remote.$remotename.fetch", fetchspec(); open H, "> .git/HEAD" or die $!; print H "ref: ".lref()."\n" or die $!; close H or die $!; @@ -1404,6 +1593,7 @@ sub clone ($) { $vcsgiturl =~ s/\s+-b\s+\S+//g; runcmd @git, qw(remote add vcs-git), $vcsgiturl; } + setup_mergechangelogs(); runcmd @git, qw(reset --hard), lrref(); printdone "ready for work in $dstdir"; } @@ -1490,7 +1680,7 @@ sub push_parse_changelog ($) { $package = getfield $clogp, 'Source'; my $cversion = getfield $clogp, 'Version'; - my $tag = debiantag($cversion); + my $tag = debiantag($cversion, access_basedistro); runcmd @git, qw(check-ref-format), $tag; my $dscfn = dscfn($cversion); @@ -1542,9 +1732,9 @@ tagger $authline $package release $cversion for $clogsuite ($csuite) [dgit] [dgit distro=$declaredistro$delibs] END - foreach my $ref (sort keys %supersedes) { + foreach my $ref (sort keys %previously) { print TO <{format}); changedir '../../../..'; my $diffopt = $debuglevel>0 ? '--exit-code' : '--quiet'; my @diffcmd = (@git, qw(diff), $diffopt, $tree); @@ -1661,12 +1852,12 @@ sub dopush ($) { responder_send_command("param head $head"); responder_send_command("param csuite $csuite"); - if ($forceflag) { + if (deliberately_not_fast_forward) { git_for_each_ref(lrfetchrefs, sub { my ($objid,$objtype,$lrfetchrefname,$reftail) = @_; my $rrefname= substr($lrfetchrefname, length(lrfetchrefs) + 1); - responder_send_command("supersedes $rrefname=$objid"); - $supersedes{$rrefname} = $objid; + responder_send_command("previously $rrefname=$objid"); + $previously{$rrefname} = $objid; }); } @@ -1687,13 +1878,12 @@ sub dopush ($) { my $tag_obj_hash = cmdoutput @git, qw(hash-object -w -t tag), $tagobjfn; runcmd_ordryrun @git, qw(verify-tag), $tag_obj_hash; runcmd_ordryrun_local @git, qw(update-ref), "refs/tags/$tag", $tag_obj_hash; - runcmd_ordryrun @git, qw(tag -v --), $tag; if (!check_for_git()) { create_remote_git_repo(); } runcmd_ordryrun @git, qw(push),access_giturl(), - $forceflag."HEAD:".rrref(), "refs/tags/$tag"; + $forceflag."HEAD:".rrref(), $forceflag."refs/tags/$tag"; runcmd_ordryrun @git, qw(update-ref -m), 'dgit push', lrref(), 'HEAD'; if ($we_are_responder) { @@ -1800,6 +1990,7 @@ sub cmd_pull { } sub cmd_push { + pushing(); parseopts(); badusage "-p is not allowed with dgit push" if defined $package; check_not_dirty(); @@ -1816,10 +2007,12 @@ sub cmd_push { if ($new_package) { local ($package) = $existing_package; # this is a hack canonicalise_suite(); - } - if (defined $specsuite && $specsuite ne $isuite) { + } else { canonicalise_suite(); - $csuite eq $specsuite or + } + if (defined $specsuite && + $specsuite ne $isuite && + $specsuite ne $csuite) { fail "dgit push: changelog specifies $isuite ($csuite)". " but command line specifies $specsuite"; } @@ -1851,6 +2044,7 @@ sub cmd_push { #---------- remote commands' implementation ---------- sub cmd_remote_push_build_host { + pushing(); my ($nrargs) = shift @ARGV; my (@rargs) = @ARGV[0..$nrargs-1]; @ARGV = @ARGV[$nrargs..$#ARGV]; @@ -1911,6 +2105,7 @@ sub i_method { } sub cmd_rpush { + pushing(); my $host = nextarg; my $dir; if ($host =~ m/^((?:[^][]|\[[^][]*\])*)\:/) { @@ -1984,12 +2179,12 @@ sub i_resp_param ($) { $i_param{$1} = $2; } -sub i_resp_supersedes ($) { +sub i_resp_previously ($) { $_[0] =~ m#^(refs/tags/\S+)=(\w+)$# - or badproto \*RO, "bad supersedes spec"; + or badproto \*RO, "bad previously spec"; my $r = system qw(git check-ref-format), $1; - die "bad supersedes ref spec ($r)" if $r; - $supersedes{$1} = $2; + die "bad previously ref spec ($r)" if $r; + $previously{$1} = $2; } our %i_wanted; @@ -2254,7 +2449,7 @@ sub quiltify ($$) { my $s = $abbrev->($notp); my $c = $notp->{Child}; $s .= "..".$abbrev->($c) if $c; - $s .= ": ".$c->{Whynot}; + $s .= ": ".$notp->{Whynot}; return $s; }; if ($quilt_mode eq 'linear') { @@ -2338,6 +2533,8 @@ sub build_maybe_quilt_fixup () { return unless madformat $format; # sigh + check_for_vendor_patches(); + # Our objective is: # - honour any existing .pc in case it has any strangeness # - determine the git commit corresponding to the tip of @@ -2462,7 +2659,7 @@ END commit_quilty_patch(); if ($mustdeletepc) { - runcmd @git, qw(rm -rq .pc); + runcmd @git, qw(rm -rqf .pc); commit_admin "Commit removal of .pc (quilt series tracking data)"; } @@ -2494,8 +2691,18 @@ sub quilt_fixup_editor () { sub clean_tree () { if ($cleanmode eq 'dpkg-source') { runcmd_ordryrun_local @dpkgbuildpackage, qw(-T clean); + } elsif ($cleanmode eq 'dpkg-source-d') { + runcmd_ordryrun_local @dpkgbuildpackage, qw(-d -T clean); } elsif ($cleanmode eq 'git') { runcmd_ordryrun_local @git, qw(clean -xdf); + } elsif ($cleanmode eq 'git-ff') { + runcmd_ordryrun_local @git, qw(clean -xdff); + } elsif ($cleanmode eq 'check') { + my $leftovers = cmdoutput @git, qw(clean -xdn); + if (length $leftovers) { + print STDERR $leftovers, "\n" or die $!; + fail "tree contains uncommitted files and --clean=check specified"; + } } elsif ($cleanmode eq 'none') { } else { die "$cleanmode ?"; @@ -2545,17 +2752,35 @@ sub changesopts () { return @opts; } +sub massage_dbp_args ($) { + my ($cmd) = @_; + return unless $cleanmode =~ m/git|none/; + debugcmd '#massaging#', @$cmd if $debuglevel>1; + my @newcmd = shift @$cmd; + # -nc has the side effect of specifying -b if nothing else specified + push @newcmd, '-nc'; + # and some combinations of -S, -b, et al, are errors, rather than + # later simply overriding earlier + push @newcmd, '-F' unless grep { m/^-[bBASF]$/ } @$cmd; + push @newcmd, @$cmd; + @$cmd = @newcmd; +} + sub cmd_build { build_prep(); - runcmd_ordryrun_local @dpkgbuildpackage, qw(-us -uc), changesopts(), @ARGV; + my @dbp = (@dpkgbuildpackage, qw(-us -uc), changesopts(), @ARGV); + massage_dbp_args \@dbp; + runcmd_ordryrun_local @dbp; printdone "build successful\n"; } sub cmd_git_build { build_prep(); + my @dbp = @dpkgbuildpackage; + massage_dbp_args \@dbp; my @cmd = (qw(git-buildpackage -us -uc --git-no-sign-tags), - "--git-builder=@dpkgbuildpackage"); + "--git-builder=@dbp"); unless (grep { m/^--git-debian-branch|^--git-ignore-branch/ } @ARGV) { canonicalise_suite(); push @cmd, "--git-debian-branch=".lbranch(); @@ -2572,6 +2797,9 @@ sub build_source { if ($cleanmode eq 'dpkg-source') { runcmd_ordryrun_local (@dpkgbuildpackage, qw(-us -uc -S)), changesopts(); + } elsif ($cleanmode eq 'dpkg-source-d') { + runcmd_ordryrun_local (@dpkgbuildpackage, qw(-us -uc -S -d)), + changesopts(); } else { my $pwd = must_getcwd(); my $leafdir = basename $pwd; @@ -2596,7 +2824,7 @@ sub cmd_sbuild { changedir ".."; my $pat = "${package}_".(stripepoch $version)."_*.changes"; if (act_local()) { - stat_exist $dscfn or fail "$dscfn (in parent directory): $!"; + stat_exists $dscfn or fail "$dscfn (in parent directory): $!"; stat_exists $sourcechanges or fail "$sourcechanges (in parent directory): $!"; foreach my $cf (glob $pat) { @@ -2636,6 +2864,20 @@ sub cmd_archive_api_query { exec @cmd or fail "exec curl: $!\n"; } +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); + debugcmd ">",@cmd; + exec @cmd or fail "exec git clone: $!\n"; +} + +sub cmd_setup_mergechangelogs { + badusage "no arguments allowed to dgit setup-mergechangelogs" if @ARGV; + setup_mergechangelogs(); +} + #---------- argument parsing and main program ---------- sub cmd_version { @@ -2700,7 +2942,7 @@ sub parseopts () { } elsif (m/^--build-products-dir=(.*)/s) { push @ropts, $_; $buildproductsdir = $1; - } elsif (m/^--clean=(dpkg-source|git|none)$/s) { + } elsif (m/^--clean=(dpkg-source(?:-d)?|git|git-ff|check|none)$/s) { push @ropts, $_; $cleanmode = $1; } elsif (m/^--clean=(.*)$/s) { @@ -2772,9 +3014,18 @@ sub parseopts () { } elsif (s/^-wg$//s) { push @ropts, $&; $cleanmode = 'git'; + } elsif (s/^-wgf$//s) { + push @ropts, $&; + $cleanmode = 'git-ff'; } elsif (s/^-wd$//s) { push @ropts, $&; $cleanmode = 'dpkg-source'; + } elsif (s/^-wdd$//s) { + push @ropts, $&; + $cleanmode = 'dpkg-source-d'; + } elsif (s/^-wc$//s) { + push @ropts, $&; + $cleanmode = 'check'; } else { badusage "unknown short option \`$_'"; }