2016-10-15 07:47:43 +03:00
|
|
|
# server.py - utility and factory of server
|
|
|
|
#
|
|
|
|
# Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
|
|
|
|
#
|
|
|
|
# This software may be used and distributed according to the terms of the
|
|
|
|
# GNU General Public License version 2 or any later version.
|
|
|
|
|
|
|
|
from __future__ import absolute_import
|
|
|
|
|
|
|
|
import os
|
|
|
|
import tempfile
|
|
|
|
|
|
|
|
from .i18n import _
|
|
|
|
|
|
|
|
from . import (
|
2016-10-15 08:30:16 +03:00
|
|
|
chgserver,
|
serve: add support for Mercurial subrepositories
I've been using `hg serve --web-conf ...` with a simple '/=projects/**' [paths]
configuration for awhile without issue. Let's ditch the need for the manual
configuration in this case, and limit the repos served to the actual subrepos.
This doesn't attempt to handle the case where a new subrepo appears while the
server is running. That could probably be handled with a hook if somebody wants
it. But it's such a rare case, it probably doesn't matter for the temporary
serves.
The main repo is served at '/', just like a repository without subrepos. I'm
not sure why the duplicate 'adding ...' lines appear on Linux. They don't
appear on Windows (see 3f4ff1bdf101), so they are optional.
Subrepositories that are configured with '../path' or absolute paths are not
cloneable from the server. (They aren't cloneable locally either, unless they
also exist at their configured source, perhaps via the share extension.) They
are still served, so that they can be browsed, or cloned individually. If we
care about that cloning someday, we can probably just add the extra entries to
the webconf dictionary. Even if the entries use '../' to escape the root, only
the related subrepositories would end up in the dictionary.
2017-04-16 01:05:40 +03:00
|
|
|
cmdutil,
|
2016-10-15 07:57:17 +03:00
|
|
|
commandserver,
|
2016-10-15 07:47:43 +03:00
|
|
|
error,
|
2016-10-15 08:09:36 +03:00
|
|
|
hgweb,
|
2017-05-28 22:43:26 +03:00
|
|
|
pycompat,
|
2016-10-15 07:47:43 +03:00
|
|
|
util,
|
|
|
|
)
|
|
|
|
|
flake8: enable F821 check
Summary:
This check is useful and detects real errors (ex. fbconduit). Unfortunately
`arc lint` will run it with both py2 and py3 so a lot of py2 builtins will
still be warned.
I didn't find a clean way to disable py3 check. So this diff tries to fix them.
For `xrange`, the change was done by a script:
```
import sys
import redbaron
headertypes = {'comment', 'endl', 'from_import', 'import', 'string',
'assignment', 'atomtrailers'}
xrangefix = '''try:
xrange(0)
except NameError:
xrange = range
'''
def isxrange(x):
try:
return x[0].value == 'xrange'
except Exception:
return False
def main(argv):
for i, path in enumerate(argv):
print('(%d/%d) scanning %s' % (i + 1, len(argv), path))
content = open(path).read()
try:
red = redbaron.RedBaron(content)
except Exception:
print(' warning: failed to parse')
continue
hasxrange = red.find('atomtrailersnode', value=isxrange)
hasxrangefix = 'xrange = range' in content
if hasxrangefix or not hasxrange:
print(' no need to change')
continue
# find a place to insert the compatibility statement
changed = False
for node in red:
if node.type in headertypes:
continue
# node.insert_before is an easier API, but it has bugs changing
# other "finally" and "except" positions. So do the insert
# manually.
# # node.insert_before(xrangefix)
line = node.absolute_bounding_box.top_left.line - 1
lines = content.splitlines(1)
content = ''.join(lines[:line]) + xrangefix + ''.join(lines[line:])
changed = True
break
if changed:
# "content" is faster than "red.dumps()"
open(path, 'w').write(content)
print(' updated')
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
```
For other py2 builtins that do not have a py3 equivalent, some `# noqa`
were added as a workaround for now.
Reviewed By: DurhamG
Differential Revision: D6934535
fbshipit-source-id: 546b62830af144bc8b46788d2e0fd00496838939
2018-02-10 04:31:44 +03:00
|
|
|
try:
|
|
|
|
xrange(0)
|
|
|
|
except NameError:
|
|
|
|
xrange = range
|
|
|
|
|
2016-10-15 07:47:43 +03:00
|
|
|
def runservice(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
|
|
|
|
runargs=None, appendpid=False):
|
|
|
|
'''Run a command as a service.'''
|
|
|
|
|
|
|
|
def writepid(pid):
|
|
|
|
if opts['pid_file']:
|
|
|
|
if appendpid:
|
2017-05-28 22:43:06 +03:00
|
|
|
mode = 'ab'
|
2016-10-15 07:47:43 +03:00
|
|
|
else:
|
2017-05-28 22:43:06 +03:00
|
|
|
mode = 'wb'
|
2016-10-15 07:47:43 +03:00
|
|
|
fp = open(opts['pid_file'], mode)
|
2017-06-01 17:05:29 +03:00
|
|
|
fp.write('%d\n' % pid)
|
2016-10-15 07:47:43 +03:00
|
|
|
fp.close()
|
|
|
|
|
|
|
|
if opts['daemon'] and not opts['daemon_postexec']:
|
|
|
|
# Signal child process startup with file removal
|
|
|
|
lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
|
|
|
|
os.close(lockfd)
|
2018-02-08 02:18:27 +03:00
|
|
|
portpath = opts.get('port_file')
|
|
|
|
if portpath:
|
|
|
|
util.tryunlink(portpath)
|
2016-10-15 07:47:43 +03:00
|
|
|
try:
|
|
|
|
if not runargs:
|
2017-05-28 22:43:26 +03:00
|
|
|
runargs = util.hgcmd() + pycompat.sysargv[1:]
|
2016-10-15 07:47:43 +03:00
|
|
|
runargs.append('--daemon-postexec=unlink:%s' % lockpath)
|
|
|
|
# Don't pass --cwd to the child process, because we've already
|
|
|
|
# changed directory.
|
|
|
|
for i in xrange(1, len(runargs)):
|
|
|
|
if runargs[i].startswith('--cwd='):
|
|
|
|
del runargs[i]
|
|
|
|
break
|
|
|
|
elif runargs[i].startswith('--cwd'):
|
|
|
|
del runargs[i:i + 2]
|
|
|
|
break
|
|
|
|
def condfn():
|
2018-02-08 02:18:27 +03:00
|
|
|
if portpath and not os.path.exists(portpath):
|
|
|
|
return False
|
2016-10-15 07:47:43 +03:00
|
|
|
return not os.path.exists(lockpath)
|
|
|
|
pid = util.rundetached(runargs, condfn)
|
|
|
|
if pid < 0:
|
|
|
|
raise error.Abort(_('child process failed to start'))
|
|
|
|
writepid(pid)
|
|
|
|
finally:
|
2017-03-21 16:50:28 +03:00
|
|
|
util.tryunlink(lockpath)
|
2016-10-15 07:47:43 +03:00
|
|
|
if parentfn:
|
|
|
|
return parentfn(pid)
|
|
|
|
else:
|
|
|
|
return
|
|
|
|
|
|
|
|
if initfn:
|
|
|
|
initfn()
|
|
|
|
|
|
|
|
if not opts['daemon']:
|
|
|
|
writepid(util.getpid())
|
|
|
|
|
|
|
|
if opts['daemon_postexec']:
|
|
|
|
try:
|
|
|
|
os.setsid()
|
|
|
|
except AttributeError:
|
|
|
|
pass
|
|
|
|
for inst in opts['daemon_postexec']:
|
|
|
|
if inst.startswith('unlink:'):
|
|
|
|
lockpath = inst[7:]
|
|
|
|
os.unlink(lockpath)
|
|
|
|
elif inst.startswith('chdir:'):
|
|
|
|
os.chdir(inst[6:])
|
|
|
|
elif inst != 'none':
|
|
|
|
raise error.Abort(_('invalid value for --daemon-postexec: %s')
|
|
|
|
% inst)
|
|
|
|
util.hidewindow()
|
|
|
|
util.stdout.flush()
|
|
|
|
util.stderr.flush()
|
|
|
|
|
|
|
|
nullfd = os.open(os.devnull, os.O_RDWR)
|
|
|
|
logfilefd = nullfd
|
|
|
|
if logfile:
|
2017-10-25 15:20:01 +03:00
|
|
|
logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND,
|
|
|
|
0o666)
|
2016-10-15 07:47:43 +03:00
|
|
|
os.dup2(nullfd, 0)
|
|
|
|
os.dup2(logfilefd, 1)
|
|
|
|
os.dup2(logfilefd, 2)
|
|
|
|
if nullfd not in (0, 1, 2):
|
|
|
|
os.close(nullfd)
|
|
|
|
if logfile and logfilefd not in (0, 1, 2):
|
|
|
|
os.close(logfilefd)
|
|
|
|
|
|
|
|
if runfn:
|
|
|
|
return runfn()
|
2016-10-15 07:57:17 +03:00
|
|
|
|
|
|
|
_cmdservicemap = {
|
2016-10-15 08:30:16 +03:00
|
|
|
'chgunix': chgserver.chgunixservice,
|
2016-10-15 07:57:17 +03:00
|
|
|
'pipe': commandserver.pipeservice,
|
|
|
|
'unix': commandserver.unixforkingservice,
|
|
|
|
}
|
|
|
|
|
2016-10-15 08:19:16 +03:00
|
|
|
def _createcmdservice(ui, repo, opts):
|
2016-10-15 07:57:17 +03:00
|
|
|
mode = opts['cmdserver']
|
|
|
|
try:
|
|
|
|
return _cmdservicemap[mode](ui, repo, opts)
|
|
|
|
except KeyError:
|
|
|
|
raise error.Abort(_('unknown mode %s') % mode)
|
2016-10-15 08:09:36 +03:00
|
|
|
|
2016-10-15 08:19:16 +03:00
|
|
|
def _createhgwebservice(ui, repo, opts):
|
2016-10-15 08:09:36 +03:00
|
|
|
# this way we can check if something was given in the command-line
|
|
|
|
if opts.get('port'):
|
|
|
|
opts['port'] = util.getport(opts.get('port'))
|
|
|
|
|
2017-02-11 03:56:29 +03:00
|
|
|
alluis = {ui}
|
2016-10-15 08:09:36 +03:00
|
|
|
if repo:
|
|
|
|
baseui = repo.baseui
|
|
|
|
alluis.update([repo.baseui, repo.ui])
|
|
|
|
else:
|
|
|
|
baseui = ui
|
|
|
|
webconf = opts.get('web_conf') or opts.get('webdir_conf')
|
|
|
|
if webconf:
|
serve: add support for Mercurial subrepositories
I've been using `hg serve --web-conf ...` with a simple '/=projects/**' [paths]
configuration for awhile without issue. Let's ditch the need for the manual
configuration in this case, and limit the repos served to the actual subrepos.
This doesn't attempt to handle the case where a new subrepo appears while the
server is running. That could probably be handled with a hook if somebody wants
it. But it's such a rare case, it probably doesn't matter for the temporary
serves.
The main repo is served at '/', just like a repository without subrepos. I'm
not sure why the duplicate 'adding ...' lines appear on Linux. They don't
appear on Windows (see 3f4ff1bdf101), so they are optional.
Subrepositories that are configured with '../path' or absolute paths are not
cloneable from the server. (They aren't cloneable locally either, unless they
also exist at their configured source, perhaps via the share extension.) They
are still served, so that they can be browsed, or cloned individually. If we
care about that cloning someday, we can probably just add the extra entries to
the webconf dictionary. Even if the entries use '../' to escape the root, only
the related subrepositories would end up in the dictionary.
2017-04-16 01:05:40 +03:00
|
|
|
if opts.get('subrepos'):
|
|
|
|
raise error.Abort(_('--web-conf cannot be used with --subrepos'))
|
|
|
|
|
2016-10-15 08:09:36 +03:00
|
|
|
# load server settings (e.g. web.port) to "copied" ui, which allows
|
|
|
|
# hgwebdir to reload webconf cleanly
|
|
|
|
servui = ui.copy()
|
|
|
|
servui.readconfig(webconf, sections=['web'])
|
|
|
|
alluis.add(servui)
|
serve: add support for Mercurial subrepositories
I've been using `hg serve --web-conf ...` with a simple '/=projects/**' [paths]
configuration for awhile without issue. Let's ditch the need for the manual
configuration in this case, and limit the repos served to the actual subrepos.
This doesn't attempt to handle the case where a new subrepo appears while the
server is running. That could probably be handled with a hook if somebody wants
it. But it's such a rare case, it probably doesn't matter for the temporary
serves.
The main repo is served at '/', just like a repository without subrepos. I'm
not sure why the duplicate 'adding ...' lines appear on Linux. They don't
appear on Windows (see 3f4ff1bdf101), so they are optional.
Subrepositories that are configured with '../path' or absolute paths are not
cloneable from the server. (They aren't cloneable locally either, unless they
also exist at their configured source, perhaps via the share extension.) They
are still served, so that they can be browsed, or cloned individually. If we
care about that cloning someday, we can probably just add the extra entries to
the webconf dictionary. Even if the entries use '../' to escape the root, only
the related subrepositories would end up in the dictionary.
2017-04-16 01:05:40 +03:00
|
|
|
elif opts.get('subrepos'):
|
|
|
|
servui = ui
|
|
|
|
|
|
|
|
# If repo is None, hgweb.createapp() already raises a proper abort
|
|
|
|
# message as long as webconf is None.
|
|
|
|
if repo:
|
|
|
|
webconf = dict()
|
|
|
|
cmdutil.addwebdirpath(repo, "", webconf)
|
2016-10-15 08:09:36 +03:00
|
|
|
else:
|
|
|
|
servui = ui
|
|
|
|
|
|
|
|
optlist = ("name templates style address port prefix ipv6"
|
|
|
|
" accesslog errorlog certificate encoding")
|
|
|
|
for o in optlist.split():
|
|
|
|
val = opts.get(o, '')
|
|
|
|
if val in (None, ''): # should check against default options instead
|
|
|
|
continue
|
|
|
|
for u in alluis:
|
|
|
|
u.setconfig("web", o, val, 'serve')
|
|
|
|
|
|
|
|
app = hgweb.createapp(baseui, repo, webconf)
|
|
|
|
return hgweb.httpservice(servui, app, opts)
|
2016-10-15 08:19:16 +03:00
|
|
|
|
|
|
|
def createservice(ui, repo, opts):
|
|
|
|
if opts["cmdserver"]:
|
|
|
|
return _createcmdservice(ui, repo, opts)
|
|
|
|
else:
|
|
|
|
return _createhgwebservice(ui, repo, opts)
|