Add support for phases
authorDavid Douard <david.douard@logilab.fr>
Mon, 03 Nov 2014 11:12:45 +0100
changeset 372 80f78674c56e
parent 371 e9ce904b62a9
child 373 a286d6c6b19c
Add support for phases This adds a "publish" permission level (between "init" and "write") required to be able to change the phase of a changeset from "draft" to "public". Update documentation accordingly. This is meant to be used for using the changeset evolution feature of mercurial, see http://evolution.experimentalworks.net/doc/
doc/manual.docbook
src/init/conf/remote-hgrc.d/access.rc
src/mercurialserver/access.py
src/mercurialserver/ruleset.py
test/unittest_ruleset.py
--- a/doc/manual.docbook	Mon Nov 03 11:12:35 2014 +0100
+++ b/doc/manual.docbook	Mon Nov 03 11:12:45 2014 +0100
@@ -16,6 +16,8 @@
 centralized <link xlink:href="http://hg-scm.org/">Mercurial</link>
 repositories using SSH public key authentication; it provides convenient
 and fine-grained key management and access control.
+It provides access control for <link xlink:href="http://mercurial.selenic.com/wiki/Phases">phases</link>
+move (from "draft" to "public").
 </para>
 <para>
 Though mercurial-server is currently targeted at Debian-based systems such
@@ -179,10 +181,14 @@
 </para>
 <itemizedlist>
 <listitem>
-<literal>init</literal>: allow reads, writes, and the creation of new repositories
+<literal>init</literal>: allow reads, writes, publish (move the phase
+of a chageset to "public"), and the creation of new repositories
 </listitem>
 <listitem>
-<literal>write</literal>: allow reads and writes
+<literal>publish</literal>: allow reads, writes and publish changesets
+</listitem>
+<listitem>
+<literal>write</literal>: allow reads and writes (ie. create draft changesets)
 </listitem>
 <listitem>
 <literal>read</literal>: allow only read operations
@@ -192,6 +198,13 @@
 </listitem>
 </itemizedlist>
 <para>
+  The distinction between "write" and "publish" access levels is only
+  meaningful for non-publishing repositories, in which case, the
+  "write" access level allows a user to push "draft" changesets, but
+  not to change their phase to "public". This latter operation require
+  the "publish" access level.
+</para>
+<para>
 A condition is a globpattern matched against a relative path. The two most
 important conditions are
 </para>
--- a/src/init/conf/remote-hgrc.d/access.rc	Mon Nov 03 11:12:35 2014 +0100
+++ b/src/init/conf/remote-hgrc.d/access.rc	Mon Nov 03 11:12:45 2014 +0100
@@ -2,3 +2,4 @@
 
 [hooks]
 pretxnchangegroup.access = python:mercurialserver.access.hook
+prepushkey.access = python:mercurialserver.access.phasehook
--- a/src/mercurialserver/access.py	Mon Nov 03 11:12:35 2014 +0100
+++ b/src/mercurialserver/access.py	Mon Nov 03 11:12:45 2014 +0100
@@ -25,3 +25,12 @@
         if not allow(ctx):
             raise mercurial.util.Abort(_('%s: access denied for changeset %s') %
                 (__name__, mercurial.node.short(ctx.node())))
+
+def phasehook(ui, repo, hooktype, **kwargs):
+    if hooktype != 'prepushkey':
+        raise mercurial.util.Abort(_('config error - hook type "%s" cannot stop '
+                                     'incoming phase changes') % hooktype)
+    if kwargs.get('namespace') == 'phases':
+        if not ruleset.rules.allow("publish"):
+            raise mercurial.util.Abort(_('%s: access denied for making %s public') %
+                                       (__name__, kwargs['key'][:12]))
--- a/src/mercurialserver/ruleset.py	Mon Nov 03 11:12:35 2014 +0100
+++ b/src/mercurialserver/ruleset.py	Mon Nov 03 11:12:45 2014 +0100
@@ -37,7 +37,7 @@
 class Ruleset(object):
     '''Class representing the rules in a rule file'''
 
-    levels = ["init", "write", "read", "deny"]
+    levels = ["init", "publish", "write", "read", "deny"]
 
     def __init__(self):
         self.rules = []
--- a/test/unittest_ruleset.py	Mon Nov 03 11:12:35 2014 +0100
+++ b/test/unittest_ruleset.py	Mon Nov 03 11:12:45 2014 +0100
@@ -4,7 +4,7 @@
 from mercurialserver import ruleset
 
 class _RuleSetBaseTC(TestCase):
-    alllevels = ["init", "write", "read", "deny", "none"]
+    alllevels = ["init", "publish", "write", "read", "deny", "none"]
     levels = alllevels[:-1]
     def setUp(self):
         self.rs = ruleset.Ruleset()
@@ -161,6 +161,27 @@
         self.check_level('deny', repo='no')
         self.check_level('write', repo='other')
 
+class RuleSetPublishTC(_RuleSetBaseTC):
+    accessrules = '''
+init user=root/**
+deny repo=hgadmin
+init user=users/toto/* repo=toto
+publish user=users/toto/* repo=pub/**
+publish repo=allpub/**
+write user=users/w/*
+read user=users/**
+'''
+    def test_publish(self):
+        self.rs.set(user='users/w/key')
+        self.check_level('publish', repo='allpub/stuff')
+        self.check_level('write', repo='toto')
+        self.check_level('write', repo='other/stuff')
+
+        self.rs.set(repo='pub/stuff')
+        self.check_level('write', user='users/w/key')
+        self.check_level('publish', user='users/toto/key')
+
+
 if __name__ == '__main__':
     from unittest import main
     main()