# HG changeset patch # User David Douard # Date 1415009565 -3600 # Node ID 80f78674c56ef4e2114e78e24e85d306de11636f # Parent e9ce904b62a998185441462978d32c059d054995 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/ diff -r e9ce904b62a9 -r 80f78674c56e doc/manual.docbook --- 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 Mercurial repositories using SSH public key authentication; it provides convenient and fine-grained key management and access control. +It provides access control for phases +move (from "draft" to "public"). Though mercurial-server is currently targeted at Debian-based systems such @@ -179,10 +181,14 @@ -init: allow reads, writes, and the creation of new repositories +init: allow reads, writes, publish (move the phase +of a chageset to "public"), and the creation of new repositories -write: allow reads and writes +publish: allow reads, writes and publish changesets + + +write: allow reads and writes (ie. create draft changesets) read: allow only read operations @@ -192,6 +198,13 @@ + 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. + + A condition is a globpattern matched against a relative path. The two most important conditions are diff -r e9ce904b62a9 -r 80f78674c56e src/init/conf/remote-hgrc.d/access.rc --- 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 diff -r e9ce904b62a9 -r 80f78674c56e src/mercurialserver/access.py --- 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])) diff -r e9ce904b62a9 -r 80f78674c56e src/mercurialserver/ruleset.py --- 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 = [] diff -r e9ce904b62a9 -r 80f78674c56e test/unittest_ruleset.py --- 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()