[PATCH v3] hg-ssh: read-only flag

David Schleimer dschleimer at fb.com
Tue May 22 17:20:34 CDT 2012


# HG changeset patch
# User David Schleimer <dschleimer at fb.com>
# Date 1337725057 25200
# Node ID 186a9ebfa7b2b0c9b5be44efea601898efdbb473
# Parent  b52b7fe0dd08b257dfc69c8a5de503cec94f4b76
hg-ssh: read-only flag

Allows you to restrict a ssh key to have read-only access to a set of
repos by passing the --read-only flag to hg-ssh.

This is useful in an environment where the number of unix users you
can or are willing to create is limited.  In such an environment,
multiple users or applications will share a single unix account.  Some
of those applications will likely need read-only access to the
repository.  This change makes it possible to grant them such access
without requiring that they use a separate unix account.

diff --git a/contrib/hg-ssh b/contrib/hg-ssh
--- a/contrib/hg-ssh
+++ b/contrib/hg-ssh
@@ -24,6 +24,9 @@
 
 You can use pattern matching of your normal shell, e.g.:
 command="cd repos && hg-ssh user/thomas/* projects/{mercurial,foo}"
+
+You can also add a --read-only flag to allow read-only access to a key, e.g.:
+command="hg-ssh --read-only repos/*"
 """
 
 # enable importing on demand to reduce startup time
@@ -35,9 +38,17 @@
 
 def main():
     cwd = os.getcwd()
+    readonly = False
+    args = sys.argv[1:]
+    while len(args):
+        if args[0] == '--read-only':
+            readonly = True
+            args.pop(0)
+        else:
+            break
     allowed_paths = [os.path.normpath(os.path.join(cwd,
                                                    os.path.expanduser(path)))
-                     for path in sys.argv[1:]]
+                     for path in args]
     orig_cmd = os.getenv('SSH_ORIGINAL_COMMAND', '?')
     try:
         cmdargv = shlex.split(orig_cmd)
@@ -49,9 +60,15 @@
         path = cmdargv[2]
         repo = os.path.normpath(os.path.join(cwd, os.path.expanduser(path)))
         if repo in allowed_paths:
-            dispatch.dispatch(dispatch.request(['-R', repo,
-                                                'serve',
-                                                '--stdio']))
+            cmd = ['-R', repo, 'serve', '--stdio']
+            if readonly:
+                cmd += [
+                    '--config',
+                    'hooks.prechangegroup.hg-ssh=python:__main__.rejectpush',
+                    '--config',
+                    'hooks.prepushkey.hg-ssh=python:__main__.rejectpush'
+                    ]
+            dispatch.dispatch(dispatch.request(cmd))
         else:
             sys.stderr.write('Illegal repository "%s"\n' % repo)
             sys.exit(255)
@@ -59,5 +76,11 @@
         sys.stderr.write('Illegal command "%s"\n' % orig_cmd)
         sys.exit(255)
 
+def rejectpush(ui, **kwargs):
+    ui.warn("Permission denied\n")
+    # mercurial hooks use unix process conventions for hook return values
+    # so a truthy return means failure
+    return True
+
 if __name__ == '__main__':
     main()
diff --git a/tests/test-ssh.t b/tests/test-ssh.t
--- a/tests/test-ssh.t
+++ b/tests/test-ssh.t
@@ -308,6 +308,41 @@
   Illegal command "'hg' -R 'a'repo' serve --stdio": No closing quotation
   [255]
 
+Test hg-ssh in read-only mode:
+
+  $ cat > ssh.sh << EOF
+  > userhost="\$1"
+  > SSH_ORIGINAL_COMMAND="\$2"
+  > export SSH_ORIGINAL_COMMAND
+  > PYTHONPATH="$PYTHONPATH"
+  > export PYTHONPATH
+  > python "$TESTDIR/../contrib/hg-ssh" --read-only "$TESTTMP/remote"
+  > EOF
+
+  $ hg clone --ssh "sh ssh.sh" "ssh://user@dummy/$TESTTMP/remote" read-only-local
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 4 changesets with 5 changes to 4 files (+1 heads)
+  updating to branch default
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+  $ cd read-only-local
+  $ echo "baz" > bar
+  $ hg ci -A -m "unpushable commit" bar
+  $ hg push --ssh "sh ../ssh.sh"
+  pushing to ssh://user@dummy/$TESTTMP/remote
+  searching for changes
+  remote: Permission denied
+  remote: abort: prechangegroup.hg-ssh hook failed
+  remote: Permission denied
+  remote: abort: prepushkey.hg-ssh hook failed
+  abort: unexpected response: empty string
+  [255]
+
+  $ cd ..
+
   $ cat dummylog
   Got arguments 1:user at dummy 2:hg -R nonexistent serve --stdio
   Got arguments 1:user at dummy 2:hg -R /$TESTTMP/nonexistent serve --stdio


More information about the Mercurial-devel mailing list