@@ -67,6 +67,7 @@ from . import (
)
from .utils import (
dateutil,
+ procutil,
stringutil,
)
@@ -5294,7 +5295,18 @@ def serve(ui, repo, **opts):
if repo is None:
raise error.RepoError(_("there is no Mercurial repository here"
" (.hg not found)"))
- s = wireprotoserver.sshserver(ui, repo)
+ accesshidden = False
+ if repo.filtername is None:
+ allow = ui.configlist('experimental', 'server.allow-hidden-access')
+ user = procutil.getuser()
+ if (allow and scmutil.ismember(ui, user, allow)):
+ accesshidden = True
+ else:
+ msg = _('ignoring request to access hidden changeset by '
+ 'unauthorized user: %s\n') % user
+ ui.warn(msg)
+
+ s = wireprotoserver.sshserver(ui, repo, accesshidden=accesshidden)
s.serve_forever()
service = server.createservice(ui, repo, opts)
@@ -154,17 +154,19 @@ def _cleanuppipes(ui, pipei, pipeo, pipe
pipee.close()
-def _makeconnection(ui, sshcmd, args, remotecmd, path, sshenv=None):
+def _makeconnection(ui, sshcmd, args, remotecmd, path, sshenv=None,
+ remotehidden=False):
"""Create an SSH connection to a server.
Returns a tuple of (process, stdin, stdout, stderr) for the
spawned process.
"""
- cmd = '%s %s %s' % (
- sshcmd,
- args,
- procutil.shellquote('%s -R %s serve --stdio' % (
- _serverquote(remotecmd), _serverquote(path))))
+ remoteserve = '%s -R %s serve --stdio'
+ remoteserve %= (_serverquote(remotecmd), _serverquote(path))
+ if remotehidden:
+ remoteserve += ' --hidden'
+ remoteserve = _serverquote(remoteserve)
+ cmd = '%s %s %s' % (sshcmd, args, remoteserve)
ui.debug('running %s\n' % cmd)
cmd = procutil.quotecommand(cmd)
@@ -365,7 +367,7 @@ def _performhandshake(ui, stdin, stdout,
class sshv1peer(wireprotov1peer.wirepeer):
def __init__(self, ui, url, proc, stdin, stdout, stderr, caps,
- autoreadstderr=True):
+ autoreadstderr=True, remotehidden=False):
"""Create a peer from an existing SSH connection.
``proc`` is a handle on the underlying SSH process.
@@ -392,6 +394,7 @@ class sshv1peer(wireprotov1peer.wirepeer
self._pipee = stderr
self._caps = caps
self._autoreadstderr = autoreadstderr
+ self._remotehidden = remotehidden
# Commands that have a "framed" response where the first line of the
# response contains the length of that response.
@@ -408,11 +411,7 @@ class sshv1peer(wireprotov1peer.wirepeer
return None
def peer(self, remotehidden=False):
- if remotehidden:
- msg = _("ignoring `--remote-hidden` request\n"
- "(access to hidden changeset for ssh peers not supported "
- "yet)\n")
- self.ui.warn(msg)
+ assert remotehidden == self._remotehidden
return self
def canpush(self):
@@ -586,11 +585,6 @@ def makepeer(ui, path, proc, stdin, stdo
servers and clients via non-standard means, which can be useful for
testing.
"""
- if remotehidden:
- msg = _("ignoring `--remote-hidden` request\n"
- "(access to hidden changeset for ssh peers not supported "
- "yet)\n")
- ui.warn(msg)
try:
protoname, caps = _performhandshake(ui, stdin, stdout, stderr)
except Exception:
@@ -599,10 +593,12 @@ def makepeer(ui, path, proc, stdin, stdo
if protoname == wireprototypes.SSHV1:
return sshv1peer(ui, path, proc, stdin, stdout, stderr, caps,
- autoreadstderr=autoreadstderr)
+ autoreadstderr=autoreadstderr,
+ remotehidden=remotehidden)
elif protoname == wireprototypes.SSHV2:
return sshv2peer(ui, path, proc, stdin, stdout, stderr, caps,
- autoreadstderr=autoreadstderr)
+ autoreadstderr=autoreadstderr,
+ remotehidden=remotehidden)
else:
_cleanuppipes(ui, stdout, stdin, stderr)
raise error.RepoError(_('unknown version of SSH protocol: %s') %
@@ -649,7 +645,8 @@ def instance(ui, path, create, intents=N
raise error.RepoError(_('could not create remote repo'))
proc, stdin, stdout, stderr = _makeconnection(ui, sshcmd, args, remotecmd,
- remotepath, sshenv)
+ remotepath, sshenv,
+ remotehidden=remotehidden)
peer = makepeer(ui, path, proc, stdin, stdout, stderr,
remotehidden=remotehidden)
@@ -566,7 +566,7 @@ class sshv2protocolhandler(sshv1protocol
def addcapabilities(self, repo, caps):
return caps
-def _runsshserver(ui, repo, fin, fout, ev):
+def _runsshserver(ui, repo, fin, fout, ev, accesshidden=False):
# This function operates like a state machine of sorts. The following
# states are defined:
#
@@ -655,7 +655,8 @@ def _runsshserver(ui, repo, fin, fout, e
_sshv1respondbytes(fout, b'')
continue
- rsp = wireprotov1server.dispatch(repo, proto, request)
+ rsp = wireprotov1server.dispatch(repo, proto, request,
+ accesshidden=accesshidden)
if isinstance(rsp, bytes):
_sshv1respondbytes(fout, rsp)
@@ -781,10 +782,11 @@ def _runsshserver(ui, repo, fin, fout, e
state)
class sshserver(object):
- def __init__(self, ui, repo, logfh=None):
+ def __init__(self, ui, repo, logfh=None, accesshidden=False):
self._ui = ui
self._repo = repo
self._fin, self._fout = ui.protectfinout()
+ self._accesshidden = accesshidden
# Log write I/O to stdout and stderr if configured.
if logfh:
@@ -800,4 +802,5 @@ class sshserver(object):
def serveuntil(self, ev):
"""Serve until a threading.Event is set."""
- _runsshserver(self._ui, self._repo, self._fin, self._fout, ev)
+ _runsshserver(self._ui, self._repo, self._fin, self._fout, ev,
+ self._accesshidden)
@@ -6,6 +6,8 @@ Test the ability to access a hidden revi
$ . $TESTDIR/testlib/obsmarker-common.sh
$ cat >> $HGRCPATH << EOF
+ > [ui]
+ > ssh = "$PYTHON" "$RUNTESTDIR/dummyssh"
> [phases]
> # public changeset are not obsolete
> publish=false
@@ -304,6 +306,98 @@ pulling an hidden changeset with --remot
abort: filtered revision 'be215fbb8c50' (not in 'served' subset)!
[255]
+Test --remote-hidden for ssh peer
+----------------------------------
+
+ $ hg clone --pull ssh://user@dummy/repo-with-hidden client-ssh
+ requesting all changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 2 changesets with 2 changes to 1 files
+ 2 new obsolescence markers
+ new changesets 5f354f46e585:c33affeb3f6b (1 drafts)
+ updating to branch default
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ hg -R client-ssh log -G --hidden -v
+ @ 1:c33affeb3f6b c_Amend_New [draft]
+ |
+ o 0:5f354f46e585 c_Public [public]
+
+
+Check on a server that do not allow hidden access:
+``````````````````````````````````````````````````
+
+pulling an hidden changeset should fail:
+
+ $ hg -R client-ssh pull -r be215fbb8c50
+ pulling from ssh://user@dummy/repo-with-hidden
+ abort: filtered revision 'be215fbb8c50' (not in 'served' subset)!
+ [255]
+
+pulling an hidden changeset with --remote-hidden should succeed:
+
+ $ hg -R client-ssh pull --remote-hidden -r be215fbb8c50
+ pulling from ssh://user@dummy/repo-with-hidden
+ remote: ignoring request to access hidden changeset by unauthorized user: * (glob)
+ abort: filtered revision 'be215fbb8c50' (not in 'served' subset)!
+ [255]
+ $ hg -R client-ssh log -G --hidden -v
+ @ 1:c33affeb3f6b c_Amend_New [draft]
+ |
+ o 0:5f354f46e585 c_Public [public]
+
+
+Check on a server that do allow hidden access:
+``````````````````````````````````````````````
+
+ $ cat << EOF >> repo-with-hidden/.hg/hgrc
+ > [experimental]
+ > server.allow-hidden-access=*
+ > EOF
+
+pulling an hidden changeset should fail:
+
+ $ hg -R client-ssh pull -r be215fbb8c50
+ pulling from ssh://user@dummy/repo-with-hidden
+ abort: filtered revision 'be215fbb8c50' (not in 'served' subset)!
+ [255]
+
+pulling an hidden changeset with --remote-hidden should succeed:
+
+ $ hg -R client-ssh pull --remote-hidden -r be215fbb8c50
+ pulling from ssh://user@dummy/repo-with-hidden
+ searching for changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 1 changesets with 1 changes to 1 files (+1 heads)
+ (1 other changesets obsolete on arrival)
+ (run 'hg heads' to see heads)
+ $ hg -R client-ssh log -G --hidden -v
+ x 2:be215fbb8c50 c_Amend_Old [draft]
+ |
+ | @ 1:c33affeb3f6b c_Amend_New [draft]
+ |/
+ o 0:5f354f46e585 c_Public [public]
+
+
+Pulling a secret changeset is still forbidden:
+
+secret visible:
+
+ $ hg -R client-ssh pull --remote-hidden -r 8d28cbe335f3
+ pulling from ssh://user@dummy/repo-with-hidden
+ abort: filtered revision '8d28cbe335f3' (not in 'served.hidden' subset)!
+ [255]
+
+secret hidden:
+
+ $ hg -R client-ssh pull --remote-hidden -r 1c6afd79eb66
+ pulling from ssh://user@dummy/repo-with-hidden
+ abort: filtered revision '1c6afd79eb66' (not in 'served.hidden' subset)!
+ [255]
+
=============
Final cleanup
=============