diff --git a/mercurial/configitems.py b/mercurial/configitems.py --- a/mercurial/configitems.py +++ b/mercurial/configitems.py @@ -1244,6 +1244,11 @@ coreconfigitem( ) coreconfigitem( b'experimental', + b'server.allow-hidden-access', + default=list, +) +coreconfigitem( + b'experimental', b'server.filesdata.recommended-batch-size', default=50000, ) diff --git a/mercurial/hgweb/common.py b/mercurial/hgweb/common.py --- a/mercurial/hgweb/common.py +++ b/mercurial/hgweb/common.py @@ -13,6 +13,7 @@ import mimetypes import os import stat +from ..i18n import _ from ..pycompat import ( getattr, open, @@ -49,6 +50,32 @@ def ismember(ui, username, userlist): return userlist == [b'*'] or username in userlist +def hashiddenaccess(repo, req): + if bool(req.qsparams.get(b'access-hidden')): + # Disable this by default for now. Main risk is to get critical + # information exposed through this. This is expecially risky if + # someone decided to make a changeset secret for good reason, but + # its predecessors are still draft. + # + # The feature is currently experimental, so we can still decide to + # change the default. + ui = repo.ui + allow = ui.configlist(b'experimental', b'server.allow-hidden-access') + user = req.remoteuser + if allow and ismember(ui, user, allow): + return True + else: + msg = ( + _( + b'ignoring request to access hidden changeset by ' + b'unauthorized user: %r\n' + ) + % user + ) + ui.warn(msg) + return False + + def checkauthz(hgweb, req, op): """Check permission for operation based on request data (including authentication info). Return if op allowed, else raise an ErrorResponse diff --git a/mercurial/hgweb/hgweb_mod.py b/mercurial/hgweb/hgweb_mod.py --- a/mercurial/hgweb/hgweb_mod.py +++ b/mercurial/hgweb/hgweb_mod.py @@ -39,6 +39,7 @@ from .. import ( ) from . import ( + common, request as requestmod, webcommands, webutil, @@ -124,6 +125,16 @@ class requestcontext: self.req = req self.res = res + # Only works if the filter actually support being upgraded to show + # visible changesets + current_filter = repo.filtername + if ( + common.hashiddenaccess(repo, req) + and current_filter is not None + and current_filter + b'.hidden' in repoview.filtertable + ): + self.repo = self.repo.filtered(repo.filtername + b'.hidden') + self.maxchanges = self.configint(b'web', b'maxchanges') self.stripecount = self.configint(b'web', b'stripes') self.maxshortchanges = self.configint(b'web', b'maxshortchanges') 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 @@ -111,3 +111,47 @@ changesets in secret and higher phases a revision: 0 $ killdaemons.py + +Test accessing hidden changeset through hgweb +--------------------------------------------- + + $ hg -R repo-with-hidden serve -p $HGPORT -d --pid-file hg.pid --config "experimental.server.allow-hidden-access=*" -E error.log --accesslog access.log + $ cat hg.pid >> $DAEMON_PIDS + +Hidden changeset are hidden by default: + + $ get-with-headers.py localhost:$HGPORT 'log?style=raw' | grep revision: + revision: 2 + revision: 0 + +Hidden changeset are visible when requested: + + $ get-with-headers.py localhost:$HGPORT 'log?style=raw&access-hidden=1' | grep revision: + revision: 3 + revision: 2 + revision: 1 + revision: 0 + +Same check on a server that do not allow hidden access: +``````````````````````````````````````````````````````` + + $ hg -R repo-with-hidden serve -p $HGPORT1 -d --pid-file hg2.pid --config "experimental.server.allow-hidden-access=" -E error.log --accesslog access.log + $ cat hg2.pid >> $DAEMON_PIDS + +Hidden changeset are hidden by default: + + $ get-with-headers.py localhost:$HGPORT1 'log?style=raw' | grep revision: + revision: 2 + revision: 0 + +Hidden changeset are still hidden despite being the hidden access request: + + $ get-with-headers.py localhost:$HGPORT1 'log?style=raw&access-hidden=1' | grep revision: + revision: 2 + revision: 0 + +============= +Final cleanup +============= + + $ killdaemons.py