# dgit-repos-push-receiver
#
# usages:
-# .../dgit-repos-push-receiver KEYRING-AUTH-SPEC DGIT-REPOS-DIR --ssh
-# .../dgit-repos-push-receiver KEYRING-AUTH-SPEC DGIT-REPOS-DIR PACKAGE
+# .../dgit-repos-push-receiver SUITES KEYRING-AUTH-SPEC DGIT-REPOS-DIR --ssh
+# .../dgit-repos-push-receiver SUITES KEYRING-AUTH-SPEC DGIT-REPOS-DIR PACKAGE
# internal usage:
# .../dgit-repos-push-receiver --pre-receive-hook PACKAGE
#
#
# Works like git-receive-pack
#
+# SUITES is the name of a file which lists the permissible suites
+# one per line (#-comments and blank lines ignored)
+#
# KEYRING-AUTH-SPEC is a :-separated list of
# KEYRING.GPG,AUTH-SPEC
# where AUTH-SPEC is one of
use strict;
# What we do is this:
-# - extract the destination repo name somehow
+# - extract the destination repo name
# - make a hardlink clone of the destination repo
# - provide the destination with a stunt pre-receive hook
# - run actual git-receive-pack with that new destination
# as a result of this the stunt pre-receive hook runs; it does this
-# find the keyring(s) to use for verification
-# verify the signed tag
-# check that the signed tag has a suitable name
-# parse the signed tag body to extract the intended
-# distro and suite
-# check that the distro is right
-# check that the suite is the same as the branch we are
-# supposed to update
-# check that the signed tag refers to the same commit
-# as the new suite
-# check that the signer was correct
-# push the signed tag to the actual repo
-# push the new dgit branch head to the actual repo
+# + understand what refs we are allegedly updating and
+# check some correspondences:
+# * we are updating only refs/tags/debian/* and refs/dgit/*
+# * and only one of each
+# * and the tag does not already exist
+# and
+# * recovering the suite name from the destination refs/dgit/ ref
+# + disassemble the signed tag into its various fields and signature
+# including:
+# * parsing the first line of the tag message to recover
+# the package name, version and suite
+# * checking that the package name corresponds to the dest repo name
+# * checking that the suite name is as recovered above
+# + verify the signature on the signed tag
+# and if necessary check that the keyid and package are listed in dm.txt
+# + check various correspondences:
+# * the suite is one of those permitted
+# * the signed tag must refer to a commit
+# * the signed tag commit must be the refs/dgit value
+# * the name in the signed tag must correspond to its ref name
+# * the tag name must be debian/<version> (massaged as needed)
+# * the signed tag has a suitable name
+# * the commit is a fast forward
+# + push the signed tag and new dgit branch to the actual repo
+#
+# If the destination repo does not already exist, we need to make
+# sure that we create it reasonably atomically, and also that
+# we don't every have a destination repo containing no refs at all
+# (because such a thing causes git-fetch-pack to barf). So then we
+# do as above, except:
+# - before starting, we take out our own lock for the destination repo
+# - we create a prospective new destination repo by making a copy
+# of _template
+# - we use the prospective new destination repo instead of the
+# actual new destination repo (since the latter doesn't exist)
+# - we set up a post-receive hook as well, which
+# + checks that exactly two refs were updated
+# + touches a stamp file
+# - after git-receive-pack exits, we rename the prospective
+# destination repo into place
+#
+# Cleanup strategy:
+# - We are crash-only
+# - Temporary working trees and their locks are cleaned up
+# opportunistically by a program which tries to take each lock and
+# if successful deletes both the tree and the lockfile
+# - Prospective working trees and their locks are cleaned up by
+# a program which tries to take each lock and if successful
+# deletes any prospective working tree and the lock (but not
+# of course any actual tree)
+# - It is forbidden to _remove_ the lockfile without removing
+# the corresponding temporary tree, as the lockfile is also
+# a stampfile whose presence indicates that there may be
+# cleanup to do
use POSIX;
use Fcntl qw(:flock);
open PT, ">dgit-tmp/plaintext" or die $!;
open DS, ">dgit-tmp/plaintext.asc" or die $!;
open T, "-|", qw(git cat-file tag), $tagval or die $!;
- my %tagh;
for (;;) {
$!=0; $_=<T>; defined or die $!;
print PT or die $!;
reject "key not found in keyrings";
}
-sub checktag () {
- tagh1('object') eq $branchval or die;
+sub checks () {
+fixme check the suite against the approved list
tagh1('type') eq 'commit' or die;
+ tagh1('object') eq $commit or die;
tagh1('tag') eq $tagname or die;
my $v = $version;
$v =~ y/~:/_%/;
$tagname eq "debian/$v" or die;
- check fast forward;
+ # check that our ref is being fast-forwarded
+ if ($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 () {
+ $!=0;
+ my $r = system (qw(git send-pack),
+ $destrepo,
+ "$commit:refs/dgit/$suite",
+ "$tagval:refs/tags/$tagname");
+ !$r or die "onward push failed: $r $!";
+}
sub stunthook () {
chdir $workrepo or die "chdir $workrepo: $!";
readupdates();
parsetag();
verifytag();
- checktag();
-... ...
+ checks();
+ onwardpush();
}
#----- arg parsing and main program -----
exit 0;
}
- die unless @ARGV>=2;
+ die unless @ARGV>=3;
+
+ die if $ARGV[0] =~ m/^-/;
+ $suitesfile = shift @ARGV;
die if $ARGV[0] =~ m/^-/;
$ENV{'DGIT_RPR_KEYRINGS'} = shift @ARGV;
+
die if $ARGV[0] =~ m/^-/;
$dgitrepos = shift @ARGV;
or die "requested command $cmd not understood";
$method = $1;
$pkg = $2;
+ my $func = $method;
+ $func =~ y/-/_/;
+ $func = $main::{"main__$func"};
+ &$func;
} else {
die;
}
$destrepo = "$dgitrepos/$pkg.git";
}
-sub main () {
+sub main__git_receive_pack () {
parseargs();
fixme check method;
makeworkingclone();