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/
--- 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()