##// END OF EJS Templates
dispatch: add HGPLAIN=+strictflags to restrict early parsing of global options...
Yuya Nishihara -
r35180:c9740b69 stable
parent child Browse files
Show More
@@ -1,593 +1,601
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 = {}
224 if srcui.plain('strictflags'):
225 options.update(dispatch._earlyparseopts(args))
226 else:
223 args = args[:]
227 args = args[:]
224 dispatch._parseconfig(newui, dispatch._earlygetopt(['--config'], 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'])
225
234
226 # stolen from tortoisehg.util.copydynamicconfig()
235 # stolen from tortoisehg.util.copydynamicconfig()
227 for section, name, value in srcui.walkconfig():
236 for section, name, value in srcui.walkconfig():
228 source = srcui.configsource(section, name)
237 source = srcui.configsource(section, name)
229 if ':' in source or source == '--config' or source.startswith('$'):
238 if ':' in source or source == '--config' or source.startswith('$'):
230 # path:line or command line, or environ
239 # path:line or command line, or environ
231 continue
240 continue
232 newui.setconfig(section, name, value, source)
241 newui.setconfig(section, name, value, source)
233
242
234 # load wd and repo config, copied from dispatch.py
243 # load wd and repo config, copied from dispatch.py
235 cwds = dispatch._earlygetopt(['--cwd'], args)
244 cwd = options['cwd']
236 cwd = cwds and os.path.realpath(cwds[-1]) or None
245 cwd = cwd and os.path.realpath(cwd) or None
237 rpath = dispatch._earlygetopt(["-R", "--repository", "--repo"], args)
246 rpath = options['repository']
238 rpath = rpath and rpath[-1] or ''
239 path, newlui = dispatch._getlocal(newui, rpath, wd=cwd)
247 path, newlui = dispatch._getlocal(newui, rpath, wd=cwd)
240
248
241 return (newui, newlui)
249 return (newui, newlui)
242
250
243 class channeledsystem(object):
251 class channeledsystem(object):
244 """Propagate ui.system() request in the following format:
252 """Propagate ui.system() request in the following format:
245
253
246 payload length (unsigned int),
254 payload length (unsigned int),
247 type, '\0',
255 type, '\0',
248 cmd, '\0',
256 cmd, '\0',
249 cwd, '\0',
257 cwd, '\0',
250 envkey, '=', val, '\0',
258 envkey, '=', val, '\0',
251 ...
259 ...
252 envkey, '=', val
260 envkey, '=', val
253
261
254 if type == 'system', waits for:
262 if type == 'system', waits for:
255
263
256 exitcode length (unsigned int),
264 exitcode length (unsigned int),
257 exitcode (int)
265 exitcode (int)
258
266
259 if type == 'pager', repetitively waits for a command name ending with '\n'
267 if type == 'pager', repetitively waits for a command name ending with '\n'
260 and executes it defined by cmdtable, or exits the loop if the command name
268 and executes it defined by cmdtable, or exits the loop if the command name
261 is empty.
269 is empty.
262 """
270 """
263 def __init__(self, in_, out, channel):
271 def __init__(self, in_, out, channel):
264 self.in_ = in_
272 self.in_ = in_
265 self.out = out
273 self.out = out
266 self.channel = channel
274 self.channel = channel
267
275
268 def __call__(self, cmd, environ, cwd=None, type='system', cmdtable=None):
276 def __call__(self, cmd, environ, cwd=None, type='system', cmdtable=None):
269 args = [type, util.quotecommand(cmd), os.path.abspath(cwd or '.')]
277 args = [type, util.quotecommand(cmd), os.path.abspath(cwd or '.')]
270 args.extend('%s=%s' % (k, v) for k, v in environ.iteritems())
278 args.extend('%s=%s' % (k, v) for k, v in environ.iteritems())
271 data = '\0'.join(args)
279 data = '\0'.join(args)
272 self.out.write(struct.pack('>cI', self.channel, len(data)))
280 self.out.write(struct.pack('>cI', self.channel, len(data)))
273 self.out.write(data)
281 self.out.write(data)
274 self.out.flush()
282 self.out.flush()
275
283
276 if type == 'system':
284 if type == 'system':
277 length = self.in_.read(4)
285 length = self.in_.read(4)
278 length, = struct.unpack('>I', length)
286 length, = struct.unpack('>I', length)
279 if length != 4:
287 if length != 4:
280 raise error.Abort(_('invalid response'))
288 raise error.Abort(_('invalid response'))
281 rc, = struct.unpack('>i', self.in_.read(4))
289 rc, = struct.unpack('>i', self.in_.read(4))
282 return rc
290 return rc
283 elif type == 'pager':
291 elif type == 'pager':
284 while True:
292 while True:
285 cmd = self.in_.readline()[:-1]
293 cmd = self.in_.readline()[:-1]
286 if not cmd:
294 if not cmd:
287 break
295 break
288 if cmdtable and cmd in cmdtable:
296 if cmdtable and cmd in cmdtable:
289 _log('pager subcommand: %s' % cmd)
297 _log('pager subcommand: %s' % cmd)
290 cmdtable[cmd]()
298 cmdtable[cmd]()
291 else:
299 else:
292 raise error.Abort(_('unexpected command: %s') % cmd)
300 raise error.Abort(_('unexpected command: %s') % cmd)
293 else:
301 else:
294 raise error.ProgrammingError('invalid S channel type: %s' % type)
302 raise error.ProgrammingError('invalid S channel type: %s' % type)
295
303
296 _iochannels = [
304 _iochannels = [
297 # server.ch, ui.fp, mode
305 # server.ch, ui.fp, mode
298 ('cin', 'fin', pycompat.sysstr('rb')),
306 ('cin', 'fin', pycompat.sysstr('rb')),
299 ('cout', 'fout', pycompat.sysstr('wb')),
307 ('cout', 'fout', pycompat.sysstr('wb')),
300 ('cerr', 'ferr', pycompat.sysstr('wb')),
308 ('cerr', 'ferr', pycompat.sysstr('wb')),
301 ]
309 ]
302
310
303 class chgcmdserver(commandserver.server):
311 class chgcmdserver(commandserver.server):
304 def __init__(self, ui, repo, fin, fout, sock, hashstate, baseaddress):
312 def __init__(self, ui, repo, fin, fout, sock, hashstate, baseaddress):
305 super(chgcmdserver, self).__init__(
313 super(chgcmdserver, self).__init__(
306 _newchgui(ui, channeledsystem(fin, fout, 'S'), self.attachio),
314 _newchgui(ui, channeledsystem(fin, fout, 'S'), self.attachio),
307 repo, fin, fout)
315 repo, fin, fout)
308 self.clientsock = sock
316 self.clientsock = sock
309 self._oldios = [] # original (self.ch, ui.fp, fd) before "attachio"
317 self._oldios = [] # original (self.ch, ui.fp, fd) before "attachio"
310 self.hashstate = hashstate
318 self.hashstate = hashstate
311 self.baseaddress = baseaddress
319 self.baseaddress = baseaddress
312 if hashstate is not None:
320 if hashstate is not None:
313 self.capabilities = self.capabilities.copy()
321 self.capabilities = self.capabilities.copy()
314 self.capabilities['validate'] = chgcmdserver.validate
322 self.capabilities['validate'] = chgcmdserver.validate
315
323
316 def cleanup(self):
324 def cleanup(self):
317 super(chgcmdserver, self).cleanup()
325 super(chgcmdserver, self).cleanup()
318 # dispatch._runcatch() does not flush outputs if exception is not
326 # dispatch._runcatch() does not flush outputs if exception is not
319 # handled by dispatch._dispatch()
327 # handled by dispatch._dispatch()
320 self.ui.flush()
328 self.ui.flush()
321 self._restoreio()
329 self._restoreio()
322
330
323 def attachio(self):
331 def attachio(self):
324 """Attach to client's stdio passed via unix domain socket; all
332 """Attach to client's stdio passed via unix domain socket; all
325 channels except cresult will no longer be used
333 channels except cresult will no longer be used
326 """
334 """
327 # tell client to sendmsg() with 1-byte payload, which makes it
335 # tell client to sendmsg() with 1-byte payload, which makes it
328 # distinctive from "attachio\n" command consumed by client.read()
336 # distinctive from "attachio\n" command consumed by client.read()
329 self.clientsock.sendall(struct.pack('>cI', 'I', 1))
337 self.clientsock.sendall(struct.pack('>cI', 'I', 1))
330 clientfds = util.recvfds(self.clientsock.fileno())
338 clientfds = util.recvfds(self.clientsock.fileno())
331 _log('received fds: %r\n' % clientfds)
339 _log('received fds: %r\n' % clientfds)
332
340
333 ui = self.ui
341 ui = self.ui
334 ui.flush()
342 ui.flush()
335 first = self._saveio()
343 first = self._saveio()
336 for fd, (cn, fn, mode) in zip(clientfds, _iochannels):
344 for fd, (cn, fn, mode) in zip(clientfds, _iochannels):
337 assert fd > 0
345 assert fd > 0
338 fp = getattr(ui, fn)
346 fp = getattr(ui, fn)
339 os.dup2(fd, fp.fileno())
347 os.dup2(fd, fp.fileno())
340 os.close(fd)
348 os.close(fd)
341 if not first:
349 if not first:
342 continue
350 continue
343 # reset buffering mode when client is first attached. as we want
351 # reset buffering mode when client is first attached. as we want
344 # to see output immediately on pager, the mode stays unchanged
352 # to see output immediately on pager, the mode stays unchanged
345 # when client re-attached. ferr is unchanged because it should
353 # when client re-attached. ferr is unchanged because it should
346 # be unbuffered no matter if it is a tty or not.
354 # be unbuffered no matter if it is a tty or not.
347 if fn == 'ferr':
355 if fn == 'ferr':
348 newfp = fp
356 newfp = fp
349 else:
357 else:
350 # make it line buffered explicitly because the default is
358 # make it line buffered explicitly because the default is
351 # decided on first write(), where fout could be a pager.
359 # decided on first write(), where fout could be a pager.
352 if fp.isatty():
360 if fp.isatty():
353 bufsize = 1 # line buffered
361 bufsize = 1 # line buffered
354 else:
362 else:
355 bufsize = -1 # system default
363 bufsize = -1 # system default
356 newfp = os.fdopen(fp.fileno(), mode, bufsize)
364 newfp = os.fdopen(fp.fileno(), mode, bufsize)
357 setattr(ui, fn, newfp)
365 setattr(ui, fn, newfp)
358 setattr(self, cn, newfp)
366 setattr(self, cn, newfp)
359
367
360 self.cresult.write(struct.pack('>i', len(clientfds)))
368 self.cresult.write(struct.pack('>i', len(clientfds)))
361
369
362 def _saveio(self):
370 def _saveio(self):
363 if self._oldios:
371 if self._oldios:
364 return False
372 return False
365 ui = self.ui
373 ui = self.ui
366 for cn, fn, _mode in _iochannels:
374 for cn, fn, _mode in _iochannels:
367 ch = getattr(self, cn)
375 ch = getattr(self, cn)
368 fp = getattr(ui, fn)
376 fp = getattr(ui, fn)
369 fd = os.dup(fp.fileno())
377 fd = os.dup(fp.fileno())
370 self._oldios.append((ch, fp, fd))
378 self._oldios.append((ch, fp, fd))
371 return True
379 return True
372
380
373 def _restoreio(self):
381 def _restoreio(self):
374 ui = self.ui
382 ui = self.ui
375 for (ch, fp, fd), (cn, fn, _mode) in zip(self._oldios, _iochannels):
383 for (ch, fp, fd), (cn, fn, _mode) in zip(self._oldios, _iochannels):
376 newfp = getattr(ui, fn)
384 newfp = getattr(ui, fn)
377 # close newfp while it's associated with client; otherwise it
385 # close newfp while it's associated with client; otherwise it
378 # would be closed when newfp is deleted
386 # would be closed when newfp is deleted
379 if newfp is not fp:
387 if newfp is not fp:
380 newfp.close()
388 newfp.close()
381 # restore original fd: fp is open again
389 # restore original fd: fp is open again
382 os.dup2(fd, fp.fileno())
390 os.dup2(fd, fp.fileno())
383 os.close(fd)
391 os.close(fd)
384 setattr(self, cn, ch)
392 setattr(self, cn, ch)
385 setattr(ui, fn, fp)
393 setattr(ui, fn, fp)
386 del self._oldios[:]
394 del self._oldios[:]
387
395
388 def validate(self):
396 def validate(self):
389 """Reload the config and check if the server is up to date
397 """Reload the config and check if the server is up to date
390
398
391 Read a list of '\0' separated arguments.
399 Read a list of '\0' separated arguments.
392 Write a non-empty list of '\0' separated instruction strings or '\0'
400 Write a non-empty list of '\0' separated instruction strings or '\0'
393 if the list is empty.
401 if the list is empty.
394 An instruction string could be either:
402 An instruction string could be either:
395 - "unlink $path", the client should unlink the path to stop the
403 - "unlink $path", the client should unlink the path to stop the
396 outdated server.
404 outdated server.
397 - "redirect $path", the client should attempt to connect to $path
405 - "redirect $path", the client should attempt to connect to $path
398 first. If it does not work, start a new server. It implies
406 first. If it does not work, start a new server. It implies
399 "reconnect".
407 "reconnect".
400 - "exit $n", the client should exit directly with code n.
408 - "exit $n", the client should exit directly with code n.
401 This may happen if we cannot parse the config.
409 This may happen if we cannot parse the config.
402 - "reconnect", the client should close the connection and
410 - "reconnect", the client should close the connection and
403 reconnect.
411 reconnect.
404 If neither "reconnect" nor "redirect" is included in the instruction
412 If neither "reconnect" nor "redirect" is included in the instruction
405 list, the client can continue with this server after completing all
413 list, the client can continue with this server after completing all
406 the instructions.
414 the instructions.
407 """
415 """
408 from . import dispatch # avoid cycle
416 from . import dispatch # avoid cycle
409
417
410 args = self._readlist()
418 args = self._readlist()
411 try:
419 try:
412 self.ui, lui = _loadnewui(self.ui, args)
420 self.ui, lui = _loadnewui(self.ui, args)
413 except error.ParseError as inst:
421 except error.ParseError as inst:
414 dispatch._formatparse(self.ui.warn, inst)
422 dispatch._formatparse(self.ui.warn, inst)
415 self.ui.flush()
423 self.ui.flush()
416 self.cresult.write('exit 255')
424 self.cresult.write('exit 255')
417 return
425 return
418 newhash = hashstate.fromui(lui, self.hashstate.mtimepaths)
426 newhash = hashstate.fromui(lui, self.hashstate.mtimepaths)
419 insts = []
427 insts = []
420 if newhash.mtimehash != self.hashstate.mtimehash:
428 if newhash.mtimehash != self.hashstate.mtimehash:
421 addr = _hashaddress(self.baseaddress, self.hashstate.confighash)
429 addr = _hashaddress(self.baseaddress, self.hashstate.confighash)
422 insts.append('unlink %s' % addr)
430 insts.append('unlink %s' % addr)
423 # mtimehash is empty if one or more extensions fail to load.
431 # mtimehash is empty if one or more extensions fail to load.
424 # to be compatible with hg, still serve the client this time.
432 # to be compatible with hg, still serve the client this time.
425 if self.hashstate.mtimehash:
433 if self.hashstate.mtimehash:
426 insts.append('reconnect')
434 insts.append('reconnect')
427 if newhash.confighash != self.hashstate.confighash:
435 if newhash.confighash != self.hashstate.confighash:
428 addr = _hashaddress(self.baseaddress, newhash.confighash)
436 addr = _hashaddress(self.baseaddress, newhash.confighash)
429 insts.append('redirect %s' % addr)
437 insts.append('redirect %s' % addr)
430 _log('validate: %s\n' % insts)
438 _log('validate: %s\n' % insts)
431 self.cresult.write('\0'.join(insts) or '\0')
439 self.cresult.write('\0'.join(insts) or '\0')
432
440
433 def chdir(self):
441 def chdir(self):
434 """Change current directory
442 """Change current directory
435
443
436 Note that the behavior of --cwd option is bit different from this.
444 Note that the behavior of --cwd option is bit different from this.
437 It does not affect --config parameter.
445 It does not affect --config parameter.
438 """
446 """
439 path = self._readstr()
447 path = self._readstr()
440 if not path:
448 if not path:
441 return
449 return
442 _log('chdir to %r\n' % path)
450 _log('chdir to %r\n' % path)
443 os.chdir(path)
451 os.chdir(path)
444
452
445 def setumask(self):
453 def setumask(self):
446 """Change umask"""
454 """Change umask"""
447 mask = struct.unpack('>I', self._read(4))[0]
455 mask = struct.unpack('>I', self._read(4))[0]
448 _log('setumask %r\n' % mask)
456 _log('setumask %r\n' % mask)
449 os.umask(mask)
457 os.umask(mask)
450
458
451 def runcommand(self):
459 def runcommand(self):
452 return super(chgcmdserver, self).runcommand()
460 return super(chgcmdserver, self).runcommand()
453
461
454 def setenv(self):
462 def setenv(self):
455 """Clear and update os.environ
463 """Clear and update os.environ
456
464
457 Note that not all variables can make an effect on the running process.
465 Note that not all variables can make an effect on the running process.
458 """
466 """
459 l = self._readlist()
467 l = self._readlist()
460 try:
468 try:
461 newenv = dict(s.split('=', 1) for s in l)
469 newenv = dict(s.split('=', 1) for s in l)
462 except ValueError:
470 except ValueError:
463 raise ValueError('unexpected value in setenv request')
471 raise ValueError('unexpected value in setenv request')
464 _log('setenv: %r\n' % sorted(newenv.keys()))
472 _log('setenv: %r\n' % sorted(newenv.keys()))
465 encoding.environ.clear()
473 encoding.environ.clear()
466 encoding.environ.update(newenv)
474 encoding.environ.update(newenv)
467
475
468 capabilities = commandserver.server.capabilities.copy()
476 capabilities = commandserver.server.capabilities.copy()
469 capabilities.update({'attachio': attachio,
477 capabilities.update({'attachio': attachio,
470 'chdir': chdir,
478 'chdir': chdir,
471 'runcommand': runcommand,
479 'runcommand': runcommand,
472 'setenv': setenv,
480 'setenv': setenv,
473 'setumask': setumask})
481 'setumask': setumask})
474
482
475 if util.safehasattr(util, 'setprocname'):
483 if util.safehasattr(util, 'setprocname'):
476 def setprocname(self):
484 def setprocname(self):
477 """Change process title"""
485 """Change process title"""
478 name = self._readstr()
486 name = self._readstr()
479 _log('setprocname: %r\n' % name)
487 _log('setprocname: %r\n' % name)
480 util.setprocname(name)
488 util.setprocname(name)
481 capabilities['setprocname'] = setprocname
489 capabilities['setprocname'] = setprocname
482
490
483 def _tempaddress(address):
491 def _tempaddress(address):
484 return '%s.%d.tmp' % (address, os.getpid())
492 return '%s.%d.tmp' % (address, os.getpid())
485
493
486 def _hashaddress(address, hashstr):
494 def _hashaddress(address, hashstr):
487 # if the basename of address contains '.', use only the left part. this
495 # if the basename of address contains '.', use only the left part. this
488 # makes it possible for the client to pass 'server.tmp$PID' and follow by
496 # makes it possible for the client to pass 'server.tmp$PID' and follow by
489 # an atomic rename to avoid locking when spawning new servers.
497 # an atomic rename to avoid locking when spawning new servers.
490 dirname, basename = os.path.split(address)
498 dirname, basename = os.path.split(address)
491 basename = basename.split('.', 1)[0]
499 basename = basename.split('.', 1)[0]
492 return '%s-%s' % (os.path.join(dirname, basename), hashstr)
500 return '%s-%s' % (os.path.join(dirname, basename), hashstr)
493
501
494 class chgunixservicehandler(object):
502 class chgunixservicehandler(object):
495 """Set of operations for chg services"""
503 """Set of operations for chg services"""
496
504
497 pollinterval = 1 # [sec]
505 pollinterval = 1 # [sec]
498
506
499 def __init__(self, ui):
507 def __init__(self, ui):
500 self.ui = ui
508 self.ui = ui
501 self._idletimeout = ui.configint('chgserver', 'idletimeout')
509 self._idletimeout = ui.configint('chgserver', 'idletimeout')
502 self._lastactive = time.time()
510 self._lastactive = time.time()
503
511
504 def bindsocket(self, sock, address):
512 def bindsocket(self, sock, address):
505 self._inithashstate(address)
513 self._inithashstate(address)
506 self._checkextensions()
514 self._checkextensions()
507 self._bind(sock)
515 self._bind(sock)
508 self._createsymlink()
516 self._createsymlink()
509 # no "listening at" message should be printed to simulate hg behavior
517 # no "listening at" message should be printed to simulate hg behavior
510
518
511 def _inithashstate(self, address):
519 def _inithashstate(self, address):
512 self._baseaddress = address
520 self._baseaddress = address
513 if self.ui.configbool('chgserver', 'skiphash'):
521 if self.ui.configbool('chgserver', 'skiphash'):
514 self._hashstate = None
522 self._hashstate = None
515 self._realaddress = address
523 self._realaddress = address
516 return
524 return
517 self._hashstate = hashstate.fromui(self.ui)
525 self._hashstate = hashstate.fromui(self.ui)
518 self._realaddress = _hashaddress(address, self._hashstate.confighash)
526 self._realaddress = _hashaddress(address, self._hashstate.confighash)
519
527
520 def _checkextensions(self):
528 def _checkextensions(self):
521 if not self._hashstate:
529 if not self._hashstate:
522 return
530 return
523 if extensions.notloaded():
531 if extensions.notloaded():
524 # one or more extensions failed to load. mtimehash becomes
532 # one or more extensions failed to load. mtimehash becomes
525 # meaningless because we do not know the paths of those extensions.
533 # meaningless because we do not know the paths of those extensions.
526 # set mtimehash to an illegal hash value to invalidate the server.
534 # set mtimehash to an illegal hash value to invalidate the server.
527 self._hashstate.mtimehash = ''
535 self._hashstate.mtimehash = ''
528
536
529 def _bind(self, sock):
537 def _bind(self, sock):
530 # use a unique temp address so we can stat the file and do ownership
538 # use a unique temp address so we can stat the file and do ownership
531 # check later
539 # check later
532 tempaddress = _tempaddress(self._realaddress)
540 tempaddress = _tempaddress(self._realaddress)
533 util.bindunixsocket(sock, tempaddress)
541 util.bindunixsocket(sock, tempaddress)
534 self._socketstat = os.stat(tempaddress)
542 self._socketstat = os.stat(tempaddress)
535 sock.listen(socket.SOMAXCONN)
543 sock.listen(socket.SOMAXCONN)
536 # rename will replace the old socket file if exists atomically. the
544 # rename will replace the old socket file if exists atomically. the
537 # old server will detect ownership change and exit.
545 # old server will detect ownership change and exit.
538 util.rename(tempaddress, self._realaddress)
546 util.rename(tempaddress, self._realaddress)
539
547
540 def _createsymlink(self):
548 def _createsymlink(self):
541 if self._baseaddress == self._realaddress:
549 if self._baseaddress == self._realaddress:
542 return
550 return
543 tempaddress = _tempaddress(self._baseaddress)
551 tempaddress = _tempaddress(self._baseaddress)
544 os.symlink(os.path.basename(self._realaddress), tempaddress)
552 os.symlink(os.path.basename(self._realaddress), tempaddress)
545 util.rename(tempaddress, self._baseaddress)
553 util.rename(tempaddress, self._baseaddress)
546
554
547 def _issocketowner(self):
555 def _issocketowner(self):
548 try:
556 try:
549 stat = os.stat(self._realaddress)
557 stat = os.stat(self._realaddress)
550 return (stat.st_ino == self._socketstat.st_ino and
558 return (stat.st_ino == self._socketstat.st_ino and
551 stat.st_mtime == self._socketstat.st_mtime)
559 stat.st_mtime == self._socketstat.st_mtime)
552 except OSError:
560 except OSError:
553 return False
561 return False
554
562
555 def unlinksocket(self, address):
563 def unlinksocket(self, address):
556 if not self._issocketowner():
564 if not self._issocketowner():
557 return
565 return
558 # it is possible to have a race condition here that we may
566 # it is possible to have a race condition here that we may
559 # remove another server's socket file. but that's okay
567 # remove another server's socket file. but that's okay
560 # since that server will detect and exit automatically and
568 # since that server will detect and exit automatically and
561 # the client will start a new server on demand.
569 # the client will start a new server on demand.
562 util.tryunlink(self._realaddress)
570 util.tryunlink(self._realaddress)
563
571
564 def shouldexit(self):
572 def shouldexit(self):
565 if not self._issocketowner():
573 if not self._issocketowner():
566 self.ui.debug('%s is not owned, exiting.\n' % self._realaddress)
574 self.ui.debug('%s is not owned, exiting.\n' % self._realaddress)
567 return True
575 return True
568 if time.time() - self._lastactive > self._idletimeout:
576 if time.time() - self._lastactive > self._idletimeout:
569 self.ui.debug('being idle too long. exiting.\n')
577 self.ui.debug('being idle too long. exiting.\n')
570 return True
578 return True
571 return False
579 return False
572
580
573 def newconnection(self):
581 def newconnection(self):
574 self._lastactive = time.time()
582 self._lastactive = time.time()
575
583
576 def createcmdserver(self, repo, conn, fin, fout):
584 def createcmdserver(self, repo, conn, fin, fout):
577 return chgcmdserver(self.ui, repo, fin, fout, conn,
585 return chgcmdserver(self.ui, repo, fin, fout, conn,
578 self._hashstate, self._baseaddress)
586 self._hashstate, self._baseaddress)
579
587
580 def chgunixservice(ui, repo, opts):
588 def chgunixservice(ui, repo, opts):
581 # CHGINTERNALMARK is set by chg client. It is an indication of things are
589 # CHGINTERNALMARK is set by chg client. It is an indication of things are
582 # started by chg so other code can do things accordingly, like disabling
590 # started by chg so other code can do things accordingly, like disabling
583 # demandimport or detecting chg client started by chg client. When executed
591 # demandimport or detecting chg client started by chg client. When executed
584 # here, CHGINTERNALMARK is no longer useful and hence dropped to make
592 # here, CHGINTERNALMARK is no longer useful and hence dropped to make
585 # environ cleaner.
593 # environ cleaner.
586 if 'CHGINTERNALMARK' in encoding.environ:
594 if 'CHGINTERNALMARK' in encoding.environ:
587 del encoding.environ['CHGINTERNALMARK']
595 del encoding.environ['CHGINTERNALMARK']
588
596
589 if repo:
597 if repo:
590 # one chgserver can serve multiple repos. drop repo information
598 # one chgserver can serve multiple repos. drop repo information
591 ui.setconfig('bundle', 'mainreporoot', '', 'repo')
599 ui.setconfig('bundle', 'mainreporoot', '', 'repo')
592 h = chgunixservicehandler(ui)
600 h = chgunixservicehandler(ui)
593 return commandserver.unixforkingservice(ui, repo=None, opts=opts, handler=h)
601 return commandserver.unixforkingservice(ui, repo=None, opts=opts, handler=h)
@@ -1,1103 +1,1117
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 _earlyreqopt*()
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'):
154 req.earlyoptions.update(_earlyparseopts(req.args))
153 if _earlyreqoptbool(req, 'traceback', ['--traceback']):
155 if _earlyreqoptbool(req, 'traceback', ['--traceback']):
154 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
156 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
155
157
156 # set ui streams from the request
158 # set ui streams from the request
157 if req.fin:
159 if req.fin:
158 req.ui.fin = req.fin
160 req.ui.fin = req.fin
159 if req.fout:
161 if req.fout:
160 req.ui.fout = req.fout
162 req.ui.fout = req.fout
161 if req.ferr:
163 if req.ferr:
162 req.ui.ferr = req.ferr
164 req.ui.ferr = req.ferr
163 except error.Abort as inst:
165 except error.Abort as inst:
164 ferr.write(_("abort: %s\n") % inst)
166 ferr.write(_("abort: %s\n") % inst)
165 if inst.hint:
167 if inst.hint:
166 ferr.write(_("(%s)\n") % inst.hint)
168 ferr.write(_("(%s)\n") % inst.hint)
167 return -1
169 return -1
168 except error.ParseError as inst:
170 except error.ParseError as inst:
169 _formatparse(ferr.write, inst)
171 _formatparse(ferr.write, inst)
170 return -1
172 return -1
171
173
172 msg = _formatargs(req.args)
174 msg = _formatargs(req.args)
173 starttime = util.timer()
175 starttime = util.timer()
174 ret = None
176 ret = None
175 try:
177 try:
176 ret = _runcatch(req)
178 ret = _runcatch(req)
177 except error.ProgrammingError as inst:
179 except error.ProgrammingError as inst:
178 req.ui.warn(_('** ProgrammingError: %s\n') % inst)
180 req.ui.warn(_('** ProgrammingError: %s\n') % inst)
179 if inst.hint:
181 if inst.hint:
180 req.ui.warn(_('** (%s)\n') % inst.hint)
182 req.ui.warn(_('** (%s)\n') % inst.hint)
181 raise
183 raise
182 except KeyboardInterrupt as inst:
184 except KeyboardInterrupt as inst:
183 try:
185 try:
184 if isinstance(inst, error.SignalInterrupt):
186 if isinstance(inst, error.SignalInterrupt):
185 msg = _("killed!\n")
187 msg = _("killed!\n")
186 else:
188 else:
187 msg = _("interrupted!\n")
189 msg = _("interrupted!\n")
188 req.ui.warn(msg)
190 req.ui.warn(msg)
189 except error.SignalInterrupt:
191 except error.SignalInterrupt:
190 # maybe pager would quit without consuming all the output, and
192 # maybe pager would quit without consuming all the output, and
191 # SIGPIPE was raised. we cannot print anything in this case.
193 # SIGPIPE was raised. we cannot print anything in this case.
192 pass
194 pass
193 except IOError as inst:
195 except IOError as inst:
194 if inst.errno != errno.EPIPE:
196 if inst.errno != errno.EPIPE:
195 raise
197 raise
196 ret = -1
198 ret = -1
197 finally:
199 finally:
198 duration = util.timer() - starttime
200 duration = util.timer() - starttime
199 req.ui.flush()
201 req.ui.flush()
200 if req.ui.logblockedtimes:
202 if req.ui.logblockedtimes:
201 req.ui._blockedtimes['command_duration'] = duration * 1000
203 req.ui._blockedtimes['command_duration'] = duration * 1000
202 req.ui.log('uiblocked', 'ui blocked ms', **req.ui._blockedtimes)
204 req.ui.log('uiblocked', 'ui blocked ms', **req.ui._blockedtimes)
203 req.ui.log("commandfinish", "%s exited %d after %0.2f seconds\n",
205 req.ui.log("commandfinish", "%s exited %d after %0.2f seconds\n",
204 msg, ret or 0, duration)
206 msg, ret or 0, duration)
205 try:
207 try:
206 req._runexithandlers()
208 req._runexithandlers()
207 except: # exiting, so no re-raises
209 except: # exiting, so no re-raises
208 ret = ret or -1
210 ret = ret or -1
209 return ret
211 return ret
210
212
211 def _runcatch(req):
213 def _runcatch(req):
212 def catchterm(*args):
214 def catchterm(*args):
213 raise error.SignalInterrupt
215 raise error.SignalInterrupt
214
216
215 ui = req.ui
217 ui = req.ui
216 try:
218 try:
217 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
219 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
218 num = getattr(signal, name, None)
220 num = getattr(signal, name, None)
219 if num:
221 if num:
220 signal.signal(num, catchterm)
222 signal.signal(num, catchterm)
221 except ValueError:
223 except ValueError:
222 pass # happens if called in a thread
224 pass # happens if called in a thread
223
225
224 def _runcatchfunc():
226 def _runcatchfunc():
225 realcmd = None
227 realcmd = None
226 try:
228 try:
227 cmdargs = fancyopts.fancyopts(req.args[:], commands.globalopts, {})
229 cmdargs = fancyopts.fancyopts(req.args[:], commands.globalopts, {})
228 cmd = cmdargs[0]
230 cmd = cmdargs[0]
229 aliases, entry = cmdutil.findcmd(cmd, commands.table, False)
231 aliases, entry = cmdutil.findcmd(cmd, commands.table, False)
230 realcmd = aliases[0]
232 realcmd = aliases[0]
231 except (error.UnknownCommand, error.AmbiguousCommand,
233 except (error.UnknownCommand, error.AmbiguousCommand,
232 IndexError, getopt.GetoptError):
234 IndexError, getopt.GetoptError):
233 # Don't handle this here. We know the command is
235 # Don't handle this here. We know the command is
234 # invalid, but all we're worried about for now is that
236 # invalid, but all we're worried about for now is that
235 # it's not a command that server operators expect to
237 # it's not a command that server operators expect to
236 # be safe to offer to users in a sandbox.
238 # be safe to offer to users in a sandbox.
237 pass
239 pass
238 if realcmd == 'serve' and '--stdio' in cmdargs:
240 if realcmd == 'serve' and '--stdio' in cmdargs:
239 # We want to constrain 'hg serve --stdio' instances pretty
241 # We want to constrain 'hg serve --stdio' instances pretty
240 # closely, as many shared-ssh access tools want to grant
242 # closely, as many shared-ssh access tools want to grant
241 # access to run *only* 'hg -R $repo serve --stdio'. We
243 # access to run *only* 'hg -R $repo serve --stdio'. We
242 # restrict to exactly that set of arguments, and prohibit
244 # restrict to exactly that set of arguments, and prohibit
243 # any repo name that starts with '--' to prevent
245 # any repo name that starts with '--' to prevent
244 # shenanigans wherein a user does something like pass
246 # shenanigans wherein a user does something like pass
245 # --debugger or --config=ui.debugger=1 as a repo
247 # --debugger or --config=ui.debugger=1 as a repo
246 # name. This used to actually run the debugger.
248 # name. This used to actually run the debugger.
247 if (len(req.args) != 4 or
249 if (len(req.args) != 4 or
248 req.args[0] != '-R' or
250 req.args[0] != '-R' or
249 req.args[1].startswith('--') or
251 req.args[1].startswith('--') or
250 req.args[2] != 'serve' or
252 req.args[2] != 'serve' or
251 req.args[3] != '--stdio'):
253 req.args[3] != '--stdio'):
252 raise error.Abort(
254 raise error.Abort(
253 _('potentially unsafe serve --stdio invocation: %r') %
255 _('potentially unsafe serve --stdio invocation: %r') %
254 (req.args,))
256 (req.args,))
255
257
256 try:
258 try:
257 debugger = 'pdb'
259 debugger = 'pdb'
258 debugtrace = {
260 debugtrace = {
259 'pdb': pdb.set_trace
261 'pdb': pdb.set_trace
260 }
262 }
261 debugmortem = {
263 debugmortem = {
262 'pdb': pdb.post_mortem
264 'pdb': pdb.post_mortem
263 }
265 }
264
266
265 # read --config before doing anything else
267 # read --config before doing anything else
266 # (e.g. to change trust settings for reading .hg/hgrc)
268 # (e.g. to change trust settings for reading .hg/hgrc)
267 cfgs = _parseconfig(req.ui,
269 cfgs = _parseconfig(req.ui,
268 _earlyreqopt(req, 'config', ['--config']))
270 _earlyreqopt(req, 'config', ['--config']))
269
271
270 if req.repo:
272 if req.repo:
271 # copy configs that were passed on the cmdline (--config) to
273 # copy configs that were passed on the cmdline (--config) to
272 # the repo ui
274 # the repo ui
273 for sec, name, val in cfgs:
275 for sec, name, val in cfgs:
274 req.repo.ui.setconfig(sec, name, val, source='--config')
276 req.repo.ui.setconfig(sec, name, val, source='--config')
275
277
276 # developer config: ui.debugger
278 # developer config: ui.debugger
277 debugger = ui.config("ui", "debugger")
279 debugger = ui.config("ui", "debugger")
278 debugmod = pdb
280 debugmod = pdb
279 if not debugger or ui.plain():
281 if not debugger or ui.plain():
280 # if we are in HGPLAIN mode, then disable custom debugging
282 # if we are in HGPLAIN mode, then disable custom debugging
281 debugger = 'pdb'
283 debugger = 'pdb'
282 elif _earlyreqoptbool(req, 'debugger', ['--debugger']):
284 elif _earlyreqoptbool(req, 'debugger', ['--debugger']):
283 # This import can be slow for fancy debuggers, so only
285 # This import can be slow for fancy debuggers, so only
284 # do it when absolutely necessary, i.e. when actual
286 # do it when absolutely necessary, i.e. when actual
285 # debugging has been requested
287 # debugging has been requested
286 with demandimport.deactivated():
288 with demandimport.deactivated():
287 try:
289 try:
288 debugmod = __import__(debugger)
290 debugmod = __import__(debugger)
289 except ImportError:
291 except ImportError:
290 pass # Leave debugmod = pdb
292 pass # Leave debugmod = pdb
291
293
292 debugtrace[debugger] = debugmod.set_trace
294 debugtrace[debugger] = debugmod.set_trace
293 debugmortem[debugger] = debugmod.post_mortem
295 debugmortem[debugger] = debugmod.post_mortem
294
296
295 # enter the debugger before command execution
297 # enter the debugger before command execution
296 if _earlyreqoptbool(req, 'debugger', ['--debugger']):
298 if _earlyreqoptbool(req, 'debugger', ['--debugger']):
297 ui.warn(_("entering debugger - "
299 ui.warn(_("entering debugger - "
298 "type c to continue starting hg or h for help\n"))
300 "type c to continue starting hg or h for help\n"))
299
301
300 if (debugger != 'pdb' and
302 if (debugger != 'pdb' and
301 debugtrace[debugger] == debugtrace['pdb']):
303 debugtrace[debugger] == debugtrace['pdb']):
302 ui.warn(_("%s debugger specified "
304 ui.warn(_("%s debugger specified "
303 "but its module was not found\n") % debugger)
305 "but its module was not found\n") % debugger)
304 with demandimport.deactivated():
306 with demandimport.deactivated():
305 debugtrace[debugger]()
307 debugtrace[debugger]()
306 try:
308 try:
307 return _dispatch(req)
309 return _dispatch(req)
308 finally:
310 finally:
309 ui.flush()
311 ui.flush()
310 except: # re-raises
312 except: # re-raises
311 # enter the debugger when we hit an exception
313 # enter the debugger when we hit an exception
312 if _earlyreqoptbool(req, 'debugger', ['--debugger']):
314 if _earlyreqoptbool(req, 'debugger', ['--debugger']):
313 traceback.print_exc()
315 traceback.print_exc()
314 debugmortem[debugger](sys.exc_info()[2])
316 debugmortem[debugger](sys.exc_info()[2])
315 raise
317 raise
316
318
317 return _callcatch(ui, _runcatchfunc)
319 return _callcatch(ui, _runcatchfunc)
318
320
319 def _callcatch(ui, func):
321 def _callcatch(ui, func):
320 """like scmutil.callcatch but handles more high-level exceptions about
322 """like scmutil.callcatch but handles more high-level exceptions about
321 config parsing and commands. besides, use handlecommandexception to handle
323 config parsing and commands. besides, use handlecommandexception to handle
322 uncaught exceptions.
324 uncaught exceptions.
323 """
325 """
324 try:
326 try:
325 return scmutil.callcatch(ui, func)
327 return scmutil.callcatch(ui, func)
326 except error.AmbiguousCommand as inst:
328 except error.AmbiguousCommand as inst:
327 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
329 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
328 (inst.args[0], " ".join(inst.args[1])))
330 (inst.args[0], " ".join(inst.args[1])))
329 except error.CommandError as inst:
331 except error.CommandError as inst:
330 if inst.args[0]:
332 if inst.args[0]:
331 ui.pager('help')
333 ui.pager('help')
332 msgbytes = pycompat.bytestr(inst.args[1])
334 msgbytes = pycompat.bytestr(inst.args[1])
333 ui.warn(_("hg %s: %s\n") % (inst.args[0], msgbytes))
335 ui.warn(_("hg %s: %s\n") % (inst.args[0], msgbytes))
334 commands.help_(ui, inst.args[0], full=False, command=True)
336 commands.help_(ui, inst.args[0], full=False, command=True)
335 else:
337 else:
336 ui.pager('help')
338 ui.pager('help')
337 ui.warn(_("hg: %s\n") % inst.args[1])
339 ui.warn(_("hg: %s\n") % inst.args[1])
338 commands.help_(ui, 'shortlist')
340 commands.help_(ui, 'shortlist')
339 except error.ParseError as inst:
341 except error.ParseError as inst:
340 _formatparse(ui.warn, inst)
342 _formatparse(ui.warn, inst)
341 return -1
343 return -1
342 except error.UnknownCommand as inst:
344 except error.UnknownCommand as inst:
343 nocmdmsg = _("hg: unknown command '%s'\n") % inst.args[0]
345 nocmdmsg = _("hg: unknown command '%s'\n") % inst.args[0]
344 try:
346 try:
345 # check if the command is in a disabled extension
347 # check if the command is in a disabled extension
346 # (but don't check for extensions themselves)
348 # (but don't check for extensions themselves)
347 formatted = help.formattedhelp(ui, commands, inst.args[0],
349 formatted = help.formattedhelp(ui, commands, inst.args[0],
348 unknowncmd=True)
350 unknowncmd=True)
349 ui.warn(nocmdmsg)
351 ui.warn(nocmdmsg)
350 ui.write(formatted)
352 ui.write(formatted)
351 except (error.UnknownCommand, error.Abort):
353 except (error.UnknownCommand, error.Abort):
352 suggested = False
354 suggested = False
353 if len(inst.args) == 2:
355 if len(inst.args) == 2:
354 sim = _getsimilar(inst.args[1], inst.args[0])
356 sim = _getsimilar(inst.args[1], inst.args[0])
355 if sim:
357 if sim:
356 ui.warn(nocmdmsg)
358 ui.warn(nocmdmsg)
357 _reportsimilar(ui.warn, sim)
359 _reportsimilar(ui.warn, sim)
358 suggested = True
360 suggested = True
359 if not suggested:
361 if not suggested:
360 ui.pager('help')
362 ui.pager('help')
361 ui.warn(nocmdmsg)
363 ui.warn(nocmdmsg)
362 commands.help_(ui, 'shortlist')
364 commands.help_(ui, 'shortlist')
363 except IOError:
365 except IOError:
364 raise
366 raise
365 except KeyboardInterrupt:
367 except KeyboardInterrupt:
366 raise
368 raise
367 except: # probably re-raises
369 except: # probably re-raises
368 if not handlecommandexception(ui):
370 if not handlecommandexception(ui):
369 raise
371 raise
370
372
371 return -1
373 return -1
372
374
373 def aliasargs(fn, givenargs):
375 def aliasargs(fn, givenargs):
374 args = []
376 args = []
375 # only care about alias 'args', ignore 'args' set by extensions.wrapfunction
377 # only care about alias 'args', ignore 'args' set by extensions.wrapfunction
376 if not util.safehasattr(fn, '_origfunc'):
378 if not util.safehasattr(fn, '_origfunc'):
377 args = getattr(fn, 'args', args)
379 args = getattr(fn, 'args', args)
378 if args:
380 if args:
379 cmd = ' '.join(map(util.shellquote, args))
381 cmd = ' '.join(map(util.shellquote, args))
380
382
381 nums = []
383 nums = []
382 def replacer(m):
384 def replacer(m):
383 num = int(m.group(1)) - 1
385 num = int(m.group(1)) - 1
384 nums.append(num)
386 nums.append(num)
385 if num < len(givenargs):
387 if num < len(givenargs):
386 return givenargs[num]
388 return givenargs[num]
387 raise error.Abort(_('too few arguments for command alias'))
389 raise error.Abort(_('too few arguments for command alias'))
388 cmd = re.sub(br'\$(\d+|\$)', replacer, cmd)
390 cmd = re.sub(br'\$(\d+|\$)', replacer, cmd)
389 givenargs = [x for i, x in enumerate(givenargs)
391 givenargs = [x for i, x in enumerate(givenargs)
390 if i not in nums]
392 if i not in nums]
391 args = pycompat.shlexsplit(cmd)
393 args = pycompat.shlexsplit(cmd)
392 return args + givenargs
394 return args + givenargs
393
395
394 def aliasinterpolate(name, args, cmd):
396 def aliasinterpolate(name, args, cmd):
395 '''interpolate args into cmd for shell aliases
397 '''interpolate args into cmd for shell aliases
396
398
397 This also handles $0, $@ and "$@".
399 This also handles $0, $@ and "$@".
398 '''
400 '''
399 # util.interpolate can't deal with "$@" (with quotes) because it's only
401 # util.interpolate can't deal with "$@" (with quotes) because it's only
400 # built to match prefix + patterns.
402 # built to match prefix + patterns.
401 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
403 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
402 replacemap['$0'] = name
404 replacemap['$0'] = name
403 replacemap['$$'] = '$'
405 replacemap['$$'] = '$'
404 replacemap['$@'] = ' '.join(args)
406 replacemap['$@'] = ' '.join(args)
405 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
407 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
406 # parameters, separated out into words. Emulate the same behavior here by
408 # parameters, separated out into words. Emulate the same behavior here by
407 # quoting the arguments individually. POSIX shells will then typically
409 # quoting the arguments individually. POSIX shells will then typically
408 # tokenize each argument into exactly one word.
410 # tokenize each argument into exactly one word.
409 replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
411 replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
410 # escape '\$' for regex
412 # escape '\$' for regex
411 regex = '|'.join(replacemap.keys()).replace('$', r'\$')
413 regex = '|'.join(replacemap.keys()).replace('$', r'\$')
412 r = re.compile(regex)
414 r = re.compile(regex)
413 return r.sub(lambda x: replacemap[x.group()], cmd)
415 return r.sub(lambda x: replacemap[x.group()], cmd)
414
416
415 class cmdalias(object):
417 class cmdalias(object):
416 def __init__(self, name, definition, cmdtable, source):
418 def __init__(self, name, definition, cmdtable, source):
417 self.name = self.cmd = name
419 self.name = self.cmd = name
418 self.cmdname = ''
420 self.cmdname = ''
419 self.definition = definition
421 self.definition = definition
420 self.fn = None
422 self.fn = None
421 self.givenargs = []
423 self.givenargs = []
422 self.opts = []
424 self.opts = []
423 self.help = ''
425 self.help = ''
424 self.badalias = None
426 self.badalias = None
425 self.unknowncmd = False
427 self.unknowncmd = False
426 self.source = source
428 self.source = source
427
429
428 try:
430 try:
429 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
431 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
430 for alias, e in cmdtable.iteritems():
432 for alias, e in cmdtable.iteritems():
431 if e is entry:
433 if e is entry:
432 self.cmd = alias
434 self.cmd = alias
433 break
435 break
434 self.shadows = True
436 self.shadows = True
435 except error.UnknownCommand:
437 except error.UnknownCommand:
436 self.shadows = False
438 self.shadows = False
437
439
438 if not self.definition:
440 if not self.definition:
439 self.badalias = _("no definition for alias '%s'") % self.name
441 self.badalias = _("no definition for alias '%s'") % self.name
440 return
442 return
441
443
442 if self.definition.startswith('!'):
444 if self.definition.startswith('!'):
443 self.shell = True
445 self.shell = True
444 def fn(ui, *args):
446 def fn(ui, *args):
445 env = {'HG_ARGS': ' '.join((self.name,) + args)}
447 env = {'HG_ARGS': ' '.join((self.name,) + args)}
446 def _checkvar(m):
448 def _checkvar(m):
447 if m.groups()[0] == '$':
449 if m.groups()[0] == '$':
448 return m.group()
450 return m.group()
449 elif int(m.groups()[0]) <= len(args):
451 elif int(m.groups()[0]) <= len(args):
450 return m.group()
452 return m.group()
451 else:
453 else:
452 ui.debug("No argument found for substitution "
454 ui.debug("No argument found for substitution "
453 "of %i variable in alias '%s' definition."
455 "of %i variable in alias '%s' definition."
454 % (int(m.groups()[0]), self.name))
456 % (int(m.groups()[0]), self.name))
455 return ''
457 return ''
456 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
458 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
457 cmd = aliasinterpolate(self.name, args, cmd)
459 cmd = aliasinterpolate(self.name, args, cmd)
458 return ui.system(cmd, environ=env,
460 return ui.system(cmd, environ=env,
459 blockedtag='alias_%s' % self.name)
461 blockedtag='alias_%s' % self.name)
460 self.fn = fn
462 self.fn = fn
461 return
463 return
462
464
463 try:
465 try:
464 args = pycompat.shlexsplit(self.definition)
466 args = pycompat.shlexsplit(self.definition)
465 except ValueError as inst:
467 except ValueError as inst:
466 self.badalias = (_("error in definition for alias '%s': %s")
468 self.badalias = (_("error in definition for alias '%s': %s")
467 % (self.name, inst))
469 % (self.name, inst))
468 return
470 return
469 self.cmdname = cmd = args.pop(0)
471 self.cmdname = cmd = args.pop(0)
470 self.givenargs = args
472 self.givenargs = args
471
473
472 for invalidarg in commands.earlyoptflags:
474 for invalidarg in commands.earlyoptflags:
473 if _earlygetopt([invalidarg], args):
475 if _earlygetopt([invalidarg], args):
474 self.badalias = (_("error in definition for alias '%s': %s may "
476 self.badalias = (_("error in definition for alias '%s': %s may "
475 "only be given on the command line")
477 "only be given on the command line")
476 % (self.name, invalidarg))
478 % (self.name, invalidarg))
477 return
479 return
478
480
479 try:
481 try:
480 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
482 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
481 if len(tableentry) > 2:
483 if len(tableentry) > 2:
482 self.fn, self.opts, self.help = tableentry
484 self.fn, self.opts, self.help = tableentry
483 else:
485 else:
484 self.fn, self.opts = tableentry
486 self.fn, self.opts = tableentry
485
487
486 if self.help.startswith("hg " + cmd):
488 if self.help.startswith("hg " + cmd):
487 # drop prefix in old-style help lines so hg shows the alias
489 # drop prefix in old-style help lines so hg shows the alias
488 self.help = self.help[4 + len(cmd):]
490 self.help = self.help[4 + len(cmd):]
489 self.__doc__ = self.fn.__doc__
491 self.__doc__ = self.fn.__doc__
490
492
491 except error.UnknownCommand:
493 except error.UnknownCommand:
492 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
494 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
493 % (self.name, cmd))
495 % (self.name, cmd))
494 self.unknowncmd = True
496 self.unknowncmd = True
495 except error.AmbiguousCommand:
497 except error.AmbiguousCommand:
496 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
498 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
497 % (self.name, cmd))
499 % (self.name, cmd))
498
500
499 @property
501 @property
500 def args(self):
502 def args(self):
501 args = pycompat.maplist(util.expandpath, self.givenargs)
503 args = pycompat.maplist(util.expandpath, self.givenargs)
502 return aliasargs(self.fn, args)
504 return aliasargs(self.fn, args)
503
505
504 def __getattr__(self, name):
506 def __getattr__(self, name):
505 adefaults = {r'norepo': True, r'cmdtype': unrecoverablewrite,
507 adefaults = {r'norepo': True, r'cmdtype': unrecoverablewrite,
506 r'optionalrepo': False, r'inferrepo': False}
508 r'optionalrepo': False, r'inferrepo': False}
507 if name not in adefaults:
509 if name not in adefaults:
508 raise AttributeError(name)
510 raise AttributeError(name)
509 if self.badalias or util.safehasattr(self, 'shell'):
511 if self.badalias or util.safehasattr(self, 'shell'):
510 return adefaults[name]
512 return adefaults[name]
511 return getattr(self.fn, name)
513 return getattr(self.fn, name)
512
514
513 def __call__(self, ui, *args, **opts):
515 def __call__(self, ui, *args, **opts):
514 if self.badalias:
516 if self.badalias:
515 hint = None
517 hint = None
516 if self.unknowncmd:
518 if self.unknowncmd:
517 try:
519 try:
518 # check if the command is in a disabled extension
520 # check if the command is in a disabled extension
519 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
521 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
520 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
522 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
521 except error.UnknownCommand:
523 except error.UnknownCommand:
522 pass
524 pass
523 raise error.Abort(self.badalias, hint=hint)
525 raise error.Abort(self.badalias, hint=hint)
524 if self.shadows:
526 if self.shadows:
525 ui.debug("alias '%s' shadows command '%s'\n" %
527 ui.debug("alias '%s' shadows command '%s'\n" %
526 (self.name, self.cmdname))
528 (self.name, self.cmdname))
527
529
528 ui.log('commandalias', "alias '%s' expands to '%s'\n",
530 ui.log('commandalias', "alias '%s' expands to '%s'\n",
529 self.name, self.definition)
531 self.name, self.definition)
530 if util.safehasattr(self, 'shell'):
532 if util.safehasattr(self, 'shell'):
531 return self.fn(ui, *args, **opts)
533 return self.fn(ui, *args, **opts)
532 else:
534 else:
533 try:
535 try:
534 return util.checksignature(self.fn)(ui, *args, **opts)
536 return util.checksignature(self.fn)(ui, *args, **opts)
535 except error.SignatureError:
537 except error.SignatureError:
536 args = ' '.join([self.cmdname] + self.args)
538 args = ' '.join([self.cmdname] + self.args)
537 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
539 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
538 raise
540 raise
539
541
540 class lazyaliasentry(object):
542 class lazyaliasentry(object):
541 """like a typical command entry (func, opts, help), but is lazy"""
543 """like a typical command entry (func, opts, help), but is lazy"""
542
544
543 def __init__(self, name, definition, cmdtable, source):
545 def __init__(self, name, definition, cmdtable, source):
544 self.name = name
546 self.name = name
545 self.definition = definition
547 self.definition = definition
546 self.cmdtable = cmdtable.copy()
548 self.cmdtable = cmdtable.copy()
547 self.source = source
549 self.source = source
548
550
549 @util.propertycache
551 @util.propertycache
550 def _aliasdef(self):
552 def _aliasdef(self):
551 return cmdalias(self.name, self.definition, self.cmdtable, self.source)
553 return cmdalias(self.name, self.definition, self.cmdtable, self.source)
552
554
553 def __getitem__(self, n):
555 def __getitem__(self, n):
554 aliasdef = self._aliasdef
556 aliasdef = self._aliasdef
555 if n == 0:
557 if n == 0:
556 return aliasdef
558 return aliasdef
557 elif n == 1:
559 elif n == 1:
558 return aliasdef.opts
560 return aliasdef.opts
559 elif n == 2:
561 elif n == 2:
560 return aliasdef.help
562 return aliasdef.help
561 else:
563 else:
562 raise IndexError
564 raise IndexError
563
565
564 def __iter__(self):
566 def __iter__(self):
565 for i in range(3):
567 for i in range(3):
566 yield self[i]
568 yield self[i]
567
569
568 def __len__(self):
570 def __len__(self):
569 return 3
571 return 3
570
572
571 def addaliases(ui, cmdtable):
573 def addaliases(ui, cmdtable):
572 # aliases are processed after extensions have been loaded, so they
574 # aliases are processed after extensions have been loaded, so they
573 # may use extension commands. Aliases can also use other alias definitions,
575 # may use extension commands. Aliases can also use other alias definitions,
574 # but only if they have been defined prior to the current definition.
576 # but only if they have been defined prior to the current definition.
575 for alias, definition in ui.configitems('alias'):
577 for alias, definition in ui.configitems('alias'):
576 try:
578 try:
577 if cmdtable[alias].definition == definition:
579 if cmdtable[alias].definition == definition:
578 continue
580 continue
579 except (KeyError, AttributeError):
581 except (KeyError, AttributeError):
580 # definition might not exist or it might not be a cmdalias
582 # definition might not exist or it might not be a cmdalias
581 pass
583 pass
582
584
583 source = ui.configsource('alias', alias)
585 source = ui.configsource('alias', alias)
584 entry = lazyaliasentry(alias, definition, cmdtable, source)
586 entry = lazyaliasentry(alias, definition, cmdtable, source)
585 cmdtable[alias] = entry
587 cmdtable[alias] = entry
586
588
587 def _parse(ui, args):
589 def _parse(ui, args):
588 options = {}
590 options = {}
589 cmdoptions = {}
591 cmdoptions = {}
590
592
591 try:
593 try:
592 args = fancyopts.fancyopts(args, commands.globalopts, options)
594 args = fancyopts.fancyopts(args, commands.globalopts, options)
593 except getopt.GetoptError as inst:
595 except getopt.GetoptError as inst:
594 raise error.CommandError(None, inst)
596 raise error.CommandError(None, inst)
595
597
596 if args:
598 if args:
597 cmd, args = args[0], args[1:]
599 cmd, args = args[0], args[1:]
598 aliases, entry = cmdutil.findcmd(cmd, commands.table,
600 aliases, entry = cmdutil.findcmd(cmd, commands.table,
599 ui.configbool("ui", "strict"))
601 ui.configbool("ui", "strict"))
600 cmd = aliases[0]
602 cmd = aliases[0]
601 args = aliasargs(entry[0], args)
603 args = aliasargs(entry[0], args)
602 defaults = ui.config("defaults", cmd)
604 defaults = ui.config("defaults", cmd)
603 if defaults:
605 if defaults:
604 args = pycompat.maplist(
606 args = pycompat.maplist(
605 util.expandpath, pycompat.shlexsplit(defaults)) + args
607 util.expandpath, pycompat.shlexsplit(defaults)) + args
606 c = list(entry[1])
608 c = list(entry[1])
607 else:
609 else:
608 cmd = None
610 cmd = None
609 c = []
611 c = []
610
612
611 # combine global options into local
613 # combine global options into local
612 for o in commands.globalopts:
614 for o in commands.globalopts:
613 c.append((o[0], o[1], options[o[1]], o[3]))
615 c.append((o[0], o[1], options[o[1]], o[3]))
614
616
615 try:
617 try:
616 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
618 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
617 except getopt.GetoptError as inst:
619 except getopt.GetoptError as inst:
618 raise error.CommandError(cmd, inst)
620 raise error.CommandError(cmd, inst)
619
621
620 # separate global options back out
622 # separate global options back out
621 for o in commands.globalopts:
623 for o in commands.globalopts:
622 n = o[1]
624 n = o[1]
623 options[n] = cmdoptions[n]
625 options[n] = cmdoptions[n]
624 del cmdoptions[n]
626 del cmdoptions[n]
625
627
626 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
628 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
627
629
628 def _parseconfig(ui, config):
630 def _parseconfig(ui, config):
629 """parse the --config options from the command line"""
631 """parse the --config options from the command line"""
630 configs = []
632 configs = []
631
633
632 for cfg in config:
634 for cfg in config:
633 try:
635 try:
634 name, value = [cfgelem.strip()
636 name, value = [cfgelem.strip()
635 for cfgelem in cfg.split('=', 1)]
637 for cfgelem in cfg.split('=', 1)]
636 section, name = name.split('.', 1)
638 section, name = name.split('.', 1)
637 if not section or not name:
639 if not section or not name:
638 raise IndexError
640 raise IndexError
639 ui.setconfig(section, name, value, '--config')
641 ui.setconfig(section, name, value, '--config')
640 configs.append((section, name, value))
642 configs.append((section, name, value))
641 except (IndexError, ValueError):
643 except (IndexError, ValueError):
642 raise error.Abort(_('malformed --config option: %r '
644 raise error.Abort(_('malformed --config option: %r '
643 '(use --config section.name=value)') % cfg)
645 '(use --config section.name=value)') % cfg)
644
646
645 return configs
647 return configs
646
648
649 def _earlyparseopts(args):
650 options = {}
651 fancyopts.fancyopts(args, commands.globalopts, options,
652 gnu=False, early=True)
653 return options
654
647 def _earlygetopt(aliases, args, strip=True):
655 def _earlygetopt(aliases, args, strip=True):
648 """Return list of values for an option (or aliases).
656 """Return list of values for an option (or aliases).
649
657
650 The values are listed in the order they appear in args.
658 The values are listed in the order they appear in args.
651 The options and values are removed from args if strip=True.
659 The options and values are removed from args if strip=True.
652
660
653 >>> args = [b'x', b'--cwd', b'foo', b'y']
661 >>> args = [b'x', b'--cwd', b'foo', b'y']
654 >>> _earlygetopt([b'--cwd'], args), args
662 >>> _earlygetopt([b'--cwd'], args), args
655 (['foo'], ['x', 'y'])
663 (['foo'], ['x', 'y'])
656
664
657 >>> args = [b'x', b'--cwd=bar', b'y']
665 >>> args = [b'x', b'--cwd=bar', b'y']
658 >>> _earlygetopt([b'--cwd'], args), args
666 >>> _earlygetopt([b'--cwd'], args), args
659 (['bar'], ['x', 'y'])
667 (['bar'], ['x', 'y'])
660
668
661 >>> args = [b'x', b'--cwd=bar', b'y']
669 >>> args = [b'x', b'--cwd=bar', b'y']
662 >>> _earlygetopt([b'--cwd'], args, strip=False), args
670 >>> _earlygetopt([b'--cwd'], args, strip=False), args
663 (['bar'], ['x', '--cwd=bar', 'y'])
671 (['bar'], ['x', '--cwd=bar', 'y'])
664
672
665 >>> args = [b'x', b'-R', b'foo', b'y']
673 >>> args = [b'x', b'-R', b'foo', b'y']
666 >>> _earlygetopt([b'-R'], args), args
674 >>> _earlygetopt([b'-R'], args), args
667 (['foo'], ['x', 'y'])
675 (['foo'], ['x', 'y'])
668
676
669 >>> args = [b'x', b'-R', b'foo', b'y']
677 >>> args = [b'x', b'-R', b'foo', b'y']
670 >>> _earlygetopt([b'-R'], args, strip=False), args
678 >>> _earlygetopt([b'-R'], args, strip=False), args
671 (['foo'], ['x', '-R', 'foo', 'y'])
679 (['foo'], ['x', '-R', 'foo', 'y'])
672
680
673 >>> args = [b'x', b'-Rbar', b'y']
681 >>> args = [b'x', b'-Rbar', b'y']
674 >>> _earlygetopt([b'-R'], args), args
682 >>> _earlygetopt([b'-R'], args), args
675 (['bar'], ['x', 'y'])
683 (['bar'], ['x', 'y'])
676
684
677 >>> args = [b'x', b'-Rbar', b'y']
685 >>> args = [b'x', b'-Rbar', b'y']
678 >>> _earlygetopt([b'-R'], args, strip=False), args
686 >>> _earlygetopt([b'-R'], args, strip=False), args
679 (['bar'], ['x', '-Rbar', 'y'])
687 (['bar'], ['x', '-Rbar', 'y'])
680
688
681 >>> args = [b'x', b'-R=bar', b'y']
689 >>> args = [b'x', b'-R=bar', b'y']
682 >>> _earlygetopt([b'-R'], args), args
690 >>> _earlygetopt([b'-R'], args), args
683 (['=bar'], ['x', 'y'])
691 (['=bar'], ['x', 'y'])
684
692
685 >>> args = [b'x', b'-R', b'--', b'y']
693 >>> args = [b'x', b'-R', b'--', b'y']
686 >>> _earlygetopt([b'-R'], args), args
694 >>> _earlygetopt([b'-R'], args), args
687 ([], ['x', '-R', '--', 'y'])
695 ([], ['x', '-R', '--', 'y'])
688 """
696 """
689 try:
697 try:
690 argcount = args.index("--")
698 argcount = args.index("--")
691 except ValueError:
699 except ValueError:
692 argcount = len(args)
700 argcount = len(args)
693 shortopts = [opt for opt in aliases if len(opt) == 2]
701 shortopts = [opt for opt in aliases if len(opt) == 2]
694 values = []
702 values = []
695 pos = 0
703 pos = 0
696 while pos < argcount:
704 while pos < argcount:
697 fullarg = arg = args[pos]
705 fullarg = arg = args[pos]
698 equals = -1
706 equals = -1
699 if arg.startswith('--'):
707 if arg.startswith('--'):
700 equals = arg.find('=')
708 equals = arg.find('=')
701 if equals > -1:
709 if equals > -1:
702 arg = arg[:equals]
710 arg = arg[:equals]
703 if arg in aliases:
711 if arg in aliases:
704 if equals > -1:
712 if equals > -1:
705 values.append(fullarg[equals + 1:])
713 values.append(fullarg[equals + 1:])
706 if strip:
714 if strip:
707 del args[pos]
715 del args[pos]
708 argcount -= 1
716 argcount -= 1
709 else:
717 else:
710 pos += 1
718 pos += 1
711 else:
719 else:
712 if pos + 1 >= argcount:
720 if pos + 1 >= argcount:
713 # ignore and let getopt report an error if there is no value
721 # ignore and let getopt report an error if there is no value
714 break
722 break
715 values.append(args[pos + 1])
723 values.append(args[pos + 1])
716 if strip:
724 if strip:
717 del args[pos:pos + 2]
725 del args[pos:pos + 2]
718 argcount -= 2
726 argcount -= 2
719 else:
727 else:
720 pos += 2
728 pos += 2
721 elif arg[:2] in shortopts:
729 elif arg[:2] in shortopts:
722 # short option can have no following space, e.g. hg log -Rfoo
730 # short option can have no following space, e.g. hg log -Rfoo
723 values.append(args[pos][2:])
731 values.append(args[pos][2:])
724 if strip:
732 if strip:
725 del args[pos]
733 del args[pos]
726 argcount -= 1
734 argcount -= 1
727 else:
735 else:
728 pos += 1
736 pos += 1
729 else:
737 else:
730 pos += 1
738 pos += 1
731 return values
739 return values
732
740
733 def _earlyreqopt(req, name, aliases):
741 def _earlyreqopt(req, name, aliases):
734 """Peek a list option without using a full options table"""
742 """Peek a list option without using a full options table"""
743 if req.ui.plain('strictflags'):
744 return req.earlyoptions[name]
735 values = _earlygetopt(aliases, req.args, strip=False)
745 values = _earlygetopt(aliases, req.args, strip=False)
736 req.earlyoptions[name] = values
746 req.earlyoptions[name] = values
737 return values
747 return values
738
748
739 def _earlyreqoptstr(req, name, aliases):
749 def _earlyreqoptstr(req, name, aliases):
740 """Peek a string option without using a full options table"""
750 """Peek a string option without using a full options table"""
751 if req.ui.plain('strictflags'):
752 return req.earlyoptions[name]
741 value = (_earlygetopt(aliases, req.args, strip=False) or [''])[-1]
753 value = (_earlygetopt(aliases, req.args, strip=False) or [''])[-1]
742 req.earlyoptions[name] = value
754 req.earlyoptions[name] = value
743 return value
755 return value
744
756
745 def _earlyreqoptbool(req, name, aliases):
757 def _earlyreqoptbool(req, name, aliases):
746 """Peek a boolean option without using a full options table
758 """Peek a boolean option without using a full options table
747
759
748 >>> req = request([b'x', b'--debugger'])
760 >>> req = request([b'x', b'--debugger'], uimod.ui())
749 >>> _earlyreqoptbool(req, b'debugger', [b'--debugger'])
761 >>> _earlyreqoptbool(req, b'debugger', [b'--debugger'])
750 True
762 True
751
763
752 >>> req = request([b'x', b'--', b'--debugger'])
764 >>> req = request([b'x', b'--', b'--debugger'], uimod.ui())
753 >>> _earlyreqoptbool(req, b'debugger', [b'--debugger'])
765 >>> _earlyreqoptbool(req, b'debugger', [b'--debugger'])
754 """
766 """
767 if req.ui.plain('strictflags'):
768 return req.earlyoptions[name]
755 try:
769 try:
756 argcount = req.args.index("--")
770 argcount = req.args.index("--")
757 except ValueError:
771 except ValueError:
758 argcount = len(req.args)
772 argcount = len(req.args)
759 value = None
773 value = None
760 pos = 0
774 pos = 0
761 while pos < argcount:
775 while pos < argcount:
762 arg = req.args[pos]
776 arg = req.args[pos]
763 if arg in aliases:
777 if arg in aliases:
764 value = True
778 value = True
765 pos += 1
779 pos += 1
766 req.earlyoptions[name] = value
780 req.earlyoptions[name] = value
767 return value
781 return value
768
782
769 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
783 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
770 # run pre-hook, and abort if it fails
784 # run pre-hook, and abort if it fails
771 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
785 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
772 pats=cmdpats, opts=cmdoptions)
786 pats=cmdpats, opts=cmdoptions)
773 try:
787 try:
774 ret = _runcommand(ui, options, cmd, d)
788 ret = _runcommand(ui, options, cmd, d)
775 # run post-hook, passing command result
789 # run post-hook, passing command result
776 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
790 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
777 result=ret, pats=cmdpats, opts=cmdoptions)
791 result=ret, pats=cmdpats, opts=cmdoptions)
778 except Exception:
792 except Exception:
779 # run failure hook and re-raise
793 # run failure hook and re-raise
780 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
794 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
781 pats=cmdpats, opts=cmdoptions)
795 pats=cmdpats, opts=cmdoptions)
782 raise
796 raise
783 return ret
797 return ret
784
798
785 def _getlocal(ui, rpath, wd=None):
799 def _getlocal(ui, rpath, wd=None):
786 """Return (path, local ui object) for the given target path.
800 """Return (path, local ui object) for the given target path.
787
801
788 Takes paths in [cwd]/.hg/hgrc into account."
802 Takes paths in [cwd]/.hg/hgrc into account."
789 """
803 """
790 if wd is None:
804 if wd is None:
791 try:
805 try:
792 wd = pycompat.getcwd()
806 wd = pycompat.getcwd()
793 except OSError as e:
807 except OSError as e:
794 raise error.Abort(_("error getting current working directory: %s") %
808 raise error.Abort(_("error getting current working directory: %s") %
795 encoding.strtolocal(e.strerror))
809 encoding.strtolocal(e.strerror))
796 path = cmdutil.findrepo(wd) or ""
810 path = cmdutil.findrepo(wd) or ""
797 if not path:
811 if not path:
798 lui = ui
812 lui = ui
799 else:
813 else:
800 lui = ui.copy()
814 lui = ui.copy()
801 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
815 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
802
816
803 if rpath:
817 if rpath:
804 path = lui.expandpath(rpath)
818 path = lui.expandpath(rpath)
805 lui = ui.copy()
819 lui = ui.copy()
806 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
820 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
807
821
808 return path, lui
822 return path, lui
809
823
810 def _checkshellalias(lui, ui, args):
824 def _checkshellalias(lui, ui, args):
811 """Return the function to run the shell alias, if it is required"""
825 """Return the function to run the shell alias, if it is required"""
812 options = {}
826 options = {}
813
827
814 try:
828 try:
815 args = fancyopts.fancyopts(args, commands.globalopts, options)
829 args = fancyopts.fancyopts(args, commands.globalopts, options)
816 except getopt.GetoptError:
830 except getopt.GetoptError:
817 return
831 return
818
832
819 if not args:
833 if not args:
820 return
834 return
821
835
822 cmdtable = commands.table
836 cmdtable = commands.table
823
837
824 cmd = args[0]
838 cmd = args[0]
825 try:
839 try:
826 strict = ui.configbool("ui", "strict")
840 strict = ui.configbool("ui", "strict")
827 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
841 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
828 except (error.AmbiguousCommand, error.UnknownCommand):
842 except (error.AmbiguousCommand, error.UnknownCommand):
829 return
843 return
830
844
831 cmd = aliases[0]
845 cmd = aliases[0]
832 fn = entry[0]
846 fn = entry[0]
833
847
834 if cmd and util.safehasattr(fn, 'shell'):
848 if cmd and util.safehasattr(fn, 'shell'):
835 # shell alias shouldn't receive early options which are consumed by hg
849 # shell alias shouldn't receive early options which are consumed by hg
836 args = args[:]
850 args = args[:]
837 _earlygetopt(commands.earlyoptflags, args, strip=True)
851 _earlygetopt(commands.earlyoptflags, args, strip=True)
838 d = lambda: fn(ui, *args[1:])
852 d = lambda: fn(ui, *args[1:])
839 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
853 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
840 [], {})
854 [], {})
841
855
842 def _dispatch(req):
856 def _dispatch(req):
843 args = req.args
857 args = req.args
844 ui = req.ui
858 ui = req.ui
845
859
846 # check for cwd
860 # check for cwd
847 cwd = _earlyreqoptstr(req, 'cwd', ['--cwd'])
861 cwd = _earlyreqoptstr(req, 'cwd', ['--cwd'])
848 if cwd:
862 if cwd:
849 os.chdir(cwd)
863 os.chdir(cwd)
850
864
851 rpath = _earlyreqoptstr(req, 'repository', ["-R", "--repository", "--repo"])
865 rpath = _earlyreqoptstr(req, 'repository', ["-R", "--repository", "--repo"])
852 path, lui = _getlocal(ui, rpath)
866 path, lui = _getlocal(ui, rpath)
853
867
854 uis = {ui, lui}
868 uis = {ui, lui}
855
869
856 if req.repo:
870 if req.repo:
857 uis.add(req.repo.ui)
871 uis.add(req.repo.ui)
858
872
859 if _earlyreqoptbool(req, 'profile', ['--profile']):
873 if _earlyreqoptbool(req, 'profile', ['--profile']):
860 for ui_ in uis:
874 for ui_ in uis:
861 ui_.setconfig('profiling', 'enabled', 'true', '--profile')
875 ui_.setconfig('profiling', 'enabled', 'true', '--profile')
862
876
863 profile = lui.configbool('profiling', 'enabled')
877 profile = lui.configbool('profiling', 'enabled')
864 with profiling.profile(lui, enabled=profile) as profiler:
878 with profiling.profile(lui, enabled=profile) as profiler:
865 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
879 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
866 # reposetup
880 # reposetup
867 extensions.loadall(lui)
881 extensions.loadall(lui)
868 # Propagate any changes to lui.__class__ by extensions
882 # Propagate any changes to lui.__class__ by extensions
869 ui.__class__ = lui.__class__
883 ui.__class__ = lui.__class__
870
884
871 # (uisetup and extsetup are handled in extensions.loadall)
885 # (uisetup and extsetup are handled in extensions.loadall)
872
886
873 # (reposetup is handled in hg.repository)
887 # (reposetup is handled in hg.repository)
874
888
875 addaliases(lui, commands.table)
889 addaliases(lui, commands.table)
876
890
877 # All aliases and commands are completely defined, now.
891 # All aliases and commands are completely defined, now.
878 # Check abbreviation/ambiguity of shell alias.
892 # Check abbreviation/ambiguity of shell alias.
879 shellaliasfn = _checkshellalias(lui, ui, args)
893 shellaliasfn = _checkshellalias(lui, ui, args)
880 if shellaliasfn:
894 if shellaliasfn:
881 return shellaliasfn()
895 return shellaliasfn()
882
896
883 # check for fallback encoding
897 # check for fallback encoding
884 fallback = lui.config('ui', 'fallbackencoding')
898 fallback = lui.config('ui', 'fallbackencoding')
885 if fallback:
899 if fallback:
886 encoding.fallbackencoding = fallback
900 encoding.fallbackencoding = fallback
887
901
888 fullargs = args
902 fullargs = args
889 cmd, func, args, options, cmdoptions = _parse(lui, args)
903 cmd, func, args, options, cmdoptions = _parse(lui, args)
890
904
891 if options["config"] != req.earlyoptions["config"]:
905 if options["config"] != req.earlyoptions["config"]:
892 raise error.Abort(_("option --config may not be abbreviated!"))
906 raise error.Abort(_("option --config may not be abbreviated!"))
893 if options["cwd"] != req.earlyoptions["cwd"]:
907 if options["cwd"] != req.earlyoptions["cwd"]:
894 raise error.Abort(_("option --cwd may not be abbreviated!"))
908 raise error.Abort(_("option --cwd may not be abbreviated!"))
895 if options["repository"] != req.earlyoptions["repository"]:
909 if options["repository"] != req.earlyoptions["repository"]:
896 raise error.Abort(_(
910 raise error.Abort(_(
897 "option -R has to be separated from other options (e.g. not "
911 "option -R has to be separated from other options (e.g. not "
898 "-qR) and --repository may only be abbreviated as --repo!"))
912 "-qR) and --repository may only be abbreviated as --repo!"))
899 if options["debugger"] != req.earlyoptions["debugger"]:
913 if options["debugger"] != req.earlyoptions["debugger"]:
900 raise error.Abort(_("option --debugger may not be abbreviated!"))
914 raise error.Abort(_("option --debugger may not be abbreviated!"))
901 # don't validate --profile/--traceback, which can be enabled from now
915 # don't validate --profile/--traceback, which can be enabled from now
902
916
903 if options["encoding"]:
917 if options["encoding"]:
904 encoding.encoding = options["encoding"]
918 encoding.encoding = options["encoding"]
905 if options["encodingmode"]:
919 if options["encodingmode"]:
906 encoding.encodingmode = options["encodingmode"]
920 encoding.encodingmode = options["encodingmode"]
907 if options["time"]:
921 if options["time"]:
908 def get_times():
922 def get_times():
909 t = os.times()
923 t = os.times()
910 if t[4] == 0.0:
924 if t[4] == 0.0:
911 # Windows leaves this as zero, so use time.clock()
925 # Windows leaves this as zero, so use time.clock()
912 t = (t[0], t[1], t[2], t[3], time.clock())
926 t = (t[0], t[1], t[2], t[3], time.clock())
913 return t
927 return t
914 s = get_times()
928 s = get_times()
915 def print_time():
929 def print_time():
916 t = get_times()
930 t = get_times()
917 ui.warn(
931 ui.warn(
918 _("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
932 _("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
919 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
933 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
920 ui.atexit(print_time)
934 ui.atexit(print_time)
921 if options["profile"]:
935 if options["profile"]:
922 profiler.start()
936 profiler.start()
923
937
924 if options['verbose'] or options['debug'] or options['quiet']:
938 if options['verbose'] or options['debug'] or options['quiet']:
925 for opt in ('verbose', 'debug', 'quiet'):
939 for opt in ('verbose', 'debug', 'quiet'):
926 val = str(bool(options[opt]))
940 val = str(bool(options[opt]))
927 if pycompat.ispy3:
941 if pycompat.ispy3:
928 val = val.encode('ascii')
942 val = val.encode('ascii')
929 for ui_ in uis:
943 for ui_ in uis:
930 ui_.setconfig('ui', opt, val, '--' + opt)
944 ui_.setconfig('ui', opt, val, '--' + opt)
931
945
932 if options['traceback']:
946 if options['traceback']:
933 for ui_ in uis:
947 for ui_ in uis:
934 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
948 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
935
949
936 if options['noninteractive']:
950 if options['noninteractive']:
937 for ui_ in uis:
951 for ui_ in uis:
938 ui_.setconfig('ui', 'interactive', 'off', '-y')
952 ui_.setconfig('ui', 'interactive', 'off', '-y')
939
953
940 if cmdoptions.get('insecure', False):
954 if cmdoptions.get('insecure', False):
941 for ui_ in uis:
955 for ui_ in uis:
942 ui_.insecureconnections = True
956 ui_.insecureconnections = True
943
957
944 # setup color handling before pager, because setting up pager
958 # setup color handling before pager, because setting up pager
945 # might cause incorrect console information
959 # might cause incorrect console information
946 coloropt = options['color']
960 coloropt = options['color']
947 for ui_ in uis:
961 for ui_ in uis:
948 if coloropt:
962 if coloropt:
949 ui_.setconfig('ui', 'color', coloropt, '--color')
963 ui_.setconfig('ui', 'color', coloropt, '--color')
950 color.setup(ui_)
964 color.setup(ui_)
951
965
952 if util.parsebool(options['pager']):
966 if util.parsebool(options['pager']):
953 # ui.pager() expects 'internal-always-' prefix in this case
967 # ui.pager() expects 'internal-always-' prefix in this case
954 ui.pager('internal-always-' + cmd)
968 ui.pager('internal-always-' + cmd)
955 elif options['pager'] != 'auto':
969 elif options['pager'] != 'auto':
956 for ui_ in uis:
970 for ui_ in uis:
957 ui_.disablepager()
971 ui_.disablepager()
958
972
959 if options['version']:
973 if options['version']:
960 return commands.version_(ui)
974 return commands.version_(ui)
961 if options['help']:
975 if options['help']:
962 return commands.help_(ui, cmd, command=cmd is not None)
976 return commands.help_(ui, cmd, command=cmd is not None)
963 elif not cmd:
977 elif not cmd:
964 return commands.help_(ui, 'shortlist')
978 return commands.help_(ui, 'shortlist')
965
979
966 repo = None
980 repo = None
967 cmdpats = args[:]
981 cmdpats = args[:]
968 if not func.norepo:
982 if not func.norepo:
969 # use the repo from the request only if we don't have -R
983 # use the repo from the request only if we don't have -R
970 if not rpath and not cwd:
984 if not rpath and not cwd:
971 repo = req.repo
985 repo = req.repo
972
986
973 if repo:
987 if repo:
974 # set the descriptors of the repo ui to those of ui
988 # set the descriptors of the repo ui to those of ui
975 repo.ui.fin = ui.fin
989 repo.ui.fin = ui.fin
976 repo.ui.fout = ui.fout
990 repo.ui.fout = ui.fout
977 repo.ui.ferr = ui.ferr
991 repo.ui.ferr = ui.ferr
978 else:
992 else:
979 try:
993 try:
980 repo = hg.repository(ui, path=path,
994 repo = hg.repository(ui, path=path,
981 presetupfuncs=req.prereposetups)
995 presetupfuncs=req.prereposetups)
982 if not repo.local():
996 if not repo.local():
983 raise error.Abort(_("repository '%s' is not local")
997 raise error.Abort(_("repository '%s' is not local")
984 % path)
998 % path)
985 repo.ui.setconfig("bundle", "mainreporoot", repo.root,
999 repo.ui.setconfig("bundle", "mainreporoot", repo.root,
986 'repo')
1000 'repo')
987 except error.RequirementError:
1001 except error.RequirementError:
988 raise
1002 raise
989 except error.RepoError:
1003 except error.RepoError:
990 if rpath: # invalid -R path
1004 if rpath: # invalid -R path
991 raise
1005 raise
992 if not func.optionalrepo:
1006 if not func.optionalrepo:
993 if func.inferrepo and args and not path:
1007 if func.inferrepo and args and not path:
994 # try to infer -R from command args
1008 # try to infer -R from command args
995 repos = map(cmdutil.findrepo, args)
1009 repos = map(cmdutil.findrepo, args)
996 guess = repos[0]
1010 guess = repos[0]
997 if guess and repos.count(guess) == len(repos):
1011 if guess and repos.count(guess) == len(repos):
998 req.args = ['--repository', guess] + fullargs
1012 req.args = ['--repository', guess] + fullargs
999 return _dispatch(req)
1013 return _dispatch(req)
1000 if not path:
1014 if not path:
1001 raise error.RepoError(_("no repository found in"
1015 raise error.RepoError(_("no repository found in"
1002 " '%s' (.hg not found)")
1016 " '%s' (.hg not found)")
1003 % pycompat.getcwd())
1017 % pycompat.getcwd())
1004 raise
1018 raise
1005 if repo:
1019 if repo:
1006 ui = repo.ui
1020 ui = repo.ui
1007 if options['hidden']:
1021 if options['hidden']:
1008 repo = repo.unfiltered()
1022 repo = repo.unfiltered()
1009 args.insert(0, repo)
1023 args.insert(0, repo)
1010 elif rpath:
1024 elif rpath:
1011 ui.warn(_("warning: --repository ignored\n"))
1025 ui.warn(_("warning: --repository ignored\n"))
1012
1026
1013 msg = _formatargs(fullargs)
1027 msg = _formatargs(fullargs)
1014 ui.log("command", '%s\n', msg)
1028 ui.log("command", '%s\n', msg)
1015 strcmdopt = pycompat.strkwargs(cmdoptions)
1029 strcmdopt = pycompat.strkwargs(cmdoptions)
1016 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
1030 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
1017 try:
1031 try:
1018 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
1032 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
1019 cmdpats, cmdoptions)
1033 cmdpats, cmdoptions)
1020 finally:
1034 finally:
1021 if repo and repo != req.repo:
1035 if repo and repo != req.repo:
1022 repo.close()
1036 repo.close()
1023
1037
1024 def _runcommand(ui, options, cmd, cmdfunc):
1038 def _runcommand(ui, options, cmd, cmdfunc):
1025 """Run a command function, possibly with profiling enabled."""
1039 """Run a command function, possibly with profiling enabled."""
1026 try:
1040 try:
1027 return cmdfunc()
1041 return cmdfunc()
1028 except error.SignatureError:
1042 except error.SignatureError:
1029 raise error.CommandError(cmd, _('invalid arguments'))
1043 raise error.CommandError(cmd, _('invalid arguments'))
1030
1044
1031 def _exceptionwarning(ui):
1045 def _exceptionwarning(ui):
1032 """Produce a warning message for the current active exception"""
1046 """Produce a warning message for the current active exception"""
1033
1047
1034 # For compatibility checking, we discard the portion of the hg
1048 # For compatibility checking, we discard the portion of the hg
1035 # version after the + on the assumption that if a "normal
1049 # version after the + on the assumption that if a "normal
1036 # user" is running a build with a + in it the packager
1050 # user" is running a build with a + in it the packager
1037 # probably built from fairly close to a tag and anyone with a
1051 # probably built from fairly close to a tag and anyone with a
1038 # 'make local' copy of hg (where the version number can be out
1052 # 'make local' copy of hg (where the version number can be out
1039 # of date) will be clueful enough to notice the implausible
1053 # of date) will be clueful enough to notice the implausible
1040 # version number and try updating.
1054 # version number and try updating.
1041 ct = util.versiontuple(n=2)
1055 ct = util.versiontuple(n=2)
1042 worst = None, ct, ''
1056 worst = None, ct, ''
1043 if ui.config('ui', 'supportcontact') is None:
1057 if ui.config('ui', 'supportcontact') is None:
1044 for name, mod in extensions.extensions():
1058 for name, mod in extensions.extensions():
1045 testedwith = getattr(mod, 'testedwith', '')
1059 testedwith = getattr(mod, 'testedwith', '')
1046 if pycompat.ispy3 and isinstance(testedwith, str):
1060 if pycompat.ispy3 and isinstance(testedwith, str):
1047 testedwith = testedwith.encode(u'utf-8')
1061 testedwith = testedwith.encode(u'utf-8')
1048 report = getattr(mod, 'buglink', _('the extension author.'))
1062 report = getattr(mod, 'buglink', _('the extension author.'))
1049 if not testedwith.strip():
1063 if not testedwith.strip():
1050 # We found an untested extension. It's likely the culprit.
1064 # We found an untested extension. It's likely the culprit.
1051 worst = name, 'unknown', report
1065 worst = name, 'unknown', report
1052 break
1066 break
1053
1067
1054 # Never blame on extensions bundled with Mercurial.
1068 # Never blame on extensions bundled with Mercurial.
1055 if extensions.ismoduleinternal(mod):
1069 if extensions.ismoduleinternal(mod):
1056 continue
1070 continue
1057
1071
1058 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
1072 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
1059 if ct in tested:
1073 if ct in tested:
1060 continue
1074 continue
1061
1075
1062 lower = [t for t in tested if t < ct]
1076 lower = [t for t in tested if t < ct]
1063 nearest = max(lower or tested)
1077 nearest = max(lower or tested)
1064 if worst[0] is None or nearest < worst[1]:
1078 if worst[0] is None or nearest < worst[1]:
1065 worst = name, nearest, report
1079 worst = name, nearest, report
1066 if worst[0] is not None:
1080 if worst[0] is not None:
1067 name, testedwith, report = worst
1081 name, testedwith, report = worst
1068 if not isinstance(testedwith, (bytes, str)):
1082 if not isinstance(testedwith, (bytes, str)):
1069 testedwith = '.'.join([str(c) for c in testedwith])
1083 testedwith = '.'.join([str(c) for c in testedwith])
1070 warning = (_('** Unknown exception encountered with '
1084 warning = (_('** Unknown exception encountered with '
1071 'possibly-broken third-party extension %s\n'
1085 'possibly-broken third-party extension %s\n'
1072 '** which supports versions %s of Mercurial.\n'
1086 '** which supports versions %s of Mercurial.\n'
1073 '** Please disable %s and try your action again.\n'
1087 '** Please disable %s and try your action again.\n'
1074 '** If that fixes the bug please report it to %s\n')
1088 '** If that fixes the bug please report it to %s\n')
1075 % (name, testedwith, name, report))
1089 % (name, testedwith, name, report))
1076 else:
1090 else:
1077 bugtracker = ui.config('ui', 'supportcontact')
1091 bugtracker = ui.config('ui', 'supportcontact')
1078 if bugtracker is None:
1092 if bugtracker is None:
1079 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
1093 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
1080 warning = (_("** unknown exception encountered, "
1094 warning = (_("** unknown exception encountered, "
1081 "please report by visiting\n** ") + bugtracker + '\n')
1095 "please report by visiting\n** ") + bugtracker + '\n')
1082 if pycompat.ispy3:
1096 if pycompat.ispy3:
1083 sysversion = sys.version.encode(u'utf-8')
1097 sysversion = sys.version.encode(u'utf-8')
1084 else:
1098 else:
1085 sysversion = sys.version
1099 sysversion = sys.version
1086 sysversion = sysversion.replace('\n', '')
1100 sysversion = sysversion.replace('\n', '')
1087 warning += ((_("** Python %s\n") % sysversion) +
1101 warning += ((_("** Python %s\n") % sysversion) +
1088 (_("** Mercurial Distributed SCM (version %s)\n") %
1102 (_("** Mercurial Distributed SCM (version %s)\n") %
1089 util.version()) +
1103 util.version()) +
1090 (_("** Extensions loaded: %s\n") %
1104 (_("** Extensions loaded: %s\n") %
1091 ", ".join([x[0] for x in extensions.extensions()])))
1105 ", ".join([x[0] for x in extensions.extensions()])))
1092 return warning
1106 return warning
1093
1107
1094 def handlecommandexception(ui):
1108 def handlecommandexception(ui):
1095 """Produce a warning message for broken commands
1109 """Produce a warning message for broken commands
1096
1110
1097 Called when handling an exception; the exception is reraised if
1111 Called when handling an exception; the exception is reraised if
1098 this function returns False, ignored otherwise.
1112 this function returns False, ignored otherwise.
1099 """
1113 """
1100 warning = _exceptionwarning(ui)
1114 warning = _exceptionwarning(ui)
1101 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
1115 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
1102 ui.warn(warning)
1116 ui.warn(warning)
1103 return False # re-raise the exception
1117 return False # re-raise the exception
@@ -1,111 +1,119
1 HG
1 HG
2 Path to the 'hg' executable, automatically passed when running
2 Path to the 'hg' executable, automatically passed when running
3 hooks, extensions or external tools. If unset or empty, this is
3 hooks, extensions or external tools. If unset or empty, this is
4 the hg executable's name if it's frozen, or an executable named
4 the hg executable's name if it's frozen, or an executable named
5 'hg' (with %PATHEXT% [defaulting to COM/EXE/BAT/CMD] extensions on
5 'hg' (with %PATHEXT% [defaulting to COM/EXE/BAT/CMD] extensions on
6 Windows) is searched.
6 Windows) is searched.
7
7
8 HGEDITOR
8 HGEDITOR
9 This is the name of the editor to run when committing. See EDITOR.
9 This is the name of the editor to run when committing. See EDITOR.
10
10
11 (deprecated, see :hg:`help config.ui.editor`)
11 (deprecated, see :hg:`help config.ui.editor`)
12
12
13 HGENCODING
13 HGENCODING
14 This overrides the default locale setting detected by Mercurial.
14 This overrides the default locale setting detected by Mercurial.
15 This setting is used to convert data including usernames,
15 This setting is used to convert data including usernames,
16 changeset descriptions, tag names, and branches. This setting can
16 changeset descriptions, tag names, and branches. This setting can
17 be overridden with the --encoding command-line option.
17 be overridden with the --encoding command-line option.
18
18
19 HGENCODINGMODE
19 HGENCODINGMODE
20 This sets Mercurial's behavior for handling unknown characters
20 This sets Mercurial's behavior for handling unknown characters
21 while transcoding user input. The default is "strict", which
21 while transcoding user input. The default is "strict", which
22 causes Mercurial to abort if it can't map a character. Other
22 causes Mercurial to abort if it can't map a character. Other
23 settings include "replace", which replaces unknown characters, and
23 settings include "replace", which replaces unknown characters, and
24 "ignore", which drops them. This setting can be overridden with
24 "ignore", which drops them. This setting can be overridden with
25 the --encodingmode command-line option.
25 the --encodingmode command-line option.
26
26
27 HGENCODINGAMBIGUOUS
27 HGENCODINGAMBIGUOUS
28 This sets Mercurial's behavior for handling characters with
28 This sets Mercurial's behavior for handling characters with
29 "ambiguous" widths like accented Latin characters with East Asian
29 "ambiguous" widths like accented Latin characters with East Asian
30 fonts. By default, Mercurial assumes ambiguous characters are
30 fonts. By default, Mercurial assumes ambiguous characters are
31 narrow, set this variable to "wide" if such characters cause
31 narrow, set this variable to "wide" if such characters cause
32 formatting problems.
32 formatting problems.
33
33
34 HGMERGE
34 HGMERGE
35 An executable to use for resolving merge conflicts. The program
35 An executable to use for resolving merge conflicts. The program
36 will be executed with three arguments: local file, remote file,
36 will be executed with three arguments: local file, remote file,
37 ancestor file.
37 ancestor file.
38
38
39 (deprecated, see :hg:`help config.ui.merge`)
39 (deprecated, see :hg:`help config.ui.merge`)
40
40
41 HGRCPATH
41 HGRCPATH
42 A list of files or directories to search for configuration
42 A list of files or directories to search for configuration
43 files. Item separator is ":" on Unix, ";" on Windows. If HGRCPATH
43 files. Item separator is ":" on Unix, ";" on Windows. If HGRCPATH
44 is not set, platform default search path is used. If empty, only
44 is not set, platform default search path is used. If empty, only
45 the .hg/hgrc from the current repository is read.
45 the .hg/hgrc from the current repository is read.
46
46
47 For each element in HGRCPATH:
47 For each element in HGRCPATH:
48
48
49 - if it's a directory, all files ending with .rc are added
49 - if it's a directory, all files ending with .rc are added
50 - otherwise, the file itself will be added
50 - otherwise, the file itself will be added
51
51
52 HGPLAIN
52 HGPLAIN
53 When set, this disables any configuration settings that might
53 When set, this disables any configuration settings that might
54 change Mercurial's default output. This includes encoding,
54 change Mercurial's default output. This includes encoding,
55 defaults, verbose mode, debug mode, quiet mode, tracebacks, and
55 defaults, verbose mode, debug mode, quiet mode, tracebacks, and
56 localization. This can be useful when scripting against Mercurial
56 localization. This can be useful when scripting against Mercurial
57 in the face of existing user configuration.
57 in the face of existing user configuration.
58
58
59 In addition to the features disabled by ``HGPLAIN=``, the following
60 values can be specified to adjust behavior:
61
62 ``+strictflags``
63 Restrict parsing of command line flags.
64
59 Equivalent options set via command line flags or environment
65 Equivalent options set via command line flags or environment
60 variables are not overridden.
66 variables are not overridden.
61
67
68 See :hg:`help scripting` for details.
69
62 HGPLAINEXCEPT
70 HGPLAINEXCEPT
63 This is a comma-separated list of features to preserve when
71 This is a comma-separated list of features to preserve when
64 HGPLAIN is enabled. Currently the following values are supported:
72 HGPLAIN is enabled. Currently the following values are supported:
65
73
66 ``alias``
74 ``alias``
67 Don't remove aliases.
75 Don't remove aliases.
68 ``i18n``
76 ``i18n``
69 Preserve internationalization.
77 Preserve internationalization.
70 ``revsetalias``
78 ``revsetalias``
71 Don't remove revset aliases.
79 Don't remove revset aliases.
72 ``templatealias``
80 ``templatealias``
73 Don't remove template aliases.
81 Don't remove template aliases.
74 ``progress``
82 ``progress``
75 Don't hide progress output.
83 Don't hide progress output.
76
84
77 Setting HGPLAINEXCEPT to anything (even an empty string) will
85 Setting HGPLAINEXCEPT to anything (even an empty string) will
78 enable plain mode.
86 enable plain mode.
79
87
80 HGUSER
88 HGUSER
81 This is the string used as the author of a commit. If not set,
89 This is the string used as the author of a commit. If not set,
82 available values will be considered in this order:
90 available values will be considered in this order:
83
91
84 - HGUSER (deprecated)
92 - HGUSER (deprecated)
85 - configuration files from the HGRCPATH
93 - configuration files from the HGRCPATH
86 - EMAIL
94 - EMAIL
87 - interactive prompt
95 - interactive prompt
88 - LOGNAME (with ``@hostname`` appended)
96 - LOGNAME (with ``@hostname`` appended)
89
97
90 (deprecated, see :hg:`help config.ui.username`)
98 (deprecated, see :hg:`help config.ui.username`)
91
99
92 EMAIL
100 EMAIL
93 May be used as the author of a commit; see HGUSER.
101 May be used as the author of a commit; see HGUSER.
94
102
95 LOGNAME
103 LOGNAME
96 May be used as the author of a commit; see HGUSER.
104 May be used as the author of a commit; see HGUSER.
97
105
98 VISUAL
106 VISUAL
99 This is the name of the editor to use when committing. See EDITOR.
107 This is the name of the editor to use when committing. See EDITOR.
100
108
101 EDITOR
109 EDITOR
102 Sometimes Mercurial needs to open a text file in an editor for a
110 Sometimes Mercurial needs to open a text file in an editor for a
103 user to modify, for example when writing commit messages. The
111 user to modify, for example when writing commit messages. The
104 editor it uses is determined by looking at the environment
112 editor it uses is determined by looking at the environment
105 variables HGEDITOR, VISUAL and EDITOR, in that order. The first
113 variables HGEDITOR, VISUAL and EDITOR, in that order. The first
106 non-empty one is chosen. If all of them are empty, the editor
114 non-empty one is chosen. If all of them are empty, the editor
107 defaults to 'vi'.
115 defaults to 'vi'.
108
116
109 PYTHONPATH
117 PYTHONPATH
110 This is used by Python to find imported modules and may need to be
118 This is used by Python to find imported modules and may need to be
111 set appropriately if this Mercurial is not installed system-wide.
119 set appropriately if this Mercurial is not installed system-wide.
@@ -1,174 +1,200
1 It is common for machines (as opposed to humans) to consume Mercurial.
1 It is common for machines (as opposed to humans) to consume Mercurial.
2 This help topic describes some of the considerations for interfacing
2 This help topic describes some of the considerations for interfacing
3 machines with Mercurial.
3 machines with Mercurial.
4
4
5 Choosing an Interface
5 Choosing an Interface
6 =====================
6 =====================
7
7
8 Machines have a choice of several methods to interface with Mercurial.
8 Machines have a choice of several methods to interface with Mercurial.
9 These include:
9 These include:
10
10
11 - Executing the ``hg`` process
11 - Executing the ``hg`` process
12 - Querying a HTTP server
12 - Querying a HTTP server
13 - Calling out to a command server
13 - Calling out to a command server
14
14
15 Executing ``hg`` processes is very similar to how humans interact with
15 Executing ``hg`` processes is very similar to how humans interact with
16 Mercurial in the shell. It should already be familiar to you.
16 Mercurial in the shell. It should already be familiar to you.
17
17
18 :hg:`serve` can be used to start a server. By default, this will start
18 :hg:`serve` can be used to start a server. By default, this will start
19 a "hgweb" HTTP server. This HTTP server has support for machine-readable
19 a "hgweb" HTTP server. This HTTP server has support for machine-readable
20 output, such as JSON. For more, see :hg:`help hgweb`.
20 output, such as JSON. For more, see :hg:`help hgweb`.
21
21
22 :hg:`serve` can also start a "command server." Clients can connect
22 :hg:`serve` can also start a "command server." Clients can connect
23 to this server and issue Mercurial commands over a special protocol.
23 to this server and issue Mercurial commands over a special protocol.
24 For more details on the command server, including links to client
24 For more details on the command server, including links to client
25 libraries, see https://www.mercurial-scm.org/wiki/CommandServer.
25 libraries, see https://www.mercurial-scm.org/wiki/CommandServer.
26
26
27 :hg:`serve` based interfaces (the hgweb and command servers) have the
27 :hg:`serve` based interfaces (the hgweb and command servers) have the
28 advantage over simple ``hg`` process invocations in that they are
28 advantage over simple ``hg`` process invocations in that they are
29 likely more efficient. This is because there is significant overhead
29 likely more efficient. This is because there is significant overhead
30 to spawn new Python processes.
30 to spawn new Python processes.
31
31
32 .. tip::
32 .. tip::
33
33
34 If you need to invoke several ``hg`` processes in short order and/or
34 If you need to invoke several ``hg`` processes in short order and/or
35 performance is important to you, use of a server-based interface
35 performance is important to you, use of a server-based interface
36 is highly recommended.
36 is highly recommended.
37
37
38 Environment Variables
38 Environment Variables
39 =====================
39 =====================
40
40
41 As documented in :hg:`help environment`, various environment variables
41 As documented in :hg:`help environment`, various environment variables
42 influence the operation of Mercurial. The following are particularly
42 influence the operation of Mercurial. The following are particularly
43 relevant for machines consuming Mercurial:
43 relevant for machines consuming Mercurial:
44
44
45 HGPLAIN
45 HGPLAIN
46 If not set, Mercurial's output could be influenced by configuration
46 If not set, Mercurial's output could be influenced by configuration
47 settings that impact its encoding, verbose mode, localization, etc.
47 settings that impact its encoding, verbose mode, localization, etc.
48
48
49 It is highly recommended for machines to set this variable when
49 It is highly recommended for machines to set this variable when
50 invoking ``hg`` processes.
50 invoking ``hg`` processes.
51
51
52 HGENCODING
52 HGENCODING
53 If not set, the locale used by Mercurial will be detected from the
53 If not set, the locale used by Mercurial will be detected from the
54 environment. If the determined locale does not support display of
54 environment. If the determined locale does not support display of
55 certain characters, Mercurial may render these character sequences
55 certain characters, Mercurial may render these character sequences
56 incorrectly (often by using "?" as a placeholder for invalid
56 incorrectly (often by using "?" as a placeholder for invalid
57 characters in the current locale).
57 characters in the current locale).
58
58
59 Explicitly setting this environment variable is a good practice to
59 Explicitly setting this environment variable is a good practice to
60 guarantee consistent results. "utf-8" is a good choice on UNIX-like
60 guarantee consistent results. "utf-8" is a good choice on UNIX-like
61 environments.
61 environments.
62
62
63 HGRCPATH
63 HGRCPATH
64 If not set, Mercurial will inherit config options from config files
64 If not set, Mercurial will inherit config options from config files
65 using the process described in :hg:`help config`. This includes
65 using the process described in :hg:`help config`. This includes
66 inheriting user or system-wide config files.
66 inheriting user or system-wide config files.
67
67
68 When utmost control over the Mercurial configuration is desired, the
68 When utmost control over the Mercurial configuration is desired, the
69 value of ``HGRCPATH`` can be set to an explicit file with known good
69 value of ``HGRCPATH`` can be set to an explicit file with known good
70 configs. In rare cases, the value can be set to an empty file or the
70 configs. In rare cases, the value can be set to an empty file or the
71 null device (often ``/dev/null``) to bypass loading of any user or
71 null device (often ``/dev/null``) to bypass loading of any user or
72 system config files. Note that these approaches can have unintended
72 system config files. Note that these approaches can have unintended
73 consequences, as the user and system config files often define things
73 consequences, as the user and system config files often define things
74 like the username and extensions that may be required to interface
74 like the username and extensions that may be required to interface
75 with a repository.
75 with a repository.
76
76
77 Command-line Flags
78 ==================
79
80 Mercurial's default command-line parser is designed for humans, and is not
81 robust against malicious input. For instance, you can start a debugger by
82 passing ``--debugger`` as an option value::
83
84 $ REV=--debugger sh -c 'hg log -r "$REV"'
85
86 This happens because several command-line flags need to be scanned without
87 using a concrete command table, which may be modified while loading repository
88 settings and extensions.
89
90 Since Mercurial 4.4.2, the parsing of such flags may be restricted by setting
91 ``HGPLAIN=+strictflags``. When this feature is enabled, all early options
92 (e.g. ``-R/--repository``, ``--cwd``, ``--config``) must be specified first
93 amongst the other global options, and cannot be injected to an arbitrary
94 location::
95
96 $ HGPLAIN=+strictflags hg -R "$REPO" log -r "$REV"
97
98 In earlier Mercurial versions where ``+strictflags`` isn't available, you
99 can mitigate the issue by concatenating an option value with its flag::
100
101 $ hg log -r"$REV" --keyword="$KEYWORD"
102
77 Consuming Command Output
103 Consuming Command Output
78 ========================
104 ========================
79
105
80 It is common for machines to need to parse the output of Mercurial
106 It is common for machines to need to parse the output of Mercurial
81 commands for relevant data. This section describes the various
107 commands for relevant data. This section describes the various
82 techniques for doing so.
108 techniques for doing so.
83
109
84 Parsing Raw Command Output
110 Parsing Raw Command Output
85 --------------------------
111 --------------------------
86
112
87 Likely the simplest and most effective solution for consuming command
113 Likely the simplest and most effective solution for consuming command
88 output is to simply invoke ``hg`` commands as you would as a user and
114 output is to simply invoke ``hg`` commands as you would as a user and
89 parse their output.
115 parse their output.
90
116
91 The output of many commands can easily be parsed with tools like
117 The output of many commands can easily be parsed with tools like
92 ``grep``, ``sed``, and ``awk``.
118 ``grep``, ``sed``, and ``awk``.
93
119
94 A potential downside with parsing command output is that the output
120 A potential downside with parsing command output is that the output
95 of commands can change when Mercurial is upgraded. While Mercurial
121 of commands can change when Mercurial is upgraded. While Mercurial
96 does generally strive for strong backwards compatibility, command
122 does generally strive for strong backwards compatibility, command
97 output does occasionally change. Having tests for your automated
123 output does occasionally change. Having tests for your automated
98 interactions with ``hg`` commands is generally recommended, but is
124 interactions with ``hg`` commands is generally recommended, but is
99 even more important when raw command output parsing is involved.
125 even more important when raw command output parsing is involved.
100
126
101 Using Templates to Control Output
127 Using Templates to Control Output
102 ---------------------------------
128 ---------------------------------
103
129
104 Many ``hg`` commands support templatized output via the
130 Many ``hg`` commands support templatized output via the
105 ``-T/--template`` argument. For more, see :hg:`help templates`.
131 ``-T/--template`` argument. For more, see :hg:`help templates`.
106
132
107 Templates are useful for explicitly controlling output so that
133 Templates are useful for explicitly controlling output so that
108 you get exactly the data you want formatted how you want it. For
134 you get exactly the data you want formatted how you want it. For
109 example, ``log -T {node}\n`` can be used to print a newline
135 example, ``log -T {node}\n`` can be used to print a newline
110 delimited list of changeset nodes instead of a human-tailored
136 delimited list of changeset nodes instead of a human-tailored
111 output containing authors, dates, descriptions, etc.
137 output containing authors, dates, descriptions, etc.
112
138
113 .. tip::
139 .. tip::
114
140
115 If parsing raw command output is too complicated, consider
141 If parsing raw command output is too complicated, consider
116 using templates to make your life easier.
142 using templates to make your life easier.
117
143
118 The ``-T/--template`` argument allows specifying pre-defined styles.
144 The ``-T/--template`` argument allows specifying pre-defined styles.
119 Mercurial ships with the machine-readable styles ``json`` and ``xml``,
145 Mercurial ships with the machine-readable styles ``json`` and ``xml``,
120 which provide JSON and XML output, respectively. These are useful for
146 which provide JSON and XML output, respectively. These are useful for
121 producing output that is machine readable as-is.
147 producing output that is machine readable as-is.
122
148
123 .. important::
149 .. important::
124
150
125 The ``json`` and ``xml`` styles are considered experimental. While
151 The ``json`` and ``xml`` styles are considered experimental. While
126 they may be attractive to use for easily obtaining machine-readable
152 they may be attractive to use for easily obtaining machine-readable
127 output, their behavior may change in subsequent versions.
153 output, their behavior may change in subsequent versions.
128
154
129 These styles may also exhibit unexpected results when dealing with
155 These styles may also exhibit unexpected results when dealing with
130 certain encodings. Mercurial treats things like filenames as a
156 certain encodings. Mercurial treats things like filenames as a
131 series of bytes and normalizing certain byte sequences to JSON
157 series of bytes and normalizing certain byte sequences to JSON
132 or XML with certain encoding settings can lead to surprises.
158 or XML with certain encoding settings can lead to surprises.
133
159
134 Command Server Output
160 Command Server Output
135 ---------------------
161 ---------------------
136
162
137 If using the command server to interact with Mercurial, you are likely
163 If using the command server to interact with Mercurial, you are likely
138 using an existing library/API that abstracts implementation details of
164 using an existing library/API that abstracts implementation details of
139 the command server. If so, this interface layer may perform parsing for
165 the command server. If so, this interface layer may perform parsing for
140 you, saving you the work of implementing it yourself.
166 you, saving you the work of implementing it yourself.
141
167
142 Output Verbosity
168 Output Verbosity
143 ----------------
169 ----------------
144
170
145 Commands often have varying output verbosity, even when machine
171 Commands often have varying output verbosity, even when machine
146 readable styles are being used (e.g. ``-T json``). Adding
172 readable styles are being used (e.g. ``-T json``). Adding
147 ``-v/--verbose`` and ``--debug`` to the command's arguments can
173 ``-v/--verbose`` and ``--debug`` to the command's arguments can
148 increase the amount of data exposed by Mercurial.
174 increase the amount of data exposed by Mercurial.
149
175
150 An alternate way to get the data you need is by explicitly specifying
176 An alternate way to get the data you need is by explicitly specifying
151 a template.
177 a template.
152
178
153 Other Topics
179 Other Topics
154 ============
180 ============
155
181
156 revsets
182 revsets
157 Revisions sets is a functional query language for selecting a set
183 Revisions sets is a functional query language for selecting a set
158 of revisions. Think of it as SQL for Mercurial repositories. Revsets
184 of revisions. Think of it as SQL for Mercurial repositories. Revsets
159 are useful for querying repositories for specific data.
185 are useful for querying repositories for specific data.
160
186
161 See :hg:`help revsets` for more.
187 See :hg:`help revsets` for more.
162
188
163 share extension
189 share extension
164 The ``share`` extension provides functionality for sharing
190 The ``share`` extension provides functionality for sharing
165 repository data across several working copies. It can even
191 repository data across several working copies. It can even
166 automatically "pool" storage for logically related repositories when
192 automatically "pool" storage for logically related repositories when
167 cloning.
193 cloning.
168
194
169 Configuring the ``share`` extension can lead to significant resource
195 Configuring the ``share`` extension can lead to significant resource
170 utilization reduction, particularly around disk space and the
196 utilization reduction, particularly around disk space and the
171 network. This is especially true for continuous integration (CI)
197 network. This is especially true for continuous integration (CI)
172 environments.
198 environments.
173
199
174 See :hg:`help -e share` for more.
200 See :hg:`help -e share` for more.
@@ -1,1836 +1,1840
1 # ui.py - user interface bits for mercurial
1 # ui.py - user interface bits 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
8 from __future__ import absolute_import
9
9
10 import collections
10 import collections
11 import contextlib
11 import contextlib
12 import errno
12 import errno
13 import getpass
13 import getpass
14 import inspect
14 import inspect
15 import os
15 import os
16 import re
16 import re
17 import signal
17 import signal
18 import socket
18 import socket
19 import subprocess
19 import subprocess
20 import sys
20 import sys
21 import tempfile
21 import tempfile
22 import traceback
22 import traceback
23
23
24 from .i18n import _
24 from .i18n import _
25 from .node import hex
25 from .node import hex
26
26
27 from . import (
27 from . import (
28 color,
28 color,
29 config,
29 config,
30 configitems,
30 configitems,
31 encoding,
31 encoding,
32 error,
32 error,
33 formatter,
33 formatter,
34 progress,
34 progress,
35 pycompat,
35 pycompat,
36 rcutil,
36 rcutil,
37 scmutil,
37 scmutil,
38 util,
38 util,
39 )
39 )
40
40
41 urlreq = util.urlreq
41 urlreq = util.urlreq
42
42
43 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
43 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
44 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
44 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
45 if not c.isalnum())
45 if not c.isalnum())
46
46
47 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
47 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
48 tweakrc = """
48 tweakrc = """
49 [ui]
49 [ui]
50 # The rollback command is dangerous. As a rule, don't use it.
50 # The rollback command is dangerous. As a rule, don't use it.
51 rollback = False
51 rollback = False
52
52
53 [commands]
53 [commands]
54 # Make `hg status` emit cwd-relative paths by default.
54 # Make `hg status` emit cwd-relative paths by default.
55 status.relative = yes
55 status.relative = yes
56 # Refuse to perform an `hg update` that would cause a file content merge
56 # Refuse to perform an `hg update` that would cause a file content merge
57 update.check = noconflict
57 update.check = noconflict
58
58
59 [diff]
59 [diff]
60 git = 1
60 git = 1
61 """
61 """
62
62
63 samplehgrcs = {
63 samplehgrcs = {
64 'user':
64 'user':
65 b"""# example user config (see 'hg help config' for more info)
65 b"""# example user config (see 'hg help config' for more info)
66 [ui]
66 [ui]
67 # name and email, e.g.
67 # name and email, e.g.
68 # username = Jane Doe <jdoe@example.com>
68 # username = Jane Doe <jdoe@example.com>
69 username =
69 username =
70
70
71 # We recommend enabling tweakdefaults to get slight improvements to
71 # We recommend enabling tweakdefaults to get slight improvements to
72 # the UI over time. Make sure to set HGPLAIN in the environment when
72 # the UI over time. Make sure to set HGPLAIN in the environment when
73 # writing scripts!
73 # writing scripts!
74 # tweakdefaults = True
74 # tweakdefaults = True
75
75
76 # uncomment to disable color in command output
76 # uncomment to disable color in command output
77 # (see 'hg help color' for details)
77 # (see 'hg help color' for details)
78 # color = never
78 # color = never
79
79
80 # uncomment to disable command output pagination
80 # uncomment to disable command output pagination
81 # (see 'hg help pager' for details)
81 # (see 'hg help pager' for details)
82 # paginate = never
82 # paginate = never
83
83
84 [extensions]
84 [extensions]
85 # uncomment these lines to enable some popular extensions
85 # uncomment these lines to enable some popular extensions
86 # (see 'hg help extensions' for more info)
86 # (see 'hg help extensions' for more info)
87 #
87 #
88 # churn =
88 # churn =
89 """,
89 """,
90
90
91 'cloned':
91 'cloned':
92 b"""# example repository config (see 'hg help config' for more info)
92 b"""# example repository config (see 'hg help config' for more info)
93 [paths]
93 [paths]
94 default = %s
94 default = %s
95
95
96 # path aliases to other clones of this repo in URLs or filesystem paths
96 # path aliases to other clones of this repo in URLs or filesystem paths
97 # (see 'hg help config.paths' for more info)
97 # (see 'hg help config.paths' for more info)
98 #
98 #
99 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
99 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
100 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
100 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
101 # my-clone = /home/jdoe/jdoes-clone
101 # my-clone = /home/jdoe/jdoes-clone
102
102
103 [ui]
103 [ui]
104 # name and email (local to this repository, optional), e.g.
104 # name and email (local to this repository, optional), e.g.
105 # username = Jane Doe <jdoe@example.com>
105 # username = Jane Doe <jdoe@example.com>
106 """,
106 """,
107
107
108 'local':
108 'local':
109 b"""# example repository config (see 'hg help config' for more info)
109 b"""# example repository config (see 'hg help config' for more info)
110 [paths]
110 [paths]
111 # path aliases to other clones of this repo in URLs or filesystem paths
111 # path aliases to other clones of this repo in URLs or filesystem paths
112 # (see 'hg help config.paths' for more info)
112 # (see 'hg help config.paths' for more info)
113 #
113 #
114 # default = http://example.com/hg/example-repo
114 # default = http://example.com/hg/example-repo
115 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
115 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
116 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
116 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
117 # my-clone = /home/jdoe/jdoes-clone
117 # my-clone = /home/jdoe/jdoes-clone
118
118
119 [ui]
119 [ui]
120 # name and email (local to this repository, optional), e.g.
120 # name and email (local to this repository, optional), e.g.
121 # username = Jane Doe <jdoe@example.com>
121 # username = Jane Doe <jdoe@example.com>
122 """,
122 """,
123
123
124 'global':
124 'global':
125 b"""# example system-wide hg config (see 'hg help config' for more info)
125 b"""# example system-wide hg config (see 'hg help config' for more info)
126
126
127 [ui]
127 [ui]
128 # uncomment to disable color in command output
128 # uncomment to disable color in command output
129 # (see 'hg help color' for details)
129 # (see 'hg help color' for details)
130 # color = never
130 # color = never
131
131
132 # uncomment to disable command output pagination
132 # uncomment to disable command output pagination
133 # (see 'hg help pager' for details)
133 # (see 'hg help pager' for details)
134 # paginate = never
134 # paginate = never
135
135
136 [extensions]
136 [extensions]
137 # uncomment these lines to enable some popular extensions
137 # uncomment these lines to enable some popular extensions
138 # (see 'hg help extensions' for more info)
138 # (see 'hg help extensions' for more info)
139 #
139 #
140 # blackbox =
140 # blackbox =
141 # churn =
141 # churn =
142 """,
142 """,
143 }
143 }
144
144
145 def _maybestrurl(maybebytes):
145 def _maybestrurl(maybebytes):
146 if maybebytes is None:
146 if maybebytes is None:
147 return None
147 return None
148 return pycompat.strurl(maybebytes)
148 return pycompat.strurl(maybebytes)
149
149
150 def _maybebytesurl(maybestr):
150 def _maybebytesurl(maybestr):
151 if maybestr is None:
151 if maybestr is None:
152 return None
152 return None
153 return pycompat.bytesurl(maybestr)
153 return pycompat.bytesurl(maybestr)
154
154
155 class httppasswordmgrdbproxy(object):
155 class httppasswordmgrdbproxy(object):
156 """Delays loading urllib2 until it's needed."""
156 """Delays loading urllib2 until it's needed."""
157 def __init__(self):
157 def __init__(self):
158 self._mgr = None
158 self._mgr = None
159
159
160 def _get_mgr(self):
160 def _get_mgr(self):
161 if self._mgr is None:
161 if self._mgr is None:
162 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
162 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
163 return self._mgr
163 return self._mgr
164
164
165 def add_password(self, realm, uris, user, passwd):
165 def add_password(self, realm, uris, user, passwd):
166 if isinstance(uris, tuple):
166 if isinstance(uris, tuple):
167 uris = tuple(_maybestrurl(u) for u in uris)
167 uris = tuple(_maybestrurl(u) for u in uris)
168 else:
168 else:
169 uris = _maybestrurl(uris)
169 uris = _maybestrurl(uris)
170 return self._get_mgr().add_password(
170 return self._get_mgr().add_password(
171 _maybestrurl(realm), uris,
171 _maybestrurl(realm), uris,
172 _maybestrurl(user), _maybestrurl(passwd))
172 _maybestrurl(user), _maybestrurl(passwd))
173
173
174 def find_user_password(self, realm, uri):
174 def find_user_password(self, realm, uri):
175 return tuple(_maybebytesurl(v) for v in
175 return tuple(_maybebytesurl(v) for v in
176 self._get_mgr().find_user_password(_maybestrurl(realm),
176 self._get_mgr().find_user_password(_maybestrurl(realm),
177 _maybestrurl(uri)))
177 _maybestrurl(uri)))
178
178
179 def _catchterm(*args):
179 def _catchterm(*args):
180 raise error.SignalInterrupt
180 raise error.SignalInterrupt
181
181
182 # unique object used to detect no default value has been provided when
182 # unique object used to detect no default value has been provided when
183 # retrieving configuration value.
183 # retrieving configuration value.
184 _unset = object()
184 _unset = object()
185
185
186 # _reqexithandlers: callbacks run at the end of a request
186 # _reqexithandlers: callbacks run at the end of a request
187 _reqexithandlers = []
187 _reqexithandlers = []
188
188
189 class ui(object):
189 class ui(object):
190 def __init__(self, src=None):
190 def __init__(self, src=None):
191 """Create a fresh new ui object if no src given
191 """Create a fresh new ui object if no src given
192
192
193 Use uimod.ui.load() to create a ui which knows global and user configs.
193 Use uimod.ui.load() to create a ui which knows global and user configs.
194 In most cases, you should use ui.copy() to create a copy of an existing
194 In most cases, you should use ui.copy() to create a copy of an existing
195 ui object.
195 ui object.
196 """
196 """
197 # _buffers: used for temporary capture of output
197 # _buffers: used for temporary capture of output
198 self._buffers = []
198 self._buffers = []
199 # 3-tuple describing how each buffer in the stack behaves.
199 # 3-tuple describing how each buffer in the stack behaves.
200 # Values are (capture stderr, capture subprocesses, apply labels).
200 # Values are (capture stderr, capture subprocesses, apply labels).
201 self._bufferstates = []
201 self._bufferstates = []
202 # When a buffer is active, defines whether we are expanding labels.
202 # When a buffer is active, defines whether we are expanding labels.
203 # This exists to prevent an extra list lookup.
203 # This exists to prevent an extra list lookup.
204 self._bufferapplylabels = None
204 self._bufferapplylabels = None
205 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
205 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
206 self._reportuntrusted = True
206 self._reportuntrusted = True
207 self._knownconfig = configitems.coreitems
207 self._knownconfig = configitems.coreitems
208 self._ocfg = config.config() # overlay
208 self._ocfg = config.config() # overlay
209 self._tcfg = config.config() # trusted
209 self._tcfg = config.config() # trusted
210 self._ucfg = config.config() # untrusted
210 self._ucfg = config.config() # untrusted
211 self._trustusers = set()
211 self._trustusers = set()
212 self._trustgroups = set()
212 self._trustgroups = set()
213 self.callhooks = True
213 self.callhooks = True
214 # Insecure server connections requested.
214 # Insecure server connections requested.
215 self.insecureconnections = False
215 self.insecureconnections = False
216 # Blocked time
216 # Blocked time
217 self.logblockedtimes = False
217 self.logblockedtimes = False
218 # color mode: see mercurial/color.py for possible value
218 # color mode: see mercurial/color.py for possible value
219 self._colormode = None
219 self._colormode = None
220 self._terminfoparams = {}
220 self._terminfoparams = {}
221 self._styles = {}
221 self._styles = {}
222
222
223 if src:
223 if src:
224 self.fout = src.fout
224 self.fout = src.fout
225 self.ferr = src.ferr
225 self.ferr = src.ferr
226 self.fin = src.fin
226 self.fin = src.fin
227 self.pageractive = src.pageractive
227 self.pageractive = src.pageractive
228 self._disablepager = src._disablepager
228 self._disablepager = src._disablepager
229 self._tweaked = src._tweaked
229 self._tweaked = src._tweaked
230
230
231 self._tcfg = src._tcfg.copy()
231 self._tcfg = src._tcfg.copy()
232 self._ucfg = src._ucfg.copy()
232 self._ucfg = src._ucfg.copy()
233 self._ocfg = src._ocfg.copy()
233 self._ocfg = src._ocfg.copy()
234 self._trustusers = src._trustusers.copy()
234 self._trustusers = src._trustusers.copy()
235 self._trustgroups = src._trustgroups.copy()
235 self._trustgroups = src._trustgroups.copy()
236 self.environ = src.environ
236 self.environ = src.environ
237 self.callhooks = src.callhooks
237 self.callhooks = src.callhooks
238 self.insecureconnections = src.insecureconnections
238 self.insecureconnections = src.insecureconnections
239 self._colormode = src._colormode
239 self._colormode = src._colormode
240 self._terminfoparams = src._terminfoparams.copy()
240 self._terminfoparams = src._terminfoparams.copy()
241 self._styles = src._styles.copy()
241 self._styles = src._styles.copy()
242
242
243 self.fixconfig()
243 self.fixconfig()
244
244
245 self.httppasswordmgrdb = src.httppasswordmgrdb
245 self.httppasswordmgrdb = src.httppasswordmgrdb
246 self._blockedtimes = src._blockedtimes
246 self._blockedtimes = src._blockedtimes
247 else:
247 else:
248 self.fout = util.stdout
248 self.fout = util.stdout
249 self.ferr = util.stderr
249 self.ferr = util.stderr
250 self.fin = util.stdin
250 self.fin = util.stdin
251 self.pageractive = False
251 self.pageractive = False
252 self._disablepager = False
252 self._disablepager = False
253 self._tweaked = False
253 self._tweaked = False
254
254
255 # shared read-only environment
255 # shared read-only environment
256 self.environ = encoding.environ
256 self.environ = encoding.environ
257
257
258 self.httppasswordmgrdb = httppasswordmgrdbproxy()
258 self.httppasswordmgrdb = httppasswordmgrdbproxy()
259 self._blockedtimes = collections.defaultdict(int)
259 self._blockedtimes = collections.defaultdict(int)
260
260
261 allowed = self.configlist('experimental', 'exportableenviron')
261 allowed = self.configlist('experimental', 'exportableenviron')
262 if '*' in allowed:
262 if '*' in allowed:
263 self._exportableenviron = self.environ
263 self._exportableenviron = self.environ
264 else:
264 else:
265 self._exportableenviron = {}
265 self._exportableenviron = {}
266 for k in allowed:
266 for k in allowed:
267 if k in self.environ:
267 if k in self.environ:
268 self._exportableenviron[k] = self.environ[k]
268 self._exportableenviron[k] = self.environ[k]
269
269
270 @classmethod
270 @classmethod
271 def load(cls):
271 def load(cls):
272 """Create a ui and load global and user configs"""
272 """Create a ui and load global and user configs"""
273 u = cls()
273 u = cls()
274 # we always trust global config files and environment variables
274 # we always trust global config files and environment variables
275 for t, f in rcutil.rccomponents():
275 for t, f in rcutil.rccomponents():
276 if t == 'path':
276 if t == 'path':
277 u.readconfig(f, trust=True)
277 u.readconfig(f, trust=True)
278 elif t == 'items':
278 elif t == 'items':
279 sections = set()
279 sections = set()
280 for section, name, value, source in f:
280 for section, name, value, source in f:
281 # do not set u._ocfg
281 # do not set u._ocfg
282 # XXX clean this up once immutable config object is a thing
282 # XXX clean this up once immutable config object is a thing
283 u._tcfg.set(section, name, value, source)
283 u._tcfg.set(section, name, value, source)
284 u._ucfg.set(section, name, value, source)
284 u._ucfg.set(section, name, value, source)
285 sections.add(section)
285 sections.add(section)
286 for section in sections:
286 for section in sections:
287 u.fixconfig(section=section)
287 u.fixconfig(section=section)
288 else:
288 else:
289 raise error.ProgrammingError('unknown rctype: %s' % t)
289 raise error.ProgrammingError('unknown rctype: %s' % t)
290 u._maybetweakdefaults()
290 u._maybetweakdefaults()
291 return u
291 return u
292
292
293 def _maybetweakdefaults(self):
293 def _maybetweakdefaults(self):
294 if not self.configbool('ui', 'tweakdefaults'):
294 if not self.configbool('ui', 'tweakdefaults'):
295 return
295 return
296 if self._tweaked or self.plain('tweakdefaults'):
296 if self._tweaked or self.plain('tweakdefaults'):
297 return
297 return
298
298
299 # Note: it is SUPER IMPORTANT that you set self._tweaked to
299 # Note: it is SUPER IMPORTANT that you set self._tweaked to
300 # True *before* any calls to setconfig(), otherwise you'll get
300 # True *before* any calls to setconfig(), otherwise you'll get
301 # infinite recursion between setconfig and this method.
301 # infinite recursion between setconfig and this method.
302 #
302 #
303 # TODO: We should extract an inner method in setconfig() to
303 # TODO: We should extract an inner method in setconfig() to
304 # avoid this weirdness.
304 # avoid this weirdness.
305 self._tweaked = True
305 self._tweaked = True
306 tmpcfg = config.config()
306 tmpcfg = config.config()
307 tmpcfg.parse('<tweakdefaults>', tweakrc)
307 tmpcfg.parse('<tweakdefaults>', tweakrc)
308 for section in tmpcfg:
308 for section in tmpcfg:
309 for name, value in tmpcfg.items(section):
309 for name, value in tmpcfg.items(section):
310 if not self.hasconfig(section, name):
310 if not self.hasconfig(section, name):
311 self.setconfig(section, name, value, "<tweakdefaults>")
311 self.setconfig(section, name, value, "<tweakdefaults>")
312
312
313 def copy(self):
313 def copy(self):
314 return self.__class__(self)
314 return self.__class__(self)
315
315
316 def resetstate(self):
316 def resetstate(self):
317 """Clear internal state that shouldn't persist across commands"""
317 """Clear internal state that shouldn't persist across commands"""
318 if self._progbar:
318 if self._progbar:
319 self._progbar.resetstate() # reset last-print time of progress bar
319 self._progbar.resetstate() # reset last-print time of progress bar
320 self.httppasswordmgrdb = httppasswordmgrdbproxy()
320 self.httppasswordmgrdb = httppasswordmgrdbproxy()
321
321
322 @contextlib.contextmanager
322 @contextlib.contextmanager
323 def timeblockedsection(self, key):
323 def timeblockedsection(self, key):
324 # this is open-coded below - search for timeblockedsection to find them
324 # this is open-coded below - search for timeblockedsection to find them
325 starttime = util.timer()
325 starttime = util.timer()
326 try:
326 try:
327 yield
327 yield
328 finally:
328 finally:
329 self._blockedtimes[key + '_blocked'] += \
329 self._blockedtimes[key + '_blocked'] += \
330 (util.timer() - starttime) * 1000
330 (util.timer() - starttime) * 1000
331
331
332 def formatter(self, topic, opts):
332 def formatter(self, topic, opts):
333 return formatter.formatter(self, self, topic, opts)
333 return formatter.formatter(self, self, topic, opts)
334
334
335 def _trusted(self, fp, f):
335 def _trusted(self, fp, f):
336 st = util.fstat(fp)
336 st = util.fstat(fp)
337 if util.isowner(st):
337 if util.isowner(st):
338 return True
338 return True
339
339
340 tusers, tgroups = self._trustusers, self._trustgroups
340 tusers, tgroups = self._trustusers, self._trustgroups
341 if '*' in tusers or '*' in tgroups:
341 if '*' in tusers or '*' in tgroups:
342 return True
342 return True
343
343
344 user = util.username(st.st_uid)
344 user = util.username(st.st_uid)
345 group = util.groupname(st.st_gid)
345 group = util.groupname(st.st_gid)
346 if user in tusers or group in tgroups or user == util.username():
346 if user in tusers or group in tgroups or user == util.username():
347 return True
347 return True
348
348
349 if self._reportuntrusted:
349 if self._reportuntrusted:
350 self.warn(_('not trusting file %s from untrusted '
350 self.warn(_('not trusting file %s from untrusted '
351 'user %s, group %s\n') % (f, user, group))
351 'user %s, group %s\n') % (f, user, group))
352 return False
352 return False
353
353
354 def readconfig(self, filename, root=None, trust=False,
354 def readconfig(self, filename, root=None, trust=False,
355 sections=None, remap=None):
355 sections=None, remap=None):
356 try:
356 try:
357 fp = open(filename, u'rb')
357 fp = open(filename, u'rb')
358 except IOError:
358 except IOError:
359 if not sections: # ignore unless we were looking for something
359 if not sections: # ignore unless we were looking for something
360 return
360 return
361 raise
361 raise
362
362
363 cfg = config.config()
363 cfg = config.config()
364 trusted = sections or trust or self._trusted(fp, filename)
364 trusted = sections or trust or self._trusted(fp, filename)
365
365
366 try:
366 try:
367 cfg.read(filename, fp, sections=sections, remap=remap)
367 cfg.read(filename, fp, sections=sections, remap=remap)
368 fp.close()
368 fp.close()
369 except error.ConfigError as inst:
369 except error.ConfigError as inst:
370 if trusted:
370 if trusted:
371 raise
371 raise
372 self.warn(_("ignored: %s\n") % str(inst))
372 self.warn(_("ignored: %s\n") % str(inst))
373
373
374 if self.plain():
374 if self.plain():
375 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
375 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
376 'logtemplate', 'statuscopies', 'style',
376 'logtemplate', 'statuscopies', 'style',
377 'traceback', 'verbose'):
377 'traceback', 'verbose'):
378 if k in cfg['ui']:
378 if k in cfg['ui']:
379 del cfg['ui'][k]
379 del cfg['ui'][k]
380 for k, v in cfg.items('defaults'):
380 for k, v in cfg.items('defaults'):
381 del cfg['defaults'][k]
381 del cfg['defaults'][k]
382 for k, v in cfg.items('commands'):
382 for k, v in cfg.items('commands'):
383 del cfg['commands'][k]
383 del cfg['commands'][k]
384 # Don't remove aliases from the configuration if in the exceptionlist
384 # Don't remove aliases from the configuration if in the exceptionlist
385 if self.plain('alias'):
385 if self.plain('alias'):
386 for k, v in cfg.items('alias'):
386 for k, v in cfg.items('alias'):
387 del cfg['alias'][k]
387 del cfg['alias'][k]
388 if self.plain('revsetalias'):
388 if self.plain('revsetalias'):
389 for k, v in cfg.items('revsetalias'):
389 for k, v in cfg.items('revsetalias'):
390 del cfg['revsetalias'][k]
390 del cfg['revsetalias'][k]
391 if self.plain('templatealias'):
391 if self.plain('templatealias'):
392 for k, v in cfg.items('templatealias'):
392 for k, v in cfg.items('templatealias'):
393 del cfg['templatealias'][k]
393 del cfg['templatealias'][k]
394
394
395 if trusted:
395 if trusted:
396 self._tcfg.update(cfg)
396 self._tcfg.update(cfg)
397 self._tcfg.update(self._ocfg)
397 self._tcfg.update(self._ocfg)
398 self._ucfg.update(cfg)
398 self._ucfg.update(cfg)
399 self._ucfg.update(self._ocfg)
399 self._ucfg.update(self._ocfg)
400
400
401 if root is None:
401 if root is None:
402 root = os.path.expanduser('~')
402 root = os.path.expanduser('~')
403 self.fixconfig(root=root)
403 self.fixconfig(root=root)
404
404
405 def fixconfig(self, root=None, section=None):
405 def fixconfig(self, root=None, section=None):
406 if section in (None, 'paths'):
406 if section in (None, 'paths'):
407 # expand vars and ~
407 # expand vars and ~
408 # translate paths relative to root (or home) into absolute paths
408 # translate paths relative to root (or home) into absolute paths
409 root = root or pycompat.getcwd()
409 root = root or pycompat.getcwd()
410 for c in self._tcfg, self._ucfg, self._ocfg:
410 for c in self._tcfg, self._ucfg, self._ocfg:
411 for n, p in c.items('paths'):
411 for n, p in c.items('paths'):
412 # Ignore sub-options.
412 # Ignore sub-options.
413 if ':' in n:
413 if ':' in n:
414 continue
414 continue
415 if not p:
415 if not p:
416 continue
416 continue
417 if '%%' in p:
417 if '%%' in p:
418 s = self.configsource('paths', n) or 'none'
418 s = self.configsource('paths', n) or 'none'
419 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
419 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
420 % (n, p, s))
420 % (n, p, s))
421 p = p.replace('%%', '%')
421 p = p.replace('%%', '%')
422 p = util.expandpath(p)
422 p = util.expandpath(p)
423 if not util.hasscheme(p) and not os.path.isabs(p):
423 if not util.hasscheme(p) and not os.path.isabs(p):
424 p = os.path.normpath(os.path.join(root, p))
424 p = os.path.normpath(os.path.join(root, p))
425 c.set("paths", n, p)
425 c.set("paths", n, p)
426
426
427 if section in (None, 'ui'):
427 if section in (None, 'ui'):
428 # update ui options
428 # update ui options
429 self.debugflag = self.configbool('ui', 'debug')
429 self.debugflag = self.configbool('ui', 'debug')
430 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
430 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
431 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
431 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
432 if self.verbose and self.quiet:
432 if self.verbose and self.quiet:
433 self.quiet = self.verbose = False
433 self.quiet = self.verbose = False
434 self._reportuntrusted = self.debugflag or self.configbool("ui",
434 self._reportuntrusted = self.debugflag or self.configbool("ui",
435 "report_untrusted")
435 "report_untrusted")
436 self.tracebackflag = self.configbool('ui', 'traceback')
436 self.tracebackflag = self.configbool('ui', 'traceback')
437 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
437 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
438
438
439 if section in (None, 'trusted'):
439 if section in (None, 'trusted'):
440 # update trust information
440 # update trust information
441 self._trustusers.update(self.configlist('trusted', 'users'))
441 self._trustusers.update(self.configlist('trusted', 'users'))
442 self._trustgroups.update(self.configlist('trusted', 'groups'))
442 self._trustgroups.update(self.configlist('trusted', 'groups'))
443
443
444 def backupconfig(self, section, item):
444 def backupconfig(self, section, item):
445 return (self._ocfg.backup(section, item),
445 return (self._ocfg.backup(section, item),
446 self._tcfg.backup(section, item),
446 self._tcfg.backup(section, item),
447 self._ucfg.backup(section, item),)
447 self._ucfg.backup(section, item),)
448 def restoreconfig(self, data):
448 def restoreconfig(self, data):
449 self._ocfg.restore(data[0])
449 self._ocfg.restore(data[0])
450 self._tcfg.restore(data[1])
450 self._tcfg.restore(data[1])
451 self._ucfg.restore(data[2])
451 self._ucfg.restore(data[2])
452
452
453 def setconfig(self, section, name, value, source=''):
453 def setconfig(self, section, name, value, source=''):
454 for cfg in (self._ocfg, self._tcfg, self._ucfg):
454 for cfg in (self._ocfg, self._tcfg, self._ucfg):
455 cfg.set(section, name, value, source)
455 cfg.set(section, name, value, source)
456 self.fixconfig(section=section)
456 self.fixconfig(section=section)
457 self._maybetweakdefaults()
457 self._maybetweakdefaults()
458
458
459 def _data(self, untrusted):
459 def _data(self, untrusted):
460 return untrusted and self._ucfg or self._tcfg
460 return untrusted and self._ucfg or self._tcfg
461
461
462 def configsource(self, section, name, untrusted=False):
462 def configsource(self, section, name, untrusted=False):
463 return self._data(untrusted).source(section, name)
463 return self._data(untrusted).source(section, name)
464
464
465 def config(self, section, name, default=_unset, untrusted=False):
465 def config(self, section, name, default=_unset, untrusted=False):
466 """return the plain string version of a config"""
466 """return the plain string version of a config"""
467 value = self._config(section, name, default=default,
467 value = self._config(section, name, default=default,
468 untrusted=untrusted)
468 untrusted=untrusted)
469 if value is _unset:
469 if value is _unset:
470 return None
470 return None
471 return value
471 return value
472
472
473 def _config(self, section, name, default=_unset, untrusted=False):
473 def _config(self, section, name, default=_unset, untrusted=False):
474 value = itemdefault = default
474 value = itemdefault = default
475 item = self._knownconfig.get(section, {}).get(name)
475 item = self._knownconfig.get(section, {}).get(name)
476 alternates = [(section, name)]
476 alternates = [(section, name)]
477
477
478 if item is not None:
478 if item is not None:
479 alternates.extend(item.alias)
479 alternates.extend(item.alias)
480 if callable(item.default):
480 if callable(item.default):
481 itemdefault = item.default()
481 itemdefault = item.default()
482 else:
482 else:
483 itemdefault = item.default
483 itemdefault = item.default
484 else:
484 else:
485 msg = ("accessing unregistered config item: '%s.%s'")
485 msg = ("accessing unregistered config item: '%s.%s'")
486 msg %= (section, name)
486 msg %= (section, name)
487 self.develwarn(msg, 2, 'warn-config-unknown')
487 self.develwarn(msg, 2, 'warn-config-unknown')
488
488
489 if default is _unset:
489 if default is _unset:
490 if item is None:
490 if item is None:
491 value = default
491 value = default
492 elif item.default is configitems.dynamicdefault:
492 elif item.default is configitems.dynamicdefault:
493 value = None
493 value = None
494 msg = "config item requires an explicit default value: '%s.%s'"
494 msg = "config item requires an explicit default value: '%s.%s'"
495 msg %= (section, name)
495 msg %= (section, name)
496 self.develwarn(msg, 2, 'warn-config-default')
496 self.develwarn(msg, 2, 'warn-config-default')
497 else:
497 else:
498 value = itemdefault
498 value = itemdefault
499 elif (item is not None
499 elif (item is not None
500 and item.default is not configitems.dynamicdefault
500 and item.default is not configitems.dynamicdefault
501 and default != itemdefault):
501 and default != itemdefault):
502 msg = ("specifying a mismatched default value for a registered "
502 msg = ("specifying a mismatched default value for a registered "
503 "config item: '%s.%s' '%s'")
503 "config item: '%s.%s' '%s'")
504 msg %= (section, name, default)
504 msg %= (section, name, default)
505 self.develwarn(msg, 2, 'warn-config-default')
505 self.develwarn(msg, 2, 'warn-config-default')
506
506
507 for s, n in alternates:
507 for s, n in alternates:
508 candidate = self._data(untrusted).get(s, n, None)
508 candidate = self._data(untrusted).get(s, n, None)
509 if candidate is not None:
509 if candidate is not None:
510 value = candidate
510 value = candidate
511 section = s
511 section = s
512 name = n
512 name = n
513 break
513 break
514
514
515 if self.debugflag and not untrusted and self._reportuntrusted:
515 if self.debugflag and not untrusted and self._reportuntrusted:
516 for s, n in alternates:
516 for s, n in alternates:
517 uvalue = self._ucfg.get(s, n)
517 uvalue = self._ucfg.get(s, n)
518 if uvalue is not None and uvalue != value:
518 if uvalue is not None and uvalue != value:
519 self.debug("ignoring untrusted configuration option "
519 self.debug("ignoring untrusted configuration option "
520 "%s.%s = %s\n" % (s, n, uvalue))
520 "%s.%s = %s\n" % (s, n, uvalue))
521 return value
521 return value
522
522
523 def configsuboptions(self, section, name, default=_unset, untrusted=False):
523 def configsuboptions(self, section, name, default=_unset, untrusted=False):
524 """Get a config option and all sub-options.
524 """Get a config option and all sub-options.
525
525
526 Some config options have sub-options that are declared with the
526 Some config options have sub-options that are declared with the
527 format "key:opt = value". This method is used to return the main
527 format "key:opt = value". This method is used to return the main
528 option and all its declared sub-options.
528 option and all its declared sub-options.
529
529
530 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
530 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
531 is a dict of defined sub-options where keys and values are strings.
531 is a dict of defined sub-options where keys and values are strings.
532 """
532 """
533 main = self.config(section, name, default, untrusted=untrusted)
533 main = self.config(section, name, default, untrusted=untrusted)
534 data = self._data(untrusted)
534 data = self._data(untrusted)
535 sub = {}
535 sub = {}
536 prefix = '%s:' % name
536 prefix = '%s:' % name
537 for k, v in data.items(section):
537 for k, v in data.items(section):
538 if k.startswith(prefix):
538 if k.startswith(prefix):
539 sub[k[len(prefix):]] = v
539 sub[k[len(prefix):]] = v
540
540
541 if self.debugflag and not untrusted and self._reportuntrusted:
541 if self.debugflag and not untrusted and self._reportuntrusted:
542 for k, v in sub.items():
542 for k, v in sub.items():
543 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
543 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
544 if uvalue is not None and uvalue != v:
544 if uvalue is not None and uvalue != v:
545 self.debug('ignoring untrusted configuration option '
545 self.debug('ignoring untrusted configuration option '
546 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
546 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
547
547
548 return main, sub
548 return main, sub
549
549
550 def configpath(self, section, name, default=_unset, untrusted=False):
550 def configpath(self, section, name, default=_unset, untrusted=False):
551 'get a path config item, expanded relative to repo root or config file'
551 'get a path config item, expanded relative to repo root or config file'
552 v = self.config(section, name, default, untrusted)
552 v = self.config(section, name, default, untrusted)
553 if v is None:
553 if v is None:
554 return None
554 return None
555 if not os.path.isabs(v) or "://" not in v:
555 if not os.path.isabs(v) or "://" not in v:
556 src = self.configsource(section, name, untrusted)
556 src = self.configsource(section, name, untrusted)
557 if ':' in src:
557 if ':' in src:
558 base = os.path.dirname(src.rsplit(':')[0])
558 base = os.path.dirname(src.rsplit(':')[0])
559 v = os.path.join(base, os.path.expanduser(v))
559 v = os.path.join(base, os.path.expanduser(v))
560 return v
560 return v
561
561
562 def configbool(self, section, name, default=_unset, untrusted=False):
562 def configbool(self, section, name, default=_unset, untrusted=False):
563 """parse a configuration element as a boolean
563 """parse a configuration element as a boolean
564
564
565 >>> u = ui(); s = b'foo'
565 >>> u = ui(); s = b'foo'
566 >>> u.setconfig(s, b'true', b'yes')
566 >>> u.setconfig(s, b'true', b'yes')
567 >>> u.configbool(s, b'true')
567 >>> u.configbool(s, b'true')
568 True
568 True
569 >>> u.setconfig(s, b'false', b'no')
569 >>> u.setconfig(s, b'false', b'no')
570 >>> u.configbool(s, b'false')
570 >>> u.configbool(s, b'false')
571 False
571 False
572 >>> u.configbool(s, b'unknown')
572 >>> u.configbool(s, b'unknown')
573 False
573 False
574 >>> u.configbool(s, b'unknown', True)
574 >>> u.configbool(s, b'unknown', True)
575 True
575 True
576 >>> u.setconfig(s, b'invalid', b'somevalue')
576 >>> u.setconfig(s, b'invalid', b'somevalue')
577 >>> u.configbool(s, b'invalid')
577 >>> u.configbool(s, b'invalid')
578 Traceback (most recent call last):
578 Traceback (most recent call last):
579 ...
579 ...
580 ConfigError: foo.invalid is not a boolean ('somevalue')
580 ConfigError: foo.invalid is not a boolean ('somevalue')
581 """
581 """
582
582
583 v = self._config(section, name, default, untrusted=untrusted)
583 v = self._config(section, name, default, untrusted=untrusted)
584 if v is None:
584 if v is None:
585 return v
585 return v
586 if v is _unset:
586 if v is _unset:
587 if default is _unset:
587 if default is _unset:
588 return False
588 return False
589 return default
589 return default
590 if isinstance(v, bool):
590 if isinstance(v, bool):
591 return v
591 return v
592 b = util.parsebool(v)
592 b = util.parsebool(v)
593 if b is None:
593 if b is None:
594 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
594 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
595 % (section, name, v))
595 % (section, name, v))
596 return b
596 return b
597
597
598 def configwith(self, convert, section, name, default=_unset,
598 def configwith(self, convert, section, name, default=_unset,
599 desc=None, untrusted=False):
599 desc=None, untrusted=False):
600 """parse a configuration element with a conversion function
600 """parse a configuration element with a conversion function
601
601
602 >>> u = ui(); s = b'foo'
602 >>> u = ui(); s = b'foo'
603 >>> u.setconfig(s, b'float1', b'42')
603 >>> u.setconfig(s, b'float1', b'42')
604 >>> u.configwith(float, s, b'float1')
604 >>> u.configwith(float, s, b'float1')
605 42.0
605 42.0
606 >>> u.setconfig(s, b'float2', b'-4.25')
606 >>> u.setconfig(s, b'float2', b'-4.25')
607 >>> u.configwith(float, s, b'float2')
607 >>> u.configwith(float, s, b'float2')
608 -4.25
608 -4.25
609 >>> u.configwith(float, s, b'unknown', 7)
609 >>> u.configwith(float, s, b'unknown', 7)
610 7.0
610 7.0
611 >>> u.setconfig(s, b'invalid', b'somevalue')
611 >>> u.setconfig(s, b'invalid', b'somevalue')
612 >>> u.configwith(float, s, b'invalid')
612 >>> u.configwith(float, s, b'invalid')
613 Traceback (most recent call last):
613 Traceback (most recent call last):
614 ...
614 ...
615 ConfigError: foo.invalid is not a valid float ('somevalue')
615 ConfigError: foo.invalid is not a valid float ('somevalue')
616 >>> u.configwith(float, s, b'invalid', desc=b'womble')
616 >>> u.configwith(float, s, b'invalid', desc=b'womble')
617 Traceback (most recent call last):
617 Traceback (most recent call last):
618 ...
618 ...
619 ConfigError: foo.invalid is not a valid womble ('somevalue')
619 ConfigError: foo.invalid is not a valid womble ('somevalue')
620 """
620 """
621
621
622 v = self.config(section, name, default, untrusted)
622 v = self.config(section, name, default, untrusted)
623 if v is None:
623 if v is None:
624 return v # do not attempt to convert None
624 return v # do not attempt to convert None
625 try:
625 try:
626 return convert(v)
626 return convert(v)
627 except (ValueError, error.ParseError):
627 except (ValueError, error.ParseError):
628 if desc is None:
628 if desc is None:
629 desc = pycompat.sysbytes(convert.__name__)
629 desc = pycompat.sysbytes(convert.__name__)
630 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
630 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
631 % (section, name, desc, v))
631 % (section, name, desc, v))
632
632
633 def configint(self, section, name, default=_unset, untrusted=False):
633 def configint(self, section, name, default=_unset, untrusted=False):
634 """parse a configuration element as an integer
634 """parse a configuration element as an integer
635
635
636 >>> u = ui(); s = b'foo'
636 >>> u = ui(); s = b'foo'
637 >>> u.setconfig(s, b'int1', b'42')
637 >>> u.setconfig(s, b'int1', b'42')
638 >>> u.configint(s, b'int1')
638 >>> u.configint(s, b'int1')
639 42
639 42
640 >>> u.setconfig(s, b'int2', b'-42')
640 >>> u.setconfig(s, b'int2', b'-42')
641 >>> u.configint(s, b'int2')
641 >>> u.configint(s, b'int2')
642 -42
642 -42
643 >>> u.configint(s, b'unknown', 7)
643 >>> u.configint(s, b'unknown', 7)
644 7
644 7
645 >>> u.setconfig(s, b'invalid', b'somevalue')
645 >>> u.setconfig(s, b'invalid', b'somevalue')
646 >>> u.configint(s, b'invalid')
646 >>> u.configint(s, b'invalid')
647 Traceback (most recent call last):
647 Traceback (most recent call last):
648 ...
648 ...
649 ConfigError: foo.invalid is not a valid integer ('somevalue')
649 ConfigError: foo.invalid is not a valid integer ('somevalue')
650 """
650 """
651
651
652 return self.configwith(int, section, name, default, 'integer',
652 return self.configwith(int, section, name, default, 'integer',
653 untrusted)
653 untrusted)
654
654
655 def configbytes(self, section, name, default=_unset, untrusted=False):
655 def configbytes(self, section, name, default=_unset, untrusted=False):
656 """parse a configuration element as a quantity in bytes
656 """parse a configuration element as a quantity in bytes
657
657
658 Units can be specified as b (bytes), k or kb (kilobytes), m or
658 Units can be specified as b (bytes), k or kb (kilobytes), m or
659 mb (megabytes), g or gb (gigabytes).
659 mb (megabytes), g or gb (gigabytes).
660
660
661 >>> u = ui(); s = b'foo'
661 >>> u = ui(); s = b'foo'
662 >>> u.setconfig(s, b'val1', b'42')
662 >>> u.setconfig(s, b'val1', b'42')
663 >>> u.configbytes(s, b'val1')
663 >>> u.configbytes(s, b'val1')
664 42
664 42
665 >>> u.setconfig(s, b'val2', b'42.5 kb')
665 >>> u.setconfig(s, b'val2', b'42.5 kb')
666 >>> u.configbytes(s, b'val2')
666 >>> u.configbytes(s, b'val2')
667 43520
667 43520
668 >>> u.configbytes(s, b'unknown', b'7 MB')
668 >>> u.configbytes(s, b'unknown', b'7 MB')
669 7340032
669 7340032
670 >>> u.setconfig(s, b'invalid', b'somevalue')
670 >>> u.setconfig(s, b'invalid', b'somevalue')
671 >>> u.configbytes(s, b'invalid')
671 >>> u.configbytes(s, b'invalid')
672 Traceback (most recent call last):
672 Traceback (most recent call last):
673 ...
673 ...
674 ConfigError: foo.invalid is not a byte quantity ('somevalue')
674 ConfigError: foo.invalid is not a byte quantity ('somevalue')
675 """
675 """
676
676
677 value = self._config(section, name, default, untrusted)
677 value = self._config(section, name, default, untrusted)
678 if value is _unset:
678 if value is _unset:
679 if default is _unset:
679 if default is _unset:
680 default = 0
680 default = 0
681 value = default
681 value = default
682 if not isinstance(value, bytes):
682 if not isinstance(value, bytes):
683 return value
683 return value
684 try:
684 try:
685 return util.sizetoint(value)
685 return util.sizetoint(value)
686 except error.ParseError:
686 except error.ParseError:
687 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
687 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
688 % (section, name, value))
688 % (section, name, value))
689
689
690 def configlist(self, section, name, default=_unset, untrusted=False):
690 def configlist(self, section, name, default=_unset, untrusted=False):
691 """parse a configuration element as a list of comma/space separated
691 """parse a configuration element as a list of comma/space separated
692 strings
692 strings
693
693
694 >>> u = ui(); s = b'foo'
694 >>> u = ui(); s = b'foo'
695 >>> u.setconfig(s, b'list1', b'this,is "a small" ,test')
695 >>> u.setconfig(s, b'list1', b'this,is "a small" ,test')
696 >>> u.configlist(s, b'list1')
696 >>> u.configlist(s, b'list1')
697 ['this', 'is', 'a small', 'test']
697 ['this', 'is', 'a small', 'test']
698 """
698 """
699 # default is not always a list
699 # default is not always a list
700 v = self.configwith(config.parselist, section, name, default,
700 v = self.configwith(config.parselist, section, name, default,
701 'list', untrusted)
701 'list', untrusted)
702 if isinstance(v, bytes):
702 if isinstance(v, bytes):
703 return config.parselist(v)
703 return config.parselist(v)
704 elif v is None:
704 elif v is None:
705 return []
705 return []
706 return v
706 return v
707
707
708 def configdate(self, section, name, default=_unset, untrusted=False):
708 def configdate(self, section, name, default=_unset, untrusted=False):
709 """parse a configuration element as a tuple of ints
709 """parse a configuration element as a tuple of ints
710
710
711 >>> u = ui(); s = b'foo'
711 >>> u = ui(); s = b'foo'
712 >>> u.setconfig(s, b'date', b'0 0')
712 >>> u.setconfig(s, b'date', b'0 0')
713 >>> u.configdate(s, b'date')
713 >>> u.configdate(s, b'date')
714 (0, 0)
714 (0, 0)
715 """
715 """
716 if self.config(section, name, default, untrusted):
716 if self.config(section, name, default, untrusted):
717 return self.configwith(util.parsedate, section, name, default,
717 return self.configwith(util.parsedate, section, name, default,
718 'date', untrusted)
718 'date', untrusted)
719 if default is _unset:
719 if default is _unset:
720 return None
720 return None
721 return default
721 return default
722
722
723 def hasconfig(self, section, name, untrusted=False):
723 def hasconfig(self, section, name, untrusted=False):
724 return self._data(untrusted).hasitem(section, name)
724 return self._data(untrusted).hasitem(section, name)
725
725
726 def has_section(self, section, untrusted=False):
726 def has_section(self, section, untrusted=False):
727 '''tell whether section exists in config.'''
727 '''tell whether section exists in config.'''
728 return section in self._data(untrusted)
728 return section in self._data(untrusted)
729
729
730 def configitems(self, section, untrusted=False, ignoresub=False):
730 def configitems(self, section, untrusted=False, ignoresub=False):
731 items = self._data(untrusted).items(section)
731 items = self._data(untrusted).items(section)
732 if ignoresub:
732 if ignoresub:
733 newitems = {}
733 newitems = {}
734 for k, v in items:
734 for k, v in items:
735 if ':' not in k:
735 if ':' not in k:
736 newitems[k] = v
736 newitems[k] = v
737 items = newitems.items()
737 items = newitems.items()
738 if self.debugflag and not untrusted and self._reportuntrusted:
738 if self.debugflag and not untrusted and self._reportuntrusted:
739 for k, v in self._ucfg.items(section):
739 for k, v in self._ucfg.items(section):
740 if self._tcfg.get(section, k) != v:
740 if self._tcfg.get(section, k) != v:
741 self.debug("ignoring untrusted configuration option "
741 self.debug("ignoring untrusted configuration option "
742 "%s.%s = %s\n" % (section, k, v))
742 "%s.%s = %s\n" % (section, k, v))
743 return items
743 return items
744
744
745 def walkconfig(self, untrusted=False):
745 def walkconfig(self, untrusted=False):
746 cfg = self._data(untrusted)
746 cfg = self._data(untrusted)
747 for section in cfg.sections():
747 for section in cfg.sections():
748 for name, value in self.configitems(section, untrusted):
748 for name, value in self.configitems(section, untrusted):
749 yield section, name, value
749 yield section, name, value
750
750
751 def plain(self, feature=None):
751 def plain(self, feature=None):
752 '''is plain mode active?
752 '''is plain mode active?
753
753
754 Plain mode means that all configuration variables which affect
754 Plain mode means that all configuration variables which affect
755 the behavior and output of Mercurial should be
755 the behavior and output of Mercurial should be
756 ignored. Additionally, the output should be stable,
756 ignored. Additionally, the output should be stable,
757 reproducible and suitable for use in scripts or applications.
757 reproducible and suitable for use in scripts or applications.
758
758
759 The only way to trigger plain mode is by setting either the
759 The only way to trigger plain mode is by setting either the
760 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
760 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
761
761
762 The return value can either be
762 The return value can either be
763 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
763 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
764 - False if feature is disabled by default and not included in HGPLAIN
764 - True otherwise
765 - True otherwise
765 '''
766 '''
766 if ('HGPLAIN' not in encoding.environ and
767 if ('HGPLAIN' not in encoding.environ and
767 'HGPLAINEXCEPT' not in encoding.environ):
768 'HGPLAINEXCEPT' not in encoding.environ):
768 return False
769 return False
769 exceptions = encoding.environ.get('HGPLAINEXCEPT',
770 exceptions = encoding.environ.get('HGPLAINEXCEPT',
770 '').strip().split(',')
771 '').strip().split(',')
772 # TODO: add support for HGPLAIN=+feature,-feature syntax
773 if '+strictflags' not in encoding.environ.get('HGPLAIN', '').split(','):
774 exceptions.append('strictflags')
771 if feature and exceptions:
775 if feature and exceptions:
772 return feature not in exceptions
776 return feature not in exceptions
773 return True
777 return True
774
778
775 def username(self, acceptempty=False):
779 def username(self, acceptempty=False):
776 """Return default username to be used in commits.
780 """Return default username to be used in commits.
777
781
778 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
782 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
779 and stop searching if one of these is set.
783 and stop searching if one of these is set.
780 If not found and acceptempty is True, returns None.
784 If not found and acceptempty is True, returns None.
781 If not found and ui.askusername is True, ask the user, else use
785 If not found and ui.askusername is True, ask the user, else use
782 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
786 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
783 If no username could be found, raise an Abort error.
787 If no username could be found, raise an Abort error.
784 """
788 """
785 user = encoding.environ.get("HGUSER")
789 user = encoding.environ.get("HGUSER")
786 if user is None:
790 if user is None:
787 user = self.config("ui", "username")
791 user = self.config("ui", "username")
788 if user is not None:
792 if user is not None:
789 user = os.path.expandvars(user)
793 user = os.path.expandvars(user)
790 if user is None:
794 if user is None:
791 user = encoding.environ.get("EMAIL")
795 user = encoding.environ.get("EMAIL")
792 if user is None and acceptempty:
796 if user is None and acceptempty:
793 return user
797 return user
794 if user is None and self.configbool("ui", "askusername"):
798 if user is None and self.configbool("ui", "askusername"):
795 user = self.prompt(_("enter a commit username:"), default=None)
799 user = self.prompt(_("enter a commit username:"), default=None)
796 if user is None and not self.interactive():
800 if user is None and not self.interactive():
797 try:
801 try:
798 user = '%s@%s' % (util.getuser(), socket.getfqdn())
802 user = '%s@%s' % (util.getuser(), socket.getfqdn())
799 self.warn(_("no username found, using '%s' instead\n") % user)
803 self.warn(_("no username found, using '%s' instead\n") % user)
800 except KeyError:
804 except KeyError:
801 pass
805 pass
802 if not user:
806 if not user:
803 raise error.Abort(_('no username supplied'),
807 raise error.Abort(_('no username supplied'),
804 hint=_("use 'hg config --edit' "
808 hint=_("use 'hg config --edit' "
805 'to set your username'))
809 'to set your username'))
806 if "\n" in user:
810 if "\n" in user:
807 raise error.Abort(_("username %s contains a newline\n")
811 raise error.Abort(_("username %s contains a newline\n")
808 % repr(user))
812 % repr(user))
809 return user
813 return user
810
814
811 def shortuser(self, user):
815 def shortuser(self, user):
812 """Return a short representation of a user name or email address."""
816 """Return a short representation of a user name or email address."""
813 if not self.verbose:
817 if not self.verbose:
814 user = util.shortuser(user)
818 user = util.shortuser(user)
815 return user
819 return user
816
820
817 def expandpath(self, loc, default=None):
821 def expandpath(self, loc, default=None):
818 """Return repository location relative to cwd or from [paths]"""
822 """Return repository location relative to cwd or from [paths]"""
819 try:
823 try:
820 p = self.paths.getpath(loc)
824 p = self.paths.getpath(loc)
821 if p:
825 if p:
822 return p.rawloc
826 return p.rawloc
823 except error.RepoError:
827 except error.RepoError:
824 pass
828 pass
825
829
826 if default:
830 if default:
827 try:
831 try:
828 p = self.paths.getpath(default)
832 p = self.paths.getpath(default)
829 if p:
833 if p:
830 return p.rawloc
834 return p.rawloc
831 except error.RepoError:
835 except error.RepoError:
832 pass
836 pass
833
837
834 return loc
838 return loc
835
839
836 @util.propertycache
840 @util.propertycache
837 def paths(self):
841 def paths(self):
838 return paths(self)
842 return paths(self)
839
843
840 def pushbuffer(self, error=False, subproc=False, labeled=False):
844 def pushbuffer(self, error=False, subproc=False, labeled=False):
841 """install a buffer to capture standard output of the ui object
845 """install a buffer to capture standard output of the ui object
842
846
843 If error is True, the error output will be captured too.
847 If error is True, the error output will be captured too.
844
848
845 If subproc is True, output from subprocesses (typically hooks) will be
849 If subproc is True, output from subprocesses (typically hooks) will be
846 captured too.
850 captured too.
847
851
848 If labeled is True, any labels associated with buffered
852 If labeled is True, any labels associated with buffered
849 output will be handled. By default, this has no effect
853 output will be handled. By default, this has no effect
850 on the output returned, but extensions and GUI tools may
854 on the output returned, but extensions and GUI tools may
851 handle this argument and returned styled output. If output
855 handle this argument and returned styled output. If output
852 is being buffered so it can be captured and parsed or
856 is being buffered so it can be captured and parsed or
853 processed, labeled should not be set to True.
857 processed, labeled should not be set to True.
854 """
858 """
855 self._buffers.append([])
859 self._buffers.append([])
856 self._bufferstates.append((error, subproc, labeled))
860 self._bufferstates.append((error, subproc, labeled))
857 self._bufferapplylabels = labeled
861 self._bufferapplylabels = labeled
858
862
859 def popbuffer(self):
863 def popbuffer(self):
860 '''pop the last buffer and return the buffered output'''
864 '''pop the last buffer and return the buffered output'''
861 self._bufferstates.pop()
865 self._bufferstates.pop()
862 if self._bufferstates:
866 if self._bufferstates:
863 self._bufferapplylabels = self._bufferstates[-1][2]
867 self._bufferapplylabels = self._bufferstates[-1][2]
864 else:
868 else:
865 self._bufferapplylabels = None
869 self._bufferapplylabels = None
866
870
867 return "".join(self._buffers.pop())
871 return "".join(self._buffers.pop())
868
872
869 def write(self, *args, **opts):
873 def write(self, *args, **opts):
870 '''write args to output
874 '''write args to output
871
875
872 By default, this method simply writes to the buffer or stdout.
876 By default, this method simply writes to the buffer or stdout.
873 Color mode can be set on the UI class to have the output decorated
877 Color mode can be set on the UI class to have the output decorated
874 with color modifier before being written to stdout.
878 with color modifier before being written to stdout.
875
879
876 The color used is controlled by an optional keyword argument, "label".
880 The color used is controlled by an optional keyword argument, "label".
877 This should be a string containing label names separated by space.
881 This should be a string containing label names separated by space.
878 Label names take the form of "topic.type". For example, ui.debug()
882 Label names take the form of "topic.type". For example, ui.debug()
879 issues a label of "ui.debug".
883 issues a label of "ui.debug".
880
884
881 When labeling output for a specific command, a label of
885 When labeling output for a specific command, a label of
882 "cmdname.type" is recommended. For example, status issues
886 "cmdname.type" is recommended. For example, status issues
883 a label of "status.modified" for modified files.
887 a label of "status.modified" for modified files.
884 '''
888 '''
885 if self._buffers and not opts.get('prompt', False):
889 if self._buffers and not opts.get('prompt', False):
886 if self._bufferapplylabels:
890 if self._bufferapplylabels:
887 label = opts.get('label', '')
891 label = opts.get('label', '')
888 self._buffers[-1].extend(self.label(a, label) for a in args)
892 self._buffers[-1].extend(self.label(a, label) for a in args)
889 else:
893 else:
890 self._buffers[-1].extend(args)
894 self._buffers[-1].extend(args)
891 elif self._colormode == 'win32':
895 elif self._colormode == 'win32':
892 # windows color printing is its own can of crab, defer to
896 # windows color printing is its own can of crab, defer to
893 # the color module and that is it.
897 # the color module and that is it.
894 color.win32print(self, self._write, *args, **opts)
898 color.win32print(self, self._write, *args, **opts)
895 else:
899 else:
896 msgs = args
900 msgs = args
897 if self._colormode is not None:
901 if self._colormode is not None:
898 label = opts.get('label', '')
902 label = opts.get('label', '')
899 msgs = [self.label(a, label) for a in args]
903 msgs = [self.label(a, label) for a in args]
900 self._write(*msgs, **opts)
904 self._write(*msgs, **opts)
901
905
902 def _write(self, *msgs, **opts):
906 def _write(self, *msgs, **opts):
903 self._progclear()
907 self._progclear()
904 # opencode timeblockedsection because this is a critical path
908 # opencode timeblockedsection because this is a critical path
905 starttime = util.timer()
909 starttime = util.timer()
906 try:
910 try:
907 for a in msgs:
911 for a in msgs:
908 self.fout.write(a)
912 self.fout.write(a)
909 except IOError as err:
913 except IOError as err:
910 raise error.StdioError(err)
914 raise error.StdioError(err)
911 finally:
915 finally:
912 self._blockedtimes['stdio_blocked'] += \
916 self._blockedtimes['stdio_blocked'] += \
913 (util.timer() - starttime) * 1000
917 (util.timer() - starttime) * 1000
914
918
915 def write_err(self, *args, **opts):
919 def write_err(self, *args, **opts):
916 self._progclear()
920 self._progclear()
917 if self._bufferstates and self._bufferstates[-1][0]:
921 if self._bufferstates and self._bufferstates[-1][0]:
918 self.write(*args, **opts)
922 self.write(*args, **opts)
919 elif self._colormode == 'win32':
923 elif self._colormode == 'win32':
920 # windows color printing is its own can of crab, defer to
924 # windows color printing is its own can of crab, defer to
921 # the color module and that is it.
925 # the color module and that is it.
922 color.win32print(self, self._write_err, *args, **opts)
926 color.win32print(self, self._write_err, *args, **opts)
923 else:
927 else:
924 msgs = args
928 msgs = args
925 if self._colormode is not None:
929 if self._colormode is not None:
926 label = opts.get('label', '')
930 label = opts.get('label', '')
927 msgs = [self.label(a, label) for a in args]
931 msgs = [self.label(a, label) for a in args]
928 self._write_err(*msgs, **opts)
932 self._write_err(*msgs, **opts)
929
933
930 def _write_err(self, *msgs, **opts):
934 def _write_err(self, *msgs, **opts):
931 try:
935 try:
932 with self.timeblockedsection('stdio'):
936 with self.timeblockedsection('stdio'):
933 if not getattr(self.fout, 'closed', False):
937 if not getattr(self.fout, 'closed', False):
934 self.fout.flush()
938 self.fout.flush()
935 for a in msgs:
939 for a in msgs:
936 self.ferr.write(a)
940 self.ferr.write(a)
937 # stderr may be buffered under win32 when redirected to files,
941 # stderr may be buffered under win32 when redirected to files,
938 # including stdout.
942 # including stdout.
939 if not getattr(self.ferr, 'closed', False):
943 if not getattr(self.ferr, 'closed', False):
940 self.ferr.flush()
944 self.ferr.flush()
941 except IOError as inst:
945 except IOError as inst:
942 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
946 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
943 raise error.StdioError(inst)
947 raise error.StdioError(inst)
944
948
945 def flush(self):
949 def flush(self):
946 # opencode timeblockedsection because this is a critical path
950 # opencode timeblockedsection because this is a critical path
947 starttime = util.timer()
951 starttime = util.timer()
948 try:
952 try:
949 try:
953 try:
950 self.fout.flush()
954 self.fout.flush()
951 except IOError as err:
955 except IOError as err:
952 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
956 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
953 raise error.StdioError(err)
957 raise error.StdioError(err)
954 finally:
958 finally:
955 try:
959 try:
956 self.ferr.flush()
960 self.ferr.flush()
957 except IOError as err:
961 except IOError as err:
958 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
962 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
959 raise error.StdioError(err)
963 raise error.StdioError(err)
960 finally:
964 finally:
961 self._blockedtimes['stdio_blocked'] += \
965 self._blockedtimes['stdio_blocked'] += \
962 (util.timer() - starttime) * 1000
966 (util.timer() - starttime) * 1000
963
967
964 def _isatty(self, fh):
968 def _isatty(self, fh):
965 if self.configbool('ui', 'nontty'):
969 if self.configbool('ui', 'nontty'):
966 return False
970 return False
967 return util.isatty(fh)
971 return util.isatty(fh)
968
972
969 def disablepager(self):
973 def disablepager(self):
970 self._disablepager = True
974 self._disablepager = True
971
975
972 def pager(self, command):
976 def pager(self, command):
973 """Start a pager for subsequent command output.
977 """Start a pager for subsequent command output.
974
978
975 Commands which produce a long stream of output should call
979 Commands which produce a long stream of output should call
976 this function to activate the user's preferred pagination
980 this function to activate the user's preferred pagination
977 mechanism (which may be no pager). Calling this function
981 mechanism (which may be no pager). Calling this function
978 precludes any future use of interactive functionality, such as
982 precludes any future use of interactive functionality, such as
979 prompting the user or activating curses.
983 prompting the user or activating curses.
980
984
981 Args:
985 Args:
982 command: The full, non-aliased name of the command. That is, "log"
986 command: The full, non-aliased name of the command. That is, "log"
983 not "history, "summary" not "summ", etc.
987 not "history, "summary" not "summ", etc.
984 """
988 """
985 if (self._disablepager
989 if (self._disablepager
986 or self.pageractive):
990 or self.pageractive):
987 # how pager should do is already determined
991 # how pager should do is already determined
988 return
992 return
989
993
990 if not command.startswith('internal-always-') and (
994 if not command.startswith('internal-always-') and (
991 # explicit --pager=on (= 'internal-always-' prefix) should
995 # explicit --pager=on (= 'internal-always-' prefix) should
992 # take precedence over disabling factors below
996 # take precedence over disabling factors below
993 command in self.configlist('pager', 'ignore')
997 command in self.configlist('pager', 'ignore')
994 or not self.configbool('ui', 'paginate')
998 or not self.configbool('ui', 'paginate')
995 or not self.configbool('pager', 'attend-' + command, True)
999 or not self.configbool('pager', 'attend-' + command, True)
996 # TODO: if we want to allow HGPLAINEXCEPT=pager,
1000 # TODO: if we want to allow HGPLAINEXCEPT=pager,
997 # formatted() will need some adjustment.
1001 # formatted() will need some adjustment.
998 or not self.formatted()
1002 or not self.formatted()
999 or self.plain()
1003 or self.plain()
1000 or self._buffers
1004 or self._buffers
1001 # TODO: expose debugger-enabled on the UI object
1005 # TODO: expose debugger-enabled on the UI object
1002 or '--debugger' in pycompat.sysargv):
1006 or '--debugger' in pycompat.sysargv):
1003 # We only want to paginate if the ui appears to be
1007 # We only want to paginate if the ui appears to be
1004 # interactive, the user didn't say HGPLAIN or
1008 # interactive, the user didn't say HGPLAIN or
1005 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
1009 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
1006 return
1010 return
1007
1011
1008 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
1012 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
1009 if not pagercmd:
1013 if not pagercmd:
1010 return
1014 return
1011
1015
1012 pagerenv = {}
1016 pagerenv = {}
1013 for name, value in rcutil.defaultpagerenv().items():
1017 for name, value in rcutil.defaultpagerenv().items():
1014 if name not in encoding.environ:
1018 if name not in encoding.environ:
1015 pagerenv[name] = value
1019 pagerenv[name] = value
1016
1020
1017 self.debug('starting pager for command %r\n' % command)
1021 self.debug('starting pager for command %r\n' % command)
1018 self.flush()
1022 self.flush()
1019
1023
1020 wasformatted = self.formatted()
1024 wasformatted = self.formatted()
1021 if util.safehasattr(signal, "SIGPIPE"):
1025 if util.safehasattr(signal, "SIGPIPE"):
1022 signal.signal(signal.SIGPIPE, _catchterm)
1026 signal.signal(signal.SIGPIPE, _catchterm)
1023 if self._runpager(pagercmd, pagerenv):
1027 if self._runpager(pagercmd, pagerenv):
1024 self.pageractive = True
1028 self.pageractive = True
1025 # Preserve the formatted-ness of the UI. This is important
1029 # Preserve the formatted-ness of the UI. This is important
1026 # because we mess with stdout, which might confuse
1030 # because we mess with stdout, which might confuse
1027 # auto-detection of things being formatted.
1031 # auto-detection of things being formatted.
1028 self.setconfig('ui', 'formatted', wasformatted, 'pager')
1032 self.setconfig('ui', 'formatted', wasformatted, 'pager')
1029 self.setconfig('ui', 'interactive', False, 'pager')
1033 self.setconfig('ui', 'interactive', False, 'pager')
1030
1034
1031 # If pagermode differs from color.mode, reconfigure color now that
1035 # If pagermode differs from color.mode, reconfigure color now that
1032 # pageractive is set.
1036 # pageractive is set.
1033 cm = self._colormode
1037 cm = self._colormode
1034 if cm != self.config('color', 'pagermode', cm):
1038 if cm != self.config('color', 'pagermode', cm):
1035 color.setup(self)
1039 color.setup(self)
1036 else:
1040 else:
1037 # If the pager can't be spawned in dispatch when --pager=on is
1041 # If the pager can't be spawned in dispatch when --pager=on is
1038 # given, don't try again when the command runs, to avoid a duplicate
1042 # given, don't try again when the command runs, to avoid a duplicate
1039 # warning about a missing pager command.
1043 # warning about a missing pager command.
1040 self.disablepager()
1044 self.disablepager()
1041
1045
1042 def _runpager(self, command, env=None):
1046 def _runpager(self, command, env=None):
1043 """Actually start the pager and set up file descriptors.
1047 """Actually start the pager and set up file descriptors.
1044
1048
1045 This is separate in part so that extensions (like chg) can
1049 This is separate in part so that extensions (like chg) can
1046 override how a pager is invoked.
1050 override how a pager is invoked.
1047 """
1051 """
1048 if command == 'cat':
1052 if command == 'cat':
1049 # Save ourselves some work.
1053 # Save ourselves some work.
1050 return False
1054 return False
1051 # If the command doesn't contain any of these characters, we
1055 # If the command doesn't contain any of these characters, we
1052 # assume it's a binary and exec it directly. This means for
1056 # assume it's a binary and exec it directly. This means for
1053 # simple pager command configurations, we can degrade
1057 # simple pager command configurations, we can degrade
1054 # gracefully and tell the user about their broken pager.
1058 # gracefully and tell the user about their broken pager.
1055 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
1059 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
1056
1060
1057 if pycompat.iswindows and not shell:
1061 if pycompat.iswindows and not shell:
1058 # Window's built-in `more` cannot be invoked with shell=False, but
1062 # Window's built-in `more` cannot be invoked with shell=False, but
1059 # its `more.com` can. Hide this implementation detail from the
1063 # its `more.com` can. Hide this implementation detail from the
1060 # user so we can also get sane bad PAGER behavior. MSYS has
1064 # user so we can also get sane bad PAGER behavior. MSYS has
1061 # `more.exe`, so do a cmd.exe style resolution of the executable to
1065 # `more.exe`, so do a cmd.exe style resolution of the executable to
1062 # determine which one to use.
1066 # determine which one to use.
1063 fullcmd = util.findexe(command)
1067 fullcmd = util.findexe(command)
1064 if not fullcmd:
1068 if not fullcmd:
1065 self.warn(_("missing pager command '%s', skipping pager\n")
1069 self.warn(_("missing pager command '%s', skipping pager\n")
1066 % command)
1070 % command)
1067 return False
1071 return False
1068
1072
1069 command = fullcmd
1073 command = fullcmd
1070
1074
1071 try:
1075 try:
1072 pager = subprocess.Popen(
1076 pager = subprocess.Popen(
1073 command, shell=shell, bufsize=-1,
1077 command, shell=shell, bufsize=-1,
1074 close_fds=util.closefds, stdin=subprocess.PIPE,
1078 close_fds=util.closefds, stdin=subprocess.PIPE,
1075 stdout=util.stdout, stderr=util.stderr,
1079 stdout=util.stdout, stderr=util.stderr,
1076 env=util.shellenviron(env))
1080 env=util.shellenviron(env))
1077 except OSError as e:
1081 except OSError as e:
1078 if e.errno == errno.ENOENT and not shell:
1082 if e.errno == errno.ENOENT and not shell:
1079 self.warn(_("missing pager command '%s', skipping pager\n")
1083 self.warn(_("missing pager command '%s', skipping pager\n")
1080 % command)
1084 % command)
1081 return False
1085 return False
1082 raise
1086 raise
1083
1087
1084 # back up original file descriptors
1088 # back up original file descriptors
1085 stdoutfd = os.dup(util.stdout.fileno())
1089 stdoutfd = os.dup(util.stdout.fileno())
1086 stderrfd = os.dup(util.stderr.fileno())
1090 stderrfd = os.dup(util.stderr.fileno())
1087
1091
1088 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
1092 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
1089 if self._isatty(util.stderr):
1093 if self._isatty(util.stderr):
1090 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
1094 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
1091
1095
1092 @self.atexit
1096 @self.atexit
1093 def killpager():
1097 def killpager():
1094 if util.safehasattr(signal, "SIGINT"):
1098 if util.safehasattr(signal, "SIGINT"):
1095 signal.signal(signal.SIGINT, signal.SIG_IGN)
1099 signal.signal(signal.SIGINT, signal.SIG_IGN)
1096 # restore original fds, closing pager.stdin copies in the process
1100 # restore original fds, closing pager.stdin copies in the process
1097 os.dup2(stdoutfd, util.stdout.fileno())
1101 os.dup2(stdoutfd, util.stdout.fileno())
1098 os.dup2(stderrfd, util.stderr.fileno())
1102 os.dup2(stderrfd, util.stderr.fileno())
1099 pager.stdin.close()
1103 pager.stdin.close()
1100 pager.wait()
1104 pager.wait()
1101
1105
1102 return True
1106 return True
1103
1107
1104 @property
1108 @property
1105 def _exithandlers(self):
1109 def _exithandlers(self):
1106 return _reqexithandlers
1110 return _reqexithandlers
1107
1111
1108 def atexit(self, func, *args, **kwargs):
1112 def atexit(self, func, *args, **kwargs):
1109 '''register a function to run after dispatching a request
1113 '''register a function to run after dispatching a request
1110
1114
1111 Handlers do not stay registered across request boundaries.'''
1115 Handlers do not stay registered across request boundaries.'''
1112 self._exithandlers.append((func, args, kwargs))
1116 self._exithandlers.append((func, args, kwargs))
1113 return func
1117 return func
1114
1118
1115 def interface(self, feature):
1119 def interface(self, feature):
1116 """what interface to use for interactive console features?
1120 """what interface to use for interactive console features?
1117
1121
1118 The interface is controlled by the value of `ui.interface` but also by
1122 The interface is controlled by the value of `ui.interface` but also by
1119 the value of feature-specific configuration. For example:
1123 the value of feature-specific configuration. For example:
1120
1124
1121 ui.interface.histedit = text
1125 ui.interface.histedit = text
1122 ui.interface.chunkselector = curses
1126 ui.interface.chunkselector = curses
1123
1127
1124 Here the features are "histedit" and "chunkselector".
1128 Here the features are "histedit" and "chunkselector".
1125
1129
1126 The configuration above means that the default interfaces for commands
1130 The configuration above means that the default interfaces for commands
1127 is curses, the interface for histedit is text and the interface for
1131 is curses, the interface for histedit is text and the interface for
1128 selecting chunk is crecord (the best curses interface available).
1132 selecting chunk is crecord (the best curses interface available).
1129
1133
1130 Consider the following example:
1134 Consider the following example:
1131 ui.interface = curses
1135 ui.interface = curses
1132 ui.interface.histedit = text
1136 ui.interface.histedit = text
1133
1137
1134 Then histedit will use the text interface and chunkselector will use
1138 Then histedit will use the text interface and chunkselector will use
1135 the default curses interface (crecord at the moment).
1139 the default curses interface (crecord at the moment).
1136 """
1140 """
1137 alldefaults = frozenset(["text", "curses"])
1141 alldefaults = frozenset(["text", "curses"])
1138
1142
1139 featureinterfaces = {
1143 featureinterfaces = {
1140 "chunkselector": [
1144 "chunkselector": [
1141 "text",
1145 "text",
1142 "curses",
1146 "curses",
1143 ]
1147 ]
1144 }
1148 }
1145
1149
1146 # Feature-specific interface
1150 # Feature-specific interface
1147 if feature not in featureinterfaces.keys():
1151 if feature not in featureinterfaces.keys():
1148 # Programming error, not user error
1152 # Programming error, not user error
1149 raise ValueError("Unknown feature requested %s" % feature)
1153 raise ValueError("Unknown feature requested %s" % feature)
1150
1154
1151 availableinterfaces = frozenset(featureinterfaces[feature])
1155 availableinterfaces = frozenset(featureinterfaces[feature])
1152 if alldefaults > availableinterfaces:
1156 if alldefaults > availableinterfaces:
1153 # Programming error, not user error. We need a use case to
1157 # Programming error, not user error. We need a use case to
1154 # define the right thing to do here.
1158 # define the right thing to do here.
1155 raise ValueError(
1159 raise ValueError(
1156 "Feature %s does not handle all default interfaces" %
1160 "Feature %s does not handle all default interfaces" %
1157 feature)
1161 feature)
1158
1162
1159 if self.plain():
1163 if self.plain():
1160 return "text"
1164 return "text"
1161
1165
1162 # Default interface for all the features
1166 # Default interface for all the features
1163 defaultinterface = "text"
1167 defaultinterface = "text"
1164 i = self.config("ui", "interface")
1168 i = self.config("ui", "interface")
1165 if i in alldefaults:
1169 if i in alldefaults:
1166 defaultinterface = i
1170 defaultinterface = i
1167
1171
1168 choseninterface = defaultinterface
1172 choseninterface = defaultinterface
1169 f = self.config("ui", "interface.%s" % feature)
1173 f = self.config("ui", "interface.%s" % feature)
1170 if f in availableinterfaces:
1174 if f in availableinterfaces:
1171 choseninterface = f
1175 choseninterface = f
1172
1176
1173 if i is not None and defaultinterface != i:
1177 if i is not None and defaultinterface != i:
1174 if f is not None:
1178 if f is not None:
1175 self.warn(_("invalid value for ui.interface: %s\n") %
1179 self.warn(_("invalid value for ui.interface: %s\n") %
1176 (i,))
1180 (i,))
1177 else:
1181 else:
1178 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1182 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1179 (i, choseninterface))
1183 (i, choseninterface))
1180 if f is not None and choseninterface != f:
1184 if f is not None and choseninterface != f:
1181 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1185 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1182 (feature, f, choseninterface))
1186 (feature, f, choseninterface))
1183
1187
1184 return choseninterface
1188 return choseninterface
1185
1189
1186 def interactive(self):
1190 def interactive(self):
1187 '''is interactive input allowed?
1191 '''is interactive input allowed?
1188
1192
1189 An interactive session is a session where input can be reasonably read
1193 An interactive session is a session where input can be reasonably read
1190 from `sys.stdin'. If this function returns false, any attempt to read
1194 from `sys.stdin'. If this function returns false, any attempt to read
1191 from stdin should fail with an error, unless a sensible default has been
1195 from stdin should fail with an error, unless a sensible default has been
1192 specified.
1196 specified.
1193
1197
1194 Interactiveness is triggered by the value of the `ui.interactive'
1198 Interactiveness is triggered by the value of the `ui.interactive'
1195 configuration variable or - if it is unset - when `sys.stdin' points
1199 configuration variable or - if it is unset - when `sys.stdin' points
1196 to a terminal device.
1200 to a terminal device.
1197
1201
1198 This function refers to input only; for output, see `ui.formatted()'.
1202 This function refers to input only; for output, see `ui.formatted()'.
1199 '''
1203 '''
1200 i = self.configbool("ui", "interactive")
1204 i = self.configbool("ui", "interactive")
1201 if i is None:
1205 if i is None:
1202 # some environments replace stdin without implementing isatty
1206 # some environments replace stdin without implementing isatty
1203 # usually those are non-interactive
1207 # usually those are non-interactive
1204 return self._isatty(self.fin)
1208 return self._isatty(self.fin)
1205
1209
1206 return i
1210 return i
1207
1211
1208 def termwidth(self):
1212 def termwidth(self):
1209 '''how wide is the terminal in columns?
1213 '''how wide is the terminal in columns?
1210 '''
1214 '''
1211 if 'COLUMNS' in encoding.environ:
1215 if 'COLUMNS' in encoding.environ:
1212 try:
1216 try:
1213 return int(encoding.environ['COLUMNS'])
1217 return int(encoding.environ['COLUMNS'])
1214 except ValueError:
1218 except ValueError:
1215 pass
1219 pass
1216 return scmutil.termsize(self)[0]
1220 return scmutil.termsize(self)[0]
1217
1221
1218 def formatted(self):
1222 def formatted(self):
1219 '''should formatted output be used?
1223 '''should formatted output be used?
1220
1224
1221 It is often desirable to format the output to suite the output medium.
1225 It is often desirable to format the output to suite the output medium.
1222 Examples of this are truncating long lines or colorizing messages.
1226 Examples of this are truncating long lines or colorizing messages.
1223 However, this is not often not desirable when piping output into other
1227 However, this is not often not desirable when piping output into other
1224 utilities, e.g. `grep'.
1228 utilities, e.g. `grep'.
1225
1229
1226 Formatted output is triggered by the value of the `ui.formatted'
1230 Formatted output is triggered by the value of the `ui.formatted'
1227 configuration variable or - if it is unset - when `sys.stdout' points
1231 configuration variable or - if it is unset - when `sys.stdout' points
1228 to a terminal device. Please note that `ui.formatted' should be
1232 to a terminal device. Please note that `ui.formatted' should be
1229 considered an implementation detail; it is not intended for use outside
1233 considered an implementation detail; it is not intended for use outside
1230 Mercurial or its extensions.
1234 Mercurial or its extensions.
1231
1235
1232 This function refers to output only; for input, see `ui.interactive()'.
1236 This function refers to output only; for input, see `ui.interactive()'.
1233 This function always returns false when in plain mode, see `ui.plain()'.
1237 This function always returns false when in plain mode, see `ui.plain()'.
1234 '''
1238 '''
1235 if self.plain():
1239 if self.plain():
1236 return False
1240 return False
1237
1241
1238 i = self.configbool("ui", "formatted")
1242 i = self.configbool("ui", "formatted")
1239 if i is None:
1243 if i is None:
1240 # some environments replace stdout without implementing isatty
1244 # some environments replace stdout without implementing isatty
1241 # usually those are non-interactive
1245 # usually those are non-interactive
1242 return self._isatty(self.fout)
1246 return self._isatty(self.fout)
1243
1247
1244 return i
1248 return i
1245
1249
1246 def _readline(self, prompt=''):
1250 def _readline(self, prompt=''):
1247 if self._isatty(self.fin):
1251 if self._isatty(self.fin):
1248 try:
1252 try:
1249 # magically add command line editing support, where
1253 # magically add command line editing support, where
1250 # available
1254 # available
1251 import readline
1255 import readline
1252 # force demandimport to really load the module
1256 # force demandimport to really load the module
1253 readline.read_history_file
1257 readline.read_history_file
1254 # windows sometimes raises something other than ImportError
1258 # windows sometimes raises something other than ImportError
1255 except Exception:
1259 except Exception:
1256 pass
1260 pass
1257
1261
1258 # call write() so output goes through subclassed implementation
1262 # call write() so output goes through subclassed implementation
1259 # e.g. color extension on Windows
1263 # e.g. color extension on Windows
1260 self.write(prompt, prompt=True)
1264 self.write(prompt, prompt=True)
1261 self.flush()
1265 self.flush()
1262
1266
1263 # prompt ' ' must exist; otherwise readline may delete entire line
1267 # prompt ' ' must exist; otherwise readline may delete entire line
1264 # - http://bugs.python.org/issue12833
1268 # - http://bugs.python.org/issue12833
1265 with self.timeblockedsection('stdio'):
1269 with self.timeblockedsection('stdio'):
1266 line = util.bytesinput(self.fin, self.fout, r' ')
1270 line = util.bytesinput(self.fin, self.fout, r' ')
1267
1271
1268 # When stdin is in binary mode on Windows, it can cause
1272 # When stdin is in binary mode on Windows, it can cause
1269 # raw_input() to emit an extra trailing carriage return
1273 # raw_input() to emit an extra trailing carriage return
1270 if pycompat.oslinesep == '\r\n' and line and line[-1] == '\r':
1274 if pycompat.oslinesep == '\r\n' and line and line[-1] == '\r':
1271 line = line[:-1]
1275 line = line[:-1]
1272 return line
1276 return line
1273
1277
1274 def prompt(self, msg, default="y"):
1278 def prompt(self, msg, default="y"):
1275 """Prompt user with msg, read response.
1279 """Prompt user with msg, read response.
1276 If ui is not interactive, the default is returned.
1280 If ui is not interactive, the default is returned.
1277 """
1281 """
1278 if not self.interactive():
1282 if not self.interactive():
1279 self.write(msg, ' ', default or '', "\n")
1283 self.write(msg, ' ', default or '', "\n")
1280 return default
1284 return default
1281 try:
1285 try:
1282 r = self._readline(self.label(msg, 'ui.prompt'))
1286 r = self._readline(self.label(msg, 'ui.prompt'))
1283 if not r:
1287 if not r:
1284 r = default
1288 r = default
1285 if self.configbool('ui', 'promptecho'):
1289 if self.configbool('ui', 'promptecho'):
1286 self.write(r, "\n")
1290 self.write(r, "\n")
1287 return r
1291 return r
1288 except EOFError:
1292 except EOFError:
1289 raise error.ResponseExpected()
1293 raise error.ResponseExpected()
1290
1294
1291 @staticmethod
1295 @staticmethod
1292 def extractchoices(prompt):
1296 def extractchoices(prompt):
1293 """Extract prompt message and list of choices from specified prompt.
1297 """Extract prompt message and list of choices from specified prompt.
1294
1298
1295 This returns tuple "(message, choices)", and "choices" is the
1299 This returns tuple "(message, choices)", and "choices" is the
1296 list of tuple "(response character, text without &)".
1300 list of tuple "(response character, text without &)".
1297
1301
1298 >>> ui.extractchoices(b"awake? $$ &Yes $$ &No")
1302 >>> ui.extractchoices(b"awake? $$ &Yes $$ &No")
1299 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1303 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1300 >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No")
1304 >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No")
1301 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1305 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1302 >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o")
1306 >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o")
1303 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1307 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1304 """
1308 """
1305
1309
1306 # Sadly, the prompt string may have been built with a filename
1310 # Sadly, the prompt string may have been built with a filename
1307 # containing "$$" so let's try to find the first valid-looking
1311 # containing "$$" so let's try to find the first valid-looking
1308 # prompt to start parsing. Sadly, we also can't rely on
1312 # prompt to start parsing. Sadly, we also can't rely on
1309 # choices containing spaces, ASCII, or basically anything
1313 # choices containing spaces, ASCII, or basically anything
1310 # except an ampersand followed by a character.
1314 # except an ampersand followed by a character.
1311 m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1315 m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1312 msg = m.group(1)
1316 msg = m.group(1)
1313 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1317 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1314 def choicetuple(s):
1318 def choicetuple(s):
1315 ampidx = s.index('&')
1319 ampidx = s.index('&')
1316 return s[ampidx + 1:ampidx + 2].lower(), s.replace('&', '', 1)
1320 return s[ampidx + 1:ampidx + 2].lower(), s.replace('&', '', 1)
1317 return (msg, [choicetuple(s) for s in choices])
1321 return (msg, [choicetuple(s) for s in choices])
1318
1322
1319 def promptchoice(self, prompt, default=0):
1323 def promptchoice(self, prompt, default=0):
1320 """Prompt user with a message, read response, and ensure it matches
1324 """Prompt user with a message, read response, and ensure it matches
1321 one of the provided choices. The prompt is formatted as follows:
1325 one of the provided choices. The prompt is formatted as follows:
1322
1326
1323 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1327 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1324
1328
1325 The index of the choice is returned. Responses are case
1329 The index of the choice is returned. Responses are case
1326 insensitive. If ui is not interactive, the default is
1330 insensitive. If ui is not interactive, the default is
1327 returned.
1331 returned.
1328 """
1332 """
1329
1333
1330 msg, choices = self.extractchoices(prompt)
1334 msg, choices = self.extractchoices(prompt)
1331 resps = [r for r, t in choices]
1335 resps = [r for r, t in choices]
1332 while True:
1336 while True:
1333 r = self.prompt(msg, resps[default])
1337 r = self.prompt(msg, resps[default])
1334 if r.lower() in resps:
1338 if r.lower() in resps:
1335 return resps.index(r.lower())
1339 return resps.index(r.lower())
1336 self.write(_("unrecognized response\n"))
1340 self.write(_("unrecognized response\n"))
1337
1341
1338 def getpass(self, prompt=None, default=None):
1342 def getpass(self, prompt=None, default=None):
1339 if not self.interactive():
1343 if not self.interactive():
1340 return default
1344 return default
1341 try:
1345 try:
1342 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1346 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1343 # disable getpass() only if explicitly specified. it's still valid
1347 # disable getpass() only if explicitly specified. it's still valid
1344 # to interact with tty even if fin is not a tty.
1348 # to interact with tty even if fin is not a tty.
1345 with self.timeblockedsection('stdio'):
1349 with self.timeblockedsection('stdio'):
1346 if self.configbool('ui', 'nontty'):
1350 if self.configbool('ui', 'nontty'):
1347 l = self.fin.readline()
1351 l = self.fin.readline()
1348 if not l:
1352 if not l:
1349 raise EOFError
1353 raise EOFError
1350 return l.rstrip('\n')
1354 return l.rstrip('\n')
1351 else:
1355 else:
1352 return getpass.getpass('')
1356 return getpass.getpass('')
1353 except EOFError:
1357 except EOFError:
1354 raise error.ResponseExpected()
1358 raise error.ResponseExpected()
1355 def status(self, *msg, **opts):
1359 def status(self, *msg, **opts):
1356 '''write status message to output (if ui.quiet is False)
1360 '''write status message to output (if ui.quiet is False)
1357
1361
1358 This adds an output label of "ui.status".
1362 This adds an output label of "ui.status".
1359 '''
1363 '''
1360 if not self.quiet:
1364 if not self.quiet:
1361 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1365 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1362 self.write(*msg, **opts)
1366 self.write(*msg, **opts)
1363 def warn(self, *msg, **opts):
1367 def warn(self, *msg, **opts):
1364 '''write warning message to output (stderr)
1368 '''write warning message to output (stderr)
1365
1369
1366 This adds an output label of "ui.warning".
1370 This adds an output label of "ui.warning".
1367 '''
1371 '''
1368 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1372 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1369 self.write_err(*msg, **opts)
1373 self.write_err(*msg, **opts)
1370 def note(self, *msg, **opts):
1374 def note(self, *msg, **opts):
1371 '''write note to output (if ui.verbose is True)
1375 '''write note to output (if ui.verbose is True)
1372
1376
1373 This adds an output label of "ui.note".
1377 This adds an output label of "ui.note".
1374 '''
1378 '''
1375 if self.verbose:
1379 if self.verbose:
1376 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1380 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1377 self.write(*msg, **opts)
1381 self.write(*msg, **opts)
1378 def debug(self, *msg, **opts):
1382 def debug(self, *msg, **opts):
1379 '''write debug message to output (if ui.debugflag is True)
1383 '''write debug message to output (if ui.debugflag is True)
1380
1384
1381 This adds an output label of "ui.debug".
1385 This adds an output label of "ui.debug".
1382 '''
1386 '''
1383 if self.debugflag:
1387 if self.debugflag:
1384 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1388 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1385 self.write(*msg, **opts)
1389 self.write(*msg, **opts)
1386
1390
1387 def edit(self, text, user, extra=None, editform=None, pending=None,
1391 def edit(self, text, user, extra=None, editform=None, pending=None,
1388 repopath=None, action=None):
1392 repopath=None, action=None):
1389 if action is None:
1393 if action is None:
1390 self.develwarn('action is None but will soon be a required '
1394 self.develwarn('action is None but will soon be a required '
1391 'parameter to ui.edit()')
1395 'parameter to ui.edit()')
1392 extra_defaults = {
1396 extra_defaults = {
1393 'prefix': 'editor',
1397 'prefix': 'editor',
1394 'suffix': '.txt',
1398 'suffix': '.txt',
1395 }
1399 }
1396 if extra is not None:
1400 if extra is not None:
1397 if extra.get('suffix') is not None:
1401 if extra.get('suffix') is not None:
1398 self.develwarn('extra.suffix is not None but will soon be '
1402 self.develwarn('extra.suffix is not None but will soon be '
1399 'ignored by ui.edit()')
1403 'ignored by ui.edit()')
1400 extra_defaults.update(extra)
1404 extra_defaults.update(extra)
1401 extra = extra_defaults
1405 extra = extra_defaults
1402
1406
1403 if action == 'diff':
1407 if action == 'diff':
1404 suffix = '.diff'
1408 suffix = '.diff'
1405 elif action:
1409 elif action:
1406 suffix = '.%s.hg.txt' % action
1410 suffix = '.%s.hg.txt' % action
1407 else:
1411 else:
1408 suffix = extra['suffix']
1412 suffix = extra['suffix']
1409
1413
1410 rdir = None
1414 rdir = None
1411 if self.configbool('experimental', 'editortmpinhg'):
1415 if self.configbool('experimental', 'editortmpinhg'):
1412 rdir = repopath
1416 rdir = repopath
1413 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1417 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1414 suffix=suffix,
1418 suffix=suffix,
1415 dir=rdir)
1419 dir=rdir)
1416 try:
1420 try:
1417 f = os.fdopen(fd, r'wb')
1421 f = os.fdopen(fd, r'wb')
1418 f.write(util.tonativeeol(text))
1422 f.write(util.tonativeeol(text))
1419 f.close()
1423 f.close()
1420
1424
1421 environ = {'HGUSER': user}
1425 environ = {'HGUSER': user}
1422 if 'transplant_source' in extra:
1426 if 'transplant_source' in extra:
1423 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1427 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1424 for label in ('intermediate-source', 'source', 'rebase_source'):
1428 for label in ('intermediate-source', 'source', 'rebase_source'):
1425 if label in extra:
1429 if label in extra:
1426 environ.update({'HGREVISION': extra[label]})
1430 environ.update({'HGREVISION': extra[label]})
1427 break
1431 break
1428 if editform:
1432 if editform:
1429 environ.update({'HGEDITFORM': editform})
1433 environ.update({'HGEDITFORM': editform})
1430 if pending:
1434 if pending:
1431 environ.update({'HG_PENDING': pending})
1435 environ.update({'HG_PENDING': pending})
1432
1436
1433 editor = self.geteditor()
1437 editor = self.geteditor()
1434
1438
1435 self.system("%s \"%s\"" % (editor, name),
1439 self.system("%s \"%s\"" % (editor, name),
1436 environ=environ,
1440 environ=environ,
1437 onerr=error.Abort, errprefix=_("edit failed"),
1441 onerr=error.Abort, errprefix=_("edit failed"),
1438 blockedtag='editor')
1442 blockedtag='editor')
1439
1443
1440 f = open(name, r'rb')
1444 f = open(name, r'rb')
1441 t = util.fromnativeeol(f.read())
1445 t = util.fromnativeeol(f.read())
1442 f.close()
1446 f.close()
1443 finally:
1447 finally:
1444 os.unlink(name)
1448 os.unlink(name)
1445
1449
1446 return t
1450 return t
1447
1451
1448 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1452 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1449 blockedtag=None):
1453 blockedtag=None):
1450 '''execute shell command with appropriate output stream. command
1454 '''execute shell command with appropriate output stream. command
1451 output will be redirected if fout is not stdout.
1455 output will be redirected if fout is not stdout.
1452
1456
1453 if command fails and onerr is None, return status, else raise onerr
1457 if command fails and onerr is None, return status, else raise onerr
1454 object as exception.
1458 object as exception.
1455 '''
1459 '''
1456 if blockedtag is None:
1460 if blockedtag is None:
1457 # Long cmds tend to be because of an absolute path on cmd. Keep
1461 # Long cmds tend to be because of an absolute path on cmd. Keep
1458 # the tail end instead
1462 # the tail end instead
1459 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1463 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1460 blockedtag = 'unknown_system_' + cmdsuffix
1464 blockedtag = 'unknown_system_' + cmdsuffix
1461 out = self.fout
1465 out = self.fout
1462 if any(s[1] for s in self._bufferstates):
1466 if any(s[1] for s in self._bufferstates):
1463 out = self
1467 out = self
1464 with self.timeblockedsection(blockedtag):
1468 with self.timeblockedsection(blockedtag):
1465 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1469 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1466 if rc and onerr:
1470 if rc and onerr:
1467 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1471 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1468 util.explainexit(rc)[0])
1472 util.explainexit(rc)[0])
1469 if errprefix:
1473 if errprefix:
1470 errmsg = '%s: %s' % (errprefix, errmsg)
1474 errmsg = '%s: %s' % (errprefix, errmsg)
1471 raise onerr(errmsg)
1475 raise onerr(errmsg)
1472 return rc
1476 return rc
1473
1477
1474 def _runsystem(self, cmd, environ, cwd, out):
1478 def _runsystem(self, cmd, environ, cwd, out):
1475 """actually execute the given shell command (can be overridden by
1479 """actually execute the given shell command (can be overridden by
1476 extensions like chg)"""
1480 extensions like chg)"""
1477 return util.system(cmd, environ=environ, cwd=cwd, out=out)
1481 return util.system(cmd, environ=environ, cwd=cwd, out=out)
1478
1482
1479 def traceback(self, exc=None, force=False):
1483 def traceback(self, exc=None, force=False):
1480 '''print exception traceback if traceback printing enabled or forced.
1484 '''print exception traceback if traceback printing enabled or forced.
1481 only to call in exception handler. returns true if traceback
1485 only to call in exception handler. returns true if traceback
1482 printed.'''
1486 printed.'''
1483 if self.tracebackflag or force:
1487 if self.tracebackflag or force:
1484 if exc is None:
1488 if exc is None:
1485 exc = sys.exc_info()
1489 exc = sys.exc_info()
1486 cause = getattr(exc[1], 'cause', None)
1490 cause = getattr(exc[1], 'cause', None)
1487
1491
1488 if cause is not None:
1492 if cause is not None:
1489 causetb = traceback.format_tb(cause[2])
1493 causetb = traceback.format_tb(cause[2])
1490 exctb = traceback.format_tb(exc[2])
1494 exctb = traceback.format_tb(exc[2])
1491 exconly = traceback.format_exception_only(cause[0], cause[1])
1495 exconly = traceback.format_exception_only(cause[0], cause[1])
1492
1496
1493 # exclude frame where 'exc' was chained and rethrown from exctb
1497 # exclude frame where 'exc' was chained and rethrown from exctb
1494 self.write_err('Traceback (most recent call last):\n',
1498 self.write_err('Traceback (most recent call last):\n',
1495 ''.join(exctb[:-1]),
1499 ''.join(exctb[:-1]),
1496 ''.join(causetb),
1500 ''.join(causetb),
1497 ''.join(exconly))
1501 ''.join(exconly))
1498 else:
1502 else:
1499 output = traceback.format_exception(exc[0], exc[1], exc[2])
1503 output = traceback.format_exception(exc[0], exc[1], exc[2])
1500 data = r''.join(output)
1504 data = r''.join(output)
1501 if pycompat.ispy3:
1505 if pycompat.ispy3:
1502 enc = pycompat.sysstr(encoding.encoding)
1506 enc = pycompat.sysstr(encoding.encoding)
1503 data = data.encode(enc, errors=r'replace')
1507 data = data.encode(enc, errors=r'replace')
1504 self.write_err(data)
1508 self.write_err(data)
1505 return self.tracebackflag or force
1509 return self.tracebackflag or force
1506
1510
1507 def geteditor(self):
1511 def geteditor(self):
1508 '''return editor to use'''
1512 '''return editor to use'''
1509 if pycompat.sysplatform == 'plan9':
1513 if pycompat.sysplatform == 'plan9':
1510 # vi is the MIPS instruction simulator on Plan 9. We
1514 # vi is the MIPS instruction simulator on Plan 9. We
1511 # instead default to E to plumb commit messages to
1515 # instead default to E to plumb commit messages to
1512 # avoid confusion.
1516 # avoid confusion.
1513 editor = 'E'
1517 editor = 'E'
1514 else:
1518 else:
1515 editor = 'vi'
1519 editor = 'vi'
1516 return (encoding.environ.get("HGEDITOR") or
1520 return (encoding.environ.get("HGEDITOR") or
1517 self.config("ui", "editor", editor))
1521 self.config("ui", "editor", editor))
1518
1522
1519 @util.propertycache
1523 @util.propertycache
1520 def _progbar(self):
1524 def _progbar(self):
1521 """setup the progbar singleton to the ui object"""
1525 """setup the progbar singleton to the ui object"""
1522 if (self.quiet or self.debugflag
1526 if (self.quiet or self.debugflag
1523 or self.configbool('progress', 'disable')
1527 or self.configbool('progress', 'disable')
1524 or not progress.shouldprint(self)):
1528 or not progress.shouldprint(self)):
1525 return None
1529 return None
1526 return getprogbar(self)
1530 return getprogbar(self)
1527
1531
1528 def _progclear(self):
1532 def _progclear(self):
1529 """clear progress bar output if any. use it before any output"""
1533 """clear progress bar output if any. use it before any output"""
1530 if not haveprogbar(): # nothing loaded yet
1534 if not haveprogbar(): # nothing loaded yet
1531 return
1535 return
1532 if self._progbar is not None and self._progbar.printed:
1536 if self._progbar is not None and self._progbar.printed:
1533 self._progbar.clear()
1537 self._progbar.clear()
1534
1538
1535 def progress(self, topic, pos, item="", unit="", total=None):
1539 def progress(self, topic, pos, item="", unit="", total=None):
1536 '''show a progress message
1540 '''show a progress message
1537
1541
1538 By default a textual progress bar will be displayed if an operation
1542 By default a textual progress bar will be displayed if an operation
1539 takes too long. 'topic' is the current operation, 'item' is a
1543 takes too long. 'topic' is the current operation, 'item' is a
1540 non-numeric marker of the current position (i.e. the currently
1544 non-numeric marker of the current position (i.e. the currently
1541 in-process file), 'pos' is the current numeric position (i.e.
1545 in-process file), 'pos' is the current numeric position (i.e.
1542 revision, bytes, etc.), unit is a corresponding unit label,
1546 revision, bytes, etc.), unit is a corresponding unit label,
1543 and total is the highest expected pos.
1547 and total is the highest expected pos.
1544
1548
1545 Multiple nested topics may be active at a time.
1549 Multiple nested topics may be active at a time.
1546
1550
1547 All topics should be marked closed by setting pos to None at
1551 All topics should be marked closed by setting pos to None at
1548 termination.
1552 termination.
1549 '''
1553 '''
1550 if self._progbar is not None:
1554 if self._progbar is not None:
1551 self._progbar.progress(topic, pos, item=item, unit=unit,
1555 self._progbar.progress(topic, pos, item=item, unit=unit,
1552 total=total)
1556 total=total)
1553 if pos is None or not self.configbool('progress', 'debug'):
1557 if pos is None or not self.configbool('progress', 'debug'):
1554 return
1558 return
1555
1559
1556 if unit:
1560 if unit:
1557 unit = ' ' + unit
1561 unit = ' ' + unit
1558 if item:
1562 if item:
1559 item = ' ' + item
1563 item = ' ' + item
1560
1564
1561 if total:
1565 if total:
1562 pct = 100.0 * pos / total
1566 pct = 100.0 * pos / total
1563 self.debug('%s:%s %d/%d%s (%4.2f%%)\n'
1567 self.debug('%s:%s %d/%d%s (%4.2f%%)\n'
1564 % (topic, item, pos, total, unit, pct))
1568 % (topic, item, pos, total, unit, pct))
1565 else:
1569 else:
1566 self.debug('%s:%s %d%s\n' % (topic, item, pos, unit))
1570 self.debug('%s:%s %d%s\n' % (topic, item, pos, unit))
1567
1571
1568 def log(self, service, *msg, **opts):
1572 def log(self, service, *msg, **opts):
1569 '''hook for logging facility extensions
1573 '''hook for logging facility extensions
1570
1574
1571 service should be a readily-identifiable subsystem, which will
1575 service should be a readily-identifiable subsystem, which will
1572 allow filtering.
1576 allow filtering.
1573
1577
1574 *msg should be a newline-terminated format string to log, and
1578 *msg should be a newline-terminated format string to log, and
1575 then any values to %-format into that format string.
1579 then any values to %-format into that format string.
1576
1580
1577 **opts currently has no defined meanings.
1581 **opts currently has no defined meanings.
1578 '''
1582 '''
1579
1583
1580 def label(self, msg, label):
1584 def label(self, msg, label):
1581 '''style msg based on supplied label
1585 '''style msg based on supplied label
1582
1586
1583 If some color mode is enabled, this will add the necessary control
1587 If some color mode is enabled, this will add the necessary control
1584 characters to apply such color. In addition, 'debug' color mode adds
1588 characters to apply such color. In addition, 'debug' color mode adds
1585 markup showing which label affects a piece of text.
1589 markup showing which label affects a piece of text.
1586
1590
1587 ui.write(s, 'label') is equivalent to
1591 ui.write(s, 'label') is equivalent to
1588 ui.write(ui.label(s, 'label')).
1592 ui.write(ui.label(s, 'label')).
1589 '''
1593 '''
1590 if self._colormode is not None:
1594 if self._colormode is not None:
1591 return color.colorlabel(self, msg, label)
1595 return color.colorlabel(self, msg, label)
1592 return msg
1596 return msg
1593
1597
1594 def develwarn(self, msg, stacklevel=1, config=None):
1598 def develwarn(self, msg, stacklevel=1, config=None):
1595 """issue a developer warning message
1599 """issue a developer warning message
1596
1600
1597 Use 'stacklevel' to report the offender some layers further up in the
1601 Use 'stacklevel' to report the offender some layers further up in the
1598 stack.
1602 stack.
1599 """
1603 """
1600 if not self.configbool('devel', 'all-warnings'):
1604 if not self.configbool('devel', 'all-warnings'):
1601 if config is not None and not self.configbool('devel', config):
1605 if config is not None and not self.configbool('devel', config):
1602 return
1606 return
1603 msg = 'devel-warn: ' + msg
1607 msg = 'devel-warn: ' + msg
1604 stacklevel += 1 # get in develwarn
1608 stacklevel += 1 # get in develwarn
1605 if self.tracebackflag:
1609 if self.tracebackflag:
1606 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1610 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1607 self.log('develwarn', '%s at:\n%s' %
1611 self.log('develwarn', '%s at:\n%s' %
1608 (msg, ''.join(util.getstackframes(stacklevel))))
1612 (msg, ''.join(util.getstackframes(stacklevel))))
1609 else:
1613 else:
1610 curframe = inspect.currentframe()
1614 curframe = inspect.currentframe()
1611 calframe = inspect.getouterframes(curframe, 2)
1615 calframe = inspect.getouterframes(curframe, 2)
1612 self.write_err('%s at: %s:%s (%s)\n'
1616 self.write_err('%s at: %s:%s (%s)\n'
1613 % ((msg,) + calframe[stacklevel][1:4]))
1617 % ((msg,) + calframe[stacklevel][1:4]))
1614 self.log('develwarn', '%s at: %s:%s (%s)\n',
1618 self.log('develwarn', '%s at: %s:%s (%s)\n',
1615 msg, *calframe[stacklevel][1:4])
1619 msg, *calframe[stacklevel][1:4])
1616 curframe = calframe = None # avoid cycles
1620 curframe = calframe = None # avoid cycles
1617
1621
1618 def deprecwarn(self, msg, version):
1622 def deprecwarn(self, msg, version):
1619 """issue a deprecation warning
1623 """issue a deprecation warning
1620
1624
1621 - msg: message explaining what is deprecated and how to upgrade,
1625 - msg: message explaining what is deprecated and how to upgrade,
1622 - version: last version where the API will be supported,
1626 - version: last version where the API will be supported,
1623 """
1627 """
1624 if not (self.configbool('devel', 'all-warnings')
1628 if not (self.configbool('devel', 'all-warnings')
1625 or self.configbool('devel', 'deprec-warn')):
1629 or self.configbool('devel', 'deprec-warn')):
1626 return
1630 return
1627 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1631 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1628 " update your code.)") % version
1632 " update your code.)") % version
1629 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1633 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1630
1634
1631 def exportableenviron(self):
1635 def exportableenviron(self):
1632 """The environment variables that are safe to export, e.g. through
1636 """The environment variables that are safe to export, e.g. through
1633 hgweb.
1637 hgweb.
1634 """
1638 """
1635 return self._exportableenviron
1639 return self._exportableenviron
1636
1640
1637 @contextlib.contextmanager
1641 @contextlib.contextmanager
1638 def configoverride(self, overrides, source=""):
1642 def configoverride(self, overrides, source=""):
1639 """Context manager for temporary config overrides
1643 """Context manager for temporary config overrides
1640 `overrides` must be a dict of the following structure:
1644 `overrides` must be a dict of the following structure:
1641 {(section, name) : value}"""
1645 {(section, name) : value}"""
1642 backups = {}
1646 backups = {}
1643 try:
1647 try:
1644 for (section, name), value in overrides.items():
1648 for (section, name), value in overrides.items():
1645 backups[(section, name)] = self.backupconfig(section, name)
1649 backups[(section, name)] = self.backupconfig(section, name)
1646 self.setconfig(section, name, value, source)
1650 self.setconfig(section, name, value, source)
1647 yield
1651 yield
1648 finally:
1652 finally:
1649 for __, backup in backups.items():
1653 for __, backup in backups.items():
1650 self.restoreconfig(backup)
1654 self.restoreconfig(backup)
1651 # just restoring ui.quiet config to the previous value is not enough
1655 # just restoring ui.quiet config to the previous value is not enough
1652 # as it does not update ui.quiet class member
1656 # as it does not update ui.quiet class member
1653 if ('ui', 'quiet') in overrides:
1657 if ('ui', 'quiet') in overrides:
1654 self.fixconfig(section='ui')
1658 self.fixconfig(section='ui')
1655
1659
1656 class paths(dict):
1660 class paths(dict):
1657 """Represents a collection of paths and their configs.
1661 """Represents a collection of paths and their configs.
1658
1662
1659 Data is initially derived from ui instances and the config files they have
1663 Data is initially derived from ui instances and the config files they have
1660 loaded.
1664 loaded.
1661 """
1665 """
1662 def __init__(self, ui):
1666 def __init__(self, ui):
1663 dict.__init__(self)
1667 dict.__init__(self)
1664
1668
1665 for name, loc in ui.configitems('paths', ignoresub=True):
1669 for name, loc in ui.configitems('paths', ignoresub=True):
1666 # No location is the same as not existing.
1670 # No location is the same as not existing.
1667 if not loc:
1671 if not loc:
1668 continue
1672 continue
1669 loc, sub = ui.configsuboptions('paths', name)
1673 loc, sub = ui.configsuboptions('paths', name)
1670 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1674 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1671
1675
1672 def getpath(self, name, default=None):
1676 def getpath(self, name, default=None):
1673 """Return a ``path`` from a string, falling back to default.
1677 """Return a ``path`` from a string, falling back to default.
1674
1678
1675 ``name`` can be a named path or locations. Locations are filesystem
1679 ``name`` can be a named path or locations. Locations are filesystem
1676 paths or URIs.
1680 paths or URIs.
1677
1681
1678 Returns None if ``name`` is not a registered path, a URI, or a local
1682 Returns None if ``name`` is not a registered path, a URI, or a local
1679 path to a repo.
1683 path to a repo.
1680 """
1684 """
1681 # Only fall back to default if no path was requested.
1685 # Only fall back to default if no path was requested.
1682 if name is None:
1686 if name is None:
1683 if not default:
1687 if not default:
1684 default = ()
1688 default = ()
1685 elif not isinstance(default, (tuple, list)):
1689 elif not isinstance(default, (tuple, list)):
1686 default = (default,)
1690 default = (default,)
1687 for k in default:
1691 for k in default:
1688 try:
1692 try:
1689 return self[k]
1693 return self[k]
1690 except KeyError:
1694 except KeyError:
1691 continue
1695 continue
1692 return None
1696 return None
1693
1697
1694 # Most likely empty string.
1698 # Most likely empty string.
1695 # This may need to raise in the future.
1699 # This may need to raise in the future.
1696 if not name:
1700 if not name:
1697 return None
1701 return None
1698
1702
1699 try:
1703 try:
1700 return self[name]
1704 return self[name]
1701 except KeyError:
1705 except KeyError:
1702 # Try to resolve as a local path or URI.
1706 # Try to resolve as a local path or URI.
1703 try:
1707 try:
1704 # We don't pass sub-options in, so no need to pass ui instance.
1708 # We don't pass sub-options in, so no need to pass ui instance.
1705 return path(None, None, rawloc=name)
1709 return path(None, None, rawloc=name)
1706 except ValueError:
1710 except ValueError:
1707 raise error.RepoError(_('repository %s does not exist') %
1711 raise error.RepoError(_('repository %s does not exist') %
1708 name)
1712 name)
1709
1713
1710 _pathsuboptions = {}
1714 _pathsuboptions = {}
1711
1715
1712 def pathsuboption(option, attr):
1716 def pathsuboption(option, attr):
1713 """Decorator used to declare a path sub-option.
1717 """Decorator used to declare a path sub-option.
1714
1718
1715 Arguments are the sub-option name and the attribute it should set on
1719 Arguments are the sub-option name and the attribute it should set on
1716 ``path`` instances.
1720 ``path`` instances.
1717
1721
1718 The decorated function will receive as arguments a ``ui`` instance,
1722 The decorated function will receive as arguments a ``ui`` instance,
1719 ``path`` instance, and the string value of this option from the config.
1723 ``path`` instance, and the string value of this option from the config.
1720 The function should return the value that will be set on the ``path``
1724 The function should return the value that will be set on the ``path``
1721 instance.
1725 instance.
1722
1726
1723 This decorator can be used to perform additional verification of
1727 This decorator can be used to perform additional verification of
1724 sub-options and to change the type of sub-options.
1728 sub-options and to change the type of sub-options.
1725 """
1729 """
1726 def register(func):
1730 def register(func):
1727 _pathsuboptions[option] = (attr, func)
1731 _pathsuboptions[option] = (attr, func)
1728 return func
1732 return func
1729 return register
1733 return register
1730
1734
1731 @pathsuboption('pushurl', 'pushloc')
1735 @pathsuboption('pushurl', 'pushloc')
1732 def pushurlpathoption(ui, path, value):
1736 def pushurlpathoption(ui, path, value):
1733 u = util.url(value)
1737 u = util.url(value)
1734 # Actually require a URL.
1738 # Actually require a URL.
1735 if not u.scheme:
1739 if not u.scheme:
1736 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1740 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1737 return None
1741 return None
1738
1742
1739 # Don't support the #foo syntax in the push URL to declare branch to
1743 # Don't support the #foo syntax in the push URL to declare branch to
1740 # push.
1744 # push.
1741 if u.fragment:
1745 if u.fragment:
1742 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1746 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1743 'ignoring)\n') % path.name)
1747 'ignoring)\n') % path.name)
1744 u.fragment = None
1748 u.fragment = None
1745
1749
1746 return str(u)
1750 return str(u)
1747
1751
1748 @pathsuboption('pushrev', 'pushrev')
1752 @pathsuboption('pushrev', 'pushrev')
1749 def pushrevpathoption(ui, path, value):
1753 def pushrevpathoption(ui, path, value):
1750 return value
1754 return value
1751
1755
1752 class path(object):
1756 class path(object):
1753 """Represents an individual path and its configuration."""
1757 """Represents an individual path and its configuration."""
1754
1758
1755 def __init__(self, ui, name, rawloc=None, suboptions=None):
1759 def __init__(self, ui, name, rawloc=None, suboptions=None):
1756 """Construct a path from its config options.
1760 """Construct a path from its config options.
1757
1761
1758 ``ui`` is the ``ui`` instance the path is coming from.
1762 ``ui`` is the ``ui`` instance the path is coming from.
1759 ``name`` is the symbolic name of the path.
1763 ``name`` is the symbolic name of the path.
1760 ``rawloc`` is the raw location, as defined in the config.
1764 ``rawloc`` is the raw location, as defined in the config.
1761 ``pushloc`` is the raw locations pushes should be made to.
1765 ``pushloc`` is the raw locations pushes should be made to.
1762
1766
1763 If ``name`` is not defined, we require that the location be a) a local
1767 If ``name`` is not defined, we require that the location be a) a local
1764 filesystem path with a .hg directory or b) a URL. If not,
1768 filesystem path with a .hg directory or b) a URL. If not,
1765 ``ValueError`` is raised.
1769 ``ValueError`` is raised.
1766 """
1770 """
1767 if not rawloc:
1771 if not rawloc:
1768 raise ValueError('rawloc must be defined')
1772 raise ValueError('rawloc must be defined')
1769
1773
1770 # Locations may define branches via syntax <base>#<branch>.
1774 # Locations may define branches via syntax <base>#<branch>.
1771 u = util.url(rawloc)
1775 u = util.url(rawloc)
1772 branch = None
1776 branch = None
1773 if u.fragment:
1777 if u.fragment:
1774 branch = u.fragment
1778 branch = u.fragment
1775 u.fragment = None
1779 u.fragment = None
1776
1780
1777 self.url = u
1781 self.url = u
1778 self.branch = branch
1782 self.branch = branch
1779
1783
1780 self.name = name
1784 self.name = name
1781 self.rawloc = rawloc
1785 self.rawloc = rawloc
1782 self.loc = '%s' % u
1786 self.loc = '%s' % u
1783
1787
1784 # When given a raw location but not a symbolic name, validate the
1788 # When given a raw location but not a symbolic name, validate the
1785 # location is valid.
1789 # location is valid.
1786 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1790 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1787 raise ValueError('location is not a URL or path to a local '
1791 raise ValueError('location is not a URL or path to a local '
1788 'repo: %s' % rawloc)
1792 'repo: %s' % rawloc)
1789
1793
1790 suboptions = suboptions or {}
1794 suboptions = suboptions or {}
1791
1795
1792 # Now process the sub-options. If a sub-option is registered, its
1796 # Now process the sub-options. If a sub-option is registered, its
1793 # attribute will always be present. The value will be None if there
1797 # attribute will always be present. The value will be None if there
1794 # was no valid sub-option.
1798 # was no valid sub-option.
1795 for suboption, (attr, func) in _pathsuboptions.iteritems():
1799 for suboption, (attr, func) in _pathsuboptions.iteritems():
1796 if suboption not in suboptions:
1800 if suboption not in suboptions:
1797 setattr(self, attr, None)
1801 setattr(self, attr, None)
1798 continue
1802 continue
1799
1803
1800 value = func(ui, self, suboptions[suboption])
1804 value = func(ui, self, suboptions[suboption])
1801 setattr(self, attr, value)
1805 setattr(self, attr, value)
1802
1806
1803 def _isvalidlocalpath(self, path):
1807 def _isvalidlocalpath(self, path):
1804 """Returns True if the given path is a potentially valid repository.
1808 """Returns True if the given path is a potentially valid repository.
1805 This is its own function so that extensions can change the definition of
1809 This is its own function so that extensions can change the definition of
1806 'valid' in this case (like when pulling from a git repo into a hg
1810 'valid' in this case (like when pulling from a git repo into a hg
1807 one)."""
1811 one)."""
1808 return os.path.isdir(os.path.join(path, '.hg'))
1812 return os.path.isdir(os.path.join(path, '.hg'))
1809
1813
1810 @property
1814 @property
1811 def suboptions(self):
1815 def suboptions(self):
1812 """Return sub-options and their values for this path.
1816 """Return sub-options and their values for this path.
1813
1817
1814 This is intended to be used for presentation purposes.
1818 This is intended to be used for presentation purposes.
1815 """
1819 """
1816 d = {}
1820 d = {}
1817 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1821 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1818 value = getattr(self, attr)
1822 value = getattr(self, attr)
1819 if value is not None:
1823 if value is not None:
1820 d[subopt] = value
1824 d[subopt] = value
1821 return d
1825 return d
1822
1826
1823 # we instantiate one globally shared progress bar to avoid
1827 # we instantiate one globally shared progress bar to avoid
1824 # competing progress bars when multiple UI objects get created
1828 # competing progress bars when multiple UI objects get created
1825 _progresssingleton = None
1829 _progresssingleton = None
1826
1830
1827 def getprogbar(ui):
1831 def getprogbar(ui):
1828 global _progresssingleton
1832 global _progresssingleton
1829 if _progresssingleton is None:
1833 if _progresssingleton is None:
1830 # passing 'ui' object to the singleton is fishy,
1834 # passing 'ui' object to the singleton is fishy,
1831 # this is how the extension used to work but feel free to rework it.
1835 # this is how the extension used to work but feel free to rework it.
1832 _progresssingleton = progress.progbar(ui)
1836 _progresssingleton = progress.progbar(ui)
1833 return _progresssingleton
1837 return _progresssingleton
1834
1838
1835 def haveprogbar():
1839 def haveprogbar():
1836 return _progresssingleton is not None
1840 return _progresssingleton is not None
@@ -1,1009 +1,1023
1 #if windows
1 #if windows
2 $ PYTHONPATH="$TESTDIR/../contrib;$PYTHONPATH"
2 $ PYTHONPATH="$TESTDIR/../contrib;$PYTHONPATH"
3 #else
3 #else
4 $ PYTHONPATH="$TESTDIR/../contrib:$PYTHONPATH"
4 $ PYTHONPATH="$TESTDIR/../contrib:$PYTHONPATH"
5 #endif
5 #endif
6 $ export PYTHONPATH
6 $ export PYTHONPATH
7
7
8 typical client does not want echo-back messages, so test without it:
8 typical client does not want echo-back messages, so test without it:
9
9
10 $ grep -v '^promptecho ' < $HGRCPATH >> $HGRCPATH.new
10 $ grep -v '^promptecho ' < $HGRCPATH >> $HGRCPATH.new
11 $ mv $HGRCPATH.new $HGRCPATH
11 $ mv $HGRCPATH.new $HGRCPATH
12
12
13 $ hg init repo
13 $ hg init repo
14 $ cd repo
14 $ cd repo
15
15
16 >>> from __future__ import absolute_import, print_function
16 >>> from __future__ import absolute_import, print_function
17 >>> import os
17 >>> import os
18 >>> import sys
18 >>> import sys
19 >>> from hgclient import check, readchannel, runcommand
19 >>> from hgclient import check, readchannel, runcommand
20 >>> @check
20 >>> @check
21 ... def hellomessage(server):
21 ... def hellomessage(server):
22 ... ch, data = readchannel(server)
22 ... ch, data = readchannel(server)
23 ... print('%c, %r' % (ch, data))
23 ... print('%c, %r' % (ch, data))
24 ... # run an arbitrary command to make sure the next thing the server
24 ... # run an arbitrary command to make sure the next thing the server
25 ... # sends isn't part of the hello message
25 ... # sends isn't part of the hello message
26 ... runcommand(server, ['id'])
26 ... runcommand(server, ['id'])
27 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
27 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
28 *** runcommand id
28 *** runcommand id
29 000000000000 tip
29 000000000000 tip
30
30
31 >>> from hgclient import check
31 >>> from hgclient import check
32 >>> @check
32 >>> @check
33 ... def unknowncommand(server):
33 ... def unknowncommand(server):
34 ... server.stdin.write('unknowncommand\n')
34 ... server.stdin.write('unknowncommand\n')
35 abort: unknown command unknowncommand
35 abort: unknown command unknowncommand
36
36
37 >>> from hgclient import check, readchannel, runcommand
37 >>> from hgclient import check, readchannel, runcommand
38 >>> @check
38 >>> @check
39 ... def checkruncommand(server):
39 ... def checkruncommand(server):
40 ... # hello block
40 ... # hello block
41 ... readchannel(server)
41 ... readchannel(server)
42 ...
42 ...
43 ... # no args
43 ... # no args
44 ... runcommand(server, [])
44 ... runcommand(server, [])
45 ...
45 ...
46 ... # global options
46 ... # global options
47 ... runcommand(server, ['id', '--quiet'])
47 ... runcommand(server, ['id', '--quiet'])
48 ...
48 ...
49 ... # make sure global options don't stick through requests
49 ... # make sure global options don't stick through requests
50 ... runcommand(server, ['id'])
50 ... runcommand(server, ['id'])
51 ...
51 ...
52 ... # --config
52 ... # --config
53 ... runcommand(server, ['id', '--config', 'ui.quiet=True'])
53 ... runcommand(server, ['id', '--config', 'ui.quiet=True'])
54 ...
54 ...
55 ... # make sure --config doesn't stick
55 ... # make sure --config doesn't stick
56 ... runcommand(server, ['id'])
56 ... runcommand(server, ['id'])
57 ...
57 ...
58 ... # negative return code should be masked
58 ... # negative return code should be masked
59 ... runcommand(server, ['id', '-runknown'])
59 ... runcommand(server, ['id', '-runknown'])
60 *** runcommand
60 *** runcommand
61 Mercurial Distributed SCM
61 Mercurial Distributed SCM
62
62
63 basic commands:
63 basic commands:
64
64
65 add add the specified files on the next commit
65 add add the specified files on the next commit
66 annotate show changeset information by line for each file
66 annotate show changeset information by line for each file
67 clone make a copy of an existing repository
67 clone make a copy of an existing repository
68 commit commit the specified files or all outstanding changes
68 commit commit the specified files or all outstanding changes
69 diff diff repository (or selected files)
69 diff diff repository (or selected files)
70 export dump the header and diffs for one or more changesets
70 export dump the header and diffs for one or more changesets
71 forget forget the specified files on the next commit
71 forget forget the specified files on the next commit
72 init create a new repository in the given directory
72 init create a new repository in the given directory
73 log show revision history of entire repository or files
73 log show revision history of entire repository or files
74 merge merge another revision into working directory
74 merge merge another revision into working directory
75 pull pull changes from the specified source
75 pull pull changes from the specified source
76 push push changes to the specified destination
76 push push changes to the specified destination
77 remove remove the specified files on the next commit
77 remove remove the specified files on the next commit
78 serve start stand-alone webserver
78 serve start stand-alone webserver
79 status show changed files in the working directory
79 status show changed files in the working directory
80 summary summarize working directory state
80 summary summarize working directory state
81 update update working directory (or switch revisions)
81 update update working directory (or switch revisions)
82
82
83 (use 'hg help' for the full list of commands or 'hg -v' for details)
83 (use 'hg help' for the full list of commands or 'hg -v' for details)
84 *** runcommand id --quiet
84 *** runcommand id --quiet
85 000000000000
85 000000000000
86 *** runcommand id
86 *** runcommand id
87 000000000000 tip
87 000000000000 tip
88 *** runcommand id --config ui.quiet=True
88 *** runcommand id --config ui.quiet=True
89 000000000000
89 000000000000
90 *** runcommand id
90 *** runcommand id
91 000000000000 tip
91 000000000000 tip
92 *** runcommand id -runknown
92 *** runcommand id -runknown
93 abort: unknown revision 'unknown'!
93 abort: unknown revision 'unknown'!
94 [255]
94 [255]
95
95
96 >>> from hgclient import check, readchannel
96 >>> from hgclient import check, readchannel
97 >>> @check
97 >>> @check
98 ... def inputeof(server):
98 ... def inputeof(server):
99 ... readchannel(server)
99 ... readchannel(server)
100 ... server.stdin.write('runcommand\n')
100 ... server.stdin.write('runcommand\n')
101 ... # close stdin while server is waiting for input
101 ... # close stdin while server is waiting for input
102 ... server.stdin.close()
102 ... server.stdin.close()
103 ...
103 ...
104 ... # server exits with 1 if the pipe closed while reading the command
104 ... # server exits with 1 if the pipe closed while reading the command
105 ... print('server exit code =', server.wait())
105 ... print('server exit code =', server.wait())
106 server exit code = 1
106 server exit code = 1
107
107
108 >>> from hgclient import check, readchannel, runcommand, stringio
108 >>> from hgclient import check, readchannel, runcommand, stringio
109 >>> @check
109 >>> @check
110 ... def serverinput(server):
110 ... def serverinput(server):
111 ... readchannel(server)
111 ... readchannel(server)
112 ...
112 ...
113 ... patch = """
113 ... patch = """
114 ... # HG changeset patch
114 ... # HG changeset patch
115 ... # User test
115 ... # User test
116 ... # Date 0 0
116 ... # Date 0 0
117 ... # Node ID c103a3dec114d882c98382d684d8af798d09d857
117 ... # Node ID c103a3dec114d882c98382d684d8af798d09d857
118 ... # Parent 0000000000000000000000000000000000000000
118 ... # Parent 0000000000000000000000000000000000000000
119 ... 1
119 ... 1
120 ...
120 ...
121 ... diff -r 000000000000 -r c103a3dec114 a
121 ... diff -r 000000000000 -r c103a3dec114 a
122 ... --- /dev/null Thu Jan 01 00:00:00 1970 +0000
122 ... --- /dev/null Thu Jan 01 00:00:00 1970 +0000
123 ... +++ b/a Thu Jan 01 00:00:00 1970 +0000
123 ... +++ b/a Thu Jan 01 00:00:00 1970 +0000
124 ... @@ -0,0 +1,1 @@
124 ... @@ -0,0 +1,1 @@
125 ... +1
125 ... +1
126 ... """
126 ... """
127 ...
127 ...
128 ... runcommand(server, ['import', '-'], input=stringio(patch))
128 ... runcommand(server, ['import', '-'], input=stringio(patch))
129 ... runcommand(server, ['log'])
129 ... runcommand(server, ['log'])
130 *** runcommand import -
130 *** runcommand import -
131 applying patch from stdin
131 applying patch from stdin
132 *** runcommand log
132 *** runcommand log
133 changeset: 0:eff892de26ec
133 changeset: 0:eff892de26ec
134 tag: tip
134 tag: tip
135 user: test
135 user: test
136 date: Thu Jan 01 00:00:00 1970 +0000
136 date: Thu Jan 01 00:00:00 1970 +0000
137 summary: 1
137 summary: 1
138
138
139
139
140 check strict parsing of early options:
141
142 >>> import os
143 >>> from hgclient import check, readchannel, runcommand
144 >>> os.environ['HGPLAIN'] = '+strictflags'
145 >>> @check
146 ... def cwd(server):
147 ... readchannel(server)
148 ... runcommand(server, ['log', '-b', '--config=alias.log=!echo pwned',
149 ... 'default'])
150 *** runcommand log -b --config=alias.log=!echo pwned default
151 abort: unknown revision '--config=alias.log=!echo pwned'!
152 [255]
153
140 check that "histedit --commands=-" can read rules from the input channel:
154 check that "histedit --commands=-" can read rules from the input channel:
141
155
142 >>> import cStringIO
156 >>> import cStringIO
143 >>> from hgclient import check, readchannel, runcommand
157 >>> from hgclient import check, readchannel, runcommand
144 >>> @check
158 >>> @check
145 ... def serverinput(server):
159 ... def serverinput(server):
146 ... readchannel(server)
160 ... readchannel(server)
147 ... rules = 'pick eff892de26ec\n'
161 ... rules = 'pick eff892de26ec\n'
148 ... runcommand(server, ['histedit', '0', '--commands=-',
162 ... runcommand(server, ['histedit', '0', '--commands=-',
149 ... '--config', 'extensions.histedit='],
163 ... '--config', 'extensions.histedit='],
150 ... input=cStringIO.StringIO(rules))
164 ... input=cStringIO.StringIO(rules))
151 *** runcommand histedit 0 --commands=- --config extensions.histedit=
165 *** runcommand histedit 0 --commands=- --config extensions.histedit=
152
166
153 check that --cwd doesn't persist between requests:
167 check that --cwd doesn't persist between requests:
154
168
155 $ mkdir foo
169 $ mkdir foo
156 $ touch foo/bar
170 $ touch foo/bar
157 >>> from hgclient import check, readchannel, runcommand
171 >>> from hgclient import check, readchannel, runcommand
158 >>> @check
172 >>> @check
159 ... def cwd(server):
173 ... def cwd(server):
160 ... readchannel(server)
174 ... readchannel(server)
161 ... runcommand(server, ['--cwd', 'foo', 'st', 'bar'])
175 ... runcommand(server, ['--cwd', 'foo', 'st', 'bar'])
162 ... runcommand(server, ['st', 'foo/bar'])
176 ... runcommand(server, ['st', 'foo/bar'])
163 *** runcommand --cwd foo st bar
177 *** runcommand --cwd foo st bar
164 ? bar
178 ? bar
165 *** runcommand st foo/bar
179 *** runcommand st foo/bar
166 ? foo/bar
180 ? foo/bar
167
181
168 $ rm foo/bar
182 $ rm foo/bar
169
183
170
184
171 check that local configs for the cached repo aren't inherited when -R is used:
185 check that local configs for the cached repo aren't inherited when -R is used:
172
186
173 $ cat <<EOF >> .hg/hgrc
187 $ cat <<EOF >> .hg/hgrc
174 > [ui]
188 > [ui]
175 > foo = bar
189 > foo = bar
176 > EOF
190 > EOF
177
191
178 >>> from hgclient import check, readchannel, runcommand, sep
192 >>> from hgclient import check, readchannel, runcommand, sep
179 >>> @check
193 >>> @check
180 ... def localhgrc(server):
194 ... def localhgrc(server):
181 ... readchannel(server)
195 ... readchannel(server)
182 ...
196 ...
183 ... # the cached repo local hgrc contains ui.foo=bar, so showconfig should
197 ... # the cached repo local hgrc contains ui.foo=bar, so showconfig should
184 ... # show it
198 ... # show it
185 ... runcommand(server, ['showconfig'], outfilter=sep)
199 ... runcommand(server, ['showconfig'], outfilter=sep)
186 ...
200 ...
187 ... # but not for this repo
201 ... # but not for this repo
188 ... runcommand(server, ['init', 'foo'])
202 ... runcommand(server, ['init', 'foo'])
189 ... runcommand(server, ['-R', 'foo', 'showconfig', 'ui', 'defaults'])
203 ... runcommand(server, ['-R', 'foo', 'showconfig', 'ui', 'defaults'])
190 *** runcommand showconfig
204 *** runcommand showconfig
191 bundle.mainreporoot=$TESTTMP/repo
205 bundle.mainreporoot=$TESTTMP/repo
192 devel.all-warnings=true
206 devel.all-warnings=true
193 devel.default-date=0 0
207 devel.default-date=0 0
194 extensions.fsmonitor= (fsmonitor !)
208 extensions.fsmonitor= (fsmonitor !)
195 largefiles.usercache=$TESTTMP/.cache/largefiles
209 largefiles.usercache=$TESTTMP/.cache/largefiles
196 ui.slash=True
210 ui.slash=True
197 ui.interactive=False
211 ui.interactive=False
198 ui.mergemarkers=detailed
212 ui.mergemarkers=detailed
199 ui.usehttp2=true (?)
213 ui.usehttp2=true (?)
200 ui.foo=bar
214 ui.foo=bar
201 ui.nontty=true
215 ui.nontty=true
202 web.address=localhost
216 web.address=localhost
203 web\.ipv6=(?:True|False) (re)
217 web\.ipv6=(?:True|False) (re)
204 *** runcommand init foo
218 *** runcommand init foo
205 *** runcommand -R foo showconfig ui defaults
219 *** runcommand -R foo showconfig ui defaults
206 ui.slash=True
220 ui.slash=True
207 ui.interactive=False
221 ui.interactive=False
208 ui.mergemarkers=detailed
222 ui.mergemarkers=detailed
209 ui.usehttp2=true (?)
223 ui.usehttp2=true (?)
210 ui.nontty=true
224 ui.nontty=true
211
225
212 $ rm -R foo
226 $ rm -R foo
213
227
214 #if windows
228 #if windows
215 $ PYTHONPATH="$TESTTMP/repo;$PYTHONPATH"
229 $ PYTHONPATH="$TESTTMP/repo;$PYTHONPATH"
216 #else
230 #else
217 $ PYTHONPATH="$TESTTMP/repo:$PYTHONPATH"
231 $ PYTHONPATH="$TESTTMP/repo:$PYTHONPATH"
218 #endif
232 #endif
219
233
220 $ cat <<EOF > hook.py
234 $ cat <<EOF > hook.py
221 > from __future__ import print_function
235 > from __future__ import print_function
222 > import sys
236 > import sys
223 > def hook(**args):
237 > def hook(**args):
224 > print('hook talking')
238 > print('hook talking')
225 > print('now try to read something: %r' % sys.stdin.read())
239 > print('now try to read something: %r' % sys.stdin.read())
226 > EOF
240 > EOF
227
241
228 >>> from hgclient import check, readchannel, runcommand, stringio
242 >>> from hgclient import check, readchannel, runcommand, stringio
229 >>> @check
243 >>> @check
230 ... def hookoutput(server):
244 ... def hookoutput(server):
231 ... readchannel(server)
245 ... readchannel(server)
232 ... runcommand(server, ['--config',
246 ... runcommand(server, ['--config',
233 ... 'hooks.pre-identify=python:hook.hook',
247 ... 'hooks.pre-identify=python:hook.hook',
234 ... 'id'],
248 ... 'id'],
235 ... input=stringio('some input'))
249 ... input=stringio('some input'))
236 *** runcommand --config hooks.pre-identify=python:hook.hook id
250 *** runcommand --config hooks.pre-identify=python:hook.hook id
237 eff892de26ec tip
251 eff892de26ec tip
238
252
239 Clean hook cached version
253 Clean hook cached version
240 $ rm hook.py*
254 $ rm hook.py*
241 $ rm -Rf __pycache__
255 $ rm -Rf __pycache__
242
256
243 $ echo a >> a
257 $ echo a >> a
244 >>> import os
258 >>> import os
245 >>> from hgclient import check, readchannel, runcommand
259 >>> from hgclient import check, readchannel, runcommand
246 >>> @check
260 >>> @check
247 ... def outsidechanges(server):
261 ... def outsidechanges(server):
248 ... readchannel(server)
262 ... readchannel(server)
249 ... runcommand(server, ['status'])
263 ... runcommand(server, ['status'])
250 ... os.system('hg ci -Am2')
264 ... os.system('hg ci -Am2')
251 ... runcommand(server, ['tip'])
265 ... runcommand(server, ['tip'])
252 ... runcommand(server, ['status'])
266 ... runcommand(server, ['status'])
253 *** runcommand status
267 *** runcommand status
254 M a
268 M a
255 *** runcommand tip
269 *** runcommand tip
256 changeset: 1:d3a0a68be6de
270 changeset: 1:d3a0a68be6de
257 tag: tip
271 tag: tip
258 user: test
272 user: test
259 date: Thu Jan 01 00:00:00 1970 +0000
273 date: Thu Jan 01 00:00:00 1970 +0000
260 summary: 2
274 summary: 2
261
275
262 *** runcommand status
276 *** runcommand status
263
277
264 >>> import os
278 >>> import os
265 >>> from hgclient import check, readchannel, runcommand
279 >>> from hgclient import check, readchannel, runcommand
266 >>> @check
280 >>> @check
267 ... def bookmarks(server):
281 ... def bookmarks(server):
268 ... readchannel(server)
282 ... readchannel(server)
269 ... runcommand(server, ['bookmarks'])
283 ... runcommand(server, ['bookmarks'])
270 ...
284 ...
271 ... # changes .hg/bookmarks
285 ... # changes .hg/bookmarks
272 ... os.system('hg bookmark -i bm1')
286 ... os.system('hg bookmark -i bm1')
273 ... os.system('hg bookmark -i bm2')
287 ... os.system('hg bookmark -i bm2')
274 ... runcommand(server, ['bookmarks'])
288 ... runcommand(server, ['bookmarks'])
275 ...
289 ...
276 ... # changes .hg/bookmarks.current
290 ... # changes .hg/bookmarks.current
277 ... os.system('hg upd bm1 -q')
291 ... os.system('hg upd bm1 -q')
278 ... runcommand(server, ['bookmarks'])
292 ... runcommand(server, ['bookmarks'])
279 ...
293 ...
280 ... runcommand(server, ['bookmarks', 'bm3'])
294 ... runcommand(server, ['bookmarks', 'bm3'])
281 ... f = open('a', 'ab')
295 ... f = open('a', 'ab')
282 ... f.write('a\n')
296 ... f.write('a\n')
283 ... f.close()
297 ... f.close()
284 ... runcommand(server, ['commit', '-Amm'])
298 ... runcommand(server, ['commit', '-Amm'])
285 ... runcommand(server, ['bookmarks'])
299 ... runcommand(server, ['bookmarks'])
286 ... print('')
300 ... print('')
287 *** runcommand bookmarks
301 *** runcommand bookmarks
288 no bookmarks set
302 no bookmarks set
289 *** runcommand bookmarks
303 *** runcommand bookmarks
290 bm1 1:d3a0a68be6de
304 bm1 1:d3a0a68be6de
291 bm2 1:d3a0a68be6de
305 bm2 1:d3a0a68be6de
292 *** runcommand bookmarks
306 *** runcommand bookmarks
293 * bm1 1:d3a0a68be6de
307 * bm1 1:d3a0a68be6de
294 bm2 1:d3a0a68be6de
308 bm2 1:d3a0a68be6de
295 *** runcommand bookmarks bm3
309 *** runcommand bookmarks bm3
296 *** runcommand commit -Amm
310 *** runcommand commit -Amm
297 *** runcommand bookmarks
311 *** runcommand bookmarks
298 bm1 1:d3a0a68be6de
312 bm1 1:d3a0a68be6de
299 bm2 1:d3a0a68be6de
313 bm2 1:d3a0a68be6de
300 * bm3 2:aef17e88f5f0
314 * bm3 2:aef17e88f5f0
301
315
302
316
303 >>> import os
317 >>> import os
304 >>> from hgclient import check, readchannel, runcommand
318 >>> from hgclient import check, readchannel, runcommand
305 >>> @check
319 >>> @check
306 ... def tagscache(server):
320 ... def tagscache(server):
307 ... readchannel(server)
321 ... readchannel(server)
308 ... runcommand(server, ['id', '-t', '-r', '0'])
322 ... runcommand(server, ['id', '-t', '-r', '0'])
309 ... os.system('hg tag -r 0 foo')
323 ... os.system('hg tag -r 0 foo')
310 ... runcommand(server, ['id', '-t', '-r', '0'])
324 ... runcommand(server, ['id', '-t', '-r', '0'])
311 *** runcommand id -t -r 0
325 *** runcommand id -t -r 0
312
326
313 *** runcommand id -t -r 0
327 *** runcommand id -t -r 0
314 foo
328 foo
315
329
316 >>> import os
330 >>> import os
317 >>> from hgclient import check, readchannel, runcommand
331 >>> from hgclient import check, readchannel, runcommand
318 >>> @check
332 >>> @check
319 ... def setphase(server):
333 ... def setphase(server):
320 ... readchannel(server)
334 ... readchannel(server)
321 ... runcommand(server, ['phase', '-r', '.'])
335 ... runcommand(server, ['phase', '-r', '.'])
322 ... os.system('hg phase -r . -p')
336 ... os.system('hg phase -r . -p')
323 ... runcommand(server, ['phase', '-r', '.'])
337 ... runcommand(server, ['phase', '-r', '.'])
324 *** runcommand phase -r .
338 *** runcommand phase -r .
325 3: draft
339 3: draft
326 *** runcommand phase -r .
340 *** runcommand phase -r .
327 3: public
341 3: public
328
342
329 $ echo a >> a
343 $ echo a >> a
330 >>> from hgclient import check, readchannel, runcommand
344 >>> from hgclient import check, readchannel, runcommand
331 >>> @check
345 >>> @check
332 ... def rollback(server):
346 ... def rollback(server):
333 ... readchannel(server)
347 ... readchannel(server)
334 ... runcommand(server, ['phase', '-r', '.', '-p'])
348 ... runcommand(server, ['phase', '-r', '.', '-p'])
335 ... runcommand(server, ['commit', '-Am.'])
349 ... runcommand(server, ['commit', '-Am.'])
336 ... runcommand(server, ['rollback'])
350 ... runcommand(server, ['rollback'])
337 ... runcommand(server, ['phase', '-r', '.'])
351 ... runcommand(server, ['phase', '-r', '.'])
338 ... print('')
352 ... print('')
339 *** runcommand phase -r . -p
353 *** runcommand phase -r . -p
340 no phases changed
354 no phases changed
341 *** runcommand commit -Am.
355 *** runcommand commit -Am.
342 *** runcommand rollback
356 *** runcommand rollback
343 repository tip rolled back to revision 3 (undo commit)
357 repository tip rolled back to revision 3 (undo commit)
344 working directory now based on revision 3
358 working directory now based on revision 3
345 *** runcommand phase -r .
359 *** runcommand phase -r .
346 3: public
360 3: public
347
361
348
362
349 >>> import os
363 >>> import os
350 >>> from hgclient import check, readchannel, runcommand
364 >>> from hgclient import check, readchannel, runcommand
351 >>> @check
365 >>> @check
352 ... def branch(server):
366 ... def branch(server):
353 ... readchannel(server)
367 ... readchannel(server)
354 ... runcommand(server, ['branch'])
368 ... runcommand(server, ['branch'])
355 ... os.system('hg branch foo')
369 ... os.system('hg branch foo')
356 ... runcommand(server, ['branch'])
370 ... runcommand(server, ['branch'])
357 ... os.system('hg branch default')
371 ... os.system('hg branch default')
358 *** runcommand branch
372 *** runcommand branch
359 default
373 default
360 marked working directory as branch foo
374 marked working directory as branch foo
361 (branches are permanent and global, did you want a bookmark?)
375 (branches are permanent and global, did you want a bookmark?)
362 *** runcommand branch
376 *** runcommand branch
363 foo
377 foo
364 marked working directory as branch default
378 marked working directory as branch default
365 (branches are permanent and global, did you want a bookmark?)
379 (branches are permanent and global, did you want a bookmark?)
366
380
367 $ touch .hgignore
381 $ touch .hgignore
368 >>> import os
382 >>> import os
369 >>> from hgclient import check, readchannel, runcommand
383 >>> from hgclient import check, readchannel, runcommand
370 >>> @check
384 >>> @check
371 ... def hgignore(server):
385 ... def hgignore(server):
372 ... readchannel(server)
386 ... readchannel(server)
373 ... runcommand(server, ['commit', '-Am.'])
387 ... runcommand(server, ['commit', '-Am.'])
374 ... f = open('ignored-file', 'ab')
388 ... f = open('ignored-file', 'ab')
375 ... f.write('')
389 ... f.write('')
376 ... f.close()
390 ... f.close()
377 ... f = open('.hgignore', 'ab')
391 ... f = open('.hgignore', 'ab')
378 ... f.write('ignored-file')
392 ... f.write('ignored-file')
379 ... f.close()
393 ... f.close()
380 ... runcommand(server, ['status', '-i', '-u'])
394 ... runcommand(server, ['status', '-i', '-u'])
381 ... print('')
395 ... print('')
382 *** runcommand commit -Am.
396 *** runcommand commit -Am.
383 adding .hgignore
397 adding .hgignore
384 *** runcommand status -i -u
398 *** runcommand status -i -u
385 I ignored-file
399 I ignored-file
386
400
387
401
388 cache of non-public revisions should be invalidated on repository change
402 cache of non-public revisions should be invalidated on repository change
389 (issue4855):
403 (issue4855):
390
404
391 >>> import os
405 >>> import os
392 >>> from hgclient import check, readchannel, runcommand
406 >>> from hgclient import check, readchannel, runcommand
393 >>> @check
407 >>> @check
394 ... def phasesetscacheaftercommit(server):
408 ... def phasesetscacheaftercommit(server):
395 ... readchannel(server)
409 ... readchannel(server)
396 ... # load _phasecache._phaserevs and _phasesets
410 ... # load _phasecache._phaserevs and _phasesets
397 ... runcommand(server, ['log', '-qr', 'draft()'])
411 ... runcommand(server, ['log', '-qr', 'draft()'])
398 ... # create draft commits by another process
412 ... # create draft commits by another process
399 ... for i in xrange(5, 7):
413 ... for i in xrange(5, 7):
400 ... f = open('a', 'ab')
414 ... f = open('a', 'ab')
401 ... f.seek(0, os.SEEK_END)
415 ... f.seek(0, os.SEEK_END)
402 ... f.write('a\n')
416 ... f.write('a\n')
403 ... f.close()
417 ... f.close()
404 ... os.system('hg commit -Aqm%d' % i)
418 ... os.system('hg commit -Aqm%d' % i)
405 ... # new commits should be listed as draft revisions
419 ... # new commits should be listed as draft revisions
406 ... runcommand(server, ['log', '-qr', 'draft()'])
420 ... runcommand(server, ['log', '-qr', 'draft()'])
407 ... print('')
421 ... print('')
408 *** runcommand log -qr draft()
422 *** runcommand log -qr draft()
409 4:7966c8e3734d
423 4:7966c8e3734d
410 *** runcommand log -qr draft()
424 *** runcommand log -qr draft()
411 4:7966c8e3734d
425 4:7966c8e3734d
412 5:41f6602d1c4f
426 5:41f6602d1c4f
413 6:10501e202c35
427 6:10501e202c35
414
428
415
429
416 >>> import os
430 >>> import os
417 >>> from hgclient import check, readchannel, runcommand
431 >>> from hgclient import check, readchannel, runcommand
418 >>> @check
432 >>> @check
419 ... def phasesetscacheafterstrip(server):
433 ... def phasesetscacheafterstrip(server):
420 ... readchannel(server)
434 ... readchannel(server)
421 ... # load _phasecache._phaserevs and _phasesets
435 ... # load _phasecache._phaserevs and _phasesets
422 ... runcommand(server, ['log', '-qr', 'draft()'])
436 ... runcommand(server, ['log', '-qr', 'draft()'])
423 ... # strip cached revisions by another process
437 ... # strip cached revisions by another process
424 ... os.system('hg --config extensions.strip= strip -q 5')
438 ... os.system('hg --config extensions.strip= strip -q 5')
425 ... # shouldn't abort by "unknown revision '6'"
439 ... # shouldn't abort by "unknown revision '6'"
426 ... runcommand(server, ['log', '-qr', 'draft()'])
440 ... runcommand(server, ['log', '-qr', 'draft()'])
427 ... print('')
441 ... print('')
428 *** runcommand log -qr draft()
442 *** runcommand log -qr draft()
429 4:7966c8e3734d
443 4:7966c8e3734d
430 5:41f6602d1c4f
444 5:41f6602d1c4f
431 6:10501e202c35
445 6:10501e202c35
432 *** runcommand log -qr draft()
446 *** runcommand log -qr draft()
433 4:7966c8e3734d
447 4:7966c8e3734d
434
448
435
449
436 cache of phase roots should be invalidated on strip (issue3827):
450 cache of phase roots should be invalidated on strip (issue3827):
437
451
438 >>> import os
452 >>> import os
439 >>> from hgclient import check, readchannel, runcommand, sep
453 >>> from hgclient import check, readchannel, runcommand, sep
440 >>> @check
454 >>> @check
441 ... def phasecacheafterstrip(server):
455 ... def phasecacheafterstrip(server):
442 ... readchannel(server)
456 ... readchannel(server)
443 ...
457 ...
444 ... # create new head, 5:731265503d86
458 ... # create new head, 5:731265503d86
445 ... runcommand(server, ['update', '-C', '0'])
459 ... runcommand(server, ['update', '-C', '0'])
446 ... f = open('a', 'ab')
460 ... f = open('a', 'ab')
447 ... f.write('a\n')
461 ... f.write('a\n')
448 ... f.close()
462 ... f.close()
449 ... runcommand(server, ['commit', '-Am.', 'a'])
463 ... runcommand(server, ['commit', '-Am.', 'a'])
450 ... runcommand(server, ['log', '-Gq'])
464 ... runcommand(server, ['log', '-Gq'])
451 ...
465 ...
452 ... # make it public; draft marker moves to 4:7966c8e3734d
466 ... # make it public; draft marker moves to 4:7966c8e3734d
453 ... runcommand(server, ['phase', '-p', '.'])
467 ... runcommand(server, ['phase', '-p', '.'])
454 ... # load _phasecache.phaseroots
468 ... # load _phasecache.phaseroots
455 ... runcommand(server, ['phase', '.'], outfilter=sep)
469 ... runcommand(server, ['phase', '.'], outfilter=sep)
456 ...
470 ...
457 ... # strip 1::4 outside server
471 ... # strip 1::4 outside server
458 ... os.system('hg -q --config extensions.mq= strip 1')
472 ... os.system('hg -q --config extensions.mq= strip 1')
459 ...
473 ...
460 ... # shouldn't raise "7966c8e3734d: no node!"
474 ... # shouldn't raise "7966c8e3734d: no node!"
461 ... runcommand(server, ['branches'])
475 ... runcommand(server, ['branches'])
462 *** runcommand update -C 0
476 *** runcommand update -C 0
463 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
477 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
464 (leaving bookmark bm3)
478 (leaving bookmark bm3)
465 *** runcommand commit -Am. a
479 *** runcommand commit -Am. a
466 created new head
480 created new head
467 *** runcommand log -Gq
481 *** runcommand log -Gq
468 @ 5:731265503d86
482 @ 5:731265503d86
469 |
483 |
470 | o 4:7966c8e3734d
484 | o 4:7966c8e3734d
471 | |
485 | |
472 | o 3:b9b85890c400
486 | o 3:b9b85890c400
473 | |
487 | |
474 | o 2:aef17e88f5f0
488 | o 2:aef17e88f5f0
475 | |
489 | |
476 | o 1:d3a0a68be6de
490 | o 1:d3a0a68be6de
477 |/
491 |/
478 o 0:eff892de26ec
492 o 0:eff892de26ec
479
493
480 *** runcommand phase -p .
494 *** runcommand phase -p .
481 *** runcommand phase .
495 *** runcommand phase .
482 5: public
496 5: public
483 *** runcommand branches
497 *** runcommand branches
484 default 1:731265503d86
498 default 1:731265503d86
485
499
486 in-memory cache must be reloaded if transaction is aborted. otherwise
500 in-memory cache must be reloaded if transaction is aborted. otherwise
487 changelog and manifest would have invalid node:
501 changelog and manifest would have invalid node:
488
502
489 $ echo a >> a
503 $ echo a >> a
490 >>> from hgclient import check, readchannel, runcommand
504 >>> from hgclient import check, readchannel, runcommand
491 >>> @check
505 >>> @check
492 ... def txabort(server):
506 ... def txabort(server):
493 ... readchannel(server)
507 ... readchannel(server)
494 ... runcommand(server, ['commit', '--config', 'hooks.pretxncommit=false',
508 ... runcommand(server, ['commit', '--config', 'hooks.pretxncommit=false',
495 ... '-mfoo'])
509 ... '-mfoo'])
496 ... runcommand(server, ['verify'])
510 ... runcommand(server, ['verify'])
497 *** runcommand commit --config hooks.pretxncommit=false -mfoo
511 *** runcommand commit --config hooks.pretxncommit=false -mfoo
498 transaction abort!
512 transaction abort!
499 rollback completed
513 rollback completed
500 abort: pretxncommit hook exited with status 1
514 abort: pretxncommit hook exited with status 1
501 [255]
515 [255]
502 *** runcommand verify
516 *** runcommand verify
503 checking changesets
517 checking changesets
504 checking manifests
518 checking manifests
505 crosschecking files in changesets and manifests
519 crosschecking files in changesets and manifests
506 checking files
520 checking files
507 1 files, 2 changesets, 2 total revisions
521 1 files, 2 changesets, 2 total revisions
508 $ hg revert --no-backup -aq
522 $ hg revert --no-backup -aq
509
523
510 $ cat >> .hg/hgrc << EOF
524 $ cat >> .hg/hgrc << EOF
511 > [experimental]
525 > [experimental]
512 > evolution.createmarkers=True
526 > evolution.createmarkers=True
513 > EOF
527 > EOF
514
528
515 >>> import os
529 >>> import os
516 >>> from hgclient import check, readchannel, runcommand
530 >>> from hgclient import check, readchannel, runcommand
517 >>> @check
531 >>> @check
518 ... def obsolete(server):
532 ... def obsolete(server):
519 ... readchannel(server)
533 ... readchannel(server)
520 ...
534 ...
521 ... runcommand(server, ['up', 'null'])
535 ... runcommand(server, ['up', 'null'])
522 ... runcommand(server, ['phase', '-df', 'tip'])
536 ... runcommand(server, ['phase', '-df', 'tip'])
523 ... cmd = 'hg debugobsolete `hg log -r tip --template {node}`'
537 ... cmd = 'hg debugobsolete `hg log -r tip --template {node}`'
524 ... if os.name == 'nt':
538 ... if os.name == 'nt':
525 ... cmd = 'sh -c "%s"' % cmd # run in sh, not cmd.exe
539 ... cmd = 'sh -c "%s"' % cmd # run in sh, not cmd.exe
526 ... os.system(cmd)
540 ... os.system(cmd)
527 ... runcommand(server, ['log', '--hidden'])
541 ... runcommand(server, ['log', '--hidden'])
528 ... runcommand(server, ['log'])
542 ... runcommand(server, ['log'])
529 *** runcommand up null
543 *** runcommand up null
530 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
544 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
531 *** runcommand phase -df tip
545 *** runcommand phase -df tip
532 obsoleted 1 changesets
546 obsoleted 1 changesets
533 *** runcommand log --hidden
547 *** runcommand log --hidden
534 changeset: 1:731265503d86
548 changeset: 1:731265503d86
535 tag: tip
549 tag: tip
536 user: test
550 user: test
537 date: Thu Jan 01 00:00:00 1970 +0000
551 date: Thu Jan 01 00:00:00 1970 +0000
538 obsolete: pruned
552 obsolete: pruned
539 summary: .
553 summary: .
540
554
541 changeset: 0:eff892de26ec
555 changeset: 0:eff892de26ec
542 bookmark: bm1
556 bookmark: bm1
543 bookmark: bm2
557 bookmark: bm2
544 bookmark: bm3
558 bookmark: bm3
545 user: test
559 user: test
546 date: Thu Jan 01 00:00:00 1970 +0000
560 date: Thu Jan 01 00:00:00 1970 +0000
547 summary: 1
561 summary: 1
548
562
549 *** runcommand log
563 *** runcommand log
550 changeset: 0:eff892de26ec
564 changeset: 0:eff892de26ec
551 bookmark: bm1
565 bookmark: bm1
552 bookmark: bm2
566 bookmark: bm2
553 bookmark: bm3
567 bookmark: bm3
554 tag: tip
568 tag: tip
555 user: test
569 user: test
556 date: Thu Jan 01 00:00:00 1970 +0000
570 date: Thu Jan 01 00:00:00 1970 +0000
557 summary: 1
571 summary: 1
558
572
559
573
560 $ cat <<EOF >> .hg/hgrc
574 $ cat <<EOF >> .hg/hgrc
561 > [extensions]
575 > [extensions]
562 > mq =
576 > mq =
563 > EOF
577 > EOF
564
578
565 >>> import os
579 >>> import os
566 >>> from hgclient import check, readchannel, runcommand
580 >>> from hgclient import check, readchannel, runcommand
567 >>> @check
581 >>> @check
568 ... def mqoutsidechanges(server):
582 ... def mqoutsidechanges(server):
569 ... readchannel(server)
583 ... readchannel(server)
570 ...
584 ...
571 ... # load repo.mq
585 ... # load repo.mq
572 ... runcommand(server, ['qapplied'])
586 ... runcommand(server, ['qapplied'])
573 ... os.system('hg qnew 0.diff')
587 ... os.system('hg qnew 0.diff')
574 ... # repo.mq should be invalidated
588 ... # repo.mq should be invalidated
575 ... runcommand(server, ['qapplied'])
589 ... runcommand(server, ['qapplied'])
576 ...
590 ...
577 ... runcommand(server, ['qpop', '--all'])
591 ... runcommand(server, ['qpop', '--all'])
578 ... os.system('hg qqueue --create foo')
592 ... os.system('hg qqueue --create foo')
579 ... # repo.mq should be recreated to point to new queue
593 ... # repo.mq should be recreated to point to new queue
580 ... runcommand(server, ['qqueue', '--active'])
594 ... runcommand(server, ['qqueue', '--active'])
581 *** runcommand qapplied
595 *** runcommand qapplied
582 *** runcommand qapplied
596 *** runcommand qapplied
583 0.diff
597 0.diff
584 *** runcommand qpop --all
598 *** runcommand qpop --all
585 popping 0.diff
599 popping 0.diff
586 patch queue now empty
600 patch queue now empty
587 *** runcommand qqueue --active
601 *** runcommand qqueue --active
588 foo
602 foo
589
603
590 $ cat <<EOF > dbgui.py
604 $ cat <<EOF > dbgui.py
591 > import os
605 > import os
592 > import sys
606 > import sys
593 > from mercurial import commands, registrar
607 > from mercurial import commands, registrar
594 > cmdtable = {}
608 > cmdtable = {}
595 > command = registrar.command(cmdtable)
609 > command = registrar.command(cmdtable)
596 > @command(b"debuggetpass", norepo=True)
610 > @command(b"debuggetpass", norepo=True)
597 > def debuggetpass(ui):
611 > def debuggetpass(ui):
598 > ui.write("%s\\n" % ui.getpass())
612 > ui.write("%s\\n" % ui.getpass())
599 > @command(b"debugprompt", norepo=True)
613 > @command(b"debugprompt", norepo=True)
600 > def debugprompt(ui):
614 > def debugprompt(ui):
601 > ui.write("%s\\n" % ui.prompt("prompt:"))
615 > ui.write("%s\\n" % ui.prompt("prompt:"))
602 > @command(b"debugreadstdin", norepo=True)
616 > @command(b"debugreadstdin", norepo=True)
603 > def debugreadstdin(ui):
617 > def debugreadstdin(ui):
604 > ui.write("read: %r\n" % sys.stdin.read(1))
618 > ui.write("read: %r\n" % sys.stdin.read(1))
605 > @command(b"debugwritestdout", norepo=True)
619 > @command(b"debugwritestdout", norepo=True)
606 > def debugwritestdout(ui):
620 > def debugwritestdout(ui):
607 > os.write(1, "low-level stdout fd and\n")
621 > os.write(1, "low-level stdout fd and\n")
608 > sys.stdout.write("stdout should be redirected to /dev/null\n")
622 > sys.stdout.write("stdout should be redirected to /dev/null\n")
609 > sys.stdout.flush()
623 > sys.stdout.flush()
610 > EOF
624 > EOF
611 $ cat <<EOF >> .hg/hgrc
625 $ cat <<EOF >> .hg/hgrc
612 > [extensions]
626 > [extensions]
613 > dbgui = dbgui.py
627 > dbgui = dbgui.py
614 > EOF
628 > EOF
615
629
616 >>> from hgclient import check, readchannel, runcommand, stringio
630 >>> from hgclient import check, readchannel, runcommand, stringio
617 >>> @check
631 >>> @check
618 ... def getpass(server):
632 ... def getpass(server):
619 ... readchannel(server)
633 ... readchannel(server)
620 ... runcommand(server, ['debuggetpass', '--config',
634 ... runcommand(server, ['debuggetpass', '--config',
621 ... 'ui.interactive=True'],
635 ... 'ui.interactive=True'],
622 ... input=stringio('1234\n'))
636 ... input=stringio('1234\n'))
623 ... runcommand(server, ['debuggetpass', '--config',
637 ... runcommand(server, ['debuggetpass', '--config',
624 ... 'ui.interactive=True'],
638 ... 'ui.interactive=True'],
625 ... input=stringio('\n'))
639 ... input=stringio('\n'))
626 ... runcommand(server, ['debuggetpass', '--config',
640 ... runcommand(server, ['debuggetpass', '--config',
627 ... 'ui.interactive=True'],
641 ... 'ui.interactive=True'],
628 ... input=stringio(''))
642 ... input=stringio(''))
629 ... runcommand(server, ['debugprompt', '--config',
643 ... runcommand(server, ['debugprompt', '--config',
630 ... 'ui.interactive=True'],
644 ... 'ui.interactive=True'],
631 ... input=stringio('5678\n'))
645 ... input=stringio('5678\n'))
632 ... runcommand(server, ['debugreadstdin'])
646 ... runcommand(server, ['debugreadstdin'])
633 ... runcommand(server, ['debugwritestdout'])
647 ... runcommand(server, ['debugwritestdout'])
634 *** runcommand debuggetpass --config ui.interactive=True
648 *** runcommand debuggetpass --config ui.interactive=True
635 password: 1234
649 password: 1234
636 *** runcommand debuggetpass --config ui.interactive=True
650 *** runcommand debuggetpass --config ui.interactive=True
637 password:
651 password:
638 *** runcommand debuggetpass --config ui.interactive=True
652 *** runcommand debuggetpass --config ui.interactive=True
639 password: abort: response expected
653 password: abort: response expected
640 [255]
654 [255]
641 *** runcommand debugprompt --config ui.interactive=True
655 *** runcommand debugprompt --config ui.interactive=True
642 prompt: 5678
656 prompt: 5678
643 *** runcommand debugreadstdin
657 *** runcommand debugreadstdin
644 read: ''
658 read: ''
645 *** runcommand debugwritestdout
659 *** runcommand debugwritestdout
646
660
647
661
648 run commandserver in commandserver, which is silly but should work:
662 run commandserver in commandserver, which is silly but should work:
649
663
650 >>> from __future__ import print_function
664 >>> from __future__ import print_function
651 >>> from hgclient import check, readchannel, runcommand, stringio
665 >>> from hgclient import check, readchannel, runcommand, stringio
652 >>> @check
666 >>> @check
653 ... def nested(server):
667 ... def nested(server):
654 ... print('%c, %r' % readchannel(server))
668 ... print('%c, %r' % readchannel(server))
655 ... class nestedserver(object):
669 ... class nestedserver(object):
656 ... stdin = stringio('getencoding\n')
670 ... stdin = stringio('getencoding\n')
657 ... stdout = stringio()
671 ... stdout = stringio()
658 ... runcommand(server, ['serve', '--cmdserver', 'pipe'],
672 ... runcommand(server, ['serve', '--cmdserver', 'pipe'],
659 ... output=nestedserver.stdout, input=nestedserver.stdin)
673 ... output=nestedserver.stdout, input=nestedserver.stdin)
660 ... nestedserver.stdout.seek(0)
674 ... nestedserver.stdout.seek(0)
661 ... print('%c, %r' % readchannel(nestedserver)) # hello
675 ... print('%c, %r' % readchannel(nestedserver)) # hello
662 ... print('%c, %r' % readchannel(nestedserver)) # getencoding
676 ... print('%c, %r' % readchannel(nestedserver)) # getencoding
663 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
677 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
664 *** runcommand serve --cmdserver pipe
678 *** runcommand serve --cmdserver pipe
665 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
679 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
666 r, '*' (glob)
680 r, '*' (glob)
667
681
668
682
669 start without repository:
683 start without repository:
670
684
671 $ cd ..
685 $ cd ..
672
686
673 >>> from __future__ import print_function
687 >>> from __future__ import print_function
674 >>> from hgclient import check, readchannel, runcommand
688 >>> from hgclient import check, readchannel, runcommand
675 >>> @check
689 >>> @check
676 ... def hellomessage(server):
690 ... def hellomessage(server):
677 ... ch, data = readchannel(server)
691 ... ch, data = readchannel(server)
678 ... print('%c, %r' % (ch, data))
692 ... print('%c, %r' % (ch, data))
679 ... # run an arbitrary command to make sure the next thing the server
693 ... # run an arbitrary command to make sure the next thing the server
680 ... # sends isn't part of the hello message
694 ... # sends isn't part of the hello message
681 ... runcommand(server, ['id'])
695 ... runcommand(server, ['id'])
682 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
696 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
683 *** runcommand id
697 *** runcommand id
684 abort: there is no Mercurial repository here (.hg not found)
698 abort: there is no Mercurial repository here (.hg not found)
685 [255]
699 [255]
686
700
687 >>> from hgclient import check, readchannel, runcommand
701 >>> from hgclient import check, readchannel, runcommand
688 >>> @check
702 >>> @check
689 ... def startwithoutrepo(server):
703 ... def startwithoutrepo(server):
690 ... readchannel(server)
704 ... readchannel(server)
691 ... runcommand(server, ['init', 'repo2'])
705 ... runcommand(server, ['init', 'repo2'])
692 ... runcommand(server, ['id', '-R', 'repo2'])
706 ... runcommand(server, ['id', '-R', 'repo2'])
693 *** runcommand init repo2
707 *** runcommand init repo2
694 *** runcommand id -R repo2
708 *** runcommand id -R repo2
695 000000000000 tip
709 000000000000 tip
696
710
697
711
698 don't fall back to cwd if invalid -R path is specified (issue4805):
712 don't fall back to cwd if invalid -R path is specified (issue4805):
699
713
700 $ cd repo
714 $ cd repo
701 $ hg serve --cmdserver pipe -R ../nonexistent
715 $ hg serve --cmdserver pipe -R ../nonexistent
702 abort: repository ../nonexistent not found!
716 abort: repository ../nonexistent not found!
703 [255]
717 [255]
704 $ cd ..
718 $ cd ..
705
719
706
720
707 unix domain socket:
721 unix domain socket:
708
722
709 $ cd repo
723 $ cd repo
710 $ hg update -q
724 $ hg update -q
711
725
712 #if unix-socket unix-permissions
726 #if unix-socket unix-permissions
713
727
714 >>> from __future__ import print_function
728 >>> from __future__ import print_function
715 >>> from hgclient import check, readchannel, runcommand, stringio, unixserver
729 >>> from hgclient import check, readchannel, runcommand, stringio, unixserver
716 >>> server = unixserver('.hg/server.sock', '.hg/server.log')
730 >>> server = unixserver('.hg/server.sock', '.hg/server.log')
717 >>> def hellomessage(conn):
731 >>> def hellomessage(conn):
718 ... ch, data = readchannel(conn)
732 ... ch, data = readchannel(conn)
719 ... print('%c, %r' % (ch, data))
733 ... print('%c, %r' % (ch, data))
720 ... runcommand(conn, ['id'])
734 ... runcommand(conn, ['id'])
721 >>> check(hellomessage, server.connect)
735 >>> check(hellomessage, server.connect)
722 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
736 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
723 *** runcommand id
737 *** runcommand id
724 eff892de26ec tip bm1/bm2/bm3
738 eff892de26ec tip bm1/bm2/bm3
725 >>> def unknowncommand(conn):
739 >>> def unknowncommand(conn):
726 ... readchannel(conn)
740 ... readchannel(conn)
727 ... conn.stdin.write('unknowncommand\n')
741 ... conn.stdin.write('unknowncommand\n')
728 >>> check(unknowncommand, server.connect) # error sent to server.log
742 >>> check(unknowncommand, server.connect) # error sent to server.log
729 >>> def serverinput(conn):
743 >>> def serverinput(conn):
730 ... readchannel(conn)
744 ... readchannel(conn)
731 ... patch = """
745 ... patch = """
732 ... # HG changeset patch
746 ... # HG changeset patch
733 ... # User test
747 ... # User test
734 ... # Date 0 0
748 ... # Date 0 0
735 ... 2
749 ... 2
736 ...
750 ...
737 ... diff -r eff892de26ec -r 1ed24be7e7a0 a
751 ... diff -r eff892de26ec -r 1ed24be7e7a0 a
738 ... --- a/a
752 ... --- a/a
739 ... +++ b/a
753 ... +++ b/a
740 ... @@ -1,1 +1,2 @@
754 ... @@ -1,1 +1,2 @@
741 ... 1
755 ... 1
742 ... +2
756 ... +2
743 ... """
757 ... """
744 ... runcommand(conn, ['import', '-'], input=stringio(patch))
758 ... runcommand(conn, ['import', '-'], input=stringio(patch))
745 ... runcommand(conn, ['log', '-rtip', '-q'])
759 ... runcommand(conn, ['log', '-rtip', '-q'])
746 >>> check(serverinput, server.connect)
760 >>> check(serverinput, server.connect)
747 *** runcommand import -
761 *** runcommand import -
748 applying patch from stdin
762 applying patch from stdin
749 *** runcommand log -rtip -q
763 *** runcommand log -rtip -q
750 2:1ed24be7e7a0
764 2:1ed24be7e7a0
751 >>> server.shutdown()
765 >>> server.shutdown()
752
766
753 $ cat .hg/server.log
767 $ cat .hg/server.log
754 listening at .hg/server.sock
768 listening at .hg/server.sock
755 abort: unknown command unknowncommand
769 abort: unknown command unknowncommand
756 killed!
770 killed!
757 $ rm .hg/server.log
771 $ rm .hg/server.log
758
772
759 if server crashed before hello, traceback will be sent to 'e' channel as
773 if server crashed before hello, traceback will be sent to 'e' channel as
760 last ditch:
774 last ditch:
761
775
762 $ cat <<EOF >> .hg/hgrc
776 $ cat <<EOF >> .hg/hgrc
763 > [cmdserver]
777 > [cmdserver]
764 > log = inexistent/path.log
778 > log = inexistent/path.log
765 > EOF
779 > EOF
766 >>> from __future__ import print_function
780 >>> from __future__ import print_function
767 >>> from hgclient import check, readchannel, unixserver
781 >>> from hgclient import check, readchannel, unixserver
768 >>> server = unixserver('.hg/server.sock', '.hg/server.log')
782 >>> server = unixserver('.hg/server.sock', '.hg/server.log')
769 >>> def earlycrash(conn):
783 >>> def earlycrash(conn):
770 ... while True:
784 ... while True:
771 ... try:
785 ... try:
772 ... ch, data = readchannel(conn)
786 ... ch, data = readchannel(conn)
773 ... if not data.startswith(' '):
787 ... if not data.startswith(' '):
774 ... print('%c, %r' % (ch, data))
788 ... print('%c, %r' % (ch, data))
775 ... except EOFError:
789 ... except EOFError:
776 ... break
790 ... break
777 >>> check(earlycrash, server.connect)
791 >>> check(earlycrash, server.connect)
778 e, 'Traceback (most recent call last):\n'
792 e, 'Traceback (most recent call last):\n'
779 e, "IOError: *" (glob)
793 e, "IOError: *" (glob)
780 >>> server.shutdown()
794 >>> server.shutdown()
781
795
782 $ cat .hg/server.log | grep -v '^ '
796 $ cat .hg/server.log | grep -v '^ '
783 listening at .hg/server.sock
797 listening at .hg/server.sock
784 Traceback (most recent call last):
798 Traceback (most recent call last):
785 IOError: * (glob)
799 IOError: * (glob)
786 killed!
800 killed!
787 #endif
801 #endif
788 #if no-unix-socket
802 #if no-unix-socket
789
803
790 $ hg serve --cmdserver unix -a .hg/server.sock
804 $ hg serve --cmdserver unix -a .hg/server.sock
791 abort: unsupported platform
805 abort: unsupported platform
792 [255]
806 [255]
793
807
794 #endif
808 #endif
795
809
796 $ cd ..
810 $ cd ..
797
811
798 Test that accessing to invalid changelog cache is avoided at
812 Test that accessing to invalid changelog cache is avoided at
799 subsequent operations even if repo object is reused even after failure
813 subsequent operations even if repo object is reused even after failure
800 of transaction (see 0a7610758c42 also)
814 of transaction (see 0a7610758c42 also)
801
815
802 "hg log" after failure of transaction is needed to detect invalid
816 "hg log" after failure of transaction is needed to detect invalid
803 cache in repoview: this can't detect by "hg verify" only.
817 cache in repoview: this can't detect by "hg verify" only.
804
818
805 Combination of "finalization" and "empty-ness of changelog" (2 x 2 =
819 Combination of "finalization" and "empty-ness of changelog" (2 x 2 =
806 4) are tested, because '00changelog.i' are differently changed in each
820 4) are tested, because '00changelog.i' are differently changed in each
807 cases.
821 cases.
808
822
809 $ cat > $TESTTMP/failafterfinalize.py <<EOF
823 $ cat > $TESTTMP/failafterfinalize.py <<EOF
810 > # extension to abort transaction after finalization forcibly
824 > # extension to abort transaction after finalization forcibly
811 > from mercurial import commands, error, extensions, lock as lockmod
825 > from mercurial import commands, error, extensions, lock as lockmod
812 > from mercurial import registrar
826 > from mercurial import registrar
813 > cmdtable = {}
827 > cmdtable = {}
814 > command = registrar.command(cmdtable)
828 > command = registrar.command(cmdtable)
815 > configtable = {}
829 > configtable = {}
816 > configitem = registrar.configitem(configtable)
830 > configitem = registrar.configitem(configtable)
817 > configitem('failafterfinalize', 'fail',
831 > configitem('failafterfinalize', 'fail',
818 > default=None,
832 > default=None,
819 > )
833 > )
820 > def fail(tr):
834 > def fail(tr):
821 > raise error.Abort('fail after finalization')
835 > raise error.Abort('fail after finalization')
822 > def reposetup(ui, repo):
836 > def reposetup(ui, repo):
823 > class failrepo(repo.__class__):
837 > class failrepo(repo.__class__):
824 > def commitctx(self, ctx, error=False):
838 > def commitctx(self, ctx, error=False):
825 > if self.ui.configbool('failafterfinalize', 'fail'):
839 > if self.ui.configbool('failafterfinalize', 'fail'):
826 > # 'sorted()' by ASCII code on category names causes
840 > # 'sorted()' by ASCII code on category names causes
827 > # invoking 'fail' after finalization of changelog
841 > # invoking 'fail' after finalization of changelog
828 > # using "'cl-%i' % id(self)" as category name
842 > # using "'cl-%i' % id(self)" as category name
829 > self.currenttransaction().addfinalize('zzzzzzzz', fail)
843 > self.currenttransaction().addfinalize('zzzzzzzz', fail)
830 > return super(failrepo, self).commitctx(ctx, error)
844 > return super(failrepo, self).commitctx(ctx, error)
831 > repo.__class__ = failrepo
845 > repo.__class__ = failrepo
832 > EOF
846 > EOF
833
847
834 $ hg init repo3
848 $ hg init repo3
835 $ cd repo3
849 $ cd repo3
836
850
837 $ cat <<EOF >> $HGRCPATH
851 $ cat <<EOF >> $HGRCPATH
838 > [ui]
852 > [ui]
839 > logtemplate = {rev} {desc|firstline} ({files})\n
853 > logtemplate = {rev} {desc|firstline} ({files})\n
840 >
854 >
841 > [extensions]
855 > [extensions]
842 > failafterfinalize = $TESTTMP/failafterfinalize.py
856 > failafterfinalize = $TESTTMP/failafterfinalize.py
843 > EOF
857 > EOF
844
858
845 - test failure with "empty changelog"
859 - test failure with "empty changelog"
846
860
847 $ echo foo > foo
861 $ echo foo > foo
848 $ hg add foo
862 $ hg add foo
849
863
850 (failure before finalization)
864 (failure before finalization)
851
865
852 >>> from hgclient import check, readchannel, runcommand
866 >>> from hgclient import check, readchannel, runcommand
853 >>> @check
867 >>> @check
854 ... def abort(server):
868 ... def abort(server):
855 ... readchannel(server)
869 ... readchannel(server)
856 ... runcommand(server, ['commit',
870 ... runcommand(server, ['commit',
857 ... '--config', 'hooks.pretxncommit=false',
871 ... '--config', 'hooks.pretxncommit=false',
858 ... '-mfoo'])
872 ... '-mfoo'])
859 ... runcommand(server, ['log'])
873 ... runcommand(server, ['log'])
860 ... runcommand(server, ['verify', '-q'])
874 ... runcommand(server, ['verify', '-q'])
861 *** runcommand commit --config hooks.pretxncommit=false -mfoo
875 *** runcommand commit --config hooks.pretxncommit=false -mfoo
862 transaction abort!
876 transaction abort!
863 rollback completed
877 rollback completed
864 abort: pretxncommit hook exited with status 1
878 abort: pretxncommit hook exited with status 1
865 [255]
879 [255]
866 *** runcommand log
880 *** runcommand log
867 *** runcommand verify -q
881 *** runcommand verify -q
868
882
869 (failure after finalization)
883 (failure after finalization)
870
884
871 >>> from hgclient import check, readchannel, runcommand
885 >>> from hgclient import check, readchannel, runcommand
872 >>> @check
886 >>> @check
873 ... def abort(server):
887 ... def abort(server):
874 ... readchannel(server)
888 ... readchannel(server)
875 ... runcommand(server, ['commit',
889 ... runcommand(server, ['commit',
876 ... '--config', 'failafterfinalize.fail=true',
890 ... '--config', 'failafterfinalize.fail=true',
877 ... '-mfoo'])
891 ... '-mfoo'])
878 ... runcommand(server, ['log'])
892 ... runcommand(server, ['log'])
879 ... runcommand(server, ['verify', '-q'])
893 ... runcommand(server, ['verify', '-q'])
880 *** runcommand commit --config failafterfinalize.fail=true -mfoo
894 *** runcommand commit --config failafterfinalize.fail=true -mfoo
881 transaction abort!
895 transaction abort!
882 rollback completed
896 rollback completed
883 abort: fail after finalization
897 abort: fail after finalization
884 [255]
898 [255]
885 *** runcommand log
899 *** runcommand log
886 *** runcommand verify -q
900 *** runcommand verify -q
887
901
888 - test failure with "not-empty changelog"
902 - test failure with "not-empty changelog"
889
903
890 $ echo bar > bar
904 $ echo bar > bar
891 $ hg add bar
905 $ hg add bar
892 $ hg commit -mbar bar
906 $ hg commit -mbar bar
893
907
894 (failure before finalization)
908 (failure before finalization)
895
909
896 >>> from hgclient import check, readchannel, runcommand
910 >>> from hgclient import check, readchannel, runcommand
897 >>> @check
911 >>> @check
898 ... def abort(server):
912 ... def abort(server):
899 ... readchannel(server)
913 ... readchannel(server)
900 ... runcommand(server, ['commit',
914 ... runcommand(server, ['commit',
901 ... '--config', 'hooks.pretxncommit=false',
915 ... '--config', 'hooks.pretxncommit=false',
902 ... '-mfoo', 'foo'])
916 ... '-mfoo', 'foo'])
903 ... runcommand(server, ['log'])
917 ... runcommand(server, ['log'])
904 ... runcommand(server, ['verify', '-q'])
918 ... runcommand(server, ['verify', '-q'])
905 *** runcommand commit --config hooks.pretxncommit=false -mfoo foo
919 *** runcommand commit --config hooks.pretxncommit=false -mfoo foo
906 transaction abort!
920 transaction abort!
907 rollback completed
921 rollback completed
908 abort: pretxncommit hook exited with status 1
922 abort: pretxncommit hook exited with status 1
909 [255]
923 [255]
910 *** runcommand log
924 *** runcommand log
911 0 bar (bar)
925 0 bar (bar)
912 *** runcommand verify -q
926 *** runcommand verify -q
913
927
914 (failure after finalization)
928 (failure after finalization)
915
929
916 >>> from hgclient import check, readchannel, runcommand
930 >>> from hgclient import check, readchannel, runcommand
917 >>> @check
931 >>> @check
918 ... def abort(server):
932 ... def abort(server):
919 ... readchannel(server)
933 ... readchannel(server)
920 ... runcommand(server, ['commit',
934 ... runcommand(server, ['commit',
921 ... '--config', 'failafterfinalize.fail=true',
935 ... '--config', 'failafterfinalize.fail=true',
922 ... '-mfoo', 'foo'])
936 ... '-mfoo', 'foo'])
923 ... runcommand(server, ['log'])
937 ... runcommand(server, ['log'])
924 ... runcommand(server, ['verify', '-q'])
938 ... runcommand(server, ['verify', '-q'])
925 *** runcommand commit --config failafterfinalize.fail=true -mfoo foo
939 *** runcommand commit --config failafterfinalize.fail=true -mfoo foo
926 transaction abort!
940 transaction abort!
927 rollback completed
941 rollback completed
928 abort: fail after finalization
942 abort: fail after finalization
929 [255]
943 [255]
930 *** runcommand log
944 *** runcommand log
931 0 bar (bar)
945 0 bar (bar)
932 *** runcommand verify -q
946 *** runcommand verify -q
933
947
934 $ cd ..
948 $ cd ..
935
949
936 Test symlink traversal over cached audited paths:
950 Test symlink traversal over cached audited paths:
937 -------------------------------------------------
951 -------------------------------------------------
938
952
939 #if symlink
953 #if symlink
940
954
941 set up symlink hell
955 set up symlink hell
942
956
943 $ mkdir merge-symlink-out
957 $ mkdir merge-symlink-out
944 $ hg init merge-symlink
958 $ hg init merge-symlink
945 $ cd merge-symlink
959 $ cd merge-symlink
946 $ touch base
960 $ touch base
947 $ hg commit -qAm base
961 $ hg commit -qAm base
948 $ ln -s ../merge-symlink-out a
962 $ ln -s ../merge-symlink-out a
949 $ hg commit -qAm 'symlink a -> ../merge-symlink-out'
963 $ hg commit -qAm 'symlink a -> ../merge-symlink-out'
950 $ hg up -q 0
964 $ hg up -q 0
951 $ mkdir a
965 $ mkdir a
952 $ touch a/poisoned
966 $ touch a/poisoned
953 $ hg commit -qAm 'file a/poisoned'
967 $ hg commit -qAm 'file a/poisoned'
954 $ hg log -G -T '{rev}: {desc}\n'
968 $ hg log -G -T '{rev}: {desc}\n'
955 @ 2: file a/poisoned
969 @ 2: file a/poisoned
956 |
970 |
957 | o 1: symlink a -> ../merge-symlink-out
971 | o 1: symlink a -> ../merge-symlink-out
958 |/
972 |/
959 o 0: base
973 o 0: base
960
974
961
975
962 try trivial merge after update: cache of audited paths should be discarded,
976 try trivial merge after update: cache of audited paths should be discarded,
963 and the merge should fail (issue5628)
977 and the merge should fail (issue5628)
964
978
965 $ hg up -q null
979 $ hg up -q null
966 >>> from hgclient import check, readchannel, runcommand
980 >>> from hgclient import check, readchannel, runcommand
967 >>> @check
981 >>> @check
968 ... def merge(server):
982 ... def merge(server):
969 ... readchannel(server)
983 ... readchannel(server)
970 ... # audit a/poisoned as a good path
984 ... # audit a/poisoned as a good path
971 ... runcommand(server, ['up', '-qC', '2'])
985 ... runcommand(server, ['up', '-qC', '2'])
972 ... runcommand(server, ['up', '-qC', '1'])
986 ... runcommand(server, ['up', '-qC', '1'])
973 ... # here a is a symlink, so a/poisoned is bad
987 ... # here a is a symlink, so a/poisoned is bad
974 ... runcommand(server, ['merge', '2'])
988 ... runcommand(server, ['merge', '2'])
975 *** runcommand up -qC 2
989 *** runcommand up -qC 2
976 *** runcommand up -qC 1
990 *** runcommand up -qC 1
977 *** runcommand merge 2
991 *** runcommand merge 2
978 abort: path 'a/poisoned' traverses symbolic link 'a'
992 abort: path 'a/poisoned' traverses symbolic link 'a'
979 [255]
993 [255]
980 $ ls ../merge-symlink-out
994 $ ls ../merge-symlink-out
981
995
982 cache of repo.auditor should be discarded, so matcher would never traverse
996 cache of repo.auditor should be discarded, so matcher would never traverse
983 symlinks:
997 symlinks:
984
998
985 $ hg up -qC 0
999 $ hg up -qC 0
986 $ touch ../merge-symlink-out/poisoned
1000 $ touch ../merge-symlink-out/poisoned
987 >>> from hgclient import check, readchannel, runcommand
1001 >>> from hgclient import check, readchannel, runcommand
988 >>> @check
1002 >>> @check
989 ... def files(server):
1003 ... def files(server):
990 ... readchannel(server)
1004 ... readchannel(server)
991 ... runcommand(server, ['up', '-qC', '2'])
1005 ... runcommand(server, ['up', '-qC', '2'])
992 ... # audit a/poisoned as a good path
1006 ... # audit a/poisoned as a good path
993 ... runcommand(server, ['files', 'a/poisoned'])
1007 ... runcommand(server, ['files', 'a/poisoned'])
994 ... runcommand(server, ['up', '-qC', '0'])
1008 ... runcommand(server, ['up', '-qC', '0'])
995 ... runcommand(server, ['up', '-qC', '1'])
1009 ... runcommand(server, ['up', '-qC', '1'])
996 ... # here 'a' is a symlink, so a/poisoned should be warned
1010 ... # here 'a' is a symlink, so a/poisoned should be warned
997 ... runcommand(server, ['files', 'a/poisoned'])
1011 ... runcommand(server, ['files', 'a/poisoned'])
998 *** runcommand up -qC 2
1012 *** runcommand up -qC 2
999 *** runcommand files a/poisoned
1013 *** runcommand files a/poisoned
1000 a/poisoned
1014 a/poisoned
1001 *** runcommand up -qC 0
1015 *** runcommand up -qC 0
1002 *** runcommand up -qC 1
1016 *** runcommand up -qC 1
1003 *** runcommand files a/poisoned
1017 *** runcommand files a/poisoned
1004 abort: path 'a/poisoned' traverses symbolic link 'a'
1018 abort: path 'a/poisoned' traverses symbolic link 'a'
1005 [255]
1019 [255]
1006
1020
1007 $ cd ..
1021 $ cd ..
1008
1022
1009 #endif
1023 #endif
@@ -1,163 +1,208
1 test command parsing and dispatch
1 test command parsing and dispatch
2
2
3 $ hg init a
3 $ hg init a
4 $ cd a
4 $ cd a
5
5
6 Redundant options used to crash (issue436):
6 Redundant options used to crash (issue436):
7 $ hg -v log -v
7 $ hg -v log -v
8 $ hg -v log -v x
8 $ hg -v log -v x
9
9
10 $ echo a > a
10 $ echo a > a
11 $ hg ci -Ama
11 $ hg ci -Ama
12 adding a
12 adding a
13
13
14 Missing arg:
14 Missing arg:
15
15
16 $ hg cat
16 $ hg cat
17 hg cat: invalid arguments
17 hg cat: invalid arguments
18 hg cat [OPTION]... FILE...
18 hg cat [OPTION]... FILE...
19
19
20 output the current or given revision of files
20 output the current or given revision of files
21
21
22 options ([+] can be repeated):
22 options ([+] can be repeated):
23
23
24 -o --output FORMAT print output to file with formatted name
24 -o --output FORMAT print output to file with formatted name
25 -r --rev REV print the given revision
25 -r --rev REV print the given revision
26 --decode apply any matching decode filter
26 --decode apply any matching decode filter
27 -I --include PATTERN [+] include names matching the given patterns
27 -I --include PATTERN [+] include names matching the given patterns
28 -X --exclude PATTERN [+] exclude names matching the given patterns
28 -X --exclude PATTERN [+] exclude names matching the given patterns
29
29
30 (use 'hg cat -h' to show more help)
30 (use 'hg cat -h' to show more help)
31 [255]
31 [255]
32
32
33 Missing parameter for early option:
33 Missing parameter for early option:
34
34
35 $ hg log -R 2>&1 | grep 'hg log'
35 $ hg log -R 2>&1 | grep 'hg log'
36 hg log: option -R requires argument
36 hg log: option -R requires argument
37 hg log [OPTION]... [FILE]
37 hg log [OPTION]... [FILE]
38 (use 'hg log -h' to show more help)
38 (use 'hg log -h' to show more help)
39
39
40 $ hg log -R -- 2>&1 | grep 'hg log'
40 $ hg log -R -- 2>&1 | grep 'hg log'
41 hg log: option -R requires argument
41 hg log: option -R requires argument
42 hg log [OPTION]... [FILE]
42 hg log [OPTION]... [FILE]
43 (use 'hg log -h' to show more help)
43 (use 'hg log -h' to show more help)
44
44
45 Parsing of early options should stop at "--":
45 Parsing of early options should stop at "--":
46
46
47 $ hg cat -- --config=hooks.pre-cat=false
47 $ hg cat -- --config=hooks.pre-cat=false
48 --config=hooks.pre-cat=false: no such file in rev cb9a9f314b8b
48 --config=hooks.pre-cat=false: no such file in rev cb9a9f314b8b
49 [1]
49 [1]
50 $ hg cat -- --debugger
50 $ hg cat -- --debugger
51 --debugger: no such file in rev cb9a9f314b8b
51 --debugger: no such file in rev cb9a9f314b8b
52 [1]
52 [1]
53
53
54 Unparsable form of early options:
54 Unparsable form of early options:
55
55
56 $ hg cat --debugg
56 $ hg cat --debugg
57 abort: option --debugger may not be abbreviated!
57 abort: option --debugger may not be abbreviated!
58 [255]
58 [255]
59
59
60 Parsing failure of early options should be detected before executing the
60 Parsing failure of early options should be detected before executing the
61 command:
61 command:
62
62
63 $ hg log -b '--config=hooks.pre-log=false' default
63 $ hg log -b '--config=hooks.pre-log=false' default
64 abort: option --config may not be abbreviated!
64 abort: option --config may not be abbreviated!
65 [255]
65 [255]
66 $ hg log -b -R. default
66 $ hg log -b -R. default
67 abort: option -R has to be separated from other options (e.g. not -qR) and --repository may only be abbreviated as --repo!
67 abort: option -R has to be separated from other options (e.g. not -qR) and --repository may only be abbreviated as --repo!
68 [255]
68 [255]
69 $ hg log --cwd .. -b --cwd=. default
69 $ hg log --cwd .. -b --cwd=. default
70 abort: option --cwd may not be abbreviated!
70 abort: option --cwd may not be abbreviated!
71 [255]
71 [255]
72
72
73 However, we can't prevent it from loading extensions and configs:
73 However, we can't prevent it from loading extensions and configs:
74
74
75 $ cat <<EOF > bad.py
75 $ cat <<EOF > bad.py
76 > raise Exception('bad')
76 > raise Exception('bad')
77 > EOF
77 > EOF
78 $ hg log -b '--config=extensions.bad=bad.py' default
78 $ hg log -b '--config=extensions.bad=bad.py' default
79 *** failed to import extension bad from bad.py: bad
79 *** failed to import extension bad from bad.py: bad
80 abort: option --config may not be abbreviated!
80 abort: option --config may not be abbreviated!
81 [255]
81 [255]
82
82
83 $ mkdir -p badrepo/.hg
83 $ mkdir -p badrepo/.hg
84 $ echo 'invalid-syntax' > badrepo/.hg/hgrc
84 $ echo 'invalid-syntax' > badrepo/.hg/hgrc
85 $ hg log -b -Rbadrepo default
85 $ hg log -b -Rbadrepo default
86 hg: parse error at badrepo/.hg/hgrc:1: invalid-syntax
86 hg: parse error at badrepo/.hg/hgrc:1: invalid-syntax
87 [255]
87 [255]
88
88
89 $ hg log -b --cwd=inexistent default
89 $ hg log -b --cwd=inexistent default
90 abort: No such file or directory: 'inexistent'
90 abort: No such file or directory: 'inexistent'
91 [255]
91 [255]
92
92
93 $ hg log -b '--config=ui.traceback=yes' 2>&1 | grep '^Traceback'
93 $ hg log -b '--config=ui.traceback=yes' 2>&1 | grep '^Traceback'
94 Traceback (most recent call last):
94 Traceback (most recent call last):
95 $ hg log -b '--config=profiling.enabled=yes' 2>&1 | grep -i sample
95 $ hg log -b '--config=profiling.enabled=yes' 2>&1 | grep -i sample
96 Sample count: .*|No samples recorded\. (re)
96 Sample count: .*|No samples recorded\. (re)
97
97
98 Early options can't be specified in [aliases] and [defaults] because they are
98 Early options can't be specified in [aliases] and [defaults] because they are
99 applied before the command name is resolved:
99 applied before the command name is resolved:
100
100
101 $ hg log -b '--config=alias.log=log --config=hooks.pre-log=false'
101 $ hg log -b '--config=alias.log=log --config=hooks.pre-log=false'
102 hg log: option -b not recognized
102 hg log: option -b not recognized
103 error in definition for alias 'log': --config may only be given on the command
103 error in definition for alias 'log': --config may only be given on the command
104 line
104 line
105 [255]
105 [255]
106
106
107 $ hg log -b '--config=defaults.log=--config=hooks.pre-log=false'
107 $ hg log -b '--config=defaults.log=--config=hooks.pre-log=false'
108 abort: option --config may not be abbreviated!
108 abort: option --config may not be abbreviated!
109 [255]
109 [255]
110
110
111 Shell aliases bypass any command parsing rules but for the early one:
111 Shell aliases bypass any command parsing rules but for the early one:
112
112
113 $ hg log -b '--config=alias.log=!echo howdy'
113 $ hg log -b '--config=alias.log=!echo howdy'
114 howdy
114 howdy
115
115
116 Early options must come first if HGPLAIN=+strictflags is specified:
117 (BUG: chg cherry-picks early options to pass them as a server command)
118
119 #if no-chg
120 $ HGPLAIN=+strictflags hg log -b --config='hooks.pre-log=false' default
121 abort: unknown revision '--config=hooks.pre-log=false'!
122 [255]
123 $ HGPLAIN=+strictflags hg log -b -R. default
124 abort: unknown revision '-R.'!
125 [255]
126 $ HGPLAIN=+strictflags hg log -b --cwd=. default
127 abort: unknown revision '--cwd=.'!
128 [255]
129 #endif
130 $ HGPLAIN=+strictflags hg log -b --debugger default
131 abort: unknown revision '--debugger'!
132 [255]
133 $ HGPLAIN=+strictflags hg log -b --config='alias.log=!echo pwned' default
134 abort: unknown revision '--config=alias.log=!echo pwned'!
135 [255]
136
137 $ HGPLAIN=+strictflags hg log --config='hooks.pre-log=false' -b default
138 abort: option --config may not be abbreviated!
139 [255]
140 $ HGPLAIN=+strictflags hg log -q --cwd=.. -b default
141 abort: option --cwd may not be abbreviated!
142 [255]
143 $ HGPLAIN=+strictflags hg log -q -R . -b default
144 abort: option -R has to be separated from other options (e.g. not -qR) and --repository may only be abbreviated as --repo!
145 [255]
146
147 $ HGPLAIN=+strictflags hg --config='hooks.pre-log=false' log -b default
148 abort: pre-log hook exited with status 1
149 [255]
150 $ HGPLAIN=+strictflags hg --cwd .. -q -Ra log -b default
151 0:cb9a9f314b8b
152
153 For compatibility reasons, HGPLAIN=+strictflags is not enabled by plain HGPLAIN:
154
155 $ HGPLAIN= hg log --config='hooks.pre-log=false' -b default
156 abort: pre-log hook exited with status 1
157 [255]
158 $ HGPLAINEXCEPT= hg log --cwd .. -q -Ra -b default
159 0:cb9a9f314b8b
160
116 [defaults]
161 [defaults]
117
162
118 $ hg cat a
163 $ hg cat a
119 a
164 a
120 $ cat >> $HGRCPATH <<EOF
165 $ cat >> $HGRCPATH <<EOF
121 > [defaults]
166 > [defaults]
122 > cat = -r null
167 > cat = -r null
123 > EOF
168 > EOF
124 $ hg cat a
169 $ hg cat a
125 a: no such file in rev 000000000000
170 a: no such file in rev 000000000000
126 [1]
171 [1]
127
172
128 $ cd "$TESTTMP"
173 $ cd "$TESTTMP"
129
174
130 OSError "No such file or directory" / "The system cannot find the path
175 OSError "No such file or directory" / "The system cannot find the path
131 specified" should include filename even when it is empty
176 specified" should include filename even when it is empty
132
177
133 $ hg -R a archive ''
178 $ hg -R a archive ''
134 abort: *: '' (glob)
179 abort: *: '' (glob)
135 [255]
180 [255]
136
181
137 #if no-outer-repo
182 #if no-outer-repo
138
183
139 No repo:
184 No repo:
140
185
141 $ hg cat
186 $ hg cat
142 abort: no repository found in '$TESTTMP' (.hg not found)!
187 abort: no repository found in '$TESTTMP' (.hg not found)!
143 [255]
188 [255]
144
189
145 #endif
190 #endif
146
191
147 #if rmcwd
192 #if rmcwd
148
193
149 Current directory removed:
194 Current directory removed:
150
195
151 $ mkdir $TESTTMP/repo1
196 $ mkdir $TESTTMP/repo1
152 $ cd $TESTTMP/repo1
197 $ cd $TESTTMP/repo1
153 $ rm -rf $TESTTMP/repo1
198 $ rm -rf $TESTTMP/repo1
154
199
155 The output could be one of the following and something else:
200 The output could be one of the following and something else:
156 chg: abort: failed to getcwd (errno = *) (glob)
201 chg: abort: failed to getcwd (errno = *) (glob)
157 abort: error getting current working directory: * (glob)
202 abort: error getting current working directory: * (glob)
158 sh: 0: getcwd() failed: No such file or directory
203 sh: 0: getcwd() failed: No such file or directory
159 Since the exact behavior depends on the shell, only check it returns non-zero.
204 Since the exact behavior depends on the shell, only check it returns non-zero.
160 $ HGDEMANDIMPORT=disable hg version -q 2>/dev/null || false
205 $ HGDEMANDIMPORT=disable hg version -q 2>/dev/null || false
161 [1]
206 [1]
162
207
163 #endif
208 #endif
General Comments 0
You need to be logged in to leave comments. Login now