[PATCH] acl: add support for bookmarks [RFC]

timeless timeless at mozdev.org
Mon Mar 21 18:19:15 EDT 2016


# HG changeset patch
# User timeless <timeless at mozdev.org>
# Date 1457047766 0
#      Thu Mar 03 23:29:26 2016 +0000
# Node ID 1da6b085c47330cd6ea7348b04f79a70d8f504c8
# Parent  78e4e558fa74aa4489609953328cbcecf1a8a428
acl: add support for bookmarks [RFC]

Someone asked about whether acl could support bookmarks.
Before this, it couldn't.

This code isn't particularly well tested, but it seems to work.

It's also a template for using acl to control `phases`.

diff --git a/hgext/acl.py b/hgext/acl.py
--- a/hgext/acl.py
+++ b/hgext/acl.py
@@ -57,6 +57,28 @@
 a glob syntax by default). The corresponding values follow the same
 syntax as the other sections above.
 
+Bookmark-based Access Control
+-----------------------------
+Use the ``acl.deny.bookmarks`` and ``acl.allow.bookmarks`` sections to
+have bookmark-based access control. Keys in these sections can be
+either:
+
+- a bookmark name, or
+- an asterisk, to match any bookmark;
+
+The corresponding values can be either:
+
+- a comma-separated list containing users and groups, or
+- an asterisk, to match anyone;
+
+You can add the "!" prefix to a user or group name to invert the sense
+of the match.
+
+Note: for interactions between clients and servers using Mercurial 3.6+
+a rejection will generally reject the entire push, for interactions
+involving older clients, the commit transactions will already be accepted,
+and only the bookmark movement will be rejected.
+
 Groups
 ------
 
@@ -275,9 +297,9 @@
     return util.never
 
 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
-    if hooktype not in ['pretxnchangegroup', 'pretxncommit']:
+    if hooktype not in ['pretxnchangegroup', 'pretxncommit', 'prepushkey']:
         raise error.Abort(_('config error - hook type "%s" cannot stop '
-                           'incoming changesets nor commits') % hooktype)
+                           'incoming changesets, commits, nor bookmarks') % hooktype)
     if (hooktype == 'pretxnchangegroup' and
         source not in ui.config('acl', 'sources', 'serve').split()):
         ui.debug('acl: changes have source "%s" - skipping\n' % source)
@@ -294,6 +316,30 @@
 
     ui.debug('acl: checking access for user "%s"\n' % user)
 
+    if hooktype == 'prepushkey':
+        _pkhook(ui, repo, hooktype, node, source, user, **kwargs)
+    else:
+        _txnhook(ui, repo, hooktype, node, source, user, **kwargs)
+
+def _pkhook(ui, repo, hooktype, node, source, user, **kwargs):
+    if kwargs['namespace'] == 'bookmarks':
+        bookmark = kwargs['key']
+        ctx = kwargs['new']
+        allowbookmarks = buildmatch(ui, None, user, 'acl.allow.bookmarks')
+        denybookmarks = buildmatch(ui, None, user, 'acl.deny.bookmarks')
+
+        if denybookmarks and denybookmarks(bookmark):
+            raise error.Abort(_('acl: user "%s" denied on bookmark "%s"'
+                               ' (changeset "%s")')
+                               % (user, bookmark, ctx))
+        if allowbookmarks and not allowbookmarks(bookmark):
+            raise error.Abort(_('acl: user "%s" not allowed on bookmark "%s"'
+                               ' (changeset "%s")')
+                               % (user, bookmark, ctx))
+        ui.debug('acl: bookmark access granted: "%s" on bookmark "%s"\n'
+                 % (ctx, bookmark))
+
+def _txnhook(ui, repo, hooktype, node, source, user, **kwargs):
     # deprecated config: acl.config
     cfg = ui.config('acl', 'config')
     if cfg:
diff --git a/tests/test-acl.t b/tests/test-acl.t
--- a/tests/test-acl.t
+++ b/tests/test-acl.t
@@ -15,7 +15,7 @@
   >     #  LOGNAME=$user hg --cws a --debug push ../b
   >     # fails with "This variable is read only."
   >     # Use env to work around this.
-  >     env LOGNAME=$user hg --cwd a --debug push ../b
+  >     env LOGNAME=$user hg --cwd a --debug push ../b $*
   >     hg --cwd b rollback
   >     hg --cwd b --quiet tip
   >     echo
@@ -37,6 +37,7 @@
   >     cat > $config <<EOF
   > [hooks]
   > pretxnchangegroup.acl = python:hgext.acl.hook
