X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=dgit.git;a=blobdiff_plain;f=infra%2Fdgit-repos-server;h=5599061de2a68cb8e997c66f2d81e48c489688a9;hp=a2916d6221ceaff7ffa13d07c4c4794ff3fee339;hb=5408b0c227d942af55442389894a9ed7338a55ce;hpb=26ce53b790d76b41fe893d279e9ceb37bb81a3fe diff --git a/infra/dgit-repos-server b/infra/dgit-repos-server index a2916d62..5599061d 100755 --- a/infra/dgit-repos-server +++ b/infra/dgit-repos-server @@ -2,7 +2,8 @@ # dgit-repos-server # # usages: -# .../dgit-repos-server DISTRO SUITES KEYRING-AUTH-SPEC DGIT-REPOS-DIR --ssh +# .../dgit-repos-server DISTRO SUITES KEYRING-AUTH-SPEC \ +# DGIT-REPOS-DIR POLICY-HOOK-SCRIPT --ssh # internal usage: # .../dgit-repos-server --pre-receive-hook PACKAGE # @@ -86,20 +87,23 @@ use POSIX; use Fcntl qw(:flock); use File::Path qw(rmtree); -open DEBUG, ">/dev/null" or die $!; +use Debian::Dgit qw(:DEFAULT :policyflags); -our $package_re = '[0-9a-z][-+.0-9a-z]+'; +open DEBUG, ">/dev/null" or die $!; our $func; our $dgitrepos; our $package; our $suitesfile; +our $policyhook; our $realdestrepo; our $destrepo; our $workrepo; our $keyrings; our @lockfhs; our $debug=''; +our @deliberatelies; +our $policy; #----- utilities ----- @@ -184,16 +188,58 @@ sub runcmd { die "@_ $? $!" if $r; } +sub policyhook { + my ($policyallowbits, @polargs) = @_; + # => ($exitstatuspolicybitmap, $policylockfh); + die if $policyallowbits & ~0x3e; + my @cmd = ($policyhook,$distro,$repos,@polargs); + debugcmd @_; + my $r = system @_; + die "system: $!" if $r < 0; + die "hook (@cmd) failed ($?)" if $r & ~($policyallowbits << 8); + return $r >> 8; +} + +sub mkemptyrepo ($$) { + my ($dir,$sharedperm) = @_; + runcmd qw(git init --bare --quiet), "--shared=$sharedperm", $dir; +} + +sub mkrepo_fromtemplate ($) { + my ($dir) = @_; + my $template = "$dgitrepos/_template"; + debug "copy tempalate $template -> $dir"; + my $r = system qw(cp -a --), $template, $dir; + !$r or die "create new repo $dir failed: $r $!"; +} + +sub movetogarbage () { + my $garbagerepo = "$dgitrepos/_tmp/${package}_garbage"; + acquiretree($garbagerepo,1); + rmtree $garbagerepo; + rename $realdestrepo, $garbagerepo + or $! == ENOENT + or die "rename repo $realdestrepo to $garbagerepo: $!"; +} + +sub onwardpush () { + my @cmd = (qw(git send-pack), $destrepo); + push @cmd, qw(--force) if $policy & NOFFCHECK; + push @cmd, "$commit:refs/dgit/$suite", + "$tagval:refs/tags/$tagname"); + debugcmd @cmd; + $!=0; + my $r = system @cmd; + !$r or die "onward push to $destrepo failed: $r $!"; +} + #----- git-receive-pack ----- sub fixmissing__git_receive_pack () { mkrepotmp(); $destrepo = "$dgitrepos/_tmp/${package}_prospective"; acquiretree($destrepo, 1); - my $template = "$dgitrepos/_template"; - debug "fixmissing copy tempalate $template -> $destrepo"; - my $r = system qw(cp -a --), $template, $destrepo; - !$r or die "create new repo failed failed: $r $!"; + mkrepo_fromtemplate($destrepo); } sub makeworkingclone () { @@ -201,6 +247,7 @@ sub makeworkingclone () { $workrepo = "$dgitrepos/_tmp/${package}_incoming$$"; acquiretree($workrepo, 1); runcmd qw(git clone -l -q --mirror), $destrepo, $workrepo; + rmtree "${workrepo}_fresh"; } sub setupstunthook () { @@ -218,6 +265,15 @@ END debug " stunt hook set up $prerecv"; } +sub dealwithfreshrepo () { + my $freshrepo = "${workrepo}_fresh"; + if (!stat $freshrepo) { + $!==ENOENT or die "$freshrepo $!"; + return; + } + $destrepo = $freshrepo; +} + sub maybeinstallprospective () { return if $destrepo eq $realdestrepo; @@ -257,6 +313,8 @@ sub maybeinstallprospective () { die Dumper(\%got)." -- missing refs in new repo" if grep { !$_ } values %got; + movetogarbage; # in case of FRESHREPO + debug "install $destrepo => $realdestrepo"; rename $destrepo, $realdestrepo or die $!; remove "$destrepo.lock" or die $!; @@ -266,6 +324,7 @@ sub main__git_receive_pack () { makeworkingclone(); setupstunthook(); runcmd qw(git receive-pack), $workrepo; + dealwithfreshrepo(); maybeinstallprospective(); } @@ -331,6 +390,20 @@ sub parsetag () { for (;;) { print PT or die $!; $!=0; $_=; defined or die "missing signature? $!"; + if (m/^\[dgit ([^"].*)\]$/) { # [dgit "something"] is for future + $_ = $1." "; + for (;;) { + if (s/^distro\=(\S+) //) { + die "$1 != $distro" unless $1 eq $distro; + } elsif (s/^(--deliberately-$package_re) //) { + push @deliberatelies, $1; + } elsif (s/^[-+.=0-9a-z]\S* //) { + } else { + die "unknown dgit info in tag"; + } + } + next; + } last if m/^-----BEGIN PGP/; } for (;;) { @@ -463,7 +536,7 @@ sub tagh1 ($) { sub checks () { debug "checks"; - checksuite(); + tagh1('type') eq 'commit' or reject "tag refers to wrong kind of object"; tagh1('object') eq $commit or reject "tag refers to wrong commit"; tagh1('tag') eq $tagname or reject "tag name in tag is wrong"; @@ -474,24 +547,37 @@ sub checks () { debug "translated version $v"; $tagname eq "debian/$v" or die; + $policy = policyhook(NOFFCHECK|FRESHREPO, 'push',$package, + $version,$suite,$tagname, + join(",",@delberatelies)); + + checksuite(); + # check that our ref is being fast-forwarded debug "oldcommit $oldcommit"; - if ($oldcommit =~ m/[^0]/) { + if (!($policy & NOFFCHECK) && $oldcommit =~ m/[^0]/) { $?=0; $!=0; my $mb = `git merge-base $commit $oldcommit`; chomp $mb; $mb eq $oldcommit or reject "not fast forward on dgit branch"; } -} -sub onwardpush () { - my @cmd = (qw(git send-pack), $destrepo, - "$commit:refs/dgit/$suite", - "$tagval:refs/tags/$tagname"); - debugcmd @cmd; - $!=0; - my $r = system @cmd; - !$r or die "onward push failed: $r $!"; -} + if ($policy & FRESHREPO) { + # This is troublesome. We have been asked by the policy hook + # to receive the push into a fresh repo. But of course we + # have actually already mostly received the push into the working + # repo. (This is unavoidable because the instruction to use a new + # repo comes ultimately from the signed tag for the dgit push, + # which has to have been received into some repo.) + # + # So what we do is generate a fresh working repo right now and + # push the head and tag into it. The presence of this fresh + # working repo is detected by the parent, which responds by + # making a fresh master repo from the template. + + $destrepo = "${workrepo}_fresh"; # workrepo lock covers + mkrepo_fromtemplate $destrepo; + } +} sub stunthook () { debug "stunthook"; @@ -513,8 +599,7 @@ sub fixmissing__git_upload_pack () { return if stat $destrepo; die $! unless $!==ENOENT; rmtree "$destrepo.new"; - umask 022; - runcmd qw(git init --bare --quiet), "$destrepo.new"; + mkemptyrepo "$destrepo.new", "0644"; rename "$destrepo.new", $destrepo or die $!; unlink "$destrepo.lock" or die $!; close $lfh; @@ -554,6 +639,7 @@ sub parseargsdispatch () { defined($workrepo = $ENV{'DGIT_DRS_WORK'}) or die; defined($destrepo = $ENV{'DGIT_DRS_DEST'}) or die; defined($keyrings = $ENV{'DGIT_DRS_KEYRINGS'}) or die $!; + defined($policyhook = $ENV{'DGIT_DRS_POLICYHOOK'}) or die $!; open STDOUT, ">&STDERR" or die $!; eval { stunthook(); @@ -569,6 +655,7 @@ sub parseargsdispatch () { $ENV{'DGIT_DRS_SUITES'} = argval(); $ENV{'DGIT_DRS_KEYRINGS'} = argval(); $dgitrepos = argval(); + $ENV{'DGIT_DRS_POLICYHOOK'} = $policyhook = argval(); die unless @ARGV==1 && $ARGV[0] eq '--ssh'; @@ -593,6 +680,12 @@ sub parseargsdispatch () { reject "unknown method" unless $mainfunc; + my ($policy, $pollock) = policyhook(FRESHREPO,'check-package',$package); + if ($policy & FRESHREPO) { + movetogarbage; + } + close $pollock or die $!; + if (stat $realdestrepo) { $destrepo = $realdestrepo; } else {