##// END OF EJS Templates
dispatch: replace _earlyreq*() with new fancyopts-based parser
Yuya Nishihara -
r35224:6e6d0a5b default
parent child Browse files
Show More
@@ -1,601 +1,592 b''
1 # chgserver.py - command server extension for cHg
1 # chgserver.py - command server extension for cHg
2 #
2 #
3 # Copyright 2011 Yuya Nishihara <yuya@tcha.org>
3 # Copyright 2011 Yuya Nishihara <yuya@tcha.org>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 """command server extension for cHg
8 """command server extension for cHg
9
9
10 'S' channel (read/write)
10 'S' channel (read/write)
11 propagate ui.system() request to client
11 propagate ui.system() request to client
12
12
13 'attachio' command
13 'attachio' command
14 attach client's stdio passed by sendmsg()
14 attach client's stdio passed by sendmsg()
15
15
16 'chdir' command
16 'chdir' command
17 change current directory
17 change current directory
18
18
19 'setenv' command
19 'setenv' command
20 replace os.environ completely
20 replace os.environ completely
21
21
22 'setumask' command
22 'setumask' command
23 set umask
23 set umask
24
24
25 'validate' command
25 'validate' command
26 reload the config and check if the server is up to date
26 reload the config and check if the server is up to date
27
27
28 Config
28 Config
29 ------
29 ------
30
30
31 ::
31 ::
32
32
33 [chgserver]
33 [chgserver]
34 # how long (in seconds) should an idle chg server exit
34 # how long (in seconds) should an idle chg server exit
35 idletimeout = 3600
35 idletimeout = 3600
36
36
37 # whether to skip config or env change checks
37 # whether to skip config or env change checks
38 skiphash = False
38 skiphash = False
39 """
39 """
40
40
41 from __future__ import absolute_import
41 from __future__ import absolute_import
42
42
43 import hashlib
43 import hashlib
44 import inspect
44 import inspect
45 import os
45 import os
46 import re
46 import re
47 import socket
47 import socket
48 import struct
48 import struct
49 import time
49 import time
50
50
51 from .i18n import _
51 from .i18n import _
52
52
53 from . import (
53 from . import (
54 commandserver,
54 commandserver,
55 encoding,
55 encoding,
56 error,
56 error,
57 extensions,
57 extensions,
58 pycompat,
58 pycompat,
59 util,
59 util,
60 )
60 )
61
61
62 _log = commandserver.log
62 _log = commandserver.log
63
63
64 def _hashlist(items):
64 def _hashlist(items):
65 """return sha1 hexdigest for a list"""
65 """return sha1 hexdigest for a list"""
66 return hashlib.sha1(str(items)).hexdigest()
66 return hashlib.sha1(str(items)).hexdigest()
67
67
68 # sensitive config sections affecting confighash
68 # sensitive config sections affecting confighash
69 _configsections = [
69 _configsections = [
70 'alias', # affects global state commands.table
70 'alias', # affects global state commands.table
71 'eol', # uses setconfig('eol', ...)
71 'eol', # uses setconfig('eol', ...)
72 'extdiff', # uisetup will register new commands
72 'extdiff', # uisetup will register new commands
73 'extensions',
73 'extensions',
74 ]
74 ]
75
75
76 _configsectionitems = [
76 _configsectionitems = [
77 ('commands', 'show.aliasprefix'), # show.py reads it in extsetup
77 ('commands', 'show.aliasprefix'), # show.py reads it in extsetup
78 ]
78 ]
79
79
80 # sensitive environment variables affecting confighash
80 # sensitive environment variables affecting confighash
81 _envre = re.compile(r'''\A(?:
81 _envre = re.compile(r'''\A(?:
82 CHGHG
82 CHGHG
83 |HG(?:DEMANDIMPORT|EMITWARNINGS|MODULEPOLICY|PROF|RCPATH)?
83 |HG(?:DEMANDIMPORT|EMITWARNINGS|MODULEPOLICY|PROF|RCPATH)?
84 |HG(?:ENCODING|PLAIN).*
84 |HG(?:ENCODING|PLAIN).*
85 |LANG(?:UAGE)?
85 |LANG(?:UAGE)?
86 |LC_.*
86 |LC_.*
87 |LD_.*
87 |LD_.*
88 |PATH
88 |PATH
89 |PYTHON.*
89 |PYTHON.*
90 |TERM(?:INFO)?
90 |TERM(?:INFO)?
91 |TZ
91 |TZ
92 )\Z''', re.X)
92 )\Z''', re.X)
93
93
94 def _confighash(ui):
94 def _confighash(ui):
95 """return a quick hash for detecting config/env changes
95 """return a quick hash for detecting config/env changes
96
96
97 confighash is the hash of sensitive config items and environment variables.
97 confighash is the hash of sensitive config items and environment variables.
98
98
99 for chgserver, it is designed that once confighash changes, the server is
99 for chgserver, it is designed that once confighash changes, the server is
100 not qualified to serve its client and should redirect the client to a new
100 not qualified to serve its client and should redirect the client to a new
101 server. different from mtimehash, confighash change will not mark the
101 server. different from mtimehash, confighash change will not mark the
102 server outdated and exit since the user can have different configs at the
102 server outdated and exit since the user can have different configs at the
103 same time.
103 same time.
104 """
104 """
105 sectionitems = []
105 sectionitems = []
106 for section in _configsections:
106 for section in _configsections:
107 sectionitems.append(ui.configitems(section))
107 sectionitems.append(ui.configitems(section))
108 for section, item in _configsectionitems:
108 for section, item in _configsectionitems:
109 sectionitems.append(ui.config(section, item))
109 sectionitems.append(ui.config(section, item))
110 sectionhash = _hashlist(sectionitems)
110 sectionhash = _hashlist(sectionitems)
111 # If $CHGHG is set, the change to $HG should not trigger a new chg server
111 # If $CHGHG is set, the change to $HG should not trigger a new chg server
112 if 'CHGHG' in encoding.environ:
112 if 'CHGHG' in encoding.environ:
113 ignored = {'HG'}
113 ignored = {'HG'}
114 else:
114 else:
115 ignored = set()
115 ignored = set()
116 envitems = [(k, v) for k, v in encoding.environ.iteritems()
116 envitems = [(k, v) for k, v in encoding.environ.iteritems()
117 if _envre.match(k) and k not in ignored]
117 if _envre.match(k) and k not in ignored]
118 envhash = _hashlist(sorted(envitems))
118 envhash = _hashlist(sorted(envitems))
119 return sectionhash[:6] + envhash[:6]
119 return sectionhash[:6] + envhash[:6]
120
120
121 def _getmtimepaths(ui):
121 def _getmtimepaths(ui):
122 """get a list of paths that should be checked to detect change
122 """get a list of paths that should be checked to detect change
123
123
124 The list will include:
124 The list will include:
125 - extensions (will not cover all files for complex extensions)
125 - extensions (will not cover all files for complex extensions)
126 - mercurial/__version__.py
126 - mercurial/__version__.py
127 - python binary
127 - python binary
128 """
128 """
129 modules = [m for n, m in extensions.extensions(ui)]
129 modules = [m for n, m in extensions.extensions(ui)]
130 try:
130 try:
131 from . import __version__
131 from . import __version__
132 modules.append(__version__)
132 modules.append(__version__)
133 except ImportError:
133 except ImportError:
134 pass
134 pass
135 files = [pycompat.sysexecutable]
135 files = [pycompat.sysexecutable]
136 for m in modules:
136 for m in modules:
137 try:
137 try:
138 files.append(inspect.getabsfile(m))
138 files.append(inspect.getabsfile(m))
139 except TypeError:
139 except TypeError:
140 pass
140 pass
141 return sorted(set(files))
141 return sorted(set(files))
142
142
143 def _mtimehash(paths):
143 def _mtimehash(paths):
144 """return a quick hash for detecting file changes
144 """return a quick hash for detecting file changes
145
145
146 mtimehash calls stat on given paths and calculate a hash based on size and
146 mtimehash calls stat on given paths and calculate a hash based on size and
147 mtime of each file. mtimehash does not read file content because reading is
147 mtime of each file. mtimehash does not read file content because reading is
148 expensive. therefore it's not 100% reliable for detecting content changes.
148 expensive. therefore it's not 100% reliable for detecting content changes.
149 it's possible to return different hashes for same file contents.
149 it's possible to return different hashes for same file contents.
150 it's also possible to return a same hash for different file contents for
150 it's also possible to return a same hash for different file contents for
151 some carefully crafted situation.
151 some carefully crafted situation.
152
152
153 for chgserver, it is designed that once mtimehash changes, the server is
153 for chgserver, it is designed that once mtimehash changes, the server is
154 considered outdated immediately and should no longer provide service.
154 considered outdated immediately and should no longer provide service.
155
155
156 mtimehash is not included in confighash because we only know the paths of
156 mtimehash is not included in confighash because we only know the paths of
157 extensions after importing them (there is imp.find_module but that faces
157 extensions after importing them (there is imp.find_module but that faces
158 race conditions). We need to calculate confighash without importing.
158 race conditions). We need to calculate confighash without importing.
159 """
159 """
160 def trystat(path):
160 def trystat(path):
161 try:
161 try:
162 st = os.stat(path)
162 st = os.stat(path)
163 return (st.st_mtime, st.st_size)
163 return (st.st_mtime, st.st_size)
164 except OSError:
164 except OSError:
165 # could be ENOENT, EPERM etc. not fatal in any case
165 # could be ENOENT, EPERM etc. not fatal in any case
166 pass
166 pass
167 return _hashlist(map(trystat, paths))[:12]
167 return _hashlist(map(trystat, paths))[:12]
168
168
169 class hashstate(object):
169 class hashstate(object):
170 """a structure storing confighash, mtimehash, paths used for mtimehash"""
170 """a structure storing confighash, mtimehash, paths used for mtimehash"""
171 def __init__(self, confighash, mtimehash, mtimepaths):
171 def __init__(self, confighash, mtimehash, mtimepaths):
172 self.confighash = confighash
172 self.confighash = confighash
173 self.mtimehash = mtimehash
173 self.mtimehash = mtimehash
174 self.mtimepaths = mtimepaths
174 self.mtimepaths = mtimepaths
175
175
176 @staticmethod
176 @staticmethod
177 def fromui(ui, mtimepaths=None):
177 def fromui(ui, mtimepaths=None):
178 if mtimepaths is None:
178 if mtimepaths is None:
179 mtimepaths = _getmtimepaths(ui)
179 mtimepaths = _getmtimepaths(ui)
180 confighash = _confighash(ui)
180 confighash = _confighash(ui)
181 mtimehash = _mtimehash(mtimepaths)
181 mtimehash = _mtimehash(mtimepaths)
182 _log('confighash = %s mtimehash = %s\n' % (confighash, mtimehash))
182 _log('confighash = %s mtimehash = %s\n' % (confighash, mtimehash))
183 return hashstate(confighash, mtimehash, mtimepaths)
183 return hashstate(confighash, mtimehash, mtimepaths)
184
184
185 def _newchgui(srcui, csystem, attachio):
185 def _newchgui(srcui, csystem, attachio):
186 class chgui(srcui.__class__):
186 class chgui(srcui.__class__):
187 def __init__(self, src=None):
187 def __init__(self, src=None):
188 super(chgui, self).__init__(src)
188 super(chgui, self).__init__(src)
189 if src:
189 if src:
190 self._csystem = getattr(src, '_csystem', csystem)
190 self._csystem = getattr(src, '_csystem', csystem)
191 else:
191 else:
192 self._csystem = csystem
192 self._csystem = csystem
193
193
194 def _runsystem(self, cmd, environ, cwd, out):
194 def _runsystem(self, cmd, environ, cwd, out):
195 # fallback to the original system method if the output needs to be
195 # fallback to the original system method if the output needs to be
196 # captured (to self._buffers), or the output stream is not stdout
196 # captured (to self._buffers), or the output stream is not stdout
197 # (e.g. stderr, cStringIO), because the chg client is not aware of
197 # (e.g. stderr, cStringIO), because the chg client is not aware of
198 # these situations and will behave differently (write to stdout).
198 # these situations and will behave differently (write to stdout).
199 if (out is not self.fout
199 if (out is not self.fout
200 or not util.safehasattr(self.fout, 'fileno')
200 or not util.safehasattr(self.fout, 'fileno')
201 or self.fout.fileno() != util.stdout.fileno()):
201 or self.fout.fileno() != util.stdout.fileno()):
202 return util.system(cmd, environ=environ, cwd=cwd, out=out)
202 return util.system(cmd, environ=environ, cwd=cwd, out=out)
203 self.flush()
203 self.flush()
204 return self._csystem(cmd, util.shellenviron(environ), cwd)
204 return self._csystem(cmd, util.shellenviron(environ), cwd)
205
205
206 def _runpager(self, cmd, env=None):
206 def _runpager(self, cmd, env=None):
207 self._csystem(cmd, util.shellenviron(env), type='pager',
207 self._csystem(cmd, util.shellenviron(env), type='pager',
208 cmdtable={'attachio': attachio})
208 cmdtable={'attachio': attachio})
209 return True
209 return True
210
210
211 return chgui(srcui)
211 return chgui(srcui)
212
212
213 def _loadnewui(srcui, args):
213 def _loadnewui(srcui, args):
214 from . import dispatch # avoid cycle
214 from . import dispatch # avoid cycle
215
215
216 newui = srcui.__class__.load()
216 newui = srcui.__class__.load()
217 for a in ['fin', 'fout', 'ferr', 'environ']:
217 for a in ['fin', 'fout', 'ferr', 'environ']:
218 setattr(newui, a, getattr(srcui, a))
218 setattr(newui, a, getattr(srcui, a))
219 if util.safehasattr(srcui, '_csystem'):
219 if util.safehasattr(srcui, '_csystem'):
220 newui._csystem = srcui._csystem
220 newui._csystem = srcui._csystem
221
221
222 # command line args
222 # command line args
223 options = {}
223 options = dispatch._earlyparseopts(newui, args)
224 if srcui.plain('strictflags'):
225 options.update(dispatch._earlyparseopts(args))
226 else:
227 args = args[:]
228 options['config'] = dispatch._earlygetopt(['--config'], args)
229 cwds = dispatch._earlygetopt(['--cwd'], args)
230 options['cwd'] = cwds and cwds[-1] or ''
231 rpath = dispatch._earlygetopt(["-R", "--repository", "--repo"], args)
232 options['repository'] = rpath and rpath[-1] or ''
233 dispatch._parseconfig(newui, options['config'])
224 dispatch._parseconfig(newui, options['config'])
234
225
235 # stolen from tortoisehg.util.copydynamicconfig()
226 # stolen from tortoisehg.util.copydynamicconfig()
236 for section, name, value in srcui.walkconfig():
227 for section, name, value in srcui.walkconfig():
237 source = srcui.configsource(section, name)
228 source = srcui.configsource(section, name)
238 if ':' in source or source == '--config' or source.startswith('$'):
229 if ':' in source or source == '--config' or source.startswith('$'):
239 # path:line or command line, or environ
230 # path:line or command line, or environ
240 continue
231 continue
241 newui.setconfig(section, name, value, source)
232 newui.setconfig(section, name, value, source)
242
233
243 # load wd and repo config, copied from dispatch.py
234 # load wd and repo config, copied from dispatch.py
244 cwd = options['cwd']
235 cwd = options['cwd']
245 cwd = cwd and os.path.realpath(cwd) or None
236 cwd = cwd and os.path.realpath(cwd) or None
246 rpath = options['repository']
237 rpath = options['repository']
247 path, newlui = dispatch._getlocal(newui, rpath, wd=cwd)
238 path, newlui = dispatch._getlocal(newui, rpath, wd=cwd)
248
239
249 return (newui, newlui)
240 return (newui, newlui)
250
241
251 class channeledsystem(object):
242 class channeledsystem(object):
252 """Propagate ui.system() request in the following format:
243 """Propagate ui.system() request in the following format:
253
244
254 payload length (unsigned int),
245 payload length (unsigned int),
255 type, '\0',
246 type, '\0',
256 cmd, '\0',
247 cmd, '\0',
257 cwd, '\0',
248 cwd, '\0',
258 envkey, '=', val, '\0',
249 envkey, '=', val, '\0',
259 ...
250 ...
260 envkey, '=', val
251 envkey, '=', val
261
252
262 if type == 'system', waits for:
253 if type == 'system', waits for:
263
254
264 exitcode length (unsigned int),
255 exitcode length (unsigned int),
265 exitcode (int)
256 exitcode (int)
266
257
267 if type == 'pager', repetitively waits for a command name ending with '\n'
258 if type == 'pager', repetitively waits for a command name ending with '\n'
268 and executes it defined by cmdtable, or exits the loop if the command name
259 and executes it defined by cmdtable, or exits the loop if the command name
269 is empty.
260 is empty.
270 """
261 """
271 def __init__(self, in_, out, channel):
262 def __init__(self, in_, out, channel):
272 self.in_ = in_
263 self.in_ = in_
273 self.out = out
264 self.out = out
274 self.channel = channel
265 self.channel = channel
275
266
276 def __call__(self, cmd, environ, cwd=None, type='system', cmdtable=None):
267 def __call__(self, cmd, environ, cwd=None, type='system', cmdtable=None):
277 args = [type, util.quotecommand(cmd), os.path.abspath(cwd or '.')]
268 args = [type, util.quotecommand(cmd), os.path.abspath(cwd or '.')]
278 args.extend('%s=%s' % (k, v) for k, v in environ.iteritems())
269 args.extend('%s=%s' % (k, v) for k, v in environ.iteritems())
279 data = '\0'.join(args)
270 data = '\0'.join(args)
280 self.out.write(struct.pack('>cI', self.channel, len(data)))
271 self.out.write(struct.pack('>cI', self.channel, len(data)))
281 self.out.write(data)
272 self.out.write(data)
282 self.out.flush()
273 self.out.flush()
283
274
284 if type == 'system':
275 if type == 'system':
285 length = self.in_.read(4)
276 length = self.in_.read(4)
286 length, = struct.unpack('>I', length)
277 length, = struct.unpack('>I', length)
287 if length != 4:
278 if length != 4:
288 raise error.Abort(_('invalid response'))
279 raise error.Abort(_('invalid response'))
289 rc, = struct.unpack('>i', self.in_.read(4))
280 rc, = struct.unpack('>i', self.in_.read(4))
290 return rc
281 return rc
291 elif type == 'pager':
282 elif type == 'pager':
292 while True:
283 while True:
293 cmd = self.in_.readline()[:-1]
284 cmd = self.in_.readline()[:-1]
294 if not cmd:
285 if not cmd:
295 break
286 break
296 if cmdtable and cmd in cmdtable:
287 if cmdtable and cmd in cmdtable:
297 _log('pager subcommand: %s' % cmd)
288 _log('pager subcommand: %s' % cmd)
298 cmdtable[cmd]()
289 cmdtable[cmd]()
299 else:
290 else:
300 raise error.Abort(_('unexpected command: %s') % cmd)
291 raise error.Abort(_('unexpected command: %s') % cmd)
301 else:
292 else:
302 raise error.ProgrammingError('invalid S channel type: %s' % type)
293 raise error.ProgrammingError('invalid S channel type: %s' % type)
303
294
304 _iochannels = [
295 _iochannels = [
305 # server.ch, ui.fp, mode
296 # server.ch, ui.fp, mode
306 ('cin', 'fin', pycompat.sysstr('rb')),
297 ('cin', 'fin', pycompat.sysstr('rb')),
307 ('cout', 'fout', pycompat.sysstr('wb')),
298 ('cout', 'fout', pycompat.sysstr('wb')),
308 ('cerr', 'ferr', pycompat.sysstr('wb')),
299 ('cerr', 'ferr', pycompat.sysstr('wb')),
309 ]
300 ]
310
301
311 class chgcmdserver(commandserver.server):
302 class chgcmdserver(commandserver.server):
312 def __init__(self, ui, repo, fin, fout, sock, hashstate, baseaddress):
303 def __init__(self, ui, repo, fin, fout, sock, hashstate, baseaddress):
313 super(chgcmdserver, self).__init__(
304 super(chgcmdserver, self).__init__(
314 _newchgui(ui, channeledsystem(fin, fout, 'S'), self.attachio),
305 _newchgui(ui, channeledsystem(fin, fout, 'S'), self.attachio),
315 repo, fin, fout)
306 repo, fin, fout)
316 self.clientsock = sock
307 self.clientsock = sock
317 self._oldios = [] # original (self.ch, ui.fp, fd) before "attachio"
308 self._oldios = [] # original (self.ch, ui.fp, fd) before "attachio"
318 self.hashstate = hashstate
309 self.hashstate = hashstate
319 self.baseaddress = baseaddress
310 self.baseaddress = baseaddress
320 if hashstate is not None:
311 if hashstate is not None:
321 self.capabilities = self.capabilities.copy()
312 self.capabilities = self.capabilities.copy()
322 self.capabilities['validate'] = chgcmdserver.validate
313 self.capabilities['validate'] = chgcmdserver.validate
323
314
324 def cleanup(self):
315 def cleanup(self):
325 super(chgcmdserver, self).cleanup()
316 super(chgcmdserver, self).cleanup()
326 # dispatch._runcatch() does not flush outputs if exception is not
317 # dispatch._runcatch() does not flush outputs if exception is not
327 # handled by dispatch._dispatch()
318 # handled by dispatch._dispatch()
328 self.ui.flush()
319 self.ui.flush()
329 self._restoreio()
320 self._restoreio()
330
321
331 def attachio(self):
322 def attachio(self):
332 """Attach to client's stdio passed via unix domain socket; all
323 """Attach to client's stdio passed via unix domain socket; all
333 channels except cresult will no longer be used
324 channels except cresult will no longer be used
334 """
325 """
335 # tell client to sendmsg() with 1-byte payload, which makes it
326 # tell client to sendmsg() with 1-byte payload, which makes it
336 # distinctive from "attachio\n" command consumed by client.read()
327 # distinctive from "attachio\n" command consumed by client.read()
337 self.clientsock.sendall(struct.pack('>cI', 'I', 1))
328 self.clientsock.sendall(struct.pack('>cI', 'I', 1))
338 clientfds = util.recvfds(self.clientsock.fileno())
329 clientfds = util.recvfds(self.clientsock.fileno())
339 _log('received fds: %r\n' % clientfds)
330 _log('received fds: %r\n' % clientfds)
340
331
341 ui = self.ui
332 ui = self.ui
342 ui.flush()
333 ui.flush()
343 first = self._saveio()
334 first = self._saveio()
344 for fd, (cn, fn, mode) in zip(clientfds, _iochannels):
335 for fd, (cn, fn, mode) in zip(clientfds, _iochannels):
345 assert fd > 0
336 assert fd > 0
346 fp = getattr(ui, fn)
337 fp = getattr(ui, fn)
347 os.dup2(fd, fp.fileno())
338 os.dup2(fd, fp.fileno())
348 os.close(fd)
339 os.close(fd)
349 if not first:
340 if not first:
350 continue
341 continue
351 # reset buffering mode when client is first attached. as we want
342 # reset buffering mode when client is first attached. as we want
352 # to see output immediately on pager, the mode stays unchanged
343 # to see output immediately on pager, the mode stays unchanged
353 # when client re-attached. ferr is unchanged because it should
344 # when client re-attached. ferr is unchanged because it should
354 # be unbuffered no matter if it is a tty or not.
345 # be unbuffered no matter if it is a tty or not.
355 if fn == 'ferr':
346 if fn == 'ferr':
356 newfp = fp
347 newfp = fp
357 else:
348 else:
358 # make it line buffered explicitly because the default is
349 # make it line buffered explicitly because the default is
359 # decided on first write(), where fout could be a pager.
350 # decided on first write(), where fout could be a pager.
360 if fp.isatty():
351 if fp.isatty():
361 bufsize = 1 # line buffered
352 bufsize = 1 # line buffered
362 else:
353 else:
363 bufsize = -1 # system default
354 bufsize = -1 # system default
364 newfp = os.fdopen(fp.fileno(), mode, bufsize)
355 newfp = os.fdopen(fp.fileno(), mode, bufsize)
365 setattr(ui, fn, newfp)
356 setattr(ui, fn, newfp)
366 setattr(self, cn, newfp)
357 setattr(self, cn, newfp)
367
358
368 self.cresult.write(struct.pack('>i', len(clientfds)))
359 self.cresult.write(struct.pack('>i', len(clientfds)))
369
360
370 def _saveio(self):
361 def _saveio(self):
371 if self._oldios:
362 if self._oldios:
372 return False
363 return False
373 ui = self.ui
364 ui = self.ui
374 for cn, fn, _mode in _iochannels:
365 for cn, fn, _mode in _iochannels:
375 ch = getattr(self, cn)
366 ch = getattr(self, cn)
376 fp = getattr(ui, fn)
367 fp = getattr(ui, fn)
377 fd = os.dup(fp.fileno())
368 fd = os.dup(fp.fileno())
378 self._oldios.append((ch, fp, fd))
369 self._oldios.append((ch, fp, fd))
379 return True
370 return True
380
371
381 def _restoreio(self):
372 def _restoreio(self):
382 ui = self.ui
373 ui = self.ui
383 for (ch, fp, fd), (cn, fn, _mode) in zip(self._oldios, _iochannels):
374 for (ch, fp, fd), (cn, fn, _mode) in zip(self._oldios, _iochannels):
384 newfp = getattr(ui, fn)
375 newfp = getattr(ui, fn)
385 # close newfp while it's associated with client; otherwise it
376 # close newfp while it's associated with client; otherwise it
386 # would be closed when newfp is deleted
377 # would be closed when newfp is deleted
387 if newfp is not fp:
378 if newfp is not fp:
388 newfp.close()
379 newfp.close()
389 # restore original fd: fp is open again
380 # restore original fd: fp is open again
390 os.dup2(fd, fp.fileno())
381 os.dup2(fd, fp.fileno())
391 os.close(fd)
382 os.close(fd)
392 setattr(self, cn, ch)
383 setattr(self, cn, ch)
393 setattr(ui, fn, fp)
384 setattr(ui, fn, fp)
394 del self._oldios[:]
385 del self._oldios[:]
395
386
396 def validate(self):
387 def validate(self):
397 """Reload the config and check if the server is up to date
388 """Reload the config and check if the server is up to date
398
389
399 Read a list of '\0' separated arguments.
390 Read a list of '\0' separated arguments.
400 Write a non-empty list of '\0' separated instruction strings or '\0'
391 Write a non-empty list of '\0' separated instruction strings or '\0'
401 if the list is empty.
392 if the list is empty.
402 An instruction string could be either:
393 An instruction string could be either:
403 - "unlink $path", the client should unlink the path to stop the
394 - "unlink $path", the client should unlink the path to stop the
404 outdated server.
395 outdated server.
405 - "redirect $path", the client should attempt to connect to $path
396 - "redirect $path", the client should attempt to connect to $path
406 first. If it does not work, start a new server. It implies
397 first. If it does not work, start a new server. It implies
407 "reconnect".
398 "reconnect".
408 - "exit $n", the client should exit directly with code n.
399 - "exit $n", the client should exit directly with code n.
409 This may happen if we cannot parse the config.
400 This may happen if we cannot parse the config.
410 - "reconnect", the client should close the connection and
401 - "reconnect", the client should close the connection and
411 reconnect.
402 reconnect.
412 If neither "reconnect" nor "redirect" is included in the instruction
403 If neither "reconnect" nor "redirect" is included in the instruction
413 list, the client can continue with this server after completing all
404 list, the client can continue with this server after completing all
414 the instructions.
405 the instructions.
415 """
406 """
416 from . import dispatch # avoid cycle
407 from . import dispatch # avoid cycle
417
408
418 args = self._readlist()
409 args = self._readlist()
419 try:
410 try:
420 self.ui, lui = _loadnewui(self.ui, args)
411 self.ui, lui = _loadnewui(self.ui, args)
421 except error.ParseError as inst:
412 except error.ParseError as inst:
422 dispatch._formatparse(self.ui.warn, inst)
413 dispatch._formatparse(self.ui.warn, inst)
423 self.ui.flush()
414 self.ui.flush()
424 self.cresult.write('exit 255')
415 self.cresult.write('exit 255')
425 return
416 return
426 newhash = hashstate.fromui(lui, self.hashstate.mtimepaths)
417 newhash = hashstate.fromui(lui, self.hashstate.mtimepaths)
427 insts = []
418 insts = []
428 if newhash.mtimehash != self.hashstate.mtimehash:
419 if newhash.mtimehash != self.hashstate.mtimehash:
429 addr = _hashaddress(self.baseaddress, self.hashstate.confighash)
420 addr = _hashaddress(self.baseaddress, self.hashstate.confighash)
430 insts.append('unlink %s' % addr)
421 insts.append('unlink %s' % addr)
431 # mtimehash is empty if one or more extensions fail to load.
422 # mtimehash is empty if one or more extensions fail to load.
432 # to be compatible with hg, still serve the client this time.
423 # to be compatible with hg, still serve the client this time.
433 if self.hashstate.mtimehash:
424 if self.hashstate.mtimehash:
434 insts.append('reconnect')
425 insts.append('reconnect')
435 if newhash.confighash != self.hashstate.confighash:
426 if newhash.confighash != self.hashstate.confighash:
436 addr = _hashaddress(self.baseaddress, newhash.confighash)
427 addr = _hashaddress(self.baseaddress, newhash.confighash)
437 insts.append('redirect %s' % addr)
428 insts.append('redirect %s' % addr)
438 _log('validate: %s\n' % insts)
429 _log('validate: %s\n' % insts)
439 self.cresult.write('\0'.join(insts) or '\0')
430 self.cresult.write('\0'.join(insts) or '\0')
440
431
441 def chdir(self):
432 def chdir(self):
442 """Change current directory
433 """Change current directory
443
434
444 Note that the behavior of --cwd option is bit different from this.
435 Note that the behavior of --cwd option is bit different from this.
445 It does not affect --config parameter.
436 It does not affect --config parameter.
446 """
437 """
447 path = self._readstr()
438 path = self._readstr()
448 if not path:
439 if not path:
449 return
440 return
450 _log('chdir to %r\n' % path)
441 _log('chdir to %r\n' % path)
451 os.chdir(path)
442 os.chdir(path)
452
443
453 def setumask(self):
444 def setumask(self):
454 """Change umask"""
445 """Change umask"""
455 mask = struct.unpack('>I', self._read(4))[0]
446 mask = struct.unpack('>I', self._read(4))[0]
456 _log('setumask %r\n' % mask)
447 _log('setumask %r\n' % mask)
457 os.umask(mask)
448 os.umask(mask)
458
449
459 def runcommand(self):
450 def runcommand(self):
460 return super(chgcmdserver, self).runcommand()
451 return super(chgcmdserver, self).runcommand()
461
452
462 def setenv(self):
453 def setenv(self):
463 """Clear and update os.environ
454 """Clear and update os.environ
464
455
465 Note that not all variables can make an effect on the running process.
456 Note that not all variables can make an effect on the running process.
466 """
457 """
467 l = self._readlist()
458 l = self._readlist()
468 try:
459 try:
469 newenv = dict(s.split('=', 1) for s in l)
460 newenv = dict(s.split('=', 1) for s in l)
470 except ValueError:
461 except ValueError:
471 raise ValueError('unexpected value in setenv request')
462 raise ValueError('unexpected value in setenv request')
472 _log('setenv: %r\n' % sorted(newenv.keys()))
463 _log('setenv: %r\n' % sorted(newenv.keys()))
473 encoding.environ.clear()
464 encoding.environ.clear()
474 encoding.environ.update(newenv)
465 encoding.environ.update(newenv)
475
466
476 capabilities = commandserver.server.capabilities.copy()
467 capabilities = commandserver.server.capabilities.copy()
477 capabilities.update({'attachio': attachio,
468 capabilities.update({'attachio': attachio,
478 'chdir': chdir,
469 'chdir': chdir,
479 'runcommand': runcommand,
470 'runcommand': runcommand,
480 'setenv': setenv,
471 'setenv': setenv,
481 'setumask': setumask})
472 'setumask': setumask})
482
473
483 if util.safehasattr(util, 'setprocname'):
474 if util.safehasattr(util, 'setprocname'):
484 def setprocname(self):
475 def setprocname(self):
485 """Change process title"""
476 """Change process title"""
486 name = self._readstr()
477 name = self._readstr()
487 _log('setprocname: %r\n' % name)
478 _log('setprocname: %r\n' % name)
488 util.setprocname(name)
479 util.setprocname(name)
489 capabilities['setprocname'] = setprocname
480 capabilities['setprocname'] = setprocname
490
481
491 def _tempaddress(address):
482 def _tempaddress(address):
492 return '%s.%d.tmp' % (address, os.getpid())
483 return '%s.%d.tmp' % (address, os.getpid())
493
484
494 def _hashaddress(address, hashstr):
485 def _hashaddress(address, hashstr):
495 # if the basename of address contains '.', use only the left part. this
486 # if the basename of address contains '.', use only the left part. this
496 # makes it possible for the client to pass 'server.tmp$PID' and follow by
487 # makes it possible for the client to pass 'server.tmp$PID' and follow by
497 # an atomic rename to avoid locking when spawning new servers.
488 # an atomic rename to avoid locking when spawning new servers.
498 dirname, basename = os.path.split(address)
489 dirname, basename = os.path.split(address)
499 basename = basename.split('.', 1)[0]
490 basename = basename.split('.', 1)[0]
500 return '%s-%s' % (os.path.join(dirname, basename), hashstr)
491 return '%s-%s' % (os.path.join(dirname, basename), hashstr)
501
492
502 class chgunixservicehandler(object):
493 class chgunixservicehandler(object):
503 """Set of operations for chg services"""
494 """Set of operations for chg services"""
504
495
505 pollinterval = 1 # [sec]
496 pollinterval = 1 # [sec]
506
497
507 def __init__(self, ui):
498 def __init__(self, ui):
508 self.ui = ui
499 self.ui = ui
509 self._idletimeout = ui.configint('chgserver', 'idletimeout')
500 self._idletimeout = ui.configint('chgserver', 'idletimeout')
510 self._lastactive = time.time()
501 self._lastactive = time.time()
511
502
512 def bindsocket(self, sock, address):
503 def bindsocket(self, sock, address):
513 self._inithashstate(address)
504 self._inithashstate(address)
514 self._checkextensions()
505 self._checkextensions()
515 self._bind(sock)
506 self._bind(sock)
516 self._createsymlink()
507 self._createsymlink()
517 # no "listening at" message should be printed to simulate hg behavior
508 # no "listening at" message should be printed to simulate hg behavior
518
509
519 def _inithashstate(self, address):
510 def _inithashstate(self, address):
520 self._baseaddress = address
511 self._baseaddress = address
521 if self.ui.configbool('chgserver', 'skiphash'):
512 if self.ui.configbool('chgserver', 'skiphash'):
522 self._hashstate = None
513 self._hashstate = None
523 self._realaddress = address
514 self._realaddress = address
524 return
515 return
525 self._hashstate = hashstate.fromui(self.ui)
516 self._hashstate = hashstate.fromui(self.ui)
526 self._realaddress = _hashaddress(address, self._hashstate.confighash)
517 self._realaddress = _hashaddress(address, self._hashstate.confighash)
527
518
528 def _checkextensions(self):
519 def _checkextensions(self):
529 if not self._hashstate:
520 if not self._hashstate:
530 return
521 return
531 if extensions.notloaded():
522 if extensions.notloaded():
532 # one or more extensions failed to load. mtimehash becomes
523 # one or more extensions failed to load. mtimehash becomes
533 # meaningless because we do not know the paths of those extensions.
524 # meaningless because we do not know the paths of those extensions.
534 # set mtimehash to an illegal hash value to invalidate the server.
525 # set mtimehash to an illegal hash value to invalidate the server.
535 self._hashstate.mtimehash = ''
526 self._hashstate.mtimehash = ''
536
527
537 def _bind(self, sock):
528 def _bind(self, sock):
538 # use a unique temp address so we can stat the file and do ownership
529 # use a unique temp address so we can stat the file and do ownership
539 # check later
530 # check later
540 tempaddress = _tempaddress(self._realaddress)
531 tempaddress = _tempaddress(self._realaddress)
541 util.bindunixsocket(sock, tempaddress)
532 util.bindunixsocket(sock, tempaddress)
542 self._socketstat = os.stat(tempaddress)
533 self._socketstat = os.stat(tempaddress)
543 sock.listen(socket.SOMAXCONN)
534 sock.listen(socket.SOMAXCONN)
544 # rename will replace the old socket file if exists atomically. the
535 # rename will replace the old socket file if exists atomically. the
545 # old server will detect ownership change and exit.
536 # old server will detect ownership change and exit.
546 util.rename(tempaddress, self._realaddress)
537 util.rename(tempaddress, self._realaddress)
547
538
548 def _createsymlink(self):
539 def _createsymlink(self):
549 if self._baseaddress == self._realaddress:
540 if self._baseaddress == self._realaddress:
550 return
541 return
551 tempaddress = _tempaddress(self._baseaddress)
542 tempaddress = _tempaddress(self._baseaddress)
552 os.symlink(os.path.basename(self._realaddress), tempaddress)
543 os.symlink(os.path.basename(self._realaddress), tempaddress)
553 util.rename(tempaddress, self._baseaddress)
544 util.rename(tempaddress, self._baseaddress)
554
545
555 def _issocketowner(self):
546 def _issocketowner(self):
556 try:
547 try:
557 stat = os.stat(self._realaddress)
548 stat = os.stat(self._realaddress)
558 return (stat.st_ino == self._socketstat.st_ino and
549 return (stat.st_ino == self._socketstat.st_ino and
559 stat.st_mtime == self._socketstat.st_mtime)
550 stat.st_mtime == self._socketstat.st_mtime)
560 except OSError:
551 except OSError:
561 return False
552 return False
562
553
563 def unlinksocket(self, address):
554 def unlinksocket(self, address):
564 if not self._issocketowner():
555 if not self._issocketowner():
565 return
556 return
566 # it is possible to have a race condition here that we may
557 # it is possible to have a race condition here that we may
567 # remove another server's socket file. but that's okay
558 # remove another server's socket file. but that's okay
568 # since that server will detect and exit automatically and
559 # since that server will detect and exit automatically and
569 # the client will start a new server on demand.
560 # the client will start a new server on demand.
570 util.tryunlink(self._realaddress)
561 util.tryunlink(self._realaddress)
571
562
572 def shouldexit(self):
563 def shouldexit(self):
573 if not self._issocketowner():
564 if not self._issocketowner():
574 self.ui.debug('%s is not owned, exiting.\n' % self._realaddress)
565 self.ui.debug('%s is not owned, exiting.\n' % self._realaddress)
575 return True
566 return True
576 if time.time() - self._lastactive > self._idletimeout:
567 if time.time() - self._lastactive > self._idletimeout:
577 self.ui.debug('being idle too long. exiting.\n')
568 self.ui.debug('being idle too long. exiting.\n')
578 return True
569 return True
579 return False
570 return False
580
571
581 def newconnection(self):
572 def newconnection(self):
582 self._lastactive = time.time()
573 self._lastactive = time.time()
583
574
584 def createcmdserver(self, repo, conn, fin, fout):
575 def createcmdserver(self, repo, conn, fin, fout):
585 return chgcmdserver(self.ui, repo, fin, fout, conn,
576 return chgcmdserver(self.ui, repo, fin, fout, conn,
586 self._hashstate, self._baseaddress)
577 self._hashstate, self._baseaddress)
587
578
588 def chgunixservice(ui, repo, opts):
579 def chgunixservice(ui, repo, opts):
589 # CHGINTERNALMARK is set by chg client. It is an indication of things are
580 # CHGINTERNALMARK is set by chg client. It is an indication of things are
590 # started by chg so other code can do things accordingly, like disabling
581 # started by chg so other code can do things accordingly, like disabling
591 # demandimport or detecting chg client started by chg client. When executed
582 # demandimport or detecting chg client started by chg client. When executed
592 # here, CHGINTERNALMARK is no longer useful and hence dropped to make
583 # here, CHGINTERNALMARK is no longer useful and hence dropped to make
593 # environ cleaner.
584 # environ cleaner.
594 if 'CHGINTERNALMARK' in encoding.environ:
585 if 'CHGINTERNALMARK' in encoding.environ:
595 del encoding.environ['CHGINTERNALMARK']
586 del encoding.environ['CHGINTERNALMARK']
596
587
597 if repo:
588 if repo:
598 # one chgserver can serve multiple repos. drop repo information
589 # one chgserver can serve multiple repos. drop repo information
599 ui.setconfig('bundle', 'mainreporoot', '', 'repo')
590 ui.setconfig('bundle', 'mainreporoot', '', 'repo')
600 h = chgunixservicehandler(ui)
591 h = chgunixservicehandler(ui)
601 return commandserver.unixforkingservice(ui, repo=None, opts=opts, handler=h)
592 return commandserver.unixforkingservice(ui, repo=None, opts=opts, handler=h)
@@ -1,1118 +1,1075 b''
1 # dispatch.py - command dispatching for mercurial
1 # dispatch.py - command dispatching for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import, print_function
8 from __future__ import absolute_import, print_function
9
9
10 import difflib
10 import difflib
11 import errno
11 import errno
12 import getopt
12 import getopt
13 import os
13 import os
14 import pdb
14 import pdb
15 import re
15 import re
16 import signal
16 import signal
17 import sys
17 import sys
18 import time
18 import time
19 import traceback
19 import traceback
20
20
21
21
22 from .i18n import _
22 from .i18n import _
23
23
24 from . import (
24 from . import (
25 cmdutil,
25 cmdutil,
26 color,
26 color,
27 commands,
27 commands,
28 demandimport,
28 demandimport,
29 encoding,
29 encoding,
30 error,
30 error,
31 extensions,
31 extensions,
32 fancyopts,
32 fancyopts,
33 help,
33 help,
34 hg,
34 hg,
35 hook,
35 hook,
36 profiling,
36 profiling,
37 pycompat,
37 pycompat,
38 registrar,
38 registrar,
39 scmutil,
39 scmutil,
40 ui as uimod,
40 ui as uimod,
41 util,
41 util,
42 )
42 )
43
43
44 unrecoverablewrite = registrar.command.unrecoverablewrite
44 unrecoverablewrite = registrar.command.unrecoverablewrite
45
45
46 class request(object):
46 class request(object):
47 def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
47 def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
48 ferr=None, prereposetups=None):
48 ferr=None, prereposetups=None):
49 self.args = args
49 self.args = args
50 self.ui = ui
50 self.ui = ui
51 self.repo = repo
51 self.repo = repo
52
52
53 # input/output/error streams
53 # input/output/error streams
54 self.fin = fin
54 self.fin = fin
55 self.fout = fout
55 self.fout = fout
56 self.ferr = ferr
56 self.ferr = ferr
57
57
58 # remember options pre-parsed by _earlyreqopt*()
58 # remember options pre-parsed by _earlyparseopts()
59 self.earlyoptions = {}
59 self.earlyoptions = {}
60
60
61 # reposetups which run before extensions, useful for chg to pre-fill
61 # reposetups which run before extensions, useful for chg to pre-fill
62 # low-level repo state (for example, changelog) before extensions.
62 # low-level repo state (for example, changelog) before extensions.
63 self.prereposetups = prereposetups or []
63 self.prereposetups = prereposetups or []
64
64
65 def _runexithandlers(self):
65 def _runexithandlers(self):
66 exc = None
66 exc = None
67 handlers = self.ui._exithandlers
67 handlers = self.ui._exithandlers
68 try:
68 try:
69 while handlers:
69 while handlers:
70 func, args, kwargs = handlers.pop()
70 func, args, kwargs = handlers.pop()
71 try:
71 try:
72 func(*args, **kwargs)
72 func(*args, **kwargs)
73 except: # re-raises below
73 except: # re-raises below
74 if exc is None:
74 if exc is None:
75 exc = sys.exc_info()[1]
75 exc = sys.exc_info()[1]
76 self.ui.warn(('error in exit handlers:\n'))
76 self.ui.warn(('error in exit handlers:\n'))
77 self.ui.traceback(force=True)
77 self.ui.traceback(force=True)
78 finally:
78 finally:
79 if exc is not None:
79 if exc is not None:
80 raise exc
80 raise exc
81
81
82 def run():
82 def run():
83 "run the command in sys.argv"
83 "run the command in sys.argv"
84 _initstdio()
84 _initstdio()
85 req = request(pycompat.sysargv[1:])
85 req = request(pycompat.sysargv[1:])
86 err = None
86 err = None
87 try:
87 try:
88 status = (dispatch(req) or 0) & 255
88 status = (dispatch(req) or 0) & 255
89 except error.StdioError as e:
89 except error.StdioError as e:
90 err = e
90 err = e
91 status = -1
91 status = -1
92 if util.safehasattr(req.ui, 'fout'):
92 if util.safehasattr(req.ui, 'fout'):
93 try:
93 try:
94 req.ui.fout.flush()
94 req.ui.fout.flush()
95 except IOError as e:
95 except IOError as e:
96 err = e
96 err = e
97 status = -1
97 status = -1
98 if util.safehasattr(req.ui, 'ferr'):
98 if util.safehasattr(req.ui, 'ferr'):
99 if err is not None and err.errno != errno.EPIPE:
99 if err is not None and err.errno != errno.EPIPE:
100 req.ui.ferr.write('abort: %s\n' %
100 req.ui.ferr.write('abort: %s\n' %
101 encoding.strtolocal(err.strerror))
101 encoding.strtolocal(err.strerror))
102 req.ui.ferr.flush()
102 req.ui.ferr.flush()
103 sys.exit(status & 255)
103 sys.exit(status & 255)
104
104
105 def _initstdio():
105 def _initstdio():
106 for fp in (sys.stdin, sys.stdout, sys.stderr):
106 for fp in (sys.stdin, sys.stdout, sys.stderr):
107 util.setbinary(fp)
107 util.setbinary(fp)
108
108
109 def _getsimilar(symbols, value):
109 def _getsimilar(symbols, value):
110 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
110 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
111 # The cutoff for similarity here is pretty arbitrary. It should
111 # The cutoff for similarity here is pretty arbitrary. It should
112 # probably be investigated and tweaked.
112 # probably be investigated and tweaked.
113 return [s for s in symbols if sim(s) > 0.6]
113 return [s for s in symbols if sim(s) > 0.6]
114
114
115 def _reportsimilar(write, similar):
115 def _reportsimilar(write, similar):
116 if len(similar) == 1:
116 if len(similar) == 1:
117 write(_("(did you mean %s?)\n") % similar[0])
117 write(_("(did you mean %s?)\n") % similar[0])
118 elif similar:
118 elif similar:
119 ss = ", ".join(sorted(similar))
119 ss = ", ".join(sorted(similar))
120 write(_("(did you mean one of %s?)\n") % ss)
120 write(_("(did you mean one of %s?)\n") % ss)
121
121
122 def _formatparse(write, inst):
122 def _formatparse(write, inst):
123 similar = []
123 similar = []
124 if isinstance(inst, error.UnknownIdentifier):
124 if isinstance(inst, error.UnknownIdentifier):
125 # make sure to check fileset first, as revset can invoke fileset
125 # make sure to check fileset first, as revset can invoke fileset
126 similar = _getsimilar(inst.symbols, inst.function)
126 similar = _getsimilar(inst.symbols, inst.function)
127 if len(inst.args) > 1:
127 if len(inst.args) > 1:
128 write(_("hg: parse error at %s: %s\n") %
128 write(_("hg: parse error at %s: %s\n") %
129 (inst.args[1], inst.args[0]))
129 (inst.args[1], inst.args[0]))
130 if (inst.args[0][0] == ' '):
130 if (inst.args[0][0] == ' '):
131 write(_("unexpected leading whitespace\n"))
131 write(_("unexpected leading whitespace\n"))
132 else:
132 else:
133 write(_("hg: parse error: %s\n") % inst.args[0])
133 write(_("hg: parse error: %s\n") % inst.args[0])
134 _reportsimilar(write, similar)
134 _reportsimilar(write, similar)
135 if inst.hint:
135 if inst.hint:
136 write(_("(%s)\n") % inst.hint)
136 write(_("(%s)\n") % inst.hint)
137
137
138 def _formatargs(args):
138 def _formatargs(args):
139 return ' '.join(util.shellquote(a) for a in args)
139 return ' '.join(util.shellquote(a) for a in args)
140
140
141 def dispatch(req):
141 def dispatch(req):
142 "run the command specified in req.args"
142 "run the command specified in req.args"
143 if req.ferr:
143 if req.ferr:
144 ferr = req.ferr
144 ferr = req.ferr
145 elif req.ui:
145 elif req.ui:
146 ferr = req.ui.ferr
146 ferr = req.ui.ferr
147 else:
147 else:
148 ferr = util.stderr
148 ferr = util.stderr
149
149
150 try:
150 try:
151 if not req.ui:
151 if not req.ui:
152 req.ui = uimod.ui.load()
152 req.ui = uimod.ui.load()
153 if req.ui.plain('strictflags'):
153 req.earlyoptions.update(_earlyparseopts(req.ui, req.args))
154 req.earlyoptions.update(_earlyparseopts(req.args))
154 if req.earlyoptions['traceback']:
155 if _earlyreqoptbool(req, 'traceback', ['--traceback']):
156 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
155 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
157
156
158 # set ui streams from the request
157 # set ui streams from the request
159 if req.fin:
158 if req.fin:
160 req.ui.fin = req.fin
159 req.ui.fin = req.fin
161 if req.fout:
160 if req.fout:
162 req.ui.fout = req.fout
161 req.ui.fout = req.fout
163 if req.ferr:
162 if req.ferr:
164 req.ui.ferr = req.ferr
163 req.ui.ferr = req.ferr
165 except error.Abort as inst:
164 except error.Abort as inst:
166 ferr.write(_("abort: %s\n") % inst)
165 ferr.write(_("abort: %s\n") % inst)
167 if inst.hint:
166 if inst.hint:
168 ferr.write(_("(%s)\n") % inst.hint)
167 ferr.write(_("(%s)\n") % inst.hint)
169 return -1
168 return -1
170 except error.ParseError as inst:
169 except error.ParseError as inst:
171 _formatparse(ferr.write, inst)
170 _formatparse(ferr.write, inst)
172 return -1
171 return -1
173
172
174 msg = _formatargs(req.args)
173 msg = _formatargs(req.args)
175 starttime = util.timer()
174 starttime = util.timer()
176 ret = None
175 ret = None
177 try:
176 try:
178 ret = _runcatch(req)
177 ret = _runcatch(req)
179 except error.ProgrammingError as inst:
178 except error.ProgrammingError as inst:
180 req.ui.warn(_('** ProgrammingError: %s\n') % inst)
179 req.ui.warn(_('** ProgrammingError: %s\n') % inst)
181 if inst.hint:
180 if inst.hint:
182 req.ui.warn(_('** (%s)\n') % inst.hint)
181 req.ui.warn(_('** (%s)\n') % inst.hint)
183 raise
182 raise
184 except KeyboardInterrupt as inst:
183 except KeyboardInterrupt as inst:
185 try:
184 try:
186 if isinstance(inst, error.SignalInterrupt):
185 if isinstance(inst, error.SignalInterrupt):
187 msg = _("killed!\n")
186 msg = _("killed!\n")
188 else:
187 else:
189 msg = _("interrupted!\n")
188 msg = _("interrupted!\n")
190 req.ui.warn(msg)
189 req.ui.warn(msg)
191 except error.SignalInterrupt:
190 except error.SignalInterrupt:
192 # maybe pager would quit without consuming all the output, and
191 # maybe pager would quit without consuming all the output, and
193 # SIGPIPE was raised. we cannot print anything in this case.
192 # SIGPIPE was raised. we cannot print anything in this case.
194 pass
193 pass
195 except IOError as inst:
194 except IOError as inst:
196 if inst.errno != errno.EPIPE:
195 if inst.errno != errno.EPIPE:
197 raise
196 raise
198 ret = -1
197 ret = -1
199 finally:
198 finally:
200 duration = util.timer() - starttime
199 duration = util.timer() - starttime
201 req.ui.flush()
200 req.ui.flush()
202 if req.ui.logblockedtimes:
201 if req.ui.logblockedtimes:
203 req.ui._blockedtimes['command_duration'] = duration * 1000
202 req.ui._blockedtimes['command_duration'] = duration * 1000
204 req.ui.log('uiblocked', 'ui blocked ms', **req.ui._blockedtimes)
203 req.ui.log('uiblocked', 'ui blocked ms', **req.ui._blockedtimes)
205 req.ui.log("commandfinish", "%s exited %d after %0.2f seconds\n",
204 req.ui.log("commandfinish", "%s exited %d after %0.2f seconds\n",
206 msg, ret or 0, duration)
205 msg, ret or 0, duration)
207 try:
206 try:
208 req._runexithandlers()
207 req._runexithandlers()
209 except: # exiting, so no re-raises
208 except: # exiting, so no re-raises
210 ret = ret or -1
209 ret = ret or -1
211 return ret
210 return ret
212
211
213 def _runcatch(req):
212 def _runcatch(req):
214 def catchterm(*args):
213 def catchterm(*args):
215 raise error.SignalInterrupt
214 raise error.SignalInterrupt
216
215
217 ui = req.ui
216 ui = req.ui
218 try:
217 try:
219 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
218 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
220 num = getattr(signal, name, None)
219 num = getattr(signal, name, None)
221 if num:
220 if num:
222 signal.signal(num, catchterm)
221 signal.signal(num, catchterm)
223 except ValueError:
222 except ValueError:
224 pass # happens if called in a thread
223 pass # happens if called in a thread
225
224
226 def _runcatchfunc():
225 def _runcatchfunc():
227 realcmd = None
226 realcmd = None
228 try:
227 try:
229 cmdargs = fancyopts.fancyopts(req.args[:], commands.globalopts, {})
228 cmdargs = fancyopts.fancyopts(req.args[:], commands.globalopts, {})
230 cmd = cmdargs[0]
229 cmd = cmdargs[0]
231 aliases, entry = cmdutil.findcmd(cmd, commands.table, False)
230 aliases, entry = cmdutil.findcmd(cmd, commands.table, False)
232 realcmd = aliases[0]
231 realcmd = aliases[0]
233 except (error.UnknownCommand, error.AmbiguousCommand,
232 except (error.UnknownCommand, error.AmbiguousCommand,
234 IndexError, getopt.GetoptError):
233 IndexError, getopt.GetoptError):
235 # Don't handle this here. We know the command is
234 # Don't handle this here. We know the command is
236 # invalid, but all we're worried about for now is that
235 # invalid, but all we're worried about for now is that
237 # it's not a command that server operators expect to
236 # it's not a command that server operators expect to
238 # be safe to offer to users in a sandbox.
237 # be safe to offer to users in a sandbox.
239 pass
238 pass
240 if realcmd == 'serve' and '--stdio' in cmdargs:
239 if realcmd == 'serve' and '--stdio' in cmdargs:
241 # We want to constrain 'hg serve --stdio' instances pretty
240 # We want to constrain 'hg serve --stdio' instances pretty
242 # closely, as many shared-ssh access tools want to grant
241 # closely, as many shared-ssh access tools want to grant
243 # access to run *only* 'hg -R $repo serve --stdio'. We
242 # access to run *only* 'hg -R $repo serve --stdio'. We
244 # restrict to exactly that set of arguments, and prohibit
243 # restrict to exactly that set of arguments, and prohibit
245 # any repo name that starts with '--' to prevent
244 # any repo name that starts with '--' to prevent
246 # shenanigans wherein a user does something like pass
245 # shenanigans wherein a user does something like pass
247 # --debugger or --config=ui.debugger=1 as a repo
246 # --debugger or --config=ui.debugger=1 as a repo
248 # name. This used to actually run the debugger.
247 # name. This used to actually run the debugger.
249 if (len(req.args) != 4 or
248 if (len(req.args) != 4 or
250 req.args[0] != '-R' or
249 req.args[0] != '-R' or
251 req.args[1].startswith('--') or
250 req.args[1].startswith('--') or
252 req.args[2] != 'serve' or
251 req.args[2] != 'serve' or
253 req.args[3] != '--stdio'):
252 req.args[3] != '--stdio'):
254 raise error.Abort(
253 raise error.Abort(
255 _('potentially unsafe serve --stdio invocation: %r') %
254 _('potentially unsafe serve --stdio invocation: %r') %
256 (req.args,))
255 (req.args,))
257
256
258 try:
257 try:
259 debugger = 'pdb'
258 debugger = 'pdb'
260 debugtrace = {
259 debugtrace = {
261 'pdb': pdb.set_trace
260 'pdb': pdb.set_trace
262 }
261 }
263 debugmortem = {
262 debugmortem = {
264 'pdb': pdb.post_mortem
263 'pdb': pdb.post_mortem
265 }
264 }
266
265
267 # read --config before doing anything else
266 # read --config before doing anything else
268 # (e.g. to change trust settings for reading .hg/hgrc)
267 # (e.g. to change trust settings for reading .hg/hgrc)
269 cfgs = _parseconfig(req.ui,
268 cfgs = _parseconfig(req.ui, req.earlyoptions['config'])
270 _earlyreqopt(req, 'config', ['--config']))
271
269
272 if req.repo:
270 if req.repo:
273 # copy configs that were passed on the cmdline (--config) to
271 # copy configs that were passed on the cmdline (--config) to
274 # the repo ui
272 # the repo ui
275 for sec, name, val in cfgs:
273 for sec, name, val in cfgs:
276 req.repo.ui.setconfig(sec, name, val, source='--config')
274 req.repo.ui.setconfig(sec, name, val, source='--config')
277
275
278 # developer config: ui.debugger
276 # developer config: ui.debugger
279 debugger = ui.config("ui", "debugger")
277 debugger = ui.config("ui", "debugger")
280 debugmod = pdb
278 debugmod = pdb
281 if not debugger or ui.plain():
279 if not debugger or ui.plain():
282 # if we are in HGPLAIN mode, then disable custom debugging
280 # if we are in HGPLAIN mode, then disable custom debugging
283 debugger = 'pdb'
281 debugger = 'pdb'
284 elif _earlyreqoptbool(req, 'debugger', ['--debugger']):
282 elif req.earlyoptions['debugger']:
285 # This import can be slow for fancy debuggers, so only
283 # This import can be slow for fancy debuggers, so only
286 # do it when absolutely necessary, i.e. when actual
284 # do it when absolutely necessary, i.e. when actual
287 # debugging has been requested
285 # debugging has been requested
288 with demandimport.deactivated():
286 with demandimport.deactivated():
289 try:
287 try:
290 debugmod = __import__(debugger)
288 debugmod = __import__(debugger)
291 except ImportError:
289 except ImportError:
292 pass # Leave debugmod = pdb
290 pass # Leave debugmod = pdb
293
291
294 debugtrace[debugger] = debugmod.set_trace
292 debugtrace[debugger] = debugmod.set_trace
295 debugmortem[debugger] = debugmod.post_mortem
293 debugmortem[debugger] = debugmod.post_mortem
296
294
297 # enter the debugger before command execution
295 # enter the debugger before command execution
298 if _earlyreqoptbool(req, 'debugger', ['--debugger']):
296 if req.earlyoptions['debugger']:
299 ui.warn(_("entering debugger - "
297 ui.warn(_("entering debugger - "
300 "type c to continue starting hg or h for help\n"))
298 "type c to continue starting hg or h for help\n"))
301
299
302 if (debugger != 'pdb' and
300 if (debugger != 'pdb' and
303 debugtrace[debugger] == debugtrace['pdb']):
301 debugtrace[debugger] == debugtrace['pdb']):
304 ui.warn(_("%s debugger specified "
302 ui.warn(_("%s debugger specified "
305 "but its module was not found\n") % debugger)
303 "but its module was not found\n") % debugger)
306 with demandimport.deactivated():
304 with demandimport.deactivated():
307 debugtrace[debugger]()
305 debugtrace[debugger]()
308 try:
306 try:
309 return _dispatch(req)
307 return _dispatch(req)
310 finally:
308 finally:
311 ui.flush()
309 ui.flush()
312 except: # re-raises
310 except: # re-raises
313 # enter the debugger when we hit an exception
311 # enter the debugger when we hit an exception
314 if _earlyreqoptbool(req, 'debugger', ['--debugger']):
312 if req.earlyoptions['debugger']:
315 traceback.print_exc()
313 traceback.print_exc()
316 debugmortem[debugger](sys.exc_info()[2])
314 debugmortem[debugger](sys.exc_info()[2])
317 raise
315 raise
318
316
319 return _callcatch(ui, _runcatchfunc)
317 return _callcatch(ui, _runcatchfunc)
320
318
321 def _callcatch(ui, func):
319 def _callcatch(ui, func):
322 """like scmutil.callcatch but handles more high-level exceptions about
320 """like scmutil.callcatch but handles more high-level exceptions about
323 config parsing and commands. besides, use handlecommandexception to handle
321 config parsing and commands. besides, use handlecommandexception to handle
324 uncaught exceptions.
322 uncaught exceptions.
325 """
323 """
326 try:
324 try:
327 return scmutil.callcatch(ui, func)
325 return scmutil.callcatch(ui, func)
328 except error.AmbiguousCommand as inst:
326 except error.AmbiguousCommand as inst:
329 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
327 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
330 (inst.args[0], " ".join(inst.args[1])))
328 (inst.args[0], " ".join(inst.args[1])))
331 except error.CommandError as inst:
329 except error.CommandError as inst:
332 if inst.args[0]:
330 if inst.args[0]:
333 ui.pager('help')
331 ui.pager('help')
334 msgbytes = pycompat.bytestr(inst.args[1])
332 msgbytes = pycompat.bytestr(inst.args[1])
335 ui.warn(_("hg %s: %s\n") % (inst.args[0], msgbytes))
333 ui.warn(_("hg %s: %s\n") % (inst.args[0], msgbytes))
336 commands.help_(ui, inst.args[0], full=False, command=True)
334 commands.help_(ui, inst.args[0], full=False, command=True)
337 else:
335 else:
338 ui.pager('help')
336 ui.pager('help')
339 ui.warn(_("hg: %s\n") % inst.args[1])
337 ui.warn(_("hg: %s\n") % inst.args[1])
340 commands.help_(ui, 'shortlist')
338 commands.help_(ui, 'shortlist')
341 except error.ParseError as inst:
339 except error.ParseError as inst:
342 _formatparse(ui.warn, inst)
340 _formatparse(ui.warn, inst)
343 return -1
341 return -1
344 except error.UnknownCommand as inst:
342 except error.UnknownCommand as inst:
345 nocmdmsg = _("hg: unknown command '%s'\n") % inst.args[0]
343 nocmdmsg = _("hg: unknown command '%s'\n") % inst.args[0]
346 try:
344 try:
347 # check if the command is in a disabled extension
345 # check if the command is in a disabled extension
348 # (but don't check for extensions themselves)
346 # (but don't check for extensions themselves)
349 formatted = help.formattedhelp(ui, commands, inst.args[0],
347 formatted = help.formattedhelp(ui, commands, inst.args[0],
350 unknowncmd=True)
348 unknowncmd=True)
351 ui.warn(nocmdmsg)
349 ui.warn(nocmdmsg)
352 ui.write(formatted)
350 ui.write(formatted)
353 except (error.UnknownCommand, error.Abort):
351 except (error.UnknownCommand, error.Abort):
354 suggested = False
352 suggested = False
355 if len(inst.args) == 2:
353 if len(inst.args) == 2:
356 sim = _getsimilar(inst.args[1], inst.args[0])
354 sim = _getsimilar(inst.args[1], inst.args[0])
357 if sim:
355 if sim:
358 ui.warn(nocmdmsg)
356 ui.warn(nocmdmsg)
359 _reportsimilar(ui.warn, sim)
357 _reportsimilar(ui.warn, sim)
360 suggested = True
358 suggested = True
361 if not suggested:
359 if not suggested:
362 ui.pager('help')
360 ui.pager('help')
363 ui.warn(nocmdmsg)
361 ui.warn(nocmdmsg)
364 commands.help_(ui, 'shortlist')
362 commands.help_(ui, 'shortlist')
365 except IOError:
363 except IOError:
366 raise
364 raise
367 except KeyboardInterrupt:
365 except KeyboardInterrupt:
368 raise
366 raise
369 except: # probably re-raises
367 except: # probably re-raises
370 if not handlecommandexception(ui):
368 if not handlecommandexception(ui):
371 raise
369 raise
372
370
373 return -1
371 return -1
374
372
375 def aliasargs(fn, givenargs):
373 def aliasargs(fn, givenargs):
376 args = []
374 args = []
377 # only care about alias 'args', ignore 'args' set by extensions.wrapfunction
375 # only care about alias 'args', ignore 'args' set by extensions.wrapfunction
378 if not util.safehasattr(fn, '_origfunc'):
376 if not util.safehasattr(fn, '_origfunc'):
379 args = getattr(fn, 'args', args)
377 args = getattr(fn, 'args', args)
380 if args:
378 if args:
381 cmd = ' '.join(map(util.shellquote, args))
379 cmd = ' '.join(map(util.shellquote, args))
382
380
383 nums = []
381 nums = []
384 def replacer(m):
382 def replacer(m):
385 num = int(m.group(1)) - 1
383 num = int(m.group(1)) - 1
386 nums.append(num)
384 nums.append(num)
387 if num < len(givenargs):
385 if num < len(givenargs):
388 return givenargs[num]
386 return givenargs[num]
389 raise error.Abort(_('too few arguments for command alias'))
387 raise error.Abort(_('too few arguments for command alias'))
390 cmd = re.sub(br'\$(\d+|\$)', replacer, cmd)
388 cmd = re.sub(br'\$(\d+|\$)', replacer, cmd)
391 givenargs = [x for i, x in enumerate(givenargs)
389 givenargs = [x for i, x in enumerate(givenargs)
392 if i not in nums]
390 if i not in nums]
393 args = pycompat.shlexsplit(cmd)
391 args = pycompat.shlexsplit(cmd)
394 return args + givenargs
392 return args + givenargs
395
393
396 def aliasinterpolate(name, args, cmd):
394 def aliasinterpolate(name, args, cmd):
397 '''interpolate args into cmd for shell aliases
395 '''interpolate args into cmd for shell aliases
398
396
399 This also handles $0, $@ and "$@".
397 This also handles $0, $@ and "$@".
400 '''
398 '''
401 # util.interpolate can't deal with "$@" (with quotes) because it's only
399 # util.interpolate can't deal with "$@" (with quotes) because it's only
402 # built to match prefix + patterns.
400 # built to match prefix + patterns.
403 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
401 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
404 replacemap['$0'] = name
402 replacemap['$0'] = name
405 replacemap['$$'] = '$'
403 replacemap['$$'] = '$'
406 replacemap['$@'] = ' '.join(args)
404 replacemap['$@'] = ' '.join(args)
407 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
405 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
408 # parameters, separated out into words. Emulate the same behavior here by
406 # parameters, separated out into words. Emulate the same behavior here by
409 # quoting the arguments individually. POSIX shells will then typically
407 # quoting the arguments individually. POSIX shells will then typically
410 # tokenize each argument into exactly one word.
408 # tokenize each argument into exactly one word.
411 replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
409 replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
412 # escape '\$' for regex
410 # escape '\$' for regex
413 regex = '|'.join(replacemap.keys()).replace('$', br'\$')
411 regex = '|'.join(replacemap.keys()).replace('$', br'\$')
414 r = re.compile(regex)
412 r = re.compile(regex)
415 return r.sub(lambda x: replacemap[x.group()], cmd)
413 return r.sub(lambda x: replacemap[x.group()], cmd)
416
414
417 class cmdalias(object):
415 class cmdalias(object):
418 def __init__(self, name, definition, cmdtable, source):
416 def __init__(self, name, definition, cmdtable, source):
419 self.name = self.cmd = name
417 self.name = self.cmd = name
420 self.cmdname = ''
418 self.cmdname = ''
421 self.definition = definition
419 self.definition = definition
422 self.fn = None
420 self.fn = None
423 self.givenargs = []
421 self.givenargs = []
424 self.opts = []
422 self.opts = []
425 self.help = ''
423 self.help = ''
426 self.badalias = None
424 self.badalias = None
427 self.unknowncmd = False
425 self.unknowncmd = False
428 self.source = source
426 self.source = source
429
427
430 try:
428 try:
431 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
429 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
432 for alias, e in cmdtable.iteritems():
430 for alias, e in cmdtable.iteritems():
433 if e is entry:
431 if e is entry:
434 self.cmd = alias
432 self.cmd = alias
435 break
433 break
436 self.shadows = True
434 self.shadows = True
437 except error.UnknownCommand:
435 except error.UnknownCommand:
438 self.shadows = False
436 self.shadows = False
439
437
440 if not self.definition:
438 if not self.definition:
441 self.badalias = _("no definition for alias '%s'") % self.name
439 self.badalias = _("no definition for alias '%s'") % self.name
442 return
440 return
443
441
444 if self.definition.startswith('!'):
442 if self.definition.startswith('!'):
445 self.shell = True
443 self.shell = True
446 def fn(ui, *args):
444 def fn(ui, *args):
447 env = {'HG_ARGS': ' '.join((self.name,) + args)}
445 env = {'HG_ARGS': ' '.join((self.name,) + args)}
448 def _checkvar(m):
446 def _checkvar(m):
449 if m.groups()[0] == '$':
447 if m.groups()[0] == '$':
450 return m.group()
448 return m.group()
451 elif int(m.groups()[0]) <= len(args):
449 elif int(m.groups()[0]) <= len(args):
452 return m.group()
450 return m.group()
453 else:
451 else:
454 ui.debug("No argument found for substitution "
452 ui.debug("No argument found for substitution "
455 "of %i variable in alias '%s' definition."
453 "of %i variable in alias '%s' definition."
456 % (int(m.groups()[0]), self.name))
454 % (int(m.groups()[0]), self.name))
457 return ''
455 return ''
458 cmd = re.sub(br'\$(\d+|\$)', _checkvar, self.definition[1:])
456 cmd = re.sub(br'\$(\d+|\$)', _checkvar, self.definition[1:])
459 cmd = aliasinterpolate(self.name, args, cmd)
457 cmd = aliasinterpolate(self.name, args, cmd)
460 return ui.system(cmd, environ=env,
458 return ui.system(cmd, environ=env,
461 blockedtag='alias_%s' % self.name)
459 blockedtag='alias_%s' % self.name)
462 self.fn = fn
460 self.fn = fn
463 return
461 return
464
462
465 try:
463 try:
466 args = pycompat.shlexsplit(self.definition)
464 args = pycompat.shlexsplit(self.definition)
467 except ValueError as inst:
465 except ValueError as inst:
468 self.badalias = (_("error in definition for alias '%s': %s")
466 self.badalias = (_("error in definition for alias '%s': %s")
469 % (self.name, inst))
467 % (self.name, inst))
470 return
468 return
471 self.cmdname = cmd = args.pop(0)
469 self.cmdname = cmd = args.pop(0)
472 self.givenargs = args
470 self.givenargs = args
473
471
474 for invalidarg in commands.earlyoptflags:
472 for invalidarg in commands.earlyoptflags:
475 if _earlygetopt([invalidarg], args):
473 if _earlygetopt([invalidarg], args):
476 self.badalias = (_("error in definition for alias '%s': %s may "
474 self.badalias = (_("error in definition for alias '%s': %s may "
477 "only be given on the command line")
475 "only be given on the command line")
478 % (self.name, invalidarg))
476 % (self.name, invalidarg))
479 return
477 return
480
478
481 try:
479 try:
482 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
480 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
483 if len(tableentry) > 2:
481 if len(tableentry) > 2:
484 self.fn, self.opts, self.help = tableentry
482 self.fn, self.opts, self.help = tableentry
485 else:
483 else:
486 self.fn, self.opts = tableentry
484 self.fn, self.opts = tableentry
487
485
488 if self.help.startswith("hg " + cmd):
486 if self.help.startswith("hg " + cmd):
489 # drop prefix in old-style help lines so hg shows the alias
487 # drop prefix in old-style help lines so hg shows the alias
490 self.help = self.help[4 + len(cmd):]
488 self.help = self.help[4 + len(cmd):]
491 self.__doc__ = self.fn.__doc__
489 self.__doc__ = self.fn.__doc__
492
490
493 except error.UnknownCommand:
491 except error.UnknownCommand:
494 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
492 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
495 % (self.name, cmd))
493 % (self.name, cmd))
496 self.unknowncmd = True
494 self.unknowncmd = True
497 except error.AmbiguousCommand:
495 except error.AmbiguousCommand:
498 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
496 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
499 % (self.name, cmd))
497 % (self.name, cmd))
500
498
501 @property
499 @property
502 def args(self):
500 def args(self):
503 args = pycompat.maplist(util.expandpath, self.givenargs)
501 args = pycompat.maplist(util.expandpath, self.givenargs)
504 return aliasargs(self.fn, args)
502 return aliasargs(self.fn, args)
505
503
506 def __getattr__(self, name):
504 def __getattr__(self, name):
507 adefaults = {r'norepo': True, r'cmdtype': unrecoverablewrite,
505 adefaults = {r'norepo': True, r'cmdtype': unrecoverablewrite,
508 r'optionalrepo': False, r'inferrepo': False}
506 r'optionalrepo': False, r'inferrepo': False}
509 if name not in adefaults:
507 if name not in adefaults:
510 raise AttributeError(name)
508 raise AttributeError(name)
511 if self.badalias or util.safehasattr(self, 'shell'):
509 if self.badalias or util.safehasattr(self, 'shell'):
512 return adefaults[name]
510 return adefaults[name]
513 return getattr(self.fn, name)
511 return getattr(self.fn, name)
514
512
515 def __call__(self, ui, *args, **opts):
513 def __call__(self, ui, *args, **opts):
516 if self.badalias:
514 if self.badalias:
517 hint = None
515 hint = None
518 if self.unknowncmd:
516 if self.unknowncmd:
519 try:
517 try:
520 # check if the command is in a disabled extension
518 # check if the command is in a disabled extension
521 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
519 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
522 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
520 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
523 except error.UnknownCommand:
521 except error.UnknownCommand:
524 pass
522 pass
525 raise error.Abort(self.badalias, hint=hint)
523 raise error.Abort(self.badalias, hint=hint)
526 if self.shadows:
524 if self.shadows:
527 ui.debug("alias '%s' shadows command '%s'\n" %
525 ui.debug("alias '%s' shadows command '%s'\n" %
528 (self.name, self.cmdname))
526 (self.name, self.cmdname))
529
527
530 ui.log('commandalias', "alias '%s' expands to '%s'\n",
528 ui.log('commandalias', "alias '%s' expands to '%s'\n",
531 self.name, self.definition)
529 self.name, self.definition)
532 if util.safehasattr(self, 'shell'):
530 if util.safehasattr(self, 'shell'):
533 return self.fn(ui, *args, **opts)
531 return self.fn(ui, *args, **opts)
534 else:
532 else:
535 try:
533 try:
536 return util.checksignature(self.fn)(ui, *args, **opts)
534 return util.checksignature(self.fn)(ui, *args, **opts)
537 except error.SignatureError:
535 except error.SignatureError:
538 args = ' '.join([self.cmdname] + self.args)
536 args = ' '.join([self.cmdname] + self.args)
539 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
537 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
540 raise
538 raise
541
539
542 class lazyaliasentry(object):
540 class lazyaliasentry(object):
543 """like a typical command entry (func, opts, help), but is lazy"""
541 """like a typical command entry (func, opts, help), but is lazy"""
544
542
545 def __init__(self, name, definition, cmdtable, source):
543 def __init__(self, name, definition, cmdtable, source):
546 self.name = name
544 self.name = name
547 self.definition = definition
545 self.definition = definition
548 self.cmdtable = cmdtable.copy()
546 self.cmdtable = cmdtable.copy()
549 self.source = source
547 self.source = source
550
548
551 @util.propertycache
549 @util.propertycache
552 def _aliasdef(self):
550 def _aliasdef(self):
553 return cmdalias(self.name, self.definition, self.cmdtable, self.source)
551 return cmdalias(self.name, self.definition, self.cmdtable, self.source)
554
552
555 def __getitem__(self, n):
553 def __getitem__(self, n):
556 aliasdef = self._aliasdef
554 aliasdef = self._aliasdef
557 if n == 0:
555 if n == 0:
558 return aliasdef
556 return aliasdef
559 elif n == 1:
557 elif n == 1:
560 return aliasdef.opts
558 return aliasdef.opts
561 elif n == 2:
559 elif n == 2:
562 return aliasdef.help
560 return aliasdef.help
563 else:
561 else:
564 raise IndexError
562 raise IndexError
565
563
566 def __iter__(self):
564 def __iter__(self):
567 for i in range(3):
565 for i in range(3):
568 yield self[i]
566 yield self[i]
569
567
570 def __len__(self):
568 def __len__(self):
571 return 3
569 return 3
572
570
573 def addaliases(ui, cmdtable):
571 def addaliases(ui, cmdtable):
574 # aliases are processed after extensions have been loaded, so they
572 # aliases are processed after extensions have been loaded, so they
575 # may use extension commands. Aliases can also use other alias definitions,
573 # may use extension commands. Aliases can also use other alias definitions,
576 # but only if they have been defined prior to the current definition.
574 # but only if they have been defined prior to the current definition.
577 for alias, definition in ui.configitems('alias'):
575 for alias, definition in ui.configitems('alias'):
578 try:
576 try:
579 if cmdtable[alias].definition == definition:
577 if cmdtable[alias].definition == definition:
580 continue
578 continue
581 except (KeyError, AttributeError):
579 except (KeyError, AttributeError):
582 # definition might not exist or it might not be a cmdalias
580 # definition might not exist or it might not be a cmdalias
583 pass
581 pass
584
582
585 source = ui.configsource('alias', alias)
583 source = ui.configsource('alias', alias)
586 entry = lazyaliasentry(alias, definition, cmdtable, source)
584 entry = lazyaliasentry(alias, definition, cmdtable, source)
587 cmdtable[alias] = entry
585 cmdtable[alias] = entry
588
586
589 def _parse(ui, args):
587 def _parse(ui, args):
590 options = {}
588 options = {}
591 cmdoptions = {}
589 cmdoptions = {}
592
590
593 try:
591 try:
594 args = fancyopts.fancyopts(args, commands.globalopts, options)
592 args = fancyopts.fancyopts(args, commands.globalopts, options)
595 except getopt.GetoptError as inst:
593 except getopt.GetoptError as inst:
596 raise error.CommandError(None, inst)
594 raise error.CommandError(None, inst)
597
595
598 if args:
596 if args:
599 cmd, args = args[0], args[1:]
597 cmd, args = args[0], args[1:]
600 aliases, entry = cmdutil.findcmd(cmd, commands.table,
598 aliases, entry = cmdutil.findcmd(cmd, commands.table,
601 ui.configbool("ui", "strict"))
599 ui.configbool("ui", "strict"))
602 cmd = aliases[0]
600 cmd = aliases[0]
603 args = aliasargs(entry[0], args)
601 args = aliasargs(entry[0], args)
604 defaults = ui.config("defaults", cmd)
602 defaults = ui.config("defaults", cmd)
605 if defaults:
603 if defaults:
606 args = pycompat.maplist(
604 args = pycompat.maplist(
607 util.expandpath, pycompat.shlexsplit(defaults)) + args
605 util.expandpath, pycompat.shlexsplit(defaults)) + args
608 c = list(entry[1])
606 c = list(entry[1])
609 else:
607 else:
610 cmd = None
608 cmd = None
611 c = []
609 c = []
612
610
613 # combine global options into local
611 # combine global options into local
614 for o in commands.globalopts:
612 for o in commands.globalopts:
615 c.append((o[0], o[1], options[o[1]], o[3]))
613 c.append((o[0], o[1], options[o[1]], o[3]))
616
614
617 try:
615 try:
618 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
616 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
619 except getopt.GetoptError as inst:
617 except getopt.GetoptError as inst:
620 raise error.CommandError(cmd, inst)
618 raise error.CommandError(cmd, inst)
621
619
622 # separate global options back out
620 # separate global options back out
623 for o in commands.globalopts:
621 for o in commands.globalopts:
624 n = o[1]
622 n = o[1]
625 options[n] = cmdoptions[n]
623 options[n] = cmdoptions[n]
626 del cmdoptions[n]
624 del cmdoptions[n]
627
625
628 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
626 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
629
627
630 def _parseconfig(ui, config):
628 def _parseconfig(ui, config):
631 """parse the --config options from the command line"""
629 """parse the --config options from the command line"""
632 configs = []
630 configs = []
633
631
634 for cfg in config:
632 for cfg in config:
635 try:
633 try:
636 name, value = [cfgelem.strip()
634 name, value = [cfgelem.strip()
637 for cfgelem in cfg.split('=', 1)]
635 for cfgelem in cfg.split('=', 1)]
638 section, name = name.split('.', 1)
636 section, name = name.split('.', 1)
639 if not section or not name:
637 if not section or not name:
640 raise IndexError
638 raise IndexError
641 ui.setconfig(section, name, value, '--config')
639 ui.setconfig(section, name, value, '--config')
642 configs.append((section, name, value))
640 configs.append((section, name, value))
643 except (IndexError, ValueError):
641 except (IndexError, ValueError):
644 raise error.Abort(_('malformed --config option: %r '
642 raise error.Abort(_('malformed --config option: %r '
645 '(use --config section.name=value)') % cfg)
643 '(use --config section.name=value)') % cfg)
646
644
647 return configs
645 return configs
648
646
649 def _earlyparseopts(args):
647 def _earlyparseopts(ui, args):
650 options = {}
648 options = {}
651 fancyopts.fancyopts(args, commands.globalopts, options,
649 fancyopts.fancyopts(args, commands.globalopts, options,
652 gnu=False, early=True,
650 gnu=not ui.plain('strictflags'), early=True,
653 optaliases={'repository': ['repo']})
651 optaliases={'repository': ['repo']})
654 return options
652 return options
655
653
656 def _earlygetopt(aliases, args, strip=True):
654 def _earlygetopt(aliases, args, strip=True):
657 """Return list of values for an option (or aliases).
655 """Return list of values for an option (or aliases).
658
656
659 The values are listed in the order they appear in args.
657 The values are listed in the order they appear in args.
660 The options and values are removed from args if strip=True.
658 The options and values are removed from args if strip=True.
661
659
662 >>> args = [b'x', b'--cwd', b'foo', b'y']
660 >>> args = [b'x', b'--cwd', b'foo', b'y']
663 >>> _earlygetopt([b'--cwd'], args), args
661 >>> _earlygetopt([b'--cwd'], args), args
664 (['foo'], ['x', 'y'])
662 (['foo'], ['x', 'y'])
665
663
666 >>> args = [b'x', b'--cwd=bar', b'y']
664 >>> args = [b'x', b'--cwd=bar', b'y']
667 >>> _earlygetopt([b'--cwd'], args), args
665 >>> _earlygetopt([b'--cwd'], args), args
668 (['bar'], ['x', 'y'])
666 (['bar'], ['x', 'y'])
669
667
670 >>> args = [b'x', b'--cwd=bar', b'y']
668 >>> args = [b'x', b'--cwd=bar', b'y']
671 >>> _earlygetopt([b'--cwd'], args, strip=False), args
669 >>> _earlygetopt([b'--cwd'], args, strip=False), args
672 (['bar'], ['x', '--cwd=bar', 'y'])
670 (['bar'], ['x', '--cwd=bar', 'y'])
673
671
674 >>> args = [b'x', b'-R', b'foo', b'y']
672 >>> args = [b'x', b'-R', b'foo', b'y']
675 >>> _earlygetopt([b'-R'], args), args
673 >>> _earlygetopt([b'-R'], args), args
676 (['foo'], ['x', 'y'])
674 (['foo'], ['x', 'y'])
677
675
678 >>> args = [b'x', b'-R', b'foo', b'y']
676 >>> args = [b'x', b'-R', b'foo', b'y']
679 >>> _earlygetopt([b'-R'], args, strip=False), args
677 >>> _earlygetopt([b'-R'], args, strip=False), args
680 (['foo'], ['x', '-R', 'foo', 'y'])
678 (['foo'], ['x', '-R', 'foo', 'y'])
681
679
682 >>> args = [b'x', b'-Rbar', b'y']
680 >>> args = [b'x', b'-Rbar', b'y']
683 >>> _earlygetopt([b'-R'], args), args
681 >>> _earlygetopt([b'-R'], args), args
684 (['bar'], ['x', 'y'])
682 (['bar'], ['x', 'y'])
685
683
686 >>> args = [b'x', b'-Rbar', b'y']
684 >>> args = [b'x', b'-Rbar', b'y']
687 >>> _earlygetopt([b'-R'], args, strip=False), args
685 >>> _earlygetopt([b'-R'], args, strip=False), args
688 (['bar'], ['x', '-Rbar', 'y'])
686 (['bar'], ['x', '-Rbar', 'y'])
689
687
690 >>> args = [b'x', b'-R=bar', b'y']
688 >>> args = [b'x', b'-R=bar', b'y']
691 >>> _earlygetopt([b'-R'], args), args
689 >>> _earlygetopt([b'-R'], args), args
692 (['=bar'], ['x', 'y'])
690 (['=bar'], ['x', 'y'])
693
691
694 >>> args = [b'x', b'-R', b'--', b'y']
692 >>> args = [b'x', b'-R', b'--', b'y']
695 >>> _earlygetopt([b'-R'], args), args
693 >>> _earlygetopt([b'-R'], args), args
696 ([], ['x', '-R', '--', 'y'])
694 ([], ['x', '-R', '--', 'y'])
697 """
695 """
698 try:
696 try:
699 argcount = args.index("--")
697 argcount = args.index("--")
700 except ValueError:
698 except ValueError:
701 argcount = len(args)
699 argcount = len(args)
702 shortopts = [opt for opt in aliases if len(opt) == 2]
700 shortopts = [opt for opt in aliases if len(opt) == 2]
703 values = []
701 values = []
704 pos = 0
702 pos = 0
705 while pos < argcount:
703 while pos < argcount:
706 fullarg = arg = args[pos]
704 fullarg = arg = args[pos]
707 equals = -1
705 equals = -1
708 if arg.startswith('--'):
706 if arg.startswith('--'):
709 equals = arg.find('=')
707 equals = arg.find('=')
710 if equals > -1:
708 if equals > -1:
711 arg = arg[:equals]
709 arg = arg[:equals]
712 if arg in aliases:
710 if arg in aliases:
713 if equals > -1:
711 if equals > -1:
714 values.append(fullarg[equals + 1:])
712 values.append(fullarg[equals + 1:])
715 if strip:
713 if strip:
716 del args[pos]
714 del args[pos]
717 argcount -= 1
715 argcount -= 1
718 else:
716 else:
719 pos += 1
717 pos += 1
720 else:
718 else:
721 if pos + 1 >= argcount:
719 if pos + 1 >= argcount:
722 # ignore and let getopt report an error if there is no value
720 # ignore and let getopt report an error if there is no value
723 break
721 break
724 values.append(args[pos + 1])
722 values.append(args[pos + 1])
725 if strip:
723 if strip:
726 del args[pos:pos + 2]
724 del args[pos:pos + 2]
727 argcount -= 2
725 argcount -= 2
728 else:
726 else:
729 pos += 2
727 pos += 2
730 elif arg[:2] in shortopts:
728 elif arg[:2] in shortopts:
731 # short option can have no following space, e.g. hg log -Rfoo
729 # short option can have no following space, e.g. hg log -Rfoo
732 values.append(args[pos][2:])
730 values.append(args[pos][2:])
733 if strip:
731 if strip:
734 del args[pos]
732 del args[pos]
735 argcount -= 1
733 argcount -= 1
736 else:
734 else:
737 pos += 1
735 pos += 1
738 else:
736 else:
739 pos += 1
737 pos += 1
740 return values
738 return values
741
739
742 def _earlyreqopt(req, name, aliases):
743 """Peek a list option without using a full options table"""
744 if req.ui.plain('strictflags'):
745 return req.earlyoptions[name]
746 values = _earlygetopt(aliases, req.args, strip=False)
747 req.earlyoptions[name] = values
748 return values
749
750 def _earlyreqoptstr(req, name, aliases):
751 """Peek a string option without using a full options table"""
752 if req.ui.plain('strictflags'):
753 return req.earlyoptions[name]
754 value = (_earlygetopt(aliases, req.args, strip=False) or [''])[-1]
755 req.earlyoptions[name] = value
756 return value
757
758 def _earlyreqoptbool(req, name, aliases):
759 """Peek a boolean option without using a full options table
760
761 >>> req = request([b'x', b'--debugger'], uimod.ui())
762 >>> _earlyreqoptbool(req, b'debugger', [b'--debugger'])
763 True
764
765 >>> req = request([b'x', b'--', b'--debugger'], uimod.ui())
766 >>> _earlyreqoptbool(req, b'debugger', [b'--debugger'])
767 """
768 if req.ui.plain('strictflags'):
769 return req.earlyoptions[name]
770 try:
771 argcount = req.args.index("--")
772 except ValueError:
773 argcount = len(req.args)
774 value = None
775 pos = 0
776 while pos < argcount:
777 arg = req.args[pos]
778 if arg in aliases:
779 value = True
780 pos += 1
781 req.earlyoptions[name] = value
782 return value
783
784 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
740 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
785 # run pre-hook, and abort if it fails
741 # run pre-hook, and abort if it fails
786 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
742 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
787 pats=cmdpats, opts=cmdoptions)
743 pats=cmdpats, opts=cmdoptions)
788 try:
744 try:
789 ret = _runcommand(ui, options, cmd, d)
745 ret = _runcommand(ui, options, cmd, d)
790 # run post-hook, passing command result
746 # run post-hook, passing command result
791 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
747 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
792 result=ret, pats=cmdpats, opts=cmdoptions)
748 result=ret, pats=cmdpats, opts=cmdoptions)
793 except Exception:
749 except Exception:
794 # run failure hook and re-raise
750 # run failure hook and re-raise
795 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
751 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
796 pats=cmdpats, opts=cmdoptions)
752 pats=cmdpats, opts=cmdoptions)
797 raise
753 raise
798 return ret
754 return ret
799
755
800 def _getlocal(ui, rpath, wd=None):
756 def _getlocal(ui, rpath, wd=None):
801 """Return (path, local ui object) for the given target path.
757 """Return (path, local ui object) for the given target path.
802
758
803 Takes paths in [cwd]/.hg/hgrc into account."
759 Takes paths in [cwd]/.hg/hgrc into account."
804 """
760 """
805 if wd is None:
761 if wd is None:
806 try:
762 try:
807 wd = pycompat.getcwd()
763 wd = pycompat.getcwd()
808 except OSError as e:
764 except OSError as e:
809 raise error.Abort(_("error getting current working directory: %s") %
765 raise error.Abort(_("error getting current working directory: %s") %
810 encoding.strtolocal(e.strerror))
766 encoding.strtolocal(e.strerror))
811 path = cmdutil.findrepo(wd) or ""
767 path = cmdutil.findrepo(wd) or ""
812 if not path:
768 if not path:
813 lui = ui
769 lui = ui
814 else:
770 else:
815 lui = ui.copy()
771 lui = ui.copy()
816 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
772 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
817
773
818 if rpath:
774 if rpath:
819 path = lui.expandpath(rpath)
775 path = lui.expandpath(rpath)
820 lui = ui.copy()
776 lui = ui.copy()
821 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
777 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
822
778
823 return path, lui
779 return path, lui
824
780
825 def _checkshellalias(lui, ui, args):
781 def _checkshellalias(lui, ui, args):
826 """Return the function to run the shell alias, if it is required"""
782 """Return the function to run the shell alias, if it is required"""
827 options = {}
783 options = {}
828
784
829 try:
785 try:
830 args = fancyopts.fancyopts(args, commands.globalopts, options)
786 args = fancyopts.fancyopts(args, commands.globalopts, options)
831 except getopt.GetoptError:
787 except getopt.GetoptError:
832 return
788 return
833
789
834 if not args:
790 if not args:
835 return
791 return
836
792
837 cmdtable = commands.table
793 cmdtable = commands.table
838
794
839 cmd = args[0]
795 cmd = args[0]
840 try:
796 try:
841 strict = ui.configbool("ui", "strict")
797 strict = ui.configbool("ui", "strict")
842 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
798 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
843 except (error.AmbiguousCommand, error.UnknownCommand):
799 except (error.AmbiguousCommand, error.UnknownCommand):
844 return
800 return
845
801
846 cmd = aliases[0]
802 cmd = aliases[0]
847 fn = entry[0]
803 fn = entry[0]
848
804
849 if cmd and util.safehasattr(fn, 'shell'):
805 if cmd and util.safehasattr(fn, 'shell'):
850 # shell alias shouldn't receive early options which are consumed by hg
806 # shell alias shouldn't receive early options which are consumed by hg
851 args = args[:]
807 args = args[:]
852 _earlygetopt(commands.earlyoptflags, args, strip=True)
808 _earlygetopt(commands.earlyoptflags, args, strip=True)
853 d = lambda: fn(ui, *args[1:])
809 d = lambda: fn(ui, *args[1:])
854 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
810 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
855 [], {})
811 [], {})
856
812
857 def _dispatch(req):
813 def _dispatch(req):
858 args = req.args
814 args = req.args
859 ui = req.ui
815 ui = req.ui
860
816
861 # check for cwd
817 # check for cwd
862 cwd = _earlyreqoptstr(req, 'cwd', ['--cwd'])
818 cwd = req.earlyoptions['cwd']
863 if cwd:
819 if cwd:
864 os.chdir(cwd)
820 os.chdir(cwd)
865
821
866 rpath = _earlyreqoptstr(req, 'repository', ["-R", "--repository", "--repo"])
822 rpath = req.earlyoptions['repository']
867 path, lui = _getlocal(ui, rpath)
823 path, lui = _getlocal(ui, rpath)
868
824
869 uis = {ui, lui}
825 uis = {ui, lui}
870
826
871 if req.repo:
827 if req.repo:
872 uis.add(req.repo.ui)
828 uis.add(req.repo.ui)
873
829
874 if _earlyreqoptbool(req, 'profile', ['--profile']):
830 if req.earlyoptions['profile']:
875 for ui_ in uis:
831 for ui_ in uis:
876 ui_.setconfig('profiling', 'enabled', 'true', '--profile')
832 ui_.setconfig('profiling', 'enabled', 'true', '--profile')
877
833
878 profile = lui.configbool('profiling', 'enabled')
834 profile = lui.configbool('profiling', 'enabled')
879 with profiling.profile(lui, enabled=profile) as profiler:
835 with profiling.profile(lui, enabled=profile) as profiler:
880 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
836 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
881 # reposetup
837 # reposetup
882 extensions.loadall(lui)
838 extensions.loadall(lui)
883 # Propagate any changes to lui.__class__ by extensions
839 # Propagate any changes to lui.__class__ by extensions
884 ui.__class__ = lui.__class__
840 ui.__class__ = lui.__class__
885
841
886 # (uisetup and extsetup are handled in extensions.loadall)
842 # (uisetup and extsetup are handled in extensions.loadall)
887
843
888 # (reposetup is handled in hg.repository)
844 # (reposetup is handled in hg.repository)
889
845
890 addaliases(lui, commands.table)
846 addaliases(lui, commands.table)
891
847
892 # All aliases and commands are completely defined, now.
848 # All aliases and commands are completely defined, now.
893 # Check abbreviation/ambiguity of shell alias.
849 # Check abbreviation/ambiguity of shell alias.
894 shellaliasfn = _checkshellalias(lui, ui, args)
850 shellaliasfn = _checkshellalias(lui, ui, args)
895 if shellaliasfn:
851 if shellaliasfn:
896 return shellaliasfn()
852 return shellaliasfn()
897
853
898 # check for fallback encoding
854 # check for fallback encoding
899 fallback = lui.config('ui', 'fallbackencoding')
855 fallback = lui.config('ui', 'fallbackencoding')
900 if fallback:
856 if fallback:
901 encoding.fallbackencoding = fallback
857 encoding.fallbackencoding = fallback
902
858
903 fullargs = args
859 fullargs = args
904 cmd, func, args, options, cmdoptions = _parse(lui, args)
860 cmd, func, args, options, cmdoptions = _parse(lui, args)
905
861
906 if options["config"] != req.earlyoptions["config"]:
862 if options["config"] != req.earlyoptions["config"]:
907 raise error.Abort(_("option --config may not be abbreviated!"))
863 raise error.Abort(_("option --config may not be abbreviated!"))
908 if options["cwd"] != req.earlyoptions["cwd"]:
864 if options["cwd"] != req.earlyoptions["cwd"]:
909 raise error.Abort(_("option --cwd may not be abbreviated!"))
865 raise error.Abort(_("option --cwd may not be abbreviated!"))
910 if options["repository"] != req.earlyoptions["repository"]:
866 if options["repository"] != req.earlyoptions["repository"]:
911 raise error.Abort(_(
867 raise error.Abort(_(
912 "option -R has to be separated from other options (e.g. not "
868 "option -R has to be separated from other options (e.g. not "
913 "-qR) and --repository may only be abbreviated as --repo!"))
869 "-qR) and --repository may only be abbreviated as --repo!"))
914 if options["debugger"] != req.earlyoptions["debugger"]:
870 if options["debugger"] != req.earlyoptions["debugger"]:
915 raise error.Abort(_("option --debugger may not be abbreviated!"))
871 raise error.Abort(_("option --debugger may not be abbreviated!"))
916 # don't validate --profile/--traceback, which can be enabled from now
872 # don't validate --profile/--traceback, which can be enabled from now
917
873
918 if options["encoding"]:
874 if options["encoding"]:
919 encoding.encoding = options["encoding"]
875 encoding.encoding = options["encoding"]
920 if options["encodingmode"]:
876 if options["encodingmode"]:
921 encoding.encodingmode = options["encodingmode"]
877 encoding.encodingmode = options["encodingmode"]
922 if options["time"]:
878 if options["time"]:
923 def get_times():
879 def get_times():
924 t = os.times()
880 t = os.times()
925 if t[4] == 0.0:
881 if t[4] == 0.0:
926 # Windows leaves this as zero, so use time.clock()
882 # Windows leaves this as zero, so use time.clock()
927 t = (t[0], t[1], t[2], t[3], time.clock())
883 t = (t[0], t[1], t[2], t[3], time.clock())
928 return t
884 return t
929 s = get_times()
885 s = get_times()
930 def print_time():
886 def print_time():
931 t = get_times()
887 t = get_times()
932 ui.warn(
888 ui.warn(
933 _("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
889 _("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
934 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
890 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
935 ui.atexit(print_time)
891 ui.atexit(print_time)
936 if options["profile"]:
892 if options["profile"]:
937 profiler.start()
893 profiler.start()
938
894
939 if options['verbose'] or options['debug'] or options['quiet']:
895 if options['verbose'] or options['debug'] or options['quiet']:
940 for opt in ('verbose', 'debug', 'quiet'):
896 for opt in ('verbose', 'debug', 'quiet'):
941 val = str(bool(options[opt]))
897 val = str(bool(options[opt]))
942 if pycompat.ispy3:
898 if pycompat.ispy3:
943 val = val.encode('ascii')
899 val = val.encode('ascii')
944 for ui_ in uis:
900 for ui_ in uis:
945 ui_.setconfig('ui', opt, val, '--' + opt)
901 ui_.setconfig('ui', opt, val, '--' + opt)
946
902
947 if options['traceback']:
903 if options['traceback']:
948 for ui_ in uis:
904 for ui_ in uis:
949 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
905 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
950
906
951 if options['noninteractive']:
907 if options['noninteractive']:
952 for ui_ in uis:
908 for ui_ in uis:
953 ui_.setconfig('ui', 'interactive', 'off', '-y')
909 ui_.setconfig('ui', 'interactive', 'off', '-y')
954
910
955 if cmdoptions.get('insecure', False):
911 if cmdoptions.get('insecure', False):
956 for ui_ in uis:
912 for ui_ in uis:
957 ui_.insecureconnections = True
913 ui_.insecureconnections = True
958
914
959 # setup color handling before pager, because setting up pager
915 # setup color handling before pager, because setting up pager
960 # might cause incorrect console information
916 # might cause incorrect console information
961 coloropt = options['color']
917 coloropt = options['color']
962 for ui_ in uis:
918 for ui_ in uis:
963 if coloropt:
919 if coloropt:
964 ui_.setconfig('ui', 'color', coloropt, '--color')
920 ui_.setconfig('ui', 'color', coloropt, '--color')
965 color.setup(ui_)
921 color.setup(ui_)
966
922
967 if util.parsebool(options['pager']):
923 if util.parsebool(options['pager']):
968 # ui.pager() expects 'internal-always-' prefix in this case
924 # ui.pager() expects 'internal-always-' prefix in this case
969 ui.pager('internal-always-' + cmd)
925 ui.pager('internal-always-' + cmd)
970 elif options['pager'] != 'auto':
926 elif options['pager'] != 'auto':
971 for ui_ in uis:
927 for ui_ in uis:
972 ui_.disablepager()
928 ui_.disablepager()
973
929
974 if options['version']:
930 if options['version']:
975 return commands.version_(ui)
931 return commands.version_(ui)
976 if options['help']:
932 if options['help']:
977 return commands.help_(ui, cmd, command=cmd is not None)
933 return commands.help_(ui, cmd, command=cmd is not None)
978 elif not cmd:
934 elif not cmd:
979 return commands.help_(ui, 'shortlist')
935 return commands.help_(ui, 'shortlist')
980
936
981 repo = None
937 repo = None
982 cmdpats = args[:]
938 cmdpats = args[:]
983 if not func.norepo:
939 if not func.norepo:
984 # use the repo from the request only if we don't have -R
940 # use the repo from the request only if we don't have -R
985 if not rpath and not cwd:
941 if not rpath and not cwd:
986 repo = req.repo
942 repo = req.repo
987
943
988 if repo:
944 if repo:
989 # set the descriptors of the repo ui to those of ui
945 # set the descriptors of the repo ui to those of ui
990 repo.ui.fin = ui.fin
946 repo.ui.fin = ui.fin
991 repo.ui.fout = ui.fout
947 repo.ui.fout = ui.fout
992 repo.ui.ferr = ui.ferr
948 repo.ui.ferr = ui.ferr
993 else:
949 else:
994 try:
950 try:
995 repo = hg.repository(ui, path=path,
951 repo = hg.repository(ui, path=path,
996 presetupfuncs=req.prereposetups)
952 presetupfuncs=req.prereposetups)
997 if not repo.local():
953 if not repo.local():
998 raise error.Abort(_("repository '%s' is not local")
954 raise error.Abort(_("repository '%s' is not local")
999 % path)
955 % path)
1000 repo.ui.setconfig("bundle", "mainreporoot", repo.root,
956 repo.ui.setconfig("bundle", "mainreporoot", repo.root,
1001 'repo')
957 'repo')
1002 except error.RequirementError:
958 except error.RequirementError:
1003 raise
959 raise
1004 except error.RepoError:
960 except error.RepoError:
1005 if rpath: # invalid -R path
961 if rpath: # invalid -R path
1006 raise
962 raise
1007 if not func.optionalrepo:
963 if not func.optionalrepo:
1008 if func.inferrepo and args and not path:
964 if func.inferrepo and args and not path:
1009 # try to infer -R from command args
965 # try to infer -R from command args
1010 repos = pycompat.maplist(cmdutil.findrepo, args)
966 repos = pycompat.maplist(cmdutil.findrepo, args)
1011 guess = repos[0]
967 guess = repos[0]
1012 if guess and repos.count(guess) == len(repos):
968 if guess and repos.count(guess) == len(repos):
1013 req.args = ['--repository', guess] + fullargs
969 req.args = ['--repository', guess] + fullargs
970 req.earlyoptions['repository'] = guess
1014 return _dispatch(req)
971 return _dispatch(req)
1015 if not path:
972 if not path:
1016 raise error.RepoError(_("no repository found in"
973 raise error.RepoError(_("no repository found in"
1017 " '%s' (.hg not found)")
974 " '%s' (.hg not found)")
1018 % pycompat.getcwd())
975 % pycompat.getcwd())
1019 raise
976 raise
1020 if repo:
977 if repo:
1021 ui = repo.ui
978 ui = repo.ui
1022 if options['hidden']:
979 if options['hidden']:
1023 repo = repo.unfiltered()
980 repo = repo.unfiltered()
1024 args.insert(0, repo)
981 args.insert(0, repo)
1025 elif rpath:
982 elif rpath:
1026 ui.warn(_("warning: --repository ignored\n"))
983 ui.warn(_("warning: --repository ignored\n"))
1027
984
1028 msg = _formatargs(fullargs)
985 msg = _formatargs(fullargs)
1029 ui.log("command", '%s\n', msg)
986 ui.log("command", '%s\n', msg)
1030 strcmdopt = pycompat.strkwargs(cmdoptions)
987 strcmdopt = pycompat.strkwargs(cmdoptions)
1031 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
988 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
1032 try:
989 try:
1033 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
990 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
1034 cmdpats, cmdoptions)
991 cmdpats, cmdoptions)
1035 finally:
992 finally:
1036 if repo and repo != req.repo:
993 if repo and repo != req.repo:
1037 repo.close()
994 repo.close()
1038
995
1039 def _runcommand(ui, options, cmd, cmdfunc):
996 def _runcommand(ui, options, cmd, cmdfunc):
1040 """Run a command function, possibly with profiling enabled."""
997 """Run a command function, possibly with profiling enabled."""
1041 try:
998 try:
1042 return cmdfunc()
999 return cmdfunc()
1043 except error.SignatureError:
1000 except error.SignatureError:
1044 raise error.CommandError(cmd, _('invalid arguments'))
1001 raise error.CommandError(cmd, _('invalid arguments'))
1045
1002
1046 def _exceptionwarning(ui):
1003 def _exceptionwarning(ui):
1047 """Produce a warning message for the current active exception"""
1004 """Produce a warning message for the current active exception"""
1048
1005
1049 # For compatibility checking, we discard the portion of the hg
1006 # For compatibility checking, we discard the portion of the hg
1050 # version after the + on the assumption that if a "normal
1007 # version after the + on the assumption that if a "normal
1051 # user" is running a build with a + in it the packager
1008 # user" is running a build with a + in it the packager
1052 # probably built from fairly close to a tag and anyone with a
1009 # probably built from fairly close to a tag and anyone with a
1053 # 'make local' copy of hg (where the version number can be out
1010 # 'make local' copy of hg (where the version number can be out
1054 # of date) will be clueful enough to notice the implausible
1011 # of date) will be clueful enough to notice the implausible
1055 # version number and try updating.
1012 # version number and try updating.
1056 ct = util.versiontuple(n=2)
1013 ct = util.versiontuple(n=2)
1057 worst = None, ct, ''
1014 worst = None, ct, ''
1058 if ui.config('ui', 'supportcontact') is None:
1015 if ui.config('ui', 'supportcontact') is None:
1059 for name, mod in extensions.extensions():
1016 for name, mod in extensions.extensions():
1060 testedwith = getattr(mod, 'testedwith', '')
1017 testedwith = getattr(mod, 'testedwith', '')
1061 if pycompat.ispy3 and isinstance(testedwith, str):
1018 if pycompat.ispy3 and isinstance(testedwith, str):
1062 testedwith = testedwith.encode(u'utf-8')
1019 testedwith = testedwith.encode(u'utf-8')
1063 report = getattr(mod, 'buglink', _('the extension author.'))
1020 report = getattr(mod, 'buglink', _('the extension author.'))
1064 if not testedwith.strip():
1021 if not testedwith.strip():
1065 # We found an untested extension. It's likely the culprit.
1022 # We found an untested extension. It's likely the culprit.
1066 worst = name, 'unknown', report
1023 worst = name, 'unknown', report
1067 break
1024 break
1068
1025
1069 # Never blame on extensions bundled with Mercurial.
1026 # Never blame on extensions bundled with Mercurial.
1070 if extensions.ismoduleinternal(mod):
1027 if extensions.ismoduleinternal(mod):
1071 continue
1028 continue
1072
1029
1073 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
1030 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
1074 if ct in tested:
1031 if ct in tested:
1075 continue
1032 continue
1076
1033
1077 lower = [t for t in tested if t < ct]
1034 lower = [t for t in tested if t < ct]
1078 nearest = max(lower or tested)
1035 nearest = max(lower or tested)
1079 if worst[0] is None or nearest < worst[1]:
1036 if worst[0] is None or nearest < worst[1]:
1080 worst = name, nearest, report
1037 worst = name, nearest, report
1081 if worst[0] is not None:
1038 if worst[0] is not None:
1082 name, testedwith, report = worst
1039 name, testedwith, report = worst
1083 if not isinstance(testedwith, (bytes, str)):
1040 if not isinstance(testedwith, (bytes, str)):
1084 testedwith = '.'.join([str(c) for c in testedwith])
1041 testedwith = '.'.join([str(c) for c in testedwith])
1085 warning = (_('** Unknown exception encountered with '
1042 warning = (_('** Unknown exception encountered with '
1086 'possibly-broken third-party extension %s\n'
1043 'possibly-broken third-party extension %s\n'
1087 '** which supports versions %s of Mercurial.\n'
1044 '** which supports versions %s of Mercurial.\n'
1088 '** Please disable %s and try your action again.\n'
1045 '** Please disable %s and try your action again.\n'
1089 '** If that fixes the bug please report it to %s\n')
1046 '** If that fixes the bug please report it to %s\n')
1090 % (name, testedwith, name, report))
1047 % (name, testedwith, name, report))
1091 else:
1048 else:
1092 bugtracker = ui.config('ui', 'supportcontact')
1049 bugtracker = ui.config('ui', 'supportcontact')
1093 if bugtracker is None:
1050 if bugtracker is None:
1094 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
1051 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
1095 warning = (_("** unknown exception encountered, "
1052 warning = (_("** unknown exception encountered, "
1096 "please report by visiting\n** ") + bugtracker + '\n')
1053 "please report by visiting\n** ") + bugtracker + '\n')
1097 if pycompat.ispy3:
1054 if pycompat.ispy3:
1098 sysversion = sys.version.encode(u'utf-8')
1055 sysversion = sys.version.encode(u'utf-8')
1099 else:
1056 else:
1100 sysversion = sys.version
1057 sysversion = sys.version
1101 sysversion = sysversion.replace('\n', '')
1058 sysversion = sysversion.replace('\n', '')
1102 warning += ((_("** Python %s\n") % sysversion) +
1059 warning += ((_("** Python %s\n") % sysversion) +
1103 (_("** Mercurial Distributed SCM (version %s)\n") %
1060 (_("** Mercurial Distributed SCM (version %s)\n") %
1104 util.version()) +
1061 util.version()) +
1105 (_("** Extensions loaded: %s\n") %
1062 (_("** Extensions loaded: %s\n") %
1106 ", ".join([x[0] for x in extensions.extensions()])))
1063 ", ".join([x[0] for x in extensions.extensions()])))
1107 return warning
1064 return warning
1108
1065
1109 def handlecommandexception(ui):
1066 def handlecommandexception(ui):
1110 """Produce a warning message for broken commands
1067 """Produce a warning message for broken commands
1111
1068
1112 Called when handling an exception; the exception is reraised if
1069 Called when handling an exception; the exception is reraised if
1113 this function returns False, ignored otherwise.
1070 this function returns False, ignored otherwise.
1114 """
1071 """
1115 warning = _exceptionwarning(ui)
1072 warning = _exceptionwarning(ui)
1116 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
1073 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
1117 ui.warn(warning)
1074 ui.warn(warning)
1118 return False # re-raise the exception
1075 return False # re-raise the exception
General Comments 0
You need to be logged in to leave comments. Login now