+  > prepushkey.acl = python:hgext.acl.hook
   > [acl]
   > sources = push
   > [extensions]
@@ -138,6 +139,7 @@
 
   $ echo '[hooks]' >> $config
   $ echo 'pretxnchangegroup.acl = python:hgext.acl.hook' >> $config
+  $ echo 'prepushkey.acl = python:hgext.acl.hook' >> $config
 
 Extension disabled for lack of acl.sources
 
@@ -146,6 +148,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   """
   pushing to ../b
   query 1; heads
@@ -186,6 +189,8 @@
   acl: changes have source "push" - skipping
   bundle2-input-part: total payload size 1606
   bundle2-input-part: "pushkey" (params: 4 mandatory) supported
+  calling hook prepushkey.acl: hgext.acl.hook
+  acl: checking access for user "fred"
   pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
   bundle2-input-bundle: 3 parts total
   updating the branch cache
@@ -210,6 +215,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   """
@@ -262,6 +268,8 @@
   acl: path access granted: "911600dab2ae"
   bundle2-input-part: total payload size 1606
   bundle2-input-part: "pushkey" (params: 4 mandatory) supported
+  calling hook prepushkey.acl: hgext.acl.hook
+  acl: checking access for user "fred"
   pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
   bundle2-input-bundle: 3 parts total
   updating the branch cache
@@ -285,6 +293,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [acl.allow]
@@ -349,6 +358,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [acl.allow]
@@ -418,6 +428,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [acl.allow]
@@ -484,6 +495,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [acl.allow]
@@ -555,6 +567,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [acl.allow]
@@ -624,6 +637,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [acl.allow]
@@ -684,6 +698,175 @@
   0:6675d58eff77
   
 
