chiark / gitweb /
dgit-repos-server: wip
[dgit.git] / dgit-repos-server
index 0f7c4b0ddc7376c7aff715a105f5e194c6e2442c..51160be419e3941a608c20f382a3f1861fd31acc 100644 (file)
@@ -2,8 +2,8 @@
 # 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
 #
@@ -11,6 +11,9 @@
 #
 # 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);
@@ -132,7 +176,6 @@ sub parsetag () {
     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 $!;
@@ -241,18 +284,32 @@ sub verifytag () {
     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: $!";
@@ -260,8 +317,8 @@ sub stunthook () {
     readupdates();
     parsetag();
     verifytag();
-    checktag();
-... ...
+    checks();
+    onwardpush();
 }
 
 #----- arg parsing and main program -----
@@ -281,10 +338,14 @@ sub parseargs () {
        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;
 
@@ -308,6 +369,10 @@ sub parseargs () {
            or die "requested command $cmd not understood";
        $method = $1;
        $pkg = $2;
+       my $func = $method;
+       $func =~ y/-/_/;
+       $func = $main::{"main__$func"};
+       &$func;
     } else {
        die;
     }
@@ -315,7 +380,7 @@ sub parseargs () {
     $destrepo = "$dgitrepos/$pkg.git";
 }
 
-sub main () {
+sub main__git_receive_pack () {
     parseargs();
 fixme check method;
     makeworkingclone();