diff --git a/mercurial/commands.py b/mercurial/commands.py --- a/mercurial/commands.py +++ b/mercurial/commands.py @@ -69,6 +69,7 @@ from . import ( ) from .utils import ( dateutil, + procutil, stringutil, urlutil, ) @@ -6672,7 +6673,25 @@ def serve(ui, repo, **opts): raise error.RepoError( _(b"there is no Mercurial repository here (.hg not found)") ) - s = wireprotoserver.sshserver(ui, repo) + accesshidden = False + if repo.filtername is None: + allow = ui.configlist( + b'experimental', b'server.allow-hidden-access' + ) + user = procutil.getuser() + if allow and scmutil.ismember(ui, user, allow): + accesshidden = True + else: + msg = ( + _( + b'ignoring request to access hidden changeset by ' + b'unauthorized user: %s\n' + ) + % user + ) + ui.warn(msg) + + s = wireprotoserver.sshserver(ui, repo, accesshidden=accesshidden) s.serve_forever() return diff --git a/mercurial/sshpeer.py b/mercurial/sshpeer.py --- a/mercurial/sshpeer.py +++ b/mercurial/sshpeer.py @@ -177,7 +177,9 @@ def _cleanuppipes(ui, pipei, pipeo, pipe ui.develwarn(b'missing close on SSH connection created at:\n%s' % warn) -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 @@ -187,8 +189,12 @@ def _makeconnection(ui, sshcmd, args, re sshcmd, args, procutil.shellquote( - b'%s -R %s serve --stdio' - % (_serverquote(remotecmd), _serverquote(path)) + b'%s -R %s serve --stdio%s' + % ( + _serverquote(remotecmd), + _serverquote(path), + b' --hidden' if remotehidden else b'', + ) ), ) @@ -393,13 +399,6 @@ class sshv1peer(wireprotov1peer.wirepeer stderr and to forward its output. """ super().__init__(ui, path=path, remotehidden=remotehidden) - if remotehidden: - msg = _( - b"ignoring `--remote-hidden` request\n" - b"(access to hidden changeset for ssh peers not supported " - b"yet)\n" - ) - ui.warn(msg) # self._subprocess is unused. Keeping a handle on the process # holds a reference and prevents it from being garbage collected. self._subprocess = proc @@ -416,6 +415,7 @@ class sshv1peer(wireprotov1peer.wirepeer self._caps = caps self._autoreadstderr = autoreadstderr self._initstack = b''.join(util.getstackframes(1)) + self._remotehidden = remotehidden # Commands that have a "framed" response where the first line of the # response contains the length of that response. @@ -683,7 +683,13 @@ def make_peer( raise error.RepoError(_(b'could not create remote repo')) proc, stdin, stdout, stderr = _makeconnection( - ui, sshcmd, args, remotecmd, remotepath, sshenv + ui, + sshcmd, + args, + remotecmd, + remotepath, + sshenv, + remotehidden=remotehidden, ) peer = _make_peer( diff --git a/mercurial/wireprotoserver.py b/mercurial/wireprotoserver.py --- a/mercurial/wireprotoserver.py +++ b/mercurial/wireprotoserver.py @@ -446,7 +446,7 @@ class sshv1protocolhandler: pass -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: # @@ -487,7 +487,9 @@ 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 + ) repo.ui.fout.flush() repo.ui.ferr.flush() @@ -522,10 +524,11 @@ def _runsshserver(ui, repo, fin, fout, e class sshserver: - 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: @@ -542,4 +545,6 @@ class sshserver: 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 + ) diff --git a/tests/test-remote-hidden.t b/tests/test-remote-hidden.t --- a/tests/test-remote-hidden.t +++ b/tests/test-remote-hidden.t @@ -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 @@ -305,6 +307,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 =============