+fred is not blocked from moving bookmarks
+
+  $ hg -R a book -q moving-bookmark -r 1
+  $ hg -R b book -q moving-bookmark -r 0
+  $ cp $config normalconfig
+  $ do_push fred -r 1
+  Pushing as user fred
+  hgrc = """
+  [hooks]
+  pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
+  [acl]
+  sources = push
+  [acl.allow]
+  foo/** = fred
+  [acl.deny]
+  foo/bar/** = fred
+  foo/Bar/** = fred
+  """
+  pushing to ../b
+  query 1; heads
+  searching for changes
+  all remote heads known locally
+  listing keys for "phases"
+  checking for updated bookmarks
+  listing keys for "bookmarks"
+  invalid branchheads cache (served): tip differs
+  listing keys for "bookmarks"
+  1 changesets found
+  list of changesets:
+  ef1ea85a6374b77d6da9dcda9541f498f2d17df7
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "check:heads" streamed payload
+  bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
+  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-input-bundle: with-transaction
+  bundle2-input-part: "replycaps" supported
+  bundle2-input-part: total payload size 155
+  bundle2-input-part: "check:heads" supported
+  bundle2-input-part: total payload size 20
+  bundle2-input-part: "changegroup" (params: 1 mandatory) supported
+  adding changesets
+  add changeset ef1ea85a6374
+  adding manifests
+  adding file changes
+  adding foo/file.txt revisions
+  added 1 changesets with 1 changes to 1 files
+  calling hook pretxnchangegroup.acl: hgext.acl.hook
+  acl: checking access for user "fred"
+  acl: acl.allow.branches not enabled
+  acl: acl.deny.branches not enabled
+  acl: acl.allow enabled, 1 entries for user fred
+  acl: acl.deny enabled, 2 entries for user fred
+  acl: branch access granted: "ef1ea85a6374" on branch "default"
+  acl: path access granted: "ef1ea85a6374"
+  invalid branchheads cache (served): tip differs
+  bundle2-input-part: total payload size 537
+  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
+  calling hook prepushkey.acl: hgext.acl.hook
+  acl: checking access for user "fred"
+  pushing key for "phases:ef1ea85a6374b77d6da9dcda9541f498f2d17df7"
+  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
+  calling hook prepushkey.acl: hgext.acl.hook
+  acl: checking access for user "fred"
+  acl: acl.allow.bookmarks not enabled
+  acl: acl.deny.bookmarks not enabled
+  acl: bookmark access granted: "ef1ea85a6374b77d6da9dcda9541f498f2d17df7" on bookmark "moving-bookmark"
+  pushing key for "bookmarks:moving-bookmark"
+  bundle2-input-bundle: 4 parts total
+  updating the branch cache
+  bundle2-output-bundle: "HG20", 3 parts total
+  bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
+  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
+  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
+  bundle2-input-bundle: with-transaction
+  bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
+  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
+  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
+  bundle2-input-bundle: 2 parts total
+  updating bookmark moving-bookmark
+  listing keys for "phases"
+  repository tip rolled back to revision 0 (undo push)
+  0:6675d58eff77
+  
+fred is not allowed to move bookmarks
+
+  $ echo '[acl.deny.bookmarks]' >> $config
+  $ echo '* = fred' >> $config
+  $ do_push fred -r 1
+  Pushing as user fred
+  hgrc = """
+  [hooks]
+  pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
+  [acl]
+  sources = push
+  [acl.allow]
+  foo/** = fred
+  [acl.deny]
+  foo/bar/** = fred
+  foo/Bar/** = fred
+  [acl.deny.bookmarks]
+  * = fred
+  """
+  pushing to ../b
+  query 1; heads
+  searching for changes
+  all remote heads known locally
+  listing keys for "phases"
+  checking for updated bookmarks
+  listing keys for "bookmarks"
+  invalid branchheads cache (served): tip differs
+  listing keys for "bookmarks"
+  1 changesets found
+  list of changesets:
+  ef1ea85a6374b77d6da9dcda9541f498f2d17df7
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "check:heads" streamed payload
+  bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
+  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-input-bundle: with-transaction
+  bundle2-input-part: "replycaps" supported
+  bundle2-input-part: total payload size 155
+  bundle2-input-part: "check:heads" supported
+  bundle2-input-part: total payload size 20
+  bundle2-input-part: "changegroup" (params: 1 mandatory) supported
+  adding changesets
+  add changeset ef1ea85a6374
+  adding manifests
+  adding file changes
+  adding foo/file.txt revisions
+  added 1 changesets with 1 changes to 1 files
+  calling hook pretxnchangegroup.acl: hgext.acl.hook
+  acl: checking access for user "fred"
+  acl: acl.allow.branches not enabled
+  acl: acl.deny.branches not enabled
+  acl: acl.allow enabled, 1 entries for user fred
+  acl: acl.deny enabled, 2 entries for user fred
+  acl: branch access granted: "ef1ea85a6374" on branch "default"
+  acl: path access granted: "ef1ea85a6374"
+  invalid branchheads cache (served): tip differs
+  bundle2-input-part: total payload size 537
+  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
+  calling hook prepushkey.acl: hgext.acl.hook
+  acl: checking access for user "fred"
+  pushing key for "phases:ef1ea85a6374b77d6da9dcda9541f498f2d17df7"
+  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
+  calling hook prepushkey.acl: hgext.acl.hook
+  acl: checking access for user "fred"
+  acl: acl.allow.bookmarks not enabled
+  acl: acl.deny.bookmarks enabled, 1 entries for user fred
+  error: prepushkey.acl hook failed: acl: user "fred" denied on bookmark "moving-bookmark" (changeset "ef1ea85a6374b77d6da9dcda9541f498f2d17df7")
+  bundle2-input-bundle: 4 parts total
+  transaction abort!
+  rollback completed
+  abort: acl: user "fred" denied on bookmark "moving-bookmark" (changeset "ef1ea85a6374b77d6da9dcda9541f498f2d17df7")
+  no rollback information available
+  0:6675d58eff77
+  
+cleanup bookmark stuff
+
+  $ hg book -R a -d moving-bookmark
+  $ hg book -R b -d moving-bookmark
+  $ cp normalconfig $config
+
 barney is allowed everywhere
 
   $ echo '[acl.allow]' >> $config
@@ -693,6 +876,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [acl.allow]
@@ -711,6 +895,7 @@
   checking for updated bookmarks
   listing keys for "bookmarks"
   invalid branchheads cache (served): tip differs
+  invalid branchheads cache (base): tip differs
   listing keys for "bookmarks"
   3 changesets found
   list of changesets:
@@ -752,6 +937,8 @@
   acl: path access granted: "911600dab2ae"
   bundle2-input-part: total payload size 1606
   bundle2-input-part: "pushkey" (params: 4 mandatory) supported
+  calling hook prepushkey.acl: hgext.acl.hook
+  acl: checking access for user "barney"
   pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
   bundle2-input-bundle: 3 parts total
   updating the branch cache
@@ -775,6 +962,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [acl.allow]
@@ -851,6 +1039,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [acl.allow]
@@ -920,6 +1109,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [acl.allow]
@@ -1002,6 +1192,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [acl.allow]
@@ -1070,6 +1261,8 @@
   acl: path access granted: "911600dab2ae"
   bundle2-input-part: total payload size 1606
   bundle2-input-part: "pushkey" (params: 4 mandatory) supported
+  calling hook prepushkey.acl: hgext.acl.hook
+  acl: checking access for user "barney"
   pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
   bundle2-input-bundle: 3 parts total
   updating the branch cache
@@ -1101,6 +1294,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [extensions]
@@ -1156,6 +1350,8 @@
   acl: path access granted: "911600dab2ae"
   bundle2-input-part: total payload size 1606
   bundle2-input-part: "pushkey" (params: 4 mandatory) supported
+  calling hook prepushkey.acl: hgext.acl.hook
+  acl: checking access for user "fred"
   pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
   bundle2-input-bundle: 3 parts total
   updating the branch cache
@@ -1181,6 +1377,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [extensions]
@@ -1259,6 +1456,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [extensions]
@@ -1315,6 +1513,8 @@
   acl: path access granted: "911600dab2ae"
   bundle2-input-part: total payload size 1606
   bundle2-input-part: "pushkey" (params: 4 mandatory) supported
+  calling hook prepushkey.acl: hgext.acl.hook
+  acl: checking access for user "fred"
   pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
   bundle2-input-bundle: 3 parts total
   updating the branch cache
@@ -1340,6 +1540,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [extensions]
@@ -1459,6 +1660,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [extensions]
@@ -1517,8 +1719,12 @@
   acl: path access granted: "e8fc755d4d82"
   bundle2-input-part: total payload size 2101
   bundle2-input-part: "pushkey" (params: 4 mandatory) supported
+  calling hook prepushkey.acl: hgext.acl.hook
+  acl: checking access for user "astro"
   pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
   bundle2-input-part: "pushkey" (params: 4 mandatory) supported
+  calling hook prepushkey.acl: hgext.acl.hook
+  acl: checking access for user "astro"
   pushing key for "phases:e8fc755d4d8217ee5b0c2bb41558c40d43b92c01"
   bundle2-input-bundle: 4 parts total
   updating the branch cache
@@ -1545,6 +1751,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [extensions]
@@ -1620,6 +1827,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [extensions]
@@ -1689,6 +1897,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [extensions]
@@ -1753,6 +1962,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [extensions]
@@ -1813,8 +2023,12 @@
   acl: path access granted: "e8fc755d4d82"
   bundle2-input-part: total payload size 2101
   bundle2-input-part: "pushkey" (params: 4 mandatory) supported
+  calling hook prepushkey.acl: hgext.acl.hook
+  acl: checking access for user "george"
   pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
   bundle2-input-part: "pushkey" (params: 4 mandatory) supported
+  calling hook prepushkey.acl: hgext.acl.hook
+  acl: checking access for user "george"
   pushing key for "phases:e8fc755d4d8217ee5b0c2bb41558c40d43b92c01"
   bundle2-input-bundle: 4 parts total
   updating the branch cache
@@ -1845,6 +2059,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [extensions]
@@ -1906,8 +2121,12 @@
   acl: path access granted: "e8fc755d4d82"
   bundle2-input-part: total payload size 2101
   bundle2-input-part: "pushkey" (params: 4 mandatory) supported
+  calling hook prepushkey.acl: hgext.acl.hook
+  acl: checking access for user "george"
   pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
   bundle2-input-part: "pushkey" (params: 4 mandatory) supported
+  calling hook prepushkey.acl: hgext.acl.hook
+  acl: checking access for user "george"
   pushing key for "phases:e8fc755d4d8217ee5b0c2bb41558c40d43b92c01"
   bundle2-input-bundle: 4 parts total
   updating the branch cache
@@ -1936,6 +2155,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [extensions]
@@ -2007,6 +2227,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [extensions]
@@ -2067,8 +2288,12 @@
   acl: path access granted: "e8fc755d4d82"
   bundle2-input-part: total payload size 2101
   bundle2-input-part: "pushkey" (params: 4 mandatory) supported
+  calling hook prepushkey.acl: hgext.acl.hook
+  acl: checking access for user "astro"
   pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
   bundle2-input-part: "pushkey" (params: 4 mandatory) supported
+  calling hook prepushkey.acl: hgext.acl.hook
+  acl: checking access for user "astro"
   pushing key for "phases:e8fc755d4d8217ee5b0c2bb41558c40d43b92c01"
   bundle2-input-bundle: 4 parts total
   updating the branch cache
@@ -2093,6 +2318,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [extensions]


More information about the Mercurial-devel mailing list