##// END OF EJS Templates
chg: refactor ui.system() to be partly overridden...
Yuya Nishihara -
r31107:fbce78c5 default
parent child Browse files
Show More
@@ -1,591 +1,590 b''
1 # chgserver.py - command server extension for cHg
1 # chgserver.py - command server extension for cHg
2 #
2 #
3 # Copyright 2011 Yuya Nishihara <yuya@tcha.org>
3 # Copyright 2011 Yuya Nishihara <yuya@tcha.org>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 """command server extension for cHg
8 """command server extension for cHg
9
9
10 'S' channel (read/write)
10 'S' channel (read/write)
11 propagate ui.system() request to client
11 propagate ui.system() request to client
12
12
13 'attachio' command
13 'attachio' command
14 attach client's stdio passed by sendmsg()
14 attach client's stdio passed by sendmsg()
15
15
16 'chdir' command
16 'chdir' command
17 change current directory
17 change current directory
18
18
19 'setenv' command
19 'setenv' command
20 replace os.environ completely
20 replace os.environ completely
21
21
22 'setumask' command
22 'setumask' command
23 set umask
23 set umask
24
24
25 'validate' command
25 'validate' command
26 reload the config and check if the server is up to date
26 reload the config and check if the server is up to date
27
27
28 Config
28 Config
29 ------
29 ------
30
30
31 ::
31 ::
32
32
33 [chgserver]
33 [chgserver]
34 # how long (in seconds) should an idle chg server exit
34 # how long (in seconds) should an idle chg server exit
35 idletimeout = 3600
35 idletimeout = 3600
36
36
37 # whether to skip config or env change checks
37 # whether to skip config or env change checks
38 skiphash = False
38 skiphash = False
39 """
39 """
40
40
41 from __future__ import absolute_import
41 from __future__ import absolute_import
42
42
43 import errno
43 import errno
44 import hashlib
44 import hashlib
45 import inspect
45 import inspect
46 import os
46 import os
47 import re
47 import re
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 osutil,
58 osutil,
59 pycompat,
59 pycompat,
60 util,
60 util,
61 )
61 )
62
62
63 _log = commandserver.log
63 _log = commandserver.log
64
64
65 def _hashlist(items):
65 def _hashlist(items):
66 """return sha1 hexdigest for a list"""
66 """return sha1 hexdigest for a list"""
67 return hashlib.sha1(str(items)).hexdigest()
67 return hashlib.sha1(str(items)).hexdigest()
68
68
69 # sensitive config sections affecting confighash
69 # sensitive config sections affecting confighash
70 _configsections = [
70 _configsections = [
71 'alias', # affects global state commands.table
71 'alias', # affects global state commands.table
72 'extdiff', # uisetup will register new commands
72 'extdiff', # uisetup will register new commands
73 'extensions',
73 'extensions',
74 ]
74 ]
75
75
76 # sensitive environment variables affecting confighash
76 # sensitive environment variables affecting confighash
77 _envre = re.compile(r'''\A(?:
77 _envre = re.compile(r'''\A(?:
78 CHGHG
78 CHGHG
79 |HG(?:[A-Z].*)?
79 |HG(?:[A-Z].*)?
80 |LANG(?:UAGE)?
80 |LANG(?:UAGE)?
81 |LC_.*
81 |LC_.*
82 |LD_.*
82 |LD_.*
83 |PATH
83 |PATH
84 |PYTHON.*
84 |PYTHON.*
85 |TERM(?:INFO)?
85 |TERM(?:INFO)?
86 |TZ
86 |TZ
87 )\Z''', re.X)
87 )\Z''', re.X)
88
88
89 def _confighash(ui):
89 def _confighash(ui):
90 """return a quick hash for detecting config/env changes
90 """return a quick hash for detecting config/env changes
91
91
92 confighash is the hash of sensitive config items and environment variables.
92 confighash is the hash of sensitive config items and environment variables.
93
93
94 for chgserver, it is designed that once confighash changes, the server is
94 for chgserver, it is designed that once confighash changes, the server is
95 not qualified to serve its client and should redirect the client to a new
95 not qualified to serve its client and should redirect the client to a new
96 server. different from mtimehash, confighash change will not mark the
96 server. different from mtimehash, confighash change will not mark the
97 server outdated and exit since the user can have different configs at the
97 server outdated and exit since the user can have different configs at the
98 same time.
98 same time.
99 """
99 """
100 sectionitems = []
100 sectionitems = []
101 for section in _configsections:
101 for section in _configsections:
102 sectionitems.append(ui.configitems(section))
102 sectionitems.append(ui.configitems(section))
103 sectionhash = _hashlist(sectionitems)
103 sectionhash = _hashlist(sectionitems)
104 envitems = [(k, v) for k, v in encoding.environ.iteritems()
104 envitems = [(k, v) for k, v in encoding.environ.iteritems()
105 if _envre.match(k)]
105 if _envre.match(k)]
106 envhash = _hashlist(sorted(envitems))
106 envhash = _hashlist(sorted(envitems))
107 return sectionhash[:6] + envhash[:6]
107 return sectionhash[:6] + envhash[:6]
108
108
109 def _getmtimepaths(ui):
109 def _getmtimepaths(ui):
110 """get a list of paths that should be checked to detect change
110 """get a list of paths that should be checked to detect change
111
111
112 The list will include:
112 The list will include:
113 - extensions (will not cover all files for complex extensions)
113 - extensions (will not cover all files for complex extensions)
114 - mercurial/__version__.py
114 - mercurial/__version__.py
115 - python binary
115 - python binary
116 """
116 """
117 modules = [m for n, m in extensions.extensions(ui)]
117 modules = [m for n, m in extensions.extensions(ui)]
118 try:
118 try:
119 from . import __version__
119 from . import __version__
120 modules.append(__version__)
120 modules.append(__version__)
121 except ImportError:
121 except ImportError:
122 pass
122 pass
123 files = [pycompat.sysexecutable]
123 files = [pycompat.sysexecutable]
124 for m in modules:
124 for m in modules:
125 try:
125 try:
126 files.append(inspect.getabsfile(m))
126 files.append(inspect.getabsfile(m))
127 except TypeError:
127 except TypeError:
128 pass
128 pass
129 return sorted(set(files))
129 return sorted(set(files))
130
130
131 def _mtimehash(paths):
131 def _mtimehash(paths):
132 """return a quick hash for detecting file changes
132 """return a quick hash for detecting file changes
133
133
134 mtimehash calls stat on given paths and calculate a hash based on size and
134 mtimehash calls stat on given paths and calculate a hash based on size and
135 mtime of each file. mtimehash does not read file content because reading is
135 mtime of each file. mtimehash does not read file content because reading is
136 expensive. therefore it's not 100% reliable for detecting content changes.
136 expensive. therefore it's not 100% reliable for detecting content changes.
137 it's possible to return different hashes for same file contents.
137 it's possible to return different hashes for same file contents.
138 it's also possible to return a same hash for different file contents for
138 it's also possible to return a same hash for different file contents for
139 some carefully crafted situation.
139 some carefully crafted situation.
140
140
141 for chgserver, it is designed that once mtimehash changes, the server is
141 for chgserver, it is designed that once mtimehash changes, the server is
142 considered outdated immediately and should no longer provide service.
142 considered outdated immediately and should no longer provide service.
143
143
144 mtimehash is not included in confighash because we only know the paths of
144 mtimehash is not included in confighash because we only know the paths of
145 extensions after importing them (there is imp.find_module but that faces
145 extensions after importing them (there is imp.find_module but that faces
146 race conditions). We need to calculate confighash without importing.
146 race conditions). We need to calculate confighash without importing.
147 """
147 """
148 def trystat(path):
148 def trystat(path):
149 try:
149 try:
150 st = os.stat(path)
150 st = os.stat(path)
151 return (st.st_mtime, st.st_size)
151 return (st.st_mtime, st.st_size)
152 except OSError:
152 except OSError:
153 # could be ENOENT, EPERM etc. not fatal in any case
153 # could be ENOENT, EPERM etc. not fatal in any case
154 pass
154 pass
155 return _hashlist(map(trystat, paths))[:12]
155 return _hashlist(map(trystat, paths))[:12]
156
156
157 class hashstate(object):
157 class hashstate(object):
158 """a structure storing confighash, mtimehash, paths used for mtimehash"""
158 """a structure storing confighash, mtimehash, paths used for mtimehash"""
159 def __init__(self, confighash, mtimehash, mtimepaths):
159 def __init__(self, confighash, mtimehash, mtimepaths):
160 self.confighash = confighash
160 self.confighash = confighash
161 self.mtimehash = mtimehash
161 self.mtimehash = mtimehash
162 self.mtimepaths = mtimepaths
162 self.mtimepaths = mtimepaths
163
163
164 @staticmethod
164 @staticmethod
165 def fromui(ui, mtimepaths=None):
165 def fromui(ui, mtimepaths=None):
166 if mtimepaths is None:
166 if mtimepaths is None:
167 mtimepaths = _getmtimepaths(ui)
167 mtimepaths = _getmtimepaths(ui)
168 confighash = _confighash(ui)
168 confighash = _confighash(ui)
169 mtimehash = _mtimehash(mtimepaths)
169 mtimehash = _mtimehash(mtimepaths)
170 _log('confighash = %s mtimehash = %s\n' % (confighash, mtimehash))
170 _log('confighash = %s mtimehash = %s\n' % (confighash, mtimehash))
171 return hashstate(confighash, mtimehash, mtimepaths)
171 return hashstate(confighash, mtimehash, mtimepaths)
172
172
173 def _newchgui(srcui, csystem, attachio):
173 def _newchgui(srcui, csystem, attachio):
174 class chgui(srcui.__class__):
174 class chgui(srcui.__class__):
175 def __init__(self, src=None):
175 def __init__(self, src=None):
176 super(chgui, self).__init__(src)
176 super(chgui, self).__init__(src)
177 if src:
177 if src:
178 self._csystem = getattr(src, '_csystem', csystem)
178 self._csystem = getattr(src, '_csystem', csystem)
179 else:
179 else:
180 self._csystem = csystem
180 self._csystem = csystem
181
181
182 def system(self, cmd, environ=None, cwd=None, onerr=None,
182 def _runsystem(self, cmd, environ, cwd, onerr, errprefix, out):
183 errprefix=None):
184 # fallback to the original system method if the output needs to be
183 # fallback to the original system method if the output needs to be
185 # captured (to self._buffers), or the output stream is not stdout
184 # captured (to self._buffers), or the output stream is not stdout
186 # (e.g. stderr, cStringIO), because the chg client is not aware of
185 # (e.g. stderr, cStringIO), because the chg client is not aware of
187 # these situations and will behave differently (write to stdout).
186 # these situations and will behave differently (write to stdout).
188 if (any(s[1] for s in self._bufferstates)
187 if (out is not self.fout
189 or not util.safehasattr(self.fout, 'fileno')
188 or not util.safehasattr(self.fout, 'fileno')
190 or self.fout.fileno() != util.stdout.fileno()):
189 or self.fout.fileno() != util.stdout.fileno()):
191 return super(chgui, self).system(cmd, environ, cwd, onerr,
190 return util.system(cmd, environ=environ, cwd=cwd, onerr=onerr,
192 errprefix)
191 errprefix=errprefix, out=out)
193 self.flush()
192 self.flush()
194 rc = self._csystem(cmd, util.shellenviron(environ), cwd)
193 rc = self._csystem(cmd, util.shellenviron(environ), cwd)
195 if rc and onerr:
194 if rc and onerr:
196 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
195 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
197 util.explainexit(rc)[0])
196 util.explainexit(rc)[0])
198 if errprefix:
197 if errprefix:
199 errmsg = '%s: %s' % (errprefix, errmsg)
198 errmsg = '%s: %s' % (errprefix, errmsg)
200 raise onerr(errmsg)
199 raise onerr(errmsg)
201 return rc
200 return rc
202
201
203 def _runpager(self, cmd):
202 def _runpager(self, cmd):
204 self._csystem(cmd, util.shellenviron(), type='pager',
203 self._csystem(cmd, util.shellenviron(), type='pager',
205 cmdtable={'attachio': attachio})
204 cmdtable={'attachio': attachio})
206
205
207 return chgui(srcui)
206 return chgui(srcui)
208
207
209 def _loadnewui(srcui, args):
208 def _loadnewui(srcui, args):
210 from . import dispatch # avoid cycle
209 from . import dispatch # avoid cycle
211
210
212 newui = srcui.__class__.load()
211 newui = srcui.__class__.load()
213 for a in ['fin', 'fout', 'ferr', 'environ']:
212 for a in ['fin', 'fout', 'ferr', 'environ']:
214 setattr(newui, a, getattr(srcui, a))
213 setattr(newui, a, getattr(srcui, a))
215 if util.safehasattr(srcui, '_csystem'):
214 if util.safehasattr(srcui, '_csystem'):
216 newui._csystem = srcui._csystem
215 newui._csystem = srcui._csystem
217
216
218 # command line args
217 # command line args
219 args = args[:]
218 args = args[:]
220 dispatch._parseconfig(newui, dispatch._earlygetopt(['--config'], args))
219 dispatch._parseconfig(newui, dispatch._earlygetopt(['--config'], args))
221
220
222 # stolen from tortoisehg.util.copydynamicconfig()
221 # stolen from tortoisehg.util.copydynamicconfig()
223 for section, name, value in srcui.walkconfig():
222 for section, name, value in srcui.walkconfig():
224 source = srcui.configsource(section, name)
223 source = srcui.configsource(section, name)
225 if ':' in source or source == '--config':
224 if ':' in source or source == '--config':
226 # path:line or command line
225 # path:line or command line
227 continue
226 continue
228 newui.setconfig(section, name, value, source)
227 newui.setconfig(section, name, value, source)
229
228
230 # load wd and repo config, copied from dispatch.py
229 # load wd and repo config, copied from dispatch.py
231 cwds = dispatch._earlygetopt(['--cwd'], args)
230 cwds = dispatch._earlygetopt(['--cwd'], args)
232 cwd = cwds and os.path.realpath(cwds[-1]) or None
231 cwd = cwds and os.path.realpath(cwds[-1]) or None
233 rpath = dispatch._earlygetopt(["-R", "--repository", "--repo"], args)
232 rpath = dispatch._earlygetopt(["-R", "--repository", "--repo"], args)
234 path, newlui = dispatch._getlocal(newui, rpath, wd=cwd)
233 path, newlui = dispatch._getlocal(newui, rpath, wd=cwd)
235
234
236 return (newui, newlui)
235 return (newui, newlui)
237
236
238 class channeledsystem(object):
237 class channeledsystem(object):
239 """Propagate ui.system() request in the following format:
238 """Propagate ui.system() request in the following format:
240
239
241 payload length (unsigned int),
240 payload length (unsigned int),
242 type, '\0',
241 type, '\0',
243 cmd, '\0',
242 cmd, '\0',
244 cwd, '\0',
243 cwd, '\0',
245 envkey, '=', val, '\0',
244 envkey, '=', val, '\0',
246 ...
245 ...
247 envkey, '=', val
246 envkey, '=', val
248
247
249 if type == 'system', waits for:
248 if type == 'system', waits for:
250
249
251 exitcode length (unsigned int),
250 exitcode length (unsigned int),
252 exitcode (int)
251 exitcode (int)
253
252
254 if type == 'pager', repetitively waits for a command name ending with '\n'
253 if type == 'pager', repetitively waits for a command name ending with '\n'
255 and executes it defined by cmdtable, or exits the loop if the command name
254 and executes it defined by cmdtable, or exits the loop if the command name
256 is empty.
255 is empty.
257 """
256 """
258 def __init__(self, in_, out, channel):
257 def __init__(self, in_, out, channel):
259 self.in_ = in_
258 self.in_ = in_
260 self.out = out
259 self.out = out
261 self.channel = channel
260 self.channel = channel
262
261
263 def __call__(self, cmd, environ, cwd=None, type='system', cmdtable=None):
262 def __call__(self, cmd, environ, cwd=None, type='system', cmdtable=None):
264 args = [type, util.quotecommand(cmd), os.path.abspath(cwd or '.')]
263 args = [type, util.quotecommand(cmd), os.path.abspath(cwd or '.')]
265 args.extend('%s=%s' % (k, v) for k, v in environ.iteritems())
264 args.extend('%s=%s' % (k, v) for k, v in environ.iteritems())
266 data = '\0'.join(args)
265 data = '\0'.join(args)
267 self.out.write(struct.pack('>cI', self.channel, len(data)))
266 self.out.write(struct.pack('>cI', self.channel, len(data)))
268 self.out.write(data)
267 self.out.write(data)
269 self.out.flush()
268 self.out.flush()
270
269
271 if type == 'system':
270 if type == 'system':
272 length = self.in_.read(4)
271 length = self.in_.read(4)
273 length, = struct.unpack('>I', length)
272 length, = struct.unpack('>I', length)
274 if length != 4:
273 if length != 4:
275 raise error.Abort(_('invalid response'))
274 raise error.Abort(_('invalid response'))
276 rc, = struct.unpack('>i', self.in_.read(4))
275 rc, = struct.unpack('>i', self.in_.read(4))
277 return rc
276 return rc
278 elif type == 'pager':
277 elif type == 'pager':
279 while True:
278 while True:
280 cmd = self.in_.readline()[:-1]
279 cmd = self.in_.readline()[:-1]
281 if not cmd:
280 if not cmd:
282 break
281 break
283 if cmdtable and cmd in cmdtable:
282 if cmdtable and cmd in cmdtable:
284 _log('pager subcommand: %s' % cmd)
283 _log('pager subcommand: %s' % cmd)
285 cmdtable[cmd]()
284 cmdtable[cmd]()
286 else:
285 else:
287 raise error.Abort(_('unexpected command: %s') % cmd)
286 raise error.Abort(_('unexpected command: %s') % cmd)
288 else:
287 else:
289 raise error.ProgrammingError('invalid S channel type: %s' % type)
288 raise error.ProgrammingError('invalid S channel type: %s' % type)
290
289
291 _iochannels = [
290 _iochannels = [
292 # server.ch, ui.fp, mode
291 # server.ch, ui.fp, mode
293 ('cin', 'fin', pycompat.sysstr('rb')),
292 ('cin', 'fin', pycompat.sysstr('rb')),
294 ('cout', 'fout', pycompat.sysstr('wb')),
293 ('cout', 'fout', pycompat.sysstr('wb')),
295 ('cerr', 'ferr', pycompat.sysstr('wb')),
294 ('cerr', 'ferr', pycompat.sysstr('wb')),
296 ]
295 ]
297
296
298 class chgcmdserver(commandserver.server):
297 class chgcmdserver(commandserver.server):
299 def __init__(self, ui, repo, fin, fout, sock, hashstate, baseaddress):
298 def __init__(self, ui, repo, fin, fout, sock, hashstate, baseaddress):
300 super(chgcmdserver, self).__init__(
299 super(chgcmdserver, self).__init__(
301 _newchgui(ui, channeledsystem(fin, fout, 'S'), self.attachio),
300 _newchgui(ui, channeledsystem(fin, fout, 'S'), self.attachio),
302 repo, fin, fout)
301 repo, fin, fout)
303 self.clientsock = sock
302 self.clientsock = sock
304 self._oldios = [] # original (self.ch, ui.fp, fd) before "attachio"
303 self._oldios = [] # original (self.ch, ui.fp, fd) before "attachio"
305 self.hashstate = hashstate
304 self.hashstate = hashstate
306 self.baseaddress = baseaddress
305 self.baseaddress = baseaddress
307 if hashstate is not None:
306 if hashstate is not None:
308 self.capabilities = self.capabilities.copy()
307 self.capabilities = self.capabilities.copy()
309 self.capabilities['validate'] = chgcmdserver.validate
308 self.capabilities['validate'] = chgcmdserver.validate
310
309
311 def cleanup(self):
310 def cleanup(self):
312 super(chgcmdserver, self).cleanup()
311 super(chgcmdserver, self).cleanup()
313 # dispatch._runcatch() does not flush outputs if exception is not
312 # dispatch._runcatch() does not flush outputs if exception is not
314 # handled by dispatch._dispatch()
313 # handled by dispatch._dispatch()
315 self.ui.flush()
314 self.ui.flush()
316 self._restoreio()
315 self._restoreio()
317
316
318 def attachio(self):
317 def attachio(self):
319 """Attach to client's stdio passed via unix domain socket; all
318 """Attach to client's stdio passed via unix domain socket; all
320 channels except cresult will no longer be used
319 channels except cresult will no longer be used
321 """
320 """
322 # tell client to sendmsg() with 1-byte payload, which makes it
321 # tell client to sendmsg() with 1-byte payload, which makes it
323 # distinctive from "attachio\n" command consumed by client.read()
322 # distinctive from "attachio\n" command consumed by client.read()
324 self.clientsock.sendall(struct.pack('>cI', 'I', 1))
323 self.clientsock.sendall(struct.pack('>cI', 'I', 1))
325 clientfds = osutil.recvfds(self.clientsock.fileno())
324 clientfds = osutil.recvfds(self.clientsock.fileno())
326 _log('received fds: %r\n' % clientfds)
325 _log('received fds: %r\n' % clientfds)
327
326
328 ui = self.ui
327 ui = self.ui
329 ui.flush()
328 ui.flush()
330 first = self._saveio()
329 first = self._saveio()
331 for fd, (cn, fn, mode) in zip(clientfds, _iochannels):
330 for fd, (cn, fn, mode) in zip(clientfds, _iochannels):
332 assert fd > 0
331 assert fd > 0
333 fp = getattr(ui, fn)
332 fp = getattr(ui, fn)
334 os.dup2(fd, fp.fileno())
333 os.dup2(fd, fp.fileno())
335 os.close(fd)
334 os.close(fd)
336 if not first:
335 if not first:
337 continue
336 continue
338 # reset buffering mode when client is first attached. as we want
337 # reset buffering mode when client is first attached. as we want
339 # to see output immediately on pager, the mode stays unchanged
338 # to see output immediately on pager, the mode stays unchanged
340 # when client re-attached. ferr is unchanged because it should
339 # when client re-attached. ferr is unchanged because it should
341 # be unbuffered no matter if it is a tty or not.
340 # be unbuffered no matter if it is a tty or not.
342 if fn == 'ferr':
341 if fn == 'ferr':
343 newfp = fp
342 newfp = fp
344 else:
343 else:
345 # make it line buffered explicitly because the default is
344 # make it line buffered explicitly because the default is
346 # decided on first write(), where fout could be a pager.
345 # decided on first write(), where fout could be a pager.
347 if fp.isatty():
346 if fp.isatty():
348 bufsize = 1 # line buffered
347 bufsize = 1 # line buffered
349 else:
348 else:
350 bufsize = -1 # system default
349 bufsize = -1 # system default
351 newfp = os.fdopen(fp.fileno(), mode, bufsize)
350 newfp = os.fdopen(fp.fileno(), mode, bufsize)
352 setattr(ui, fn, newfp)
351 setattr(ui, fn, newfp)
353 setattr(self, cn, newfp)
352 setattr(self, cn, newfp)
354
353
355 self.cresult.write(struct.pack('>i', len(clientfds)))
354 self.cresult.write(struct.pack('>i', len(clientfds)))
356
355
357 def _saveio(self):
356 def _saveio(self):
358 if self._oldios:
357 if self._oldios:
359 return False
358 return False
360 ui = self.ui
359 ui = self.ui
361 for cn, fn, _mode in _iochannels:
360 for cn, fn, _mode in _iochannels:
362 ch = getattr(self, cn)
361 ch = getattr(self, cn)
363 fp = getattr(ui, fn)
362 fp = getattr(ui, fn)
364 fd = os.dup(fp.fileno())
363 fd = os.dup(fp.fileno())
365 self._oldios.append((ch, fp, fd))
364 self._oldios.append((ch, fp, fd))
366 return True
365 return True
367
366
368 def _restoreio(self):
367 def _restoreio(self):
369 ui = self.ui
368 ui = self.ui
370 for (ch, fp, fd), (cn, fn, _mode) in zip(self._oldios, _iochannels):
369 for (ch, fp, fd), (cn, fn, _mode) in zip(self._oldios, _iochannels):
371 newfp = getattr(ui, fn)
370 newfp = getattr(ui, fn)
372 # close newfp while it's associated with client; otherwise it
371 # close newfp while it's associated with client; otherwise it
373 # would be closed when newfp is deleted
372 # would be closed when newfp is deleted
374 if newfp is not fp:
373 if newfp is not fp:
375 newfp.close()
374 newfp.close()
376 # restore original fd: fp is open again
375 # restore original fd: fp is open again
377 os.dup2(fd, fp.fileno())
376 os.dup2(fd, fp.fileno())
378 os.close(fd)
377 os.close(fd)
379 setattr(self, cn, ch)
378 setattr(self, cn, ch)
380 setattr(ui, fn, fp)
379 setattr(ui, fn, fp)
381 del self._oldios[:]
380 del self._oldios[:]
382
381
383 def validate(self):
382 def validate(self):
384 """Reload the config and check if the server is up to date
383 """Reload the config and check if the server is up to date
385
384
386 Read a list of '\0' separated arguments.
385 Read a list of '\0' separated arguments.
387 Write a non-empty list of '\0' separated instruction strings or '\0'
386 Write a non-empty list of '\0' separated instruction strings or '\0'
388 if the list is empty.
387 if the list is empty.
389 An instruction string could be either:
388 An instruction string could be either:
390 - "unlink $path", the client should unlink the path to stop the
389 - "unlink $path", the client should unlink the path to stop the
391 outdated server.
390 outdated server.
392 - "redirect $path", the client should attempt to connect to $path
391 - "redirect $path", the client should attempt to connect to $path
393 first. If it does not work, start a new server. It implies
392 first. If it does not work, start a new server. It implies
394 "reconnect".
393 "reconnect".
395 - "exit $n", the client should exit directly with code n.
394 - "exit $n", the client should exit directly with code n.
396 This may happen if we cannot parse the config.
395 This may happen if we cannot parse the config.
397 - "reconnect", the client should close the connection and
396 - "reconnect", the client should close the connection and
398 reconnect.
397 reconnect.
399 If neither "reconnect" nor "redirect" is included in the instruction
398 If neither "reconnect" nor "redirect" is included in the instruction
400 list, the client can continue with this server after completing all
399 list, the client can continue with this server after completing all
401 the instructions.
400 the instructions.
402 """
401 """
403 from . import dispatch # avoid cycle
402 from . import dispatch # avoid cycle
404
403
405 args = self._readlist()
404 args = self._readlist()
406 try:
405 try:
407 self.ui, lui = _loadnewui(self.ui, args)
406 self.ui, lui = _loadnewui(self.ui, args)
408 except error.ParseError as inst:
407 except error.ParseError as inst:
409 dispatch._formatparse(self.ui.warn, inst)
408 dispatch._formatparse(self.ui.warn, inst)
410 self.ui.flush()
409 self.ui.flush()
411 self.cresult.write('exit 255')
410 self.cresult.write('exit 255')
412 return
411 return
413 newhash = hashstate.fromui(lui, self.hashstate.mtimepaths)
412 newhash = hashstate.fromui(lui, self.hashstate.mtimepaths)
414 insts = []
413 insts = []
415 if newhash.mtimehash != self.hashstate.mtimehash:
414 if newhash.mtimehash != self.hashstate.mtimehash:
416 addr = _hashaddress(self.baseaddress, self.hashstate.confighash)
415 addr = _hashaddress(self.baseaddress, self.hashstate.confighash)
417 insts.append('unlink %s' % addr)
416 insts.append('unlink %s' % addr)
418 # mtimehash is empty if one or more extensions fail to load.
417 # mtimehash is empty if one or more extensions fail to load.
419 # to be compatible with hg, still serve the client this time.
418 # to be compatible with hg, still serve the client this time.
420 if self.hashstate.mtimehash:
419 if self.hashstate.mtimehash:
421 insts.append('reconnect')
420 insts.append('reconnect')
422 if newhash.confighash != self.hashstate.confighash:
421 if newhash.confighash != self.hashstate.confighash:
423 addr = _hashaddress(self.baseaddress, newhash.confighash)
422 addr = _hashaddress(self.baseaddress, newhash.confighash)
424 insts.append('redirect %s' % addr)
423 insts.append('redirect %s' % addr)
425 _log('validate: %s\n' % insts)
424 _log('validate: %s\n' % insts)
426 self.cresult.write('\0'.join(insts) or '\0')
425 self.cresult.write('\0'.join(insts) or '\0')
427
426
428 def chdir(self):
427 def chdir(self):
429 """Change current directory
428 """Change current directory
430
429
431 Note that the behavior of --cwd option is bit different from this.
430 Note that the behavior of --cwd option is bit different from this.
432 It does not affect --config parameter.
431 It does not affect --config parameter.
433 """
432 """
434 path = self._readstr()
433 path = self._readstr()
435 if not path:
434 if not path:
436 return
435 return
437 _log('chdir to %r\n' % path)
436 _log('chdir to %r\n' % path)
438 os.chdir(path)
437 os.chdir(path)
439
438
440 def setumask(self):
439 def setumask(self):
441 """Change umask"""
440 """Change umask"""
442 mask = struct.unpack('>I', self._read(4))[0]
441 mask = struct.unpack('>I', self._read(4))[0]
443 _log('setumask %r\n' % mask)
442 _log('setumask %r\n' % mask)
444 os.umask(mask)
443 os.umask(mask)
445
444
446 def runcommand(self):
445 def runcommand(self):
447 return super(chgcmdserver, self).runcommand()
446 return super(chgcmdserver, self).runcommand()
448
447
449 def setenv(self):
448 def setenv(self):
450 """Clear and update os.environ
449 """Clear and update os.environ
451
450
452 Note that not all variables can make an effect on the running process.
451 Note that not all variables can make an effect on the running process.
453 """
452 """
454 l = self._readlist()
453 l = self._readlist()
455 try:
454 try:
456 newenv = dict(s.split('=', 1) for s in l)
455 newenv = dict(s.split('=', 1) for s in l)
457 except ValueError:
456 except ValueError:
458 raise ValueError('unexpected value in setenv request')
457 raise ValueError('unexpected value in setenv request')
459 _log('setenv: %r\n' % sorted(newenv.keys()))
458 _log('setenv: %r\n' % sorted(newenv.keys()))
460 encoding.environ.clear()
459 encoding.environ.clear()
461 encoding.environ.update(newenv)
460 encoding.environ.update(newenv)
462
461
463 capabilities = commandserver.server.capabilities.copy()
462 capabilities = commandserver.server.capabilities.copy()
464 capabilities.update({'attachio': attachio,
463 capabilities.update({'attachio': attachio,
465 'chdir': chdir,
464 'chdir': chdir,
466 'runcommand': runcommand,
465 'runcommand': runcommand,
467 'setenv': setenv,
466 'setenv': setenv,
468 'setumask': setumask})
467 'setumask': setumask})
469
468
470 if util.safehasattr(osutil, 'setprocname'):
469 if util.safehasattr(osutil, 'setprocname'):
471 def setprocname(self):
470 def setprocname(self):
472 """Change process title"""
471 """Change process title"""
473 name = self._readstr()
472 name = self._readstr()
474 _log('setprocname: %r\n' % name)
473 _log('setprocname: %r\n' % name)
475 osutil.setprocname(name)
474 osutil.setprocname(name)
476 capabilities['setprocname'] = setprocname
475 capabilities['setprocname'] = setprocname
477
476
478 def _tempaddress(address):
477 def _tempaddress(address):
479 return '%s.%d.tmp' % (address, os.getpid())
478 return '%s.%d.tmp' % (address, os.getpid())
480
479
481 def _hashaddress(address, hashstr):
480 def _hashaddress(address, hashstr):
482 # if the basename of address contains '.', use only the left part. this
481 # if the basename of address contains '.', use only the left part. this
483 # makes it possible for the client to pass 'server.tmp$PID' and follow by
482 # makes it possible for the client to pass 'server.tmp$PID' and follow by
484 # an atomic rename to avoid locking when spawning new servers.
483 # an atomic rename to avoid locking when spawning new servers.
485 dirname, basename = os.path.split(address)
484 dirname, basename = os.path.split(address)
486 basename = basename.split('.', 1)[0]
485 basename = basename.split('.', 1)[0]
487 return '%s-%s' % (os.path.join(dirname, basename), hashstr)
486 return '%s-%s' % (os.path.join(dirname, basename), hashstr)
488
487
489 class chgunixservicehandler(object):
488 class chgunixservicehandler(object):
490 """Set of operations for chg services"""
489 """Set of operations for chg services"""
491
490
492 pollinterval = 1 # [sec]
491 pollinterval = 1 # [sec]
493
492
494 def __init__(self, ui):
493 def __init__(self, ui):
495 self.ui = ui
494 self.ui = ui
496 self._idletimeout = ui.configint('chgserver', 'idletimeout', 3600)
495 self._idletimeout = ui.configint('chgserver', 'idletimeout', 3600)
497 self._lastactive = time.time()
496 self._lastactive = time.time()
498
497
499 def bindsocket(self, sock, address):
498 def bindsocket(self, sock, address):
500 self._inithashstate(address)
499 self._inithashstate(address)
501 self._checkextensions()
500 self._checkextensions()
502 self._bind(sock)
501 self._bind(sock)
503 self._createsymlink()
502 self._createsymlink()
504
503
505 def _inithashstate(self, address):
504 def _inithashstate(self, address):
506 self._baseaddress = address
505 self._baseaddress = address
507 if self.ui.configbool('chgserver', 'skiphash', False):
506 if self.ui.configbool('chgserver', 'skiphash', False):
508 self._hashstate = None
507 self._hashstate = None
509 self._realaddress = address
508 self._realaddress = address
510 return
509 return
511 self._hashstate = hashstate.fromui(self.ui)
510 self._hashstate = hashstate.fromui(self.ui)
512 self._realaddress = _hashaddress(address, self._hashstate.confighash)
511 self._realaddress = _hashaddress(address, self._hashstate.confighash)
513
512
514 def _checkextensions(self):
513 def _checkextensions(self):
515 if not self._hashstate:
514 if not self._hashstate:
516 return
515 return
517 if extensions.notloaded():
516 if extensions.notloaded():
518 # one or more extensions failed to load. mtimehash becomes
517 # one or more extensions failed to load. mtimehash becomes
519 # meaningless because we do not know the paths of those extensions.
518 # meaningless because we do not know the paths of those extensions.
520 # set mtimehash to an illegal hash value to invalidate the server.
519 # set mtimehash to an illegal hash value to invalidate the server.
521 self._hashstate.mtimehash = ''
520 self._hashstate.mtimehash = ''
522
521
523 def _bind(self, sock):
522 def _bind(self, sock):
524 # use a unique temp address so we can stat the file and do ownership
523 # use a unique temp address so we can stat the file and do ownership
525 # check later
524 # check later
526 tempaddress = _tempaddress(self._realaddress)
525 tempaddress = _tempaddress(self._realaddress)
527 util.bindunixsocket(sock, tempaddress)
526 util.bindunixsocket(sock, tempaddress)
528 self._socketstat = os.stat(tempaddress)
527 self._socketstat = os.stat(tempaddress)
529 # rename will replace the old socket file if exists atomically. the
528 # rename will replace the old socket file if exists atomically. the
530 # old server will detect ownership change and exit.
529 # old server will detect ownership change and exit.
531 util.rename(tempaddress, self._realaddress)
530 util.rename(tempaddress, self._realaddress)
532
531
533 def _createsymlink(self):
532 def _createsymlink(self):
534 if self._baseaddress == self._realaddress:
533 if self._baseaddress == self._realaddress:
535 return
534 return
536 tempaddress = _tempaddress(self._baseaddress)
535 tempaddress = _tempaddress(self._baseaddress)
537 os.symlink(os.path.basename(self._realaddress), tempaddress)
536 os.symlink(os.path.basename(self._realaddress), tempaddress)
538 util.rename(tempaddress, self._baseaddress)
537 util.rename(tempaddress, self._baseaddress)
539
538
540 def _issocketowner(self):
539 def _issocketowner(self):
541 try:
540 try:
542 stat = os.stat(self._realaddress)
541 stat = os.stat(self._realaddress)
543 return (stat.st_ino == self._socketstat.st_ino and
542 return (stat.st_ino == self._socketstat.st_ino and
544 stat.st_mtime == self._socketstat.st_mtime)
543 stat.st_mtime == self._socketstat.st_mtime)
545 except OSError:
544 except OSError:
546 return False
545 return False
547
546
548 def unlinksocket(self, address):
547 def unlinksocket(self, address):
549 if not self._issocketowner():
548 if not self._issocketowner():
550 return
549 return
551 # it is possible to have a race condition here that we may
550 # it is possible to have a race condition here that we may
552 # remove another server's socket file. but that's okay
551 # remove another server's socket file. but that's okay
553 # since that server will detect and exit automatically and
552 # since that server will detect and exit automatically and
554 # the client will start a new server on demand.
553 # the client will start a new server on demand.
555 try:
554 try:
556 os.unlink(self._realaddress)
555 os.unlink(self._realaddress)
557 except OSError as exc:
556 except OSError as exc:
558 if exc.errno != errno.ENOENT:
557 if exc.errno != errno.ENOENT:
559 raise
558 raise
560
559
561 def printbanner(self, address):
560 def printbanner(self, address):
562 # no "listening at" message should be printed to simulate hg behavior
561 # no "listening at" message should be printed to simulate hg behavior
563 pass
562 pass
564
563
565 def shouldexit(self):
564 def shouldexit(self):
566 if not self._issocketowner():
565 if not self._issocketowner():
567 self.ui.debug('%s is not owned, exiting.\n' % self._realaddress)
566 self.ui.debug('%s is not owned, exiting.\n' % self._realaddress)
568 return True
567 return True
569 if time.time() - self._lastactive > self._idletimeout:
568 if time.time() - self._lastactive > self._idletimeout:
570 self.ui.debug('being idle too long. exiting.\n')
569 self.ui.debug('being idle too long. exiting.\n')
571 return True
570 return True
572 return False
571 return False
573
572
574 def newconnection(self):
573 def newconnection(self):
575 self._lastactive = time.time()
574 self._lastactive = time.time()
576
575
577 def createcmdserver(self, repo, conn, fin, fout):
576 def createcmdserver(self, repo, conn, fin, fout):
578 return chgcmdserver(self.ui, repo, fin, fout, conn,
577 return chgcmdserver(self.ui, repo, fin, fout, conn,
579 self._hashstate, self._baseaddress)
578 self._hashstate, self._baseaddress)
580
579
581 def chgunixservice(ui, repo, opts):
580 def chgunixservice(ui, repo, opts):
582 # CHGINTERNALMARK is temporarily set by chg client to detect if chg will
581 # CHGINTERNALMARK is temporarily set by chg client to detect if chg will
583 # start another chg. drop it to avoid possible side effects.
582 # start another chg. drop it to avoid possible side effects.
584 if 'CHGINTERNALMARK' in encoding.environ:
583 if 'CHGINTERNALMARK' in encoding.environ:
585 del encoding.environ['CHGINTERNALMARK']
584 del encoding.environ['CHGINTERNALMARK']
586
585
587 if repo:
586 if repo:
588 # one chgserver can serve multiple repos. drop repo information
587 # one chgserver can serve multiple repos. drop repo information
589 ui.setconfig('bundle', 'mainreporoot', '', 'repo')
588 ui.setconfig('bundle', 'mainreporoot', '', 'repo')
590 h = chgunixservicehandler(ui)
589 h = chgunixservicehandler(ui)
591 return commandserver.unixforkingservice(ui, repo=None, opts=opts, handler=h)
590 return commandserver.unixforkingservice(ui, repo=None, opts=opts, handler=h)
@@ -1,1646 +1,1652 b''
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 atexit
10 import atexit
11 import collections
11 import collections
12 import contextlib
12 import contextlib
13 import errno
13 import errno
14 import getpass
14 import getpass
15 import inspect
15 import inspect
16 import os
16 import os
17 import re
17 import re
18 import signal
18 import signal
19 import socket
19 import socket
20 import subprocess
20 import subprocess
21 import sys
21 import sys
22 import tempfile
22 import tempfile
23 import traceback
23 import traceback
24
24
25 from .i18n import _
25 from .i18n import _
26 from .node import hex
26 from .node import hex
27
27
28 from . import (
28 from . import (
29 color,
29 color,
30 config,
30 config,
31 encoding,
31 encoding,
32 error,
32 error,
33 formatter,
33 formatter,
34 progress,
34 progress,
35 pycompat,
35 pycompat,
36 scmutil,
36 scmutil,
37 util,
37 util,
38 )
38 )
39
39
40 urlreq = util.urlreq
40 urlreq = util.urlreq
41
41
42 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
42 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
43 if pycompat.ispy3:
43 if pycompat.ispy3:
44 _bytes = [bytes([c]) for c in range(256)]
44 _bytes = [bytes([c]) for c in range(256)]
45 _notalnum = [s for s in _bytes if not s.isalnum()]
45 _notalnum = [s for s in _bytes if not s.isalnum()]
46 else:
46 else:
47 _notalnum = [c for c in map(chr, range(256)) if not c.isalnum()]
47 _notalnum = [c for c in map(chr, range(256)) if not c.isalnum()]
48 _keepalnum = ''.join(_notalnum)
48 _keepalnum = ''.join(_notalnum)
49
49
50 samplehgrcs = {
50 samplehgrcs = {
51 'user':
51 'user':
52 """# example user config (see 'hg help config' for more info)
52 """# example user config (see 'hg help config' for more info)
53 [ui]
53 [ui]
54 # name and email, e.g.
54 # name and email, e.g.
55 # username = Jane Doe <jdoe@example.com>
55 # username = Jane Doe <jdoe@example.com>
56 username =
56 username =
57
57
58 [extensions]
58 [extensions]
59 # uncomment these lines to enable some popular extensions
59 # uncomment these lines to enable some popular extensions
60 # (see 'hg help extensions' for more info)
60 # (see 'hg help extensions' for more info)
61 #
61 #
62 # pager =
62 # pager =
63 # color =""",
63 # color =""",
64
64
65 'cloned':
65 'cloned':
66 """# example repository config (see 'hg help config' for more info)
66 """# example repository config (see 'hg help config' for more info)
67 [paths]
67 [paths]
68 default = %s
68 default = %s
69
69
70 # path aliases to other clones of this repo in URLs or filesystem paths
70 # path aliases to other clones of this repo in URLs or filesystem paths
71 # (see 'hg help config.paths' for more info)
71 # (see 'hg help config.paths' for more info)
72 #
72 #
73 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
73 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
74 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
74 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
75 # my-clone = /home/jdoe/jdoes-clone
75 # my-clone = /home/jdoe/jdoes-clone
76
76
77 [ui]
77 [ui]
78 # name and email (local to this repository, optional), e.g.
78 # name and email (local to this repository, optional), e.g.
79 # username = Jane Doe <jdoe@example.com>
79 # username = Jane Doe <jdoe@example.com>
80 """,
80 """,
81
81
82 'local':
82 'local':
83 """# example repository config (see 'hg help config' for more info)
83 """# example repository config (see 'hg help config' for more info)
84 [paths]
84 [paths]
85 # path aliases to other clones of this repo in URLs or filesystem paths
85 # path aliases to other clones of this repo in URLs or filesystem paths
86 # (see 'hg help config.paths' for more info)
86 # (see 'hg help config.paths' for more info)
87 #
87 #
88 # default = http://example.com/hg/example-repo
88 # default = http://example.com/hg/example-repo
89 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
89 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
90 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
90 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
91 # my-clone = /home/jdoe/jdoes-clone
91 # my-clone = /home/jdoe/jdoes-clone
92
92
93 [ui]
93 [ui]
94 # name and email (local to this repository, optional), e.g.
94 # name and email (local to this repository, optional), e.g.
95 # username = Jane Doe <jdoe@example.com>
95 # username = Jane Doe <jdoe@example.com>
96 """,
96 """,
97
97
98 'global':
98 'global':
99 """# example system-wide hg config (see 'hg help config' for more info)
99 """# example system-wide hg config (see 'hg help config' for more info)
100
100
101 [extensions]
101 [extensions]
102 # uncomment these lines to enable some popular extensions
102 # uncomment these lines to enable some popular extensions
103 # (see 'hg help extensions' for more info)
103 # (see 'hg help extensions' for more info)
104 #
104 #
105 # blackbox =
105 # blackbox =
106 # color =
106 # color =
107 # pager =""",
107 # pager =""",
108 }
108 }
109
109
110
110
111 class httppasswordmgrdbproxy(object):
111 class httppasswordmgrdbproxy(object):
112 """Delays loading urllib2 until it's needed."""
112 """Delays loading urllib2 until it's needed."""
113 def __init__(self):
113 def __init__(self):
114 self._mgr = None
114 self._mgr = None
115
115
116 def _get_mgr(self):
116 def _get_mgr(self):
117 if self._mgr is None:
117 if self._mgr is None:
118 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
118 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
119 return self._mgr
119 return self._mgr
120
120
121 def add_password(self, *args, **kwargs):
121 def add_password(self, *args, **kwargs):
122 return self._get_mgr().add_password(*args, **kwargs)
122 return self._get_mgr().add_password(*args, **kwargs)
123
123
124 def find_user_password(self, *args, **kwargs):
124 def find_user_password(self, *args, **kwargs):
125 return self._get_mgr().find_user_password(*args, **kwargs)
125 return self._get_mgr().find_user_password(*args, **kwargs)
126
126
127 def _catchterm(*args):
127 def _catchterm(*args):
128 raise error.SignalInterrupt
128 raise error.SignalInterrupt
129
129
130 class ui(object):
130 class ui(object):
131 def __init__(self, src=None):
131 def __init__(self, src=None):
132 """Create a fresh new ui object if no src given
132 """Create a fresh new ui object if no src given
133
133
134 Use uimod.ui.load() to create a ui which knows global and user configs.
134 Use uimod.ui.load() to create a ui which knows global and user configs.
135 In most cases, you should use ui.copy() to create a copy of an existing
135 In most cases, you should use ui.copy() to create a copy of an existing
136 ui object.
136 ui object.
137 """
137 """
138 # _buffers: used for temporary capture of output
138 # _buffers: used for temporary capture of output
139 self._buffers = []
139 self._buffers = []
140 # 3-tuple describing how each buffer in the stack behaves.
140 # 3-tuple describing how each buffer in the stack behaves.
141 # Values are (capture stderr, capture subprocesses, apply labels).
141 # Values are (capture stderr, capture subprocesses, apply labels).
142 self._bufferstates = []
142 self._bufferstates = []
143 # When a buffer is active, defines whether we are expanding labels.
143 # When a buffer is active, defines whether we are expanding labels.
144 # This exists to prevent an extra list lookup.
144 # This exists to prevent an extra list lookup.
145 self._bufferapplylabels = None
145 self._bufferapplylabels = None
146 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
146 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
147 self._reportuntrusted = True
147 self._reportuntrusted = True
148 self._ocfg = config.config() # overlay
148 self._ocfg = config.config() # overlay
149 self._tcfg = config.config() # trusted
149 self._tcfg = config.config() # trusted
150 self._ucfg = config.config() # untrusted
150 self._ucfg = config.config() # untrusted
151 self._trustusers = set()
151 self._trustusers = set()
152 self._trustgroups = set()
152 self._trustgroups = set()
153 self.callhooks = True
153 self.callhooks = True
154 # Insecure server connections requested.
154 # Insecure server connections requested.
155 self.insecureconnections = False
155 self.insecureconnections = False
156 # Blocked time
156 # Blocked time
157 self.logblockedtimes = False
157 self.logblockedtimes = False
158 # color mode: see mercurial/color.py for possible value
158 # color mode: see mercurial/color.py for possible value
159 self._colormode = None
159 self._colormode = None
160
160
161 if src:
161 if src:
162 self.fout = src.fout
162 self.fout = src.fout
163 self.ferr = src.ferr
163 self.ferr = src.ferr
164 self.fin = src.fin
164 self.fin = src.fin
165 self.pageractive = src.pageractive
165 self.pageractive = src.pageractive
166 self._disablepager = src._disablepager
166 self._disablepager = src._disablepager
167
167
168 self._tcfg = src._tcfg.copy()
168 self._tcfg = src._tcfg.copy()
169 self._ucfg = src._ucfg.copy()
169 self._ucfg = src._ucfg.copy()
170 self._ocfg = src._ocfg.copy()
170 self._ocfg = src._ocfg.copy()
171 self._trustusers = src._trustusers.copy()
171 self._trustusers = src._trustusers.copy()
172 self._trustgroups = src._trustgroups.copy()
172 self._trustgroups = src._trustgroups.copy()
173 self.environ = src.environ
173 self.environ = src.environ
174 self.callhooks = src.callhooks
174 self.callhooks = src.callhooks
175 self.insecureconnections = src.insecureconnections
175 self.insecureconnections = src.insecureconnections
176 self._colormode = src._colormode
176 self._colormode = src._colormode
177
177
178 self.fixconfig()
178 self.fixconfig()
179
179
180 self.httppasswordmgrdb = src.httppasswordmgrdb
180 self.httppasswordmgrdb = src.httppasswordmgrdb
181 self._blockedtimes = src._blockedtimes
181 self._blockedtimes = src._blockedtimes
182 else:
182 else:
183 self.fout = util.stdout
183 self.fout = util.stdout
184 self.ferr = util.stderr
184 self.ferr = util.stderr
185 self.fin = util.stdin
185 self.fin = util.stdin
186 self.pageractive = False
186 self.pageractive = False
187 self._disablepager = False
187 self._disablepager = False
188
188
189 # shared read-only environment
189 # shared read-only environment
190 self.environ = encoding.environ
190 self.environ = encoding.environ
191
191
192 self.httppasswordmgrdb = httppasswordmgrdbproxy()
192 self.httppasswordmgrdb = httppasswordmgrdbproxy()
193 self._blockedtimes = collections.defaultdict(int)
193 self._blockedtimes = collections.defaultdict(int)
194
194
195 allowed = self.configlist('experimental', 'exportableenviron')
195 allowed = self.configlist('experimental', 'exportableenviron')
196 if '*' in allowed:
196 if '*' in allowed:
197 self._exportableenviron = self.environ
197 self._exportableenviron = self.environ
198 else:
198 else:
199 self._exportableenviron = {}
199 self._exportableenviron = {}
200 for k in allowed:
200 for k in allowed:
201 if k in self.environ:
201 if k in self.environ:
202 self._exportableenviron[k] = self.environ[k]
202 self._exportableenviron[k] = self.environ[k]
203
203
204 @classmethod
204 @classmethod
205 def load(cls):
205 def load(cls):
206 """Create a ui and load global and user configs"""
206 """Create a ui and load global and user configs"""
207 u = cls()
207 u = cls()
208 # we always trust global config files
208 # we always trust global config files
209 for f in scmutil.rcpath():
209 for f in scmutil.rcpath():
210 u.readconfig(f, trust=True)
210 u.readconfig(f, trust=True)
211 return u
211 return u
212
212
213 def copy(self):
213 def copy(self):
214 return self.__class__(self)
214 return self.__class__(self)
215
215
216 def resetstate(self):
216 def resetstate(self):
217 """Clear internal state that shouldn't persist across commands"""
217 """Clear internal state that shouldn't persist across commands"""
218 if self._progbar:
218 if self._progbar:
219 self._progbar.resetstate() # reset last-print time of progress bar
219 self._progbar.resetstate() # reset last-print time of progress bar
220 self.httppasswordmgrdb = httppasswordmgrdbproxy()
220 self.httppasswordmgrdb = httppasswordmgrdbproxy()
221
221
222 @contextlib.contextmanager
222 @contextlib.contextmanager
223 def timeblockedsection(self, key):
223 def timeblockedsection(self, key):
224 # this is open-coded below - search for timeblockedsection to find them
224 # this is open-coded below - search for timeblockedsection to find them
225 starttime = util.timer()
225 starttime = util.timer()
226 try:
226 try:
227 yield
227 yield
228 finally:
228 finally:
229 self._blockedtimes[key + '_blocked'] += \
229 self._blockedtimes[key + '_blocked'] += \
230 (util.timer() - starttime) * 1000
230 (util.timer() - starttime) * 1000
231
231
232 def formatter(self, topic, opts):
232 def formatter(self, topic, opts):
233 return formatter.formatter(self, topic, opts)
233 return formatter.formatter(self, topic, opts)
234
234
235 def _trusted(self, fp, f):
235 def _trusted(self, fp, f):
236 st = util.fstat(fp)
236 st = util.fstat(fp)
237 if util.isowner(st):
237 if util.isowner(st):
238 return True
238 return True
239
239
240 tusers, tgroups = self._trustusers, self._trustgroups
240 tusers, tgroups = self._trustusers, self._trustgroups
241 if '*' in tusers or '*' in tgroups:
241 if '*' in tusers or '*' in tgroups:
242 return True
242 return True
243
243
244 user = util.username(st.st_uid)
244 user = util.username(st.st_uid)
245 group = util.groupname(st.st_gid)
245 group = util.groupname(st.st_gid)
246 if user in tusers or group in tgroups or user == util.username():
246 if user in tusers or group in tgroups or user == util.username():
247 return True
247 return True
248
248
249 if self._reportuntrusted:
249 if self._reportuntrusted:
250 self.warn(_('not trusting file %s from untrusted '
250 self.warn(_('not trusting file %s from untrusted '
251 'user %s, group %s\n') % (f, user, group))
251 'user %s, group %s\n') % (f, user, group))
252 return False
252 return False
253
253
254 def readconfig(self, filename, root=None, trust=False,
254 def readconfig(self, filename, root=None, trust=False,
255 sections=None, remap=None):
255 sections=None, remap=None):
256 try:
256 try:
257 fp = open(filename, u'rb')
257 fp = open(filename, u'rb')
258 except IOError:
258 except IOError:
259 if not sections: # ignore unless we were looking for something
259 if not sections: # ignore unless we were looking for something
260 return
260 return
261 raise
261 raise
262
262
263 cfg = config.config()
263 cfg = config.config()
264 trusted = sections or trust or self._trusted(fp, filename)
264 trusted = sections or trust or self._trusted(fp, filename)
265
265
266 try:
266 try:
267 cfg.read(filename, fp, sections=sections, remap=remap)
267 cfg.read(filename, fp, sections=sections, remap=remap)
268 fp.close()
268 fp.close()
269 except error.ConfigError as inst:
269 except error.ConfigError as inst:
270 if trusted:
270 if trusted:
271 raise
271 raise
272 self.warn(_("ignored: %s\n") % str(inst))
272 self.warn(_("ignored: %s\n") % str(inst))
273
273
274 if self.plain():
274 if self.plain():
275 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
275 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
276 'logtemplate', 'statuscopies', 'style',
276 'logtemplate', 'statuscopies', 'style',
277 'traceback', 'verbose'):
277 'traceback', 'verbose'):
278 if k in cfg['ui']:
278 if k in cfg['ui']:
279 del cfg['ui'][k]
279 del cfg['ui'][k]
280 for k, v in cfg.items('defaults'):
280 for k, v in cfg.items('defaults'):
281 del cfg['defaults'][k]
281 del cfg['defaults'][k]
282 # Don't remove aliases from the configuration if in the exceptionlist
282 # Don't remove aliases from the configuration if in the exceptionlist
283 if self.plain('alias'):
283 if self.plain('alias'):
284 for k, v in cfg.items('alias'):
284 for k, v in cfg.items('alias'):
285 del cfg['alias'][k]
285 del cfg['alias'][k]
286 if self.plain('revsetalias'):
286 if self.plain('revsetalias'):
287 for k, v in cfg.items('revsetalias'):
287 for k, v in cfg.items('revsetalias'):
288 del cfg['revsetalias'][k]
288 del cfg['revsetalias'][k]
289 if self.plain('templatealias'):
289 if self.plain('templatealias'):
290 for k, v in cfg.items('templatealias'):
290 for k, v in cfg.items('templatealias'):
291 del cfg['templatealias'][k]
291 del cfg['templatealias'][k]
292
292
293 if trusted:
293 if trusted:
294 self._tcfg.update(cfg)
294 self._tcfg.update(cfg)
295 self._tcfg.update(self._ocfg)
295 self._tcfg.update(self._ocfg)
296 self._ucfg.update(cfg)
296 self._ucfg.update(cfg)
297 self._ucfg.update(self._ocfg)
297 self._ucfg.update(self._ocfg)
298
298
299 if root is None:
299 if root is None:
300 root = os.path.expanduser('~')
300 root = os.path.expanduser('~')
301 self.fixconfig(root=root)
301 self.fixconfig(root=root)
302
302
303 def fixconfig(self, root=None, section=None):
303 def fixconfig(self, root=None, section=None):
304 if section in (None, 'paths'):
304 if section in (None, 'paths'):
305 # expand vars and ~
305 # expand vars and ~
306 # translate paths relative to root (or home) into absolute paths
306 # translate paths relative to root (or home) into absolute paths
307 root = root or pycompat.getcwd()
307 root = root or pycompat.getcwd()
308 for c in self._tcfg, self._ucfg, self._ocfg:
308 for c in self._tcfg, self._ucfg, self._ocfg:
309 for n, p in c.items('paths'):
309 for n, p in c.items('paths'):
310 # Ignore sub-options.
310 # Ignore sub-options.
311 if ':' in n:
311 if ':' in n:
312 continue
312 continue
313 if not p:
313 if not p:
314 continue
314 continue
315 if '%%' in p:
315 if '%%' in p:
316 s = self.configsource('paths', n) or 'none'
316 s = self.configsource('paths', n) or 'none'
317 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
317 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
318 % (n, p, s))
318 % (n, p, s))
319 p = p.replace('%%', '%')
319 p = p.replace('%%', '%')
320 p = util.expandpath(p)
320 p = util.expandpath(p)
321 if not util.hasscheme(p) and not os.path.isabs(p):
321 if not util.hasscheme(p) and not os.path.isabs(p):
322 p = os.path.normpath(os.path.join(root, p))
322 p = os.path.normpath(os.path.join(root, p))
323 c.set("paths", n, p)
323 c.set("paths", n, p)
324
324
325 if section in (None, 'ui'):
325 if section in (None, 'ui'):
326 # update ui options
326 # update ui options
327 self.debugflag = self.configbool('ui', 'debug')
327 self.debugflag = self.configbool('ui', 'debug')
328 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
328 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
329 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
329 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
330 if self.verbose and self.quiet:
330 if self.verbose and self.quiet:
331 self.quiet = self.verbose = False
331 self.quiet = self.verbose = False
332 self._reportuntrusted = self.debugflag or self.configbool("ui",
332 self._reportuntrusted = self.debugflag or self.configbool("ui",
333 "report_untrusted", True)
333 "report_untrusted", True)
334 self.tracebackflag = self.configbool('ui', 'traceback', False)
334 self.tracebackflag = self.configbool('ui', 'traceback', False)
335 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
335 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
336
336
337 if section in (None, 'trusted'):
337 if section in (None, 'trusted'):
338 # update trust information
338 # update trust information
339 self._trustusers.update(self.configlist('trusted', 'users'))
339 self._trustusers.update(self.configlist('trusted', 'users'))
340 self._trustgroups.update(self.configlist('trusted', 'groups'))
340 self._trustgroups.update(self.configlist('trusted', 'groups'))
341
341
342 def backupconfig(self, section, item):
342 def backupconfig(self, section, item):
343 return (self._ocfg.backup(section, item),
343 return (self._ocfg.backup(section, item),
344 self._tcfg.backup(section, item),
344 self._tcfg.backup(section, item),
345 self._ucfg.backup(section, item),)
345 self._ucfg.backup(section, item),)
346 def restoreconfig(self, data):
346 def restoreconfig(self, data):
347 self._ocfg.restore(data[0])
347 self._ocfg.restore(data[0])
348 self._tcfg.restore(data[1])
348 self._tcfg.restore(data[1])
349 self._ucfg.restore(data[2])
349 self._ucfg.restore(data[2])
350
350
351 def setconfig(self, section, name, value, source=''):
351 def setconfig(self, section, name, value, source=''):
352 for cfg in (self._ocfg, self._tcfg, self._ucfg):
352 for cfg in (self._ocfg, self._tcfg, self._ucfg):
353 cfg.set(section, name, value, source)
353 cfg.set(section, name, value, source)
354 self.fixconfig(section=section)
354 self.fixconfig(section=section)
355
355
356 def _data(self, untrusted):
356 def _data(self, untrusted):
357 return untrusted and self._ucfg or self._tcfg
357 return untrusted and self._ucfg or self._tcfg
358
358
359 def configsource(self, section, name, untrusted=False):
359 def configsource(self, section, name, untrusted=False):
360 return self._data(untrusted).source(section, name)
360 return self._data(untrusted).source(section, name)
361
361
362 def config(self, section, name, default=None, untrusted=False):
362 def config(self, section, name, default=None, untrusted=False):
363 if isinstance(name, list):
363 if isinstance(name, list):
364 alternates = name
364 alternates = name
365 else:
365 else:
366 alternates = [name]
366 alternates = [name]
367
367
368 for n in alternates:
368 for n in alternates:
369 value = self._data(untrusted).get(section, n, None)
369 value = self._data(untrusted).get(section, n, None)
370 if value is not None:
370 if value is not None:
371 name = n
371 name = n
372 break
372 break
373 else:
373 else:
374 value = default
374 value = default
375
375
376 if self.debugflag and not untrusted and self._reportuntrusted:
376 if self.debugflag and not untrusted and self._reportuntrusted:
377 for n in alternates:
377 for n in alternates:
378 uvalue = self._ucfg.get(section, n)
378 uvalue = self._ucfg.get(section, n)
379 if uvalue is not None and uvalue != value:
379 if uvalue is not None and uvalue != value:
380 self.debug("ignoring untrusted configuration option "
380 self.debug("ignoring untrusted configuration option "
381 "%s.%s = %s\n" % (section, n, uvalue))
381 "%s.%s = %s\n" % (section, n, uvalue))
382 return value
382 return value
383
383
384 def configsuboptions(self, section, name, default=None, untrusted=False):
384 def configsuboptions(self, section, name, default=None, untrusted=False):
385 """Get a config option and all sub-options.
385 """Get a config option and all sub-options.
386
386
387 Some config options have sub-options that are declared with the
387 Some config options have sub-options that are declared with the
388 format "key:opt = value". This method is used to return the main
388 format "key:opt = value". This method is used to return the main
389 option and all its declared sub-options.
389 option and all its declared sub-options.
390
390
391 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
391 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
392 is a dict of defined sub-options where keys and values are strings.
392 is a dict of defined sub-options where keys and values are strings.
393 """
393 """
394 data = self._data(untrusted)
394 data = self._data(untrusted)
395 main = data.get(section, name, default)
395 main = data.get(section, name, default)
396 if self.debugflag and not untrusted and self._reportuntrusted:
396 if self.debugflag and not untrusted and self._reportuntrusted:
397 uvalue = self._ucfg.get(section, name)
397 uvalue = self._ucfg.get(section, name)
398 if uvalue is not None and uvalue != main:
398 if uvalue is not None and uvalue != main:
399 self.debug('ignoring untrusted configuration option '
399 self.debug('ignoring untrusted configuration option '
400 '%s.%s = %s\n' % (section, name, uvalue))
400 '%s.%s = %s\n' % (section, name, uvalue))
401
401
402 sub = {}
402 sub = {}
403 prefix = '%s:' % name
403 prefix = '%s:' % name
404 for k, v in data.items(section):
404 for k, v in data.items(section):
405 if k.startswith(prefix):
405 if k.startswith(prefix):
406 sub[k[len(prefix):]] = v
406 sub[k[len(prefix):]] = v
407
407
408 if self.debugflag and not untrusted and self._reportuntrusted:
408 if self.debugflag and not untrusted and self._reportuntrusted:
409 for k, v in sub.items():
409 for k, v in sub.items():
410 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
410 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
411 if uvalue is not None and uvalue != v:
411 if uvalue is not None and uvalue != v:
412 self.debug('ignoring untrusted configuration option '
412 self.debug('ignoring untrusted configuration option '
413 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
413 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
414
414
415 return main, sub
415 return main, sub
416
416
417 def configpath(self, section, name, default=None, untrusted=False):
417 def configpath(self, section, name, default=None, untrusted=False):
418 'get a path config item, expanded relative to repo root or config file'
418 'get a path config item, expanded relative to repo root or config file'
419 v = self.config(section, name, default, untrusted)
419 v = self.config(section, name, default, untrusted)
420 if v is None:
420 if v is None:
421 return None
421 return None
422 if not os.path.isabs(v) or "://" not in v:
422 if not os.path.isabs(v) or "://" not in v:
423 src = self.configsource(section, name, untrusted)
423 src = self.configsource(section, name, untrusted)
424 if ':' in src:
424 if ':' in src:
425 base = os.path.dirname(src.rsplit(':')[0])
425 base = os.path.dirname(src.rsplit(':')[0])
426 v = os.path.join(base, os.path.expanduser(v))
426 v = os.path.join(base, os.path.expanduser(v))
427 return v
427 return v
428
428
429 def configbool(self, section, name, default=False, untrusted=False):
429 def configbool(self, section, name, default=False, untrusted=False):
430 """parse a configuration element as a boolean
430 """parse a configuration element as a boolean
431
431
432 >>> u = ui(); s = 'foo'
432 >>> u = ui(); s = 'foo'
433 >>> u.setconfig(s, 'true', 'yes')
433 >>> u.setconfig(s, 'true', 'yes')
434 >>> u.configbool(s, 'true')
434 >>> u.configbool(s, 'true')
435 True
435 True
436 >>> u.setconfig(s, 'false', 'no')
436 >>> u.setconfig(s, 'false', 'no')
437 >>> u.configbool(s, 'false')
437 >>> u.configbool(s, 'false')
438 False
438 False
439 >>> u.configbool(s, 'unknown')
439 >>> u.configbool(s, 'unknown')
440 False
440 False
441 >>> u.configbool(s, 'unknown', True)
441 >>> u.configbool(s, 'unknown', True)
442 True
442 True
443 >>> u.setconfig(s, 'invalid', 'somevalue')
443 >>> u.setconfig(s, 'invalid', 'somevalue')
444 >>> u.configbool(s, 'invalid')
444 >>> u.configbool(s, 'invalid')
445 Traceback (most recent call last):
445 Traceback (most recent call last):
446 ...
446 ...
447 ConfigError: foo.invalid is not a boolean ('somevalue')
447 ConfigError: foo.invalid is not a boolean ('somevalue')
448 """
448 """
449
449
450 v = self.config(section, name, None, untrusted)
450 v = self.config(section, name, None, untrusted)
451 if v is None:
451 if v is None:
452 return default
452 return default
453 if isinstance(v, bool):
453 if isinstance(v, bool):
454 return v
454 return v
455 b = util.parsebool(v)
455 b = util.parsebool(v)
456 if b is None:
456 if b is None:
457 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
457 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
458 % (section, name, v))
458 % (section, name, v))
459 return b
459 return b
460
460
461 def configwith(self, convert, section, name, default=None,
461 def configwith(self, convert, section, name, default=None,
462 desc=None, untrusted=False):
462 desc=None, untrusted=False):
463 """parse a configuration element with a conversion function
463 """parse a configuration element with a conversion function
464
464
465 >>> u = ui(); s = 'foo'
465 >>> u = ui(); s = 'foo'
466 >>> u.setconfig(s, 'float1', '42')
466 >>> u.setconfig(s, 'float1', '42')
467 >>> u.configwith(float, s, 'float1')
467 >>> u.configwith(float, s, 'float1')
468 42.0
468 42.0
469 >>> u.setconfig(s, 'float2', '-4.25')
469 >>> u.setconfig(s, 'float2', '-4.25')
470 >>> u.configwith(float, s, 'float2')
470 >>> u.configwith(float, s, 'float2')
471 -4.25
471 -4.25
472 >>> u.configwith(float, s, 'unknown', 7)
472 >>> u.configwith(float, s, 'unknown', 7)
473 7
473 7
474 >>> u.setconfig(s, 'invalid', 'somevalue')
474 >>> u.setconfig(s, 'invalid', 'somevalue')
475 >>> u.configwith(float, s, 'invalid')
475 >>> u.configwith(float, s, 'invalid')
476 Traceback (most recent call last):
476 Traceback (most recent call last):
477 ...
477 ...
478 ConfigError: foo.invalid is not a valid float ('somevalue')
478 ConfigError: foo.invalid is not a valid float ('somevalue')
479 >>> u.configwith(float, s, 'invalid', desc='womble')
479 >>> u.configwith(float, s, 'invalid', desc='womble')
480 Traceback (most recent call last):
480 Traceback (most recent call last):
481 ...
481 ...
482 ConfigError: foo.invalid is not a valid womble ('somevalue')
482 ConfigError: foo.invalid is not a valid womble ('somevalue')
483 """
483 """
484
484
485 v = self.config(section, name, None, untrusted)
485 v = self.config(section, name, None, untrusted)
486 if v is None:
486 if v is None:
487 return default
487 return default
488 try:
488 try:
489 return convert(v)
489 return convert(v)
490 except ValueError:
490 except ValueError:
491 if desc is None:
491 if desc is None:
492 desc = convert.__name__
492 desc = convert.__name__
493 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
493 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
494 % (section, name, desc, v))
494 % (section, name, desc, v))
495
495
496 def configint(self, section, name, default=None, untrusted=False):
496 def configint(self, section, name, default=None, untrusted=False):
497 """parse a configuration element as an integer
497 """parse a configuration element as an integer
498
498
499 >>> u = ui(); s = 'foo'
499 >>> u = ui(); s = 'foo'
500 >>> u.setconfig(s, 'int1', '42')
500 >>> u.setconfig(s, 'int1', '42')
501 >>> u.configint(s, 'int1')
501 >>> u.configint(s, 'int1')
502 42
502 42
503 >>> u.setconfig(s, 'int2', '-42')
503 >>> u.setconfig(s, 'int2', '-42')
504 >>> u.configint(s, 'int2')
504 >>> u.configint(s, 'int2')
505 -42
505 -42
506 >>> u.configint(s, 'unknown', 7)
506 >>> u.configint(s, 'unknown', 7)
507 7
507 7
508 >>> u.setconfig(s, 'invalid', 'somevalue')
508 >>> u.setconfig(s, 'invalid', 'somevalue')
509 >>> u.configint(s, 'invalid')
509 >>> u.configint(s, 'invalid')
510 Traceback (most recent call last):
510 Traceback (most recent call last):
511 ...
511 ...
512 ConfigError: foo.invalid is not a valid integer ('somevalue')
512 ConfigError: foo.invalid is not a valid integer ('somevalue')
513 """
513 """
514
514
515 return self.configwith(int, section, name, default, 'integer',
515 return self.configwith(int, section, name, default, 'integer',
516 untrusted)
516 untrusted)
517
517
518 def configbytes(self, section, name, default=0, untrusted=False):
518 def configbytes(self, section, name, default=0, untrusted=False):
519 """parse a configuration element as a quantity in bytes
519 """parse a configuration element as a quantity in bytes
520
520
521 Units can be specified as b (bytes), k or kb (kilobytes), m or
521 Units can be specified as b (bytes), k or kb (kilobytes), m or
522 mb (megabytes), g or gb (gigabytes).
522 mb (megabytes), g or gb (gigabytes).
523
523
524 >>> u = ui(); s = 'foo'
524 >>> u = ui(); s = 'foo'
525 >>> u.setconfig(s, 'val1', '42')
525 >>> u.setconfig(s, 'val1', '42')
526 >>> u.configbytes(s, 'val1')
526 >>> u.configbytes(s, 'val1')
527 42
527 42
528 >>> u.setconfig(s, 'val2', '42.5 kb')
528 >>> u.setconfig(s, 'val2', '42.5 kb')
529 >>> u.configbytes(s, 'val2')
529 >>> u.configbytes(s, 'val2')
530 43520
530 43520
531 >>> u.configbytes(s, 'unknown', '7 MB')
531 >>> u.configbytes(s, 'unknown', '7 MB')
532 7340032
532 7340032
533 >>> u.setconfig(s, 'invalid', 'somevalue')
533 >>> u.setconfig(s, 'invalid', 'somevalue')
534 >>> u.configbytes(s, 'invalid')
534 >>> u.configbytes(s, 'invalid')
535 Traceback (most recent call last):
535 Traceback (most recent call last):
536 ...
536 ...
537 ConfigError: foo.invalid is not a byte quantity ('somevalue')
537 ConfigError: foo.invalid is not a byte quantity ('somevalue')
538 """
538 """
539
539
540 value = self.config(section, name)
540 value = self.config(section, name)
541 if value is None:
541 if value is None:
542 if not isinstance(default, str):
542 if not isinstance(default, str):
543 return default
543 return default
544 value = default
544 value = default
545 try:
545 try:
546 return util.sizetoint(value)
546 return util.sizetoint(value)
547 except error.ParseError:
547 except error.ParseError:
548 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
548 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
549 % (section, name, value))
549 % (section, name, value))
550
550
551 def configlist(self, section, name, default=None, untrusted=False):
551 def configlist(self, section, name, default=None, untrusted=False):
552 """parse a configuration element as a list of comma/space separated
552 """parse a configuration element as a list of comma/space separated
553 strings
553 strings
554
554
555 >>> u = ui(); s = 'foo'
555 >>> u = ui(); s = 'foo'
556 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
556 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
557 >>> u.configlist(s, 'list1')
557 >>> u.configlist(s, 'list1')
558 ['this', 'is', 'a small', 'test']
558 ['this', 'is', 'a small', 'test']
559 """
559 """
560
560
561 def _parse_plain(parts, s, offset):
561 def _parse_plain(parts, s, offset):
562 whitespace = False
562 whitespace = False
563 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
563 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
564 whitespace = True
564 whitespace = True
565 offset += 1
565 offset += 1
566 if offset >= len(s):
566 if offset >= len(s):
567 return None, parts, offset
567 return None, parts, offset
568 if whitespace:
568 if whitespace:
569 parts.append('')
569 parts.append('')
570 if s[offset] == '"' and not parts[-1]:
570 if s[offset] == '"' and not parts[-1]:
571 return _parse_quote, parts, offset + 1
571 return _parse_quote, parts, offset + 1
572 elif s[offset] == '"' and parts[-1][-1] == '\\':
572 elif s[offset] == '"' and parts[-1][-1] == '\\':
573 parts[-1] = parts[-1][:-1] + s[offset]
573 parts[-1] = parts[-1][:-1] + s[offset]
574 return _parse_plain, parts, offset + 1
574 return _parse_plain, parts, offset + 1
575 parts[-1] += s[offset]
575 parts[-1] += s[offset]
576 return _parse_plain, parts, offset + 1
576 return _parse_plain, parts, offset + 1
577
577
578 def _parse_quote(parts, s, offset):
578 def _parse_quote(parts, s, offset):
579 if offset < len(s) and s[offset] == '"': # ""
579 if offset < len(s) and s[offset] == '"': # ""
580 parts.append('')
580 parts.append('')
581 offset += 1
581 offset += 1
582 while offset < len(s) and (s[offset].isspace() or
582 while offset < len(s) and (s[offset].isspace() or
583 s[offset] == ','):
583 s[offset] == ','):
584 offset += 1
584 offset += 1
585 return _parse_plain, parts, offset
585 return _parse_plain, parts, offset
586
586
587 while offset < len(s) and s[offset] != '"':
587 while offset < len(s) and s[offset] != '"':
588 if (s[offset] == '\\' and offset + 1 < len(s)
588 if (s[offset] == '\\' and offset + 1 < len(s)
589 and s[offset + 1] == '"'):
589 and s[offset + 1] == '"'):
590 offset += 1
590 offset += 1
591 parts[-1] += '"'
591 parts[-1] += '"'
592 else:
592 else:
593 parts[-1] += s[offset]
593 parts[-1] += s[offset]
594 offset += 1
594 offset += 1
595
595
596 if offset >= len(s):
596 if offset >= len(s):
597 real_parts = _configlist(parts[-1])
597 real_parts = _configlist(parts[-1])
598 if not real_parts:
598 if not real_parts:
599 parts[-1] = '"'
599 parts[-1] = '"'
600 else:
600 else:
601 real_parts[0] = '"' + real_parts[0]
601 real_parts[0] = '"' + real_parts[0]
602 parts = parts[:-1]
602 parts = parts[:-1]
603 parts.extend(real_parts)
603 parts.extend(real_parts)
604 return None, parts, offset
604 return None, parts, offset
605
605
606 offset += 1
606 offset += 1
607 while offset < len(s) and s[offset] in [' ', ',']:
607 while offset < len(s) and s[offset] in [' ', ',']:
608 offset += 1
608 offset += 1
609
609
610 if offset < len(s):
610 if offset < len(s):
611 if offset + 1 == len(s) and s[offset] == '"':
611 if offset + 1 == len(s) and s[offset] == '"':
612 parts[-1] += '"'
612 parts[-1] += '"'
613 offset += 1
613 offset += 1
614 else:
614 else:
615 parts.append('')
615 parts.append('')
616 else:
616 else:
617 return None, parts, offset
617 return None, parts, offset
618
618
619 return _parse_plain, parts, offset
619 return _parse_plain, parts, offset
620
620
621 def _configlist(s):
621 def _configlist(s):
622 s = s.rstrip(' ,')
622 s = s.rstrip(' ,')
623 if not s:
623 if not s:
624 return []
624 return []
625 parser, parts, offset = _parse_plain, [''], 0
625 parser, parts, offset = _parse_plain, [''], 0
626 while parser:
626 while parser:
627 parser, parts, offset = parser(parts, s, offset)
627 parser, parts, offset = parser(parts, s, offset)
628 return parts
628 return parts
629
629
630 result = self.config(section, name, untrusted=untrusted)
630 result = self.config(section, name, untrusted=untrusted)
631 if result is None:
631 if result is None:
632 result = default or []
632 result = default or []
633 if isinstance(result, bytes):
633 if isinstance(result, bytes):
634 result = _configlist(result.lstrip(' ,\n'))
634 result = _configlist(result.lstrip(' ,\n'))
635 if result is None:
635 if result is None:
636 result = default or []
636 result = default or []
637 return result
637 return result
638
638
639 def hasconfig(self, section, name, untrusted=False):
639 def hasconfig(self, section, name, untrusted=False):
640 return self._data(untrusted).hasitem(section, name)
640 return self._data(untrusted).hasitem(section, name)
641
641
642 def has_section(self, section, untrusted=False):
642 def has_section(self, section, untrusted=False):
643 '''tell whether section exists in config.'''
643 '''tell whether section exists in config.'''
644 return section in self._data(untrusted)
644 return section in self._data(untrusted)
645
645
646 def configitems(self, section, untrusted=False, ignoresub=False):
646 def configitems(self, section, untrusted=False, ignoresub=False):
647 items = self._data(untrusted).items(section)
647 items = self._data(untrusted).items(section)
648 if ignoresub:
648 if ignoresub:
649 newitems = {}
649 newitems = {}
650 for k, v in items:
650 for k, v in items:
651 if ':' not in k:
651 if ':' not in k:
652 newitems[k] = v
652 newitems[k] = v
653 items = newitems.items()
653 items = newitems.items()
654 if self.debugflag and not untrusted and self._reportuntrusted:
654 if self.debugflag and not untrusted and self._reportuntrusted:
655 for k, v in self._ucfg.items(section):
655 for k, v in self._ucfg.items(section):
656 if self._tcfg.get(section, k) != v:
656 if self._tcfg.get(section, k) != v:
657 self.debug("ignoring untrusted configuration option "
657 self.debug("ignoring untrusted configuration option "
658 "%s.%s = %s\n" % (section, k, v))
658 "%s.%s = %s\n" % (section, k, v))
659 return items
659 return items
660
660
661 def walkconfig(self, untrusted=False):
661 def walkconfig(self, untrusted=False):
662 cfg = self._data(untrusted)
662 cfg = self._data(untrusted)
663 for section in cfg.sections():
663 for section in cfg.sections():
664 for name, value in self.configitems(section, untrusted):
664 for name, value in self.configitems(section, untrusted):
665 yield section, name, value
665 yield section, name, value
666
666
667 def plain(self, feature=None):
667 def plain(self, feature=None):
668 '''is plain mode active?
668 '''is plain mode active?
669
669
670 Plain mode means that all configuration variables which affect
670 Plain mode means that all configuration variables which affect
671 the behavior and output of Mercurial should be
671 the behavior and output of Mercurial should be
672 ignored. Additionally, the output should be stable,
672 ignored. Additionally, the output should be stable,
673 reproducible and suitable for use in scripts or applications.
673 reproducible and suitable for use in scripts or applications.
674
674
675 The only way to trigger plain mode is by setting either the
675 The only way to trigger plain mode is by setting either the
676 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
676 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
677
677
678 The return value can either be
678 The return value can either be
679 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
679 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
680 - True otherwise
680 - True otherwise
681 '''
681 '''
682 if ('HGPLAIN' not in encoding.environ and
682 if ('HGPLAIN' not in encoding.environ and
683 'HGPLAINEXCEPT' not in encoding.environ):
683 'HGPLAINEXCEPT' not in encoding.environ):
684 return False
684 return False
685 exceptions = encoding.environ.get('HGPLAINEXCEPT',
685 exceptions = encoding.environ.get('HGPLAINEXCEPT',
686 '').strip().split(',')
686 '').strip().split(',')
687 if feature and exceptions:
687 if feature and exceptions:
688 return feature not in exceptions
688 return feature not in exceptions
689 return True
689 return True
690
690
691 def username(self):
691 def username(self):
692 """Return default username to be used in commits.
692 """Return default username to be used in commits.
693
693
694 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
694 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
695 and stop searching if one of these is set.
695 and stop searching if one of these is set.
696 If not found and ui.askusername is True, ask the user, else use
696 If not found and ui.askusername is True, ask the user, else use
697 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
697 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
698 """
698 """
699 user = encoding.environ.get("HGUSER")
699 user = encoding.environ.get("HGUSER")
700 if user is None:
700 if user is None:
701 user = self.config("ui", ["username", "user"])
701 user = self.config("ui", ["username", "user"])
702 if user is not None:
702 if user is not None:
703 user = os.path.expandvars(user)
703 user = os.path.expandvars(user)
704 if user is None:
704 if user is None:
705 user = encoding.environ.get("EMAIL")
705 user = encoding.environ.get("EMAIL")
706 if user is None and self.configbool("ui", "askusername"):
706 if user is None and self.configbool("ui", "askusername"):
707 user = self.prompt(_("enter a commit username:"), default=None)
707 user = self.prompt(_("enter a commit username:"), default=None)
708 if user is None and not self.interactive():
708 if user is None and not self.interactive():
709 try:
709 try:
710 user = '%s@%s' % (util.getuser(), socket.getfqdn())
710 user = '%s@%s' % (util.getuser(), socket.getfqdn())
711 self.warn(_("no username found, using '%s' instead\n") % user)
711 self.warn(_("no username found, using '%s' instead\n") % user)
712 except KeyError:
712 except KeyError:
713 pass
713 pass
714 if not user:
714 if not user:
715 raise error.Abort(_('no username supplied'),
715 raise error.Abort(_('no username supplied'),
716 hint=_("use 'hg config --edit' "
716 hint=_("use 'hg config --edit' "
717 'to set your username'))
717 'to set your username'))
718 if "\n" in user:
718 if "\n" in user:
719 raise error.Abort(_("username %s contains a newline\n")
719 raise error.Abort(_("username %s contains a newline\n")
720 % repr(user))
720 % repr(user))
721 return user
721 return user
722
722
723 def shortuser(self, user):
723 def shortuser(self, user):
724 """Return a short representation of a user name or email address."""
724 """Return a short representation of a user name or email address."""
725 if not self.verbose:
725 if not self.verbose:
726 user = util.shortuser(user)
726 user = util.shortuser(user)
727 return user
727 return user
728
728
729 def expandpath(self, loc, default=None):
729 def expandpath(self, loc, default=None):
730 """Return repository location relative to cwd or from [paths]"""
730 """Return repository location relative to cwd or from [paths]"""
731 try:
731 try:
732 p = self.paths.getpath(loc)
732 p = self.paths.getpath(loc)
733 if p:
733 if p:
734 return p.rawloc
734 return p.rawloc
735 except error.RepoError:
735 except error.RepoError:
736 pass
736 pass
737
737
738 if default:
738 if default:
739 try:
739 try:
740 p = self.paths.getpath(default)
740 p = self.paths.getpath(default)
741 if p:
741 if p:
742 return p.rawloc
742 return p.rawloc
743 except error.RepoError:
743 except error.RepoError:
744 pass
744 pass
745
745
746 return loc
746 return loc
747
747
748 @util.propertycache
748 @util.propertycache
749 def paths(self):
749 def paths(self):
750 return paths(self)
750 return paths(self)
751
751
752 def pushbuffer(self, error=False, subproc=False, labeled=False):
752 def pushbuffer(self, error=False, subproc=False, labeled=False):
753 """install a buffer to capture standard output of the ui object
753 """install a buffer to capture standard output of the ui object
754
754
755 If error is True, the error output will be captured too.
755 If error is True, the error output will be captured too.
756
756
757 If subproc is True, output from subprocesses (typically hooks) will be
757 If subproc is True, output from subprocesses (typically hooks) will be
758 captured too.
758 captured too.
759
759
760 If labeled is True, any labels associated with buffered
760 If labeled is True, any labels associated with buffered
761 output will be handled. By default, this has no effect
761 output will be handled. By default, this has no effect
762 on the output returned, but extensions and GUI tools may
762 on the output returned, but extensions and GUI tools may
763 handle this argument and returned styled output. If output
763 handle this argument and returned styled output. If output
764 is being buffered so it can be captured and parsed or
764 is being buffered so it can be captured and parsed or
765 processed, labeled should not be set to True.
765 processed, labeled should not be set to True.
766 """
766 """
767 self._buffers.append([])
767 self._buffers.append([])
768 self._bufferstates.append((error, subproc, labeled))
768 self._bufferstates.append((error, subproc, labeled))
769 self._bufferapplylabels = labeled
769 self._bufferapplylabels = labeled
770
770
771 def popbuffer(self):
771 def popbuffer(self):
772 '''pop the last buffer and return the buffered output'''
772 '''pop the last buffer and return the buffered output'''
773 self._bufferstates.pop()
773 self._bufferstates.pop()
774 if self._bufferstates:
774 if self._bufferstates:
775 self._bufferapplylabels = self._bufferstates[-1][2]
775 self._bufferapplylabels = self._bufferstates[-1][2]
776 else:
776 else:
777 self._bufferapplylabels = None
777 self._bufferapplylabels = None
778
778
779 return "".join(self._buffers.pop())
779 return "".join(self._buffers.pop())
780
780
781 def write(self, *args, **opts):
781 def write(self, *args, **opts):
782 '''write args to output
782 '''write args to output
783
783
784 By default, this method simply writes to the buffer or stdout.
784 By default, this method simply writes to the buffer or stdout.
785 Color mode can be set on the UI class to have the output decorated
785 Color mode can be set on the UI class to have the output decorated
786 with color modifier before being written to stdout.
786 with color modifier before being written to stdout.
787
787
788 The color used is controlled by an optional keyword argument, "label".
788 The color used is controlled by an optional keyword argument, "label".
789 This should be a string containing label names separated by space.
789 This should be a string containing label names separated by space.
790 Label names take the form of "topic.type". For example, ui.debug()
790 Label names take the form of "topic.type". For example, ui.debug()
791 issues a label of "ui.debug".
791 issues a label of "ui.debug".
792
792
793 When labeling output for a specific command, a label of
793 When labeling output for a specific command, a label of
794 "cmdname.type" is recommended. For example, status issues
794 "cmdname.type" is recommended. For example, status issues
795 a label of "status.modified" for modified files.
795 a label of "status.modified" for modified files.
796 '''
796 '''
797 if self._buffers and not opts.get('prompt', False):
797 if self._buffers and not opts.get('prompt', False):
798 if self._bufferapplylabels:
798 if self._bufferapplylabels:
799 label = opts.get('label', '')
799 label = opts.get('label', '')
800 self._buffers[-1].extend(self.label(a, label) for a in args)
800 self._buffers[-1].extend(self.label(a, label) for a in args)
801 else:
801 else:
802 self._buffers[-1].extend(args)
802 self._buffers[-1].extend(args)
803 elif self._colormode == 'win32':
803 elif self._colormode == 'win32':
804 # windows color printing is its own can of crab, defer to
804 # windows color printing is its own can of crab, defer to
805 # the color module and that is it.
805 # the color module and that is it.
806 color.win32print(self._write, *args, **opts)
806 color.win32print(self._write, *args, **opts)
807 else:
807 else:
808 msgs = args
808 msgs = args
809 if self._colormode is not None:
809 if self._colormode is not None:
810 label = opts.get('label', '')
810 label = opts.get('label', '')
811 msgs = [self.label(a, label) for a in args]
811 msgs = [self.label(a, label) for a in args]
812 self._write(*msgs, **opts)
812 self._write(*msgs, **opts)
813
813
814 def _write(self, *msgs, **opts):
814 def _write(self, *msgs, **opts):
815 self._progclear()
815 self._progclear()
816 # opencode timeblockedsection because this is a critical path
816 # opencode timeblockedsection because this is a critical path
817 starttime = util.timer()
817 starttime = util.timer()
818 try:
818 try:
819 for a in msgs:
819 for a in msgs:
820 self.fout.write(a)
820 self.fout.write(a)
821 finally:
821 finally:
822 self._blockedtimes['stdio_blocked'] += \
822 self._blockedtimes['stdio_blocked'] += \
823 (util.timer() - starttime) * 1000
823 (util.timer() - starttime) * 1000
824
824
825 def write_err(self, *args, **opts):
825 def write_err(self, *args, **opts):
826 self._progclear()
826 self._progclear()
827 if self._bufferstates and self._bufferstates[-1][0]:
827 if self._bufferstates and self._bufferstates[-1][0]:
828 self.write(*args, **opts)
828 self.write(*args, **opts)
829 elif self._colormode == 'win32':
829 elif self._colormode == 'win32':
830 # windows color printing is its own can of crab, defer to
830 # windows color printing is its own can of crab, defer to
831 # the color module and that is it.
831 # the color module and that is it.
832 color.win32print(self._write_err, *args, **opts)
832 color.win32print(self._write_err, *args, **opts)
833 else:
833 else:
834 msgs = args
834 msgs = args
835 if self._colormode is not None:
835 if self._colormode is not None:
836 label = opts.get('label', '')
836 label = opts.get('label', '')
837 msgs = [self.label(a, label) for a in args]
837 msgs = [self.label(a, label) for a in args]
838 self._write_err(*msgs, **opts)
838 self._write_err(*msgs, **opts)
839
839
840 def _write_err(self, *msgs, **opts):
840 def _write_err(self, *msgs, **opts):
841 try:
841 try:
842 with self.timeblockedsection('stdio'):
842 with self.timeblockedsection('stdio'):
843 if not getattr(self.fout, 'closed', False):
843 if not getattr(self.fout, 'closed', False):
844 self.fout.flush()
844 self.fout.flush()
845 for a in msgs:
845 for a in msgs:
846 self.ferr.write(a)
846 self.ferr.write(a)
847 # stderr may be buffered under win32 when redirected to files,
847 # stderr may be buffered under win32 when redirected to files,
848 # including stdout.
848 # including stdout.
849 if not getattr(self.ferr, 'closed', False):
849 if not getattr(self.ferr, 'closed', False):
850 self.ferr.flush()
850 self.ferr.flush()
851 except IOError as inst:
851 except IOError as inst:
852 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
852 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
853 raise
853 raise
854
854
855 def flush(self):
855 def flush(self):
856 # opencode timeblockedsection because this is a critical path
856 # opencode timeblockedsection because this is a critical path
857 starttime = util.timer()
857 starttime = util.timer()
858 try:
858 try:
859 try: self.fout.flush()
859 try: self.fout.flush()
860 except (IOError, ValueError): pass
860 except (IOError, ValueError): pass
861 try: self.ferr.flush()
861 try: self.ferr.flush()
862 except (IOError, ValueError): pass
862 except (IOError, ValueError): pass
863 finally:
863 finally:
864 self._blockedtimes['stdio_blocked'] += \
864 self._blockedtimes['stdio_blocked'] += \
865 (util.timer() - starttime) * 1000
865 (util.timer() - starttime) * 1000
866
866
867 def _isatty(self, fh):
867 def _isatty(self, fh):
868 if self.configbool('ui', 'nontty', False):
868 if self.configbool('ui', 'nontty', False):
869 return False
869 return False
870 return util.isatty(fh)
870 return util.isatty(fh)
871
871
872 def disablepager(self):
872 def disablepager(self):
873 self._disablepager = True
873 self._disablepager = True
874
874
875 def pager(self, command):
875 def pager(self, command):
876 """Start a pager for subsequent command output.
876 """Start a pager for subsequent command output.
877
877
878 Commands which produce a long stream of output should call
878 Commands which produce a long stream of output should call
879 this function to activate the user's preferred pagination
879 this function to activate the user's preferred pagination
880 mechanism (which may be no pager). Calling this function
880 mechanism (which may be no pager). Calling this function
881 precludes any future use of interactive functionality, such as
881 precludes any future use of interactive functionality, such as
882 prompting the user or activating curses.
882 prompting the user or activating curses.
883
883
884 Args:
884 Args:
885 command: The full, non-aliased name of the command. That is, "log"
885 command: The full, non-aliased name of the command. That is, "log"
886 not "history, "summary" not "summ", etc.
886 not "history, "summary" not "summ", etc.
887 """
887 """
888 if (self._disablepager
888 if (self._disablepager
889 or self.pageractive
889 or self.pageractive
890 or command in self.configlist('pager', 'ignore')
890 or command in self.configlist('pager', 'ignore')
891 or not self.configbool('pager', 'enable', True)
891 or not self.configbool('pager', 'enable', True)
892 or not self.configbool('pager', 'attend-' + command, True)
892 or not self.configbool('pager', 'attend-' + command, True)
893 # TODO: if we want to allow HGPLAINEXCEPT=pager,
893 # TODO: if we want to allow HGPLAINEXCEPT=pager,
894 # formatted() will need some adjustment.
894 # formatted() will need some adjustment.
895 or not self.formatted()
895 or not self.formatted()
896 or self.plain()
896 or self.plain()
897 # TODO: expose debugger-enabled on the UI object
897 # TODO: expose debugger-enabled on the UI object
898 or '--debugger' in sys.argv):
898 or '--debugger' in sys.argv):
899 # We only want to paginate if the ui appears to be
899 # We only want to paginate if the ui appears to be
900 # interactive, the user didn't say HGPLAIN or
900 # interactive, the user didn't say HGPLAIN or
901 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
901 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
902 return
902 return
903
903
904 # TODO: add a "system defaults" config section so this default
904 # TODO: add a "system defaults" config section so this default
905 # of more(1) can be easily replaced with a global
905 # of more(1) can be easily replaced with a global
906 # configuration file. For example, on OS X the sane default is
906 # configuration file. For example, on OS X the sane default is
907 # less(1), not more(1), and on debian it's
907 # less(1), not more(1), and on debian it's
908 # sensible-pager(1). We should probably also give the system
908 # sensible-pager(1). We should probably also give the system
909 # default editor command similar treatment.
909 # default editor command similar treatment.
910 envpager = encoding.environ.get('PAGER', 'more')
910 envpager = encoding.environ.get('PAGER', 'more')
911 pagercmd = self.config('pager', 'pager', envpager)
911 pagercmd = self.config('pager', 'pager', envpager)
912 if not pagercmd:
912 if not pagercmd:
913 return
913 return
914
914
915 self.debug('starting pager for command %r\n' % command)
915 self.debug('starting pager for command %r\n' % command)
916 self.pageractive = True
916 self.pageractive = True
917 # Preserve the formatted-ness of the UI. This is important
917 # Preserve the formatted-ness of the UI. This is important
918 # because we mess with stdout, which might confuse
918 # because we mess with stdout, which might confuse
919 # auto-detection of things being formatted.
919 # auto-detection of things being formatted.
920 self.setconfig('ui', 'formatted', self.formatted(), 'pager')
920 self.setconfig('ui', 'formatted', self.formatted(), 'pager')
921 self.setconfig('ui', 'interactive', False, 'pager')
921 self.setconfig('ui', 'interactive', False, 'pager')
922 if util.safehasattr(signal, "SIGPIPE"):
922 if util.safehasattr(signal, "SIGPIPE"):
923 signal.signal(signal.SIGPIPE, _catchterm)
923 signal.signal(signal.SIGPIPE, _catchterm)
924 self._runpager(pagercmd)
924 self._runpager(pagercmd)
925
925
926 def _runpager(self, command):
926 def _runpager(self, command):
927 """Actually start the pager and set up file descriptors.
927 """Actually start the pager and set up file descriptors.
928
928
929 This is separate in part so that extensions (like chg) can
929 This is separate in part so that extensions (like chg) can
930 override how a pager is invoked.
930 override how a pager is invoked.
931 """
931 """
932 pager = subprocess.Popen(command, shell=True, bufsize=-1,
932 pager = subprocess.Popen(command, shell=True, bufsize=-1,
933 close_fds=util.closefds, stdin=subprocess.PIPE,
933 close_fds=util.closefds, stdin=subprocess.PIPE,
934 stdout=util.stdout, stderr=util.stderr)
934 stdout=util.stdout, stderr=util.stderr)
935
935
936 # back up original file descriptors
936 # back up original file descriptors
937 stdoutfd = os.dup(util.stdout.fileno())
937 stdoutfd = os.dup(util.stdout.fileno())
938 stderrfd = os.dup(util.stderr.fileno())
938 stderrfd = os.dup(util.stderr.fileno())
939
939
940 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
940 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
941 if self._isatty(util.stderr):
941 if self._isatty(util.stderr):
942 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
942 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
943
943
944 @atexit.register
944 @atexit.register
945 def killpager():
945 def killpager():
946 if util.safehasattr(signal, "SIGINT"):
946 if util.safehasattr(signal, "SIGINT"):
947 signal.signal(signal.SIGINT, signal.SIG_IGN)
947 signal.signal(signal.SIGINT, signal.SIG_IGN)
948 # restore original fds, closing pager.stdin copies in the process
948 # restore original fds, closing pager.stdin copies in the process
949 os.dup2(stdoutfd, util.stdout.fileno())
949 os.dup2(stdoutfd, util.stdout.fileno())
950 os.dup2(stderrfd, util.stderr.fileno())
950 os.dup2(stderrfd, util.stderr.fileno())
951 pager.stdin.close()
951 pager.stdin.close()
952 pager.wait()
952 pager.wait()
953
953
954 def interface(self, feature):
954 def interface(self, feature):
955 """what interface to use for interactive console features?
955 """what interface to use for interactive console features?
956
956
957 The interface is controlled by the value of `ui.interface` but also by
957 The interface is controlled by the value of `ui.interface` but also by
958 the value of feature-specific configuration. For example:
958 the value of feature-specific configuration. For example:
959
959
960 ui.interface.histedit = text
960 ui.interface.histedit = text
961 ui.interface.chunkselector = curses
961 ui.interface.chunkselector = curses
962
962
963 Here the features are "histedit" and "chunkselector".
963 Here the features are "histedit" and "chunkselector".
964
964
965 The configuration above means that the default interfaces for commands
965 The configuration above means that the default interfaces for commands
966 is curses, the interface for histedit is text and the interface for
966 is curses, the interface for histedit is text and the interface for
967 selecting chunk is crecord (the best curses interface available).
967 selecting chunk is crecord (the best curses interface available).
968
968
969 Consider the following example:
969 Consider the following example:
970 ui.interface = curses
970 ui.interface = curses
971 ui.interface.histedit = text
971 ui.interface.histedit = text
972
972
973 Then histedit will use the text interface and chunkselector will use
973 Then histedit will use the text interface and chunkselector will use
974 the default curses interface (crecord at the moment).
974 the default curses interface (crecord at the moment).
975 """
975 """
976 alldefaults = frozenset(["text", "curses"])
976 alldefaults = frozenset(["text", "curses"])
977
977
978 featureinterfaces = {
978 featureinterfaces = {
979 "chunkselector": [
979 "chunkselector": [
980 "text",
980 "text",
981 "curses",
981 "curses",
982 ]
982 ]
983 }
983 }
984
984
985 # Feature-specific interface
985 # Feature-specific interface
986 if feature not in featureinterfaces.keys():
986 if feature not in featureinterfaces.keys():
987 # Programming error, not user error
987 # Programming error, not user error
988 raise ValueError("Unknown feature requested %s" % feature)
988 raise ValueError("Unknown feature requested %s" % feature)
989
989
990 availableinterfaces = frozenset(featureinterfaces[feature])
990 availableinterfaces = frozenset(featureinterfaces[feature])
991 if alldefaults > availableinterfaces:
991 if alldefaults > availableinterfaces:
992 # Programming error, not user error. We need a use case to
992 # Programming error, not user error. We need a use case to
993 # define the right thing to do here.
993 # define the right thing to do here.
994 raise ValueError(
994 raise ValueError(
995 "Feature %s does not handle all default interfaces" %
995 "Feature %s does not handle all default interfaces" %
996 feature)
996 feature)
997
997
998 if self.plain():
998 if self.plain():
999 return "text"
999 return "text"
1000
1000
1001 # Default interface for all the features
1001 # Default interface for all the features
1002 defaultinterface = "text"
1002 defaultinterface = "text"
1003 i = self.config("ui", "interface", None)
1003 i = self.config("ui", "interface", None)
1004 if i in alldefaults:
1004 if i in alldefaults:
1005 defaultinterface = i
1005 defaultinterface = i
1006
1006
1007 choseninterface = defaultinterface
1007 choseninterface = defaultinterface
1008 f = self.config("ui", "interface.%s" % feature, None)
1008 f = self.config("ui", "interface.%s" % feature, None)
1009 if f in availableinterfaces:
1009 if f in availableinterfaces:
1010 choseninterface = f
1010 choseninterface = f
1011
1011
1012 if i is not None and defaultinterface != i:
1012 if i is not None and defaultinterface != i:
1013 if f is not None:
1013 if f is not None:
1014 self.warn(_("invalid value for ui.interface: %s\n") %
1014 self.warn(_("invalid value for ui.interface: %s\n") %
1015 (i,))
1015 (i,))
1016 else:
1016 else:
1017 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1017 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1018 (i, choseninterface))
1018 (i, choseninterface))
1019 if f is not None and choseninterface != f:
1019 if f is not None and choseninterface != f:
1020 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1020 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1021 (feature, f, choseninterface))
1021 (feature, f, choseninterface))
1022
1022
1023 return choseninterface
1023 return choseninterface
1024
1024
1025 def interactive(self):
1025 def interactive(self):
1026 '''is interactive input allowed?
1026 '''is interactive input allowed?
1027
1027
1028 An interactive session is a session where input can be reasonably read
1028 An interactive session is a session where input can be reasonably read
1029 from `sys.stdin'. If this function returns false, any attempt to read
1029 from `sys.stdin'. If this function returns false, any attempt to read
1030 from stdin should fail with an error, unless a sensible default has been
1030 from stdin should fail with an error, unless a sensible default has been
1031 specified.
1031 specified.
1032
1032
1033 Interactiveness is triggered by the value of the `ui.interactive'
1033 Interactiveness is triggered by the value of the `ui.interactive'
1034 configuration variable or - if it is unset - when `sys.stdin' points
1034 configuration variable or - if it is unset - when `sys.stdin' points
1035 to a terminal device.
1035 to a terminal device.
1036
1036
1037 This function refers to input only; for output, see `ui.formatted()'.
1037 This function refers to input only; for output, see `ui.formatted()'.
1038 '''
1038 '''
1039 i = self.configbool("ui", "interactive", None)
1039 i = self.configbool("ui", "interactive", None)
1040 if i is None:
1040 if i is None:
1041 # some environments replace stdin without implementing isatty
1041 # some environments replace stdin without implementing isatty
1042 # usually those are non-interactive
1042 # usually those are non-interactive
1043 return self._isatty(self.fin)
1043 return self._isatty(self.fin)
1044
1044
1045 return i
1045 return i
1046
1046
1047 def termwidth(self):
1047 def termwidth(self):
1048 '''how wide is the terminal in columns?
1048 '''how wide is the terminal in columns?
1049 '''
1049 '''
1050 if 'COLUMNS' in encoding.environ:
1050 if 'COLUMNS' in encoding.environ:
1051 try:
1051 try:
1052 return int(encoding.environ['COLUMNS'])
1052 return int(encoding.environ['COLUMNS'])
1053 except ValueError:
1053 except ValueError:
1054 pass
1054 pass
1055 return scmutil.termsize(self)[0]
1055 return scmutil.termsize(self)[0]
1056
1056
1057 def formatted(self):
1057 def formatted(self):
1058 '''should formatted output be used?
1058 '''should formatted output be used?
1059
1059
1060 It is often desirable to format the output to suite the output medium.
1060 It is often desirable to format the output to suite the output medium.
1061 Examples of this are truncating long lines or colorizing messages.
1061 Examples of this are truncating long lines or colorizing messages.
1062 However, this is not often not desirable when piping output into other
1062 However, this is not often not desirable when piping output into other
1063 utilities, e.g. `grep'.
1063 utilities, e.g. `grep'.
1064
1064
1065 Formatted output is triggered by the value of the `ui.formatted'
1065 Formatted output is triggered by the value of the `ui.formatted'
1066 configuration variable or - if it is unset - when `sys.stdout' points
1066 configuration variable or - if it is unset - when `sys.stdout' points
1067 to a terminal device. Please note that `ui.formatted' should be
1067 to a terminal device. Please note that `ui.formatted' should be
1068 considered an implementation detail; it is not intended for use outside
1068 considered an implementation detail; it is not intended for use outside
1069 Mercurial or its extensions.
1069 Mercurial or its extensions.
1070
1070
1071 This function refers to output only; for input, see `ui.interactive()'.
1071 This function refers to output only; for input, see `ui.interactive()'.
1072 This function always returns false when in plain mode, see `ui.plain()'.
1072 This function always returns false when in plain mode, see `ui.plain()'.
1073 '''
1073 '''
1074 if self.plain():
1074 if self.plain():
1075 return False
1075 return False
1076
1076
1077 i = self.configbool("ui", "formatted", None)
1077 i = self.configbool("ui", "formatted", None)
1078 if i is None:
1078 if i is None:
1079 # some environments replace stdout without implementing isatty
1079 # some environments replace stdout without implementing isatty
1080 # usually those are non-interactive
1080 # usually those are non-interactive
1081 return self._isatty(self.fout)
1081 return self._isatty(self.fout)
1082
1082
1083 return i
1083 return i
1084
1084
1085 def _readline(self, prompt=''):
1085 def _readline(self, prompt=''):
1086 if self._isatty(self.fin):
1086 if self._isatty(self.fin):
1087 try:
1087 try:
1088 # magically add command line editing support, where
1088 # magically add command line editing support, where
1089 # available
1089 # available
1090 import readline
1090 import readline
1091 # force demandimport to really load the module
1091 # force demandimport to really load the module
1092 readline.read_history_file
1092 readline.read_history_file
1093 # windows sometimes raises something other than ImportError
1093 # windows sometimes raises something other than ImportError
1094 except Exception:
1094 except Exception:
1095 pass
1095 pass
1096
1096
1097 # call write() so output goes through subclassed implementation
1097 # call write() so output goes through subclassed implementation
1098 # e.g. color extension on Windows
1098 # e.g. color extension on Windows
1099 self.write(prompt, prompt=True)
1099 self.write(prompt, prompt=True)
1100
1100
1101 # instead of trying to emulate raw_input, swap (self.fin,
1101 # instead of trying to emulate raw_input, swap (self.fin,
1102 # self.fout) with (sys.stdin, sys.stdout)
1102 # self.fout) with (sys.stdin, sys.stdout)
1103 oldin = sys.stdin
1103 oldin = sys.stdin
1104 oldout = sys.stdout
1104 oldout = sys.stdout
1105 sys.stdin = self.fin
1105 sys.stdin = self.fin
1106 sys.stdout = self.fout
1106 sys.stdout = self.fout
1107 # prompt ' ' must exist; otherwise readline may delete entire line
1107 # prompt ' ' must exist; otherwise readline may delete entire line
1108 # - http://bugs.python.org/issue12833
1108 # - http://bugs.python.org/issue12833
1109 with self.timeblockedsection('stdio'):
1109 with self.timeblockedsection('stdio'):
1110 line = raw_input(' ')
1110 line = raw_input(' ')
1111 sys.stdin = oldin
1111 sys.stdin = oldin
1112 sys.stdout = oldout
1112 sys.stdout = oldout
1113
1113
1114 # When stdin is in binary mode on Windows, it can cause
1114 # When stdin is in binary mode on Windows, it can cause
1115 # raw_input() to emit an extra trailing carriage return
1115 # raw_input() to emit an extra trailing carriage return
1116 if os.linesep == '\r\n' and line and line[-1] == '\r':
1116 if os.linesep == '\r\n' and line and line[-1] == '\r':
1117 line = line[:-1]
1117 line = line[:-1]
1118 return line
1118 return line
1119
1119
1120 def prompt(self, msg, default="y"):
1120 def prompt(self, msg, default="y"):
1121 """Prompt user with msg, read response.
1121 """Prompt user with msg, read response.
1122 If ui is not interactive, the default is returned.
1122 If ui is not interactive, the default is returned.
1123 """
1123 """
1124 if not self.interactive():
1124 if not self.interactive():
1125 self.write(msg, ' ', default or '', "\n")
1125 self.write(msg, ' ', default or '', "\n")
1126 return default
1126 return default
1127 try:
1127 try:
1128 r = self._readline(self.label(msg, 'ui.prompt'))
1128 r = self._readline(self.label(msg, 'ui.prompt'))
1129 if not r:
1129 if not r:
1130 r = default
1130 r = default
1131 if self.configbool('ui', 'promptecho'):
1131 if self.configbool('ui', 'promptecho'):
1132 self.write(r, "\n")
1132 self.write(r, "\n")
1133 return r
1133 return r
1134 except EOFError:
1134 except EOFError:
1135 raise error.ResponseExpected()
1135 raise error.ResponseExpected()
1136
1136
1137 @staticmethod
1137 @staticmethod
1138 def extractchoices(prompt):
1138 def extractchoices(prompt):
1139 """Extract prompt message and list of choices from specified prompt.
1139 """Extract prompt message and list of choices from specified prompt.
1140
1140
1141 This returns tuple "(message, choices)", and "choices" is the
1141 This returns tuple "(message, choices)", and "choices" is the
1142 list of tuple "(response character, text without &)".
1142 list of tuple "(response character, text without &)".
1143
1143
1144 >>> ui.extractchoices("awake? $$ &Yes $$ &No")
1144 >>> ui.extractchoices("awake? $$ &Yes $$ &No")
1145 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1145 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1146 >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
1146 >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
1147 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1147 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1148 >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
1148 >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
1149 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1149 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1150 """
1150 """
1151
1151
1152 # Sadly, the prompt string may have been built with a filename
1152 # Sadly, the prompt string may have been built with a filename
1153 # containing "$$" so let's try to find the first valid-looking
1153 # containing "$$" so let's try to find the first valid-looking
1154 # prompt to start parsing. Sadly, we also can't rely on
1154 # prompt to start parsing. Sadly, we also can't rely on
1155 # choices containing spaces, ASCII, or basically anything
1155 # choices containing spaces, ASCII, or basically anything
1156 # except an ampersand followed by a character.
1156 # except an ampersand followed by a character.
1157 m = re.match(r'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1157 m = re.match(r'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1158 msg = m.group(1)
1158 msg = m.group(1)
1159 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1159 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1160 return (msg,
1160 return (msg,
1161 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
1161 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
1162 for s in choices])
1162 for s in choices])
1163
1163
1164 def promptchoice(self, prompt, default=0):
1164 def promptchoice(self, prompt, default=0):
1165 """Prompt user with a message, read response, and ensure it matches
1165 """Prompt user with a message, read response, and ensure it matches
1166 one of the provided choices. The prompt is formatted as follows:
1166 one of the provided choices. The prompt is formatted as follows:
1167
1167
1168 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1168 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1169
1169
1170 The index of the choice is returned. Responses are case
1170 The index of the choice is returned. Responses are case
1171 insensitive. If ui is not interactive, the default is
1171 insensitive. If ui is not interactive, the default is
1172 returned.
1172 returned.
1173 """
1173 """
1174
1174
1175 msg, choices = self.extractchoices(prompt)
1175 msg, choices = self.extractchoices(prompt)
1176 resps = [r for r, t in choices]
1176 resps = [r for r, t in choices]
1177 while True:
1177 while True:
1178 r = self.prompt(msg, resps[default])
1178 r = self.prompt(msg, resps[default])
1179 if r.lower() in resps:
1179 if r.lower() in resps:
1180 return resps.index(r.lower())
1180 return resps.index(r.lower())
1181 self.write(_("unrecognized response\n"))
1181 self.write(_("unrecognized response\n"))
1182
1182
1183 def getpass(self, prompt=None, default=None):
1183 def getpass(self, prompt=None, default=None):
1184 if not self.interactive():
1184 if not self.interactive():
1185 return default
1185 return default
1186 try:
1186 try:
1187 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1187 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1188 # disable getpass() only if explicitly specified. it's still valid
1188 # disable getpass() only if explicitly specified. it's still valid
1189 # to interact with tty even if fin is not a tty.
1189 # to interact with tty even if fin is not a tty.
1190 with self.timeblockedsection('stdio'):
1190 with self.timeblockedsection('stdio'):
1191 if self.configbool('ui', 'nontty'):
1191 if self.configbool('ui', 'nontty'):
1192 l = self.fin.readline()
1192 l = self.fin.readline()
1193 if not l:
1193 if not l:
1194 raise EOFError
1194 raise EOFError
1195 return l.rstrip('\n')
1195 return l.rstrip('\n')
1196 else:
1196 else:
1197 return getpass.getpass('')
1197 return getpass.getpass('')
1198 except EOFError:
1198 except EOFError:
1199 raise error.ResponseExpected()
1199 raise error.ResponseExpected()
1200 def status(self, *msg, **opts):
1200 def status(self, *msg, **opts):
1201 '''write status message to output (if ui.quiet is False)
1201 '''write status message to output (if ui.quiet is False)
1202
1202
1203 This adds an output label of "ui.status".
1203 This adds an output label of "ui.status".
1204 '''
1204 '''
1205 if not self.quiet:
1205 if not self.quiet:
1206 opts['label'] = opts.get('label', '') + ' ui.status'
1206 opts['label'] = opts.get('label', '') + ' ui.status'
1207 self.write(*msg, **opts)
1207 self.write(*msg, **opts)
1208 def warn(self, *msg, **opts):
1208 def warn(self, *msg, **opts):
1209 '''write warning message to output (stderr)
1209 '''write warning message to output (stderr)
1210
1210
1211 This adds an output label of "ui.warning".
1211 This adds an output label of "ui.warning".
1212 '''
1212 '''
1213 opts['label'] = opts.get('label', '') + ' ui.warning'
1213 opts['label'] = opts.get('label', '') + ' ui.warning'
1214 self.write_err(*msg, **opts)
1214 self.write_err(*msg, **opts)
1215 def note(self, *msg, **opts):
1215 def note(self, *msg, **opts):
1216 '''write note to output (if ui.verbose is True)
1216 '''write note to output (if ui.verbose is True)
1217
1217
1218 This adds an output label of "ui.note".
1218 This adds an output label of "ui.note".
1219 '''
1219 '''
1220 if self.verbose:
1220 if self.verbose:
1221 opts['label'] = opts.get('label', '') + ' ui.note'
1221 opts['label'] = opts.get('label', '') + ' ui.note'
1222 self.write(*msg, **opts)
1222 self.write(*msg, **opts)
1223 def debug(self, *msg, **opts):
1223 def debug(self, *msg, **opts):
1224 '''write debug message to output (if ui.debugflag is True)
1224 '''write debug message to output (if ui.debugflag is True)
1225
1225
1226 This adds an output label of "ui.debug".
1226 This adds an output label of "ui.debug".
1227 '''
1227 '''
1228 if self.debugflag:
1228 if self.debugflag:
1229 opts['label'] = opts.get('label', '') + ' ui.debug'
1229 opts['label'] = opts.get('label', '') + ' ui.debug'
1230 self.write(*msg, **opts)
1230 self.write(*msg, **opts)
1231
1231
1232 def edit(self, text, user, extra=None, editform=None, pending=None,
1232 def edit(self, text, user, extra=None, editform=None, pending=None,
1233 repopath=None):
1233 repopath=None):
1234 extra_defaults = {
1234 extra_defaults = {
1235 'prefix': 'editor',
1235 'prefix': 'editor',
1236 'suffix': '.txt',
1236 'suffix': '.txt',
1237 }
1237 }
1238 if extra is not None:
1238 if extra is not None:
1239 extra_defaults.update(extra)
1239 extra_defaults.update(extra)
1240 extra = extra_defaults
1240 extra = extra_defaults
1241
1241
1242 rdir = None
1242 rdir = None
1243 if self.configbool('experimental', 'editortmpinhg'):
1243 if self.configbool('experimental', 'editortmpinhg'):
1244 rdir = repopath
1244 rdir = repopath
1245 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1245 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1246 suffix=extra['suffix'], text=True,
1246 suffix=extra['suffix'], text=True,
1247 dir=rdir)
1247 dir=rdir)
1248 try:
1248 try:
1249 f = os.fdopen(fd, pycompat.sysstr("w"))
1249 f = os.fdopen(fd, pycompat.sysstr("w"))
1250 f.write(text)
1250 f.write(text)
1251 f.close()
1251 f.close()
1252
1252
1253 environ = {'HGUSER': user}
1253 environ = {'HGUSER': user}
1254 if 'transplant_source' in extra:
1254 if 'transplant_source' in extra:
1255 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1255 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1256 for label in ('intermediate-source', 'source', 'rebase_source'):
1256 for label in ('intermediate-source', 'source', 'rebase_source'):
1257 if label in extra:
1257 if label in extra:
1258 environ.update({'HGREVISION': extra[label]})
1258 environ.update({'HGREVISION': extra[label]})
1259 break
1259 break
1260 if editform:
1260 if editform:
1261 environ.update({'HGEDITFORM': editform})
1261 environ.update({'HGEDITFORM': editform})
1262 if pending:
1262 if pending:
1263 environ.update({'HG_PENDING': pending})
1263 environ.update({'HG_PENDING': pending})
1264
1264
1265 editor = self.geteditor()
1265 editor = self.geteditor()
1266
1266
1267 self.system("%s \"%s\"" % (editor, name),
1267 self.system("%s \"%s\"" % (editor, name),
1268 environ=environ,
1268 environ=environ,
1269 onerr=error.Abort, errprefix=_("edit failed"),
1269 onerr=error.Abort, errprefix=_("edit failed"),
1270 blockedtag='editor')
1270 blockedtag='editor')
1271
1271
1272 f = open(name)
1272 f = open(name)
1273 t = f.read()
1273 t = f.read()
1274 f.close()
1274 f.close()
1275 finally:
1275 finally:
1276 os.unlink(name)
1276 os.unlink(name)
1277
1277
1278 return t
1278 return t
1279
1279
1280 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1280 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1281 blockedtag=None):
1281 blockedtag=None):
1282 '''execute shell command with appropriate output stream. command
1282 '''execute shell command with appropriate output stream. command
1283 output will be redirected if fout is not stdout.
1283 output will be redirected if fout is not stdout.
1284 '''
1284 '''
1285 if blockedtag is None:
1285 if blockedtag is None:
1286 blockedtag = 'unknown_system_' + cmd.translate(None, _keepalnum)
1286 blockedtag = 'unknown_system_' + cmd.translate(None, _keepalnum)
1287 out = self.fout
1287 out = self.fout
1288 if any(s[1] for s in self._bufferstates):
1288 if any(s[1] for s in self._bufferstates):
1289 out = self
1289 out = self
1290 with self.timeblockedsection(blockedtag):
1290 with self.timeblockedsection(blockedtag):
1291 return util.system(cmd, environ=environ, cwd=cwd, onerr=onerr,
1291 return self._runsystem(cmd, environ=environ, cwd=cwd, onerr=onerr,
1292 errprefix=errprefix, out=out)
1292 errprefix=errprefix, out=out)
1293
1294 def _runsystem(self, cmd, environ, cwd, onerr, errprefix, out):
1295 """actually execute the given shell command (can be overridden by
1296 extensions like chg)"""
1297 return util.system(cmd, environ=environ, cwd=cwd, onerr=onerr,
1298 errprefix=errprefix, out=out)
1293
1299
1294 def traceback(self, exc=None, force=False):
1300 def traceback(self, exc=None, force=False):
1295 '''print exception traceback if traceback printing enabled or forced.
1301 '''print exception traceback if traceback printing enabled or forced.
1296 only to call in exception handler. returns true if traceback
1302 only to call in exception handler. returns true if traceback
1297 printed.'''
1303 printed.'''
1298 if self.tracebackflag or force:
1304 if self.tracebackflag or force:
1299 if exc is None:
1305 if exc is None:
1300 exc = sys.exc_info()
1306 exc = sys.exc_info()
1301 cause = getattr(exc[1], 'cause', None)
1307 cause = getattr(exc[1], 'cause', None)
1302
1308
1303 if cause is not None:
1309 if cause is not None:
1304 causetb = traceback.format_tb(cause[2])
1310 causetb = traceback.format_tb(cause[2])
1305 exctb = traceback.format_tb(exc[2])
1311 exctb = traceback.format_tb(exc[2])
1306 exconly = traceback.format_exception_only(cause[0], cause[1])
1312 exconly = traceback.format_exception_only(cause[0], cause[1])
1307
1313
1308 # exclude frame where 'exc' was chained and rethrown from exctb
1314 # exclude frame where 'exc' was chained and rethrown from exctb
1309 self.write_err('Traceback (most recent call last):\n',
1315 self.write_err('Traceback (most recent call last):\n',
1310 ''.join(exctb[:-1]),
1316 ''.join(exctb[:-1]),
1311 ''.join(causetb),
1317 ''.join(causetb),
1312 ''.join(exconly))
1318 ''.join(exconly))
1313 else:
1319 else:
1314 output = traceback.format_exception(exc[0], exc[1], exc[2])
1320 output = traceback.format_exception(exc[0], exc[1], exc[2])
1315 self.write_err(''.join(output))
1321 self.write_err(''.join(output))
1316 return self.tracebackflag or force
1322 return self.tracebackflag or force
1317
1323
1318 def geteditor(self):
1324 def geteditor(self):
1319 '''return editor to use'''
1325 '''return editor to use'''
1320 if pycompat.sysplatform == 'plan9':
1326 if pycompat.sysplatform == 'plan9':
1321 # vi is the MIPS instruction simulator on Plan 9. We
1327 # vi is the MIPS instruction simulator on Plan 9. We
1322 # instead default to E to plumb commit messages to
1328 # instead default to E to plumb commit messages to
1323 # avoid confusion.
1329 # avoid confusion.
1324 editor = 'E'
1330 editor = 'E'
1325 else:
1331 else:
1326 editor = 'vi'
1332 editor = 'vi'
1327 return (encoding.environ.get("HGEDITOR") or
1333 return (encoding.environ.get("HGEDITOR") or
1328 self.config("ui", "editor") or
1334 self.config("ui", "editor") or
1329 encoding.environ.get("VISUAL") or
1335 encoding.environ.get("VISUAL") or
1330 encoding.environ.get("EDITOR", editor))
1336 encoding.environ.get("EDITOR", editor))
1331
1337
1332 @util.propertycache
1338 @util.propertycache
1333 def _progbar(self):
1339 def _progbar(self):
1334 """setup the progbar singleton to the ui object"""
1340 """setup the progbar singleton to the ui object"""
1335 if (self.quiet or self.debugflag
1341 if (self.quiet or self.debugflag
1336 or self.configbool('progress', 'disable', False)
1342 or self.configbool('progress', 'disable', False)
1337 or not progress.shouldprint(self)):
1343 or not progress.shouldprint(self)):
1338 return None
1344 return None
1339 return getprogbar(self)
1345 return getprogbar(self)
1340
1346
1341 def _progclear(self):
1347 def _progclear(self):
1342 """clear progress bar output if any. use it before any output"""
1348 """clear progress bar output if any. use it before any output"""
1343 if '_progbar' not in vars(self): # nothing loaded yet
1349 if '_progbar' not in vars(self): # nothing loaded yet
1344 return
1350 return
1345 if self._progbar is not None and self._progbar.printed:
1351 if self._progbar is not None and self._progbar.printed:
1346 self._progbar.clear()
1352 self._progbar.clear()
1347
1353
1348 def progress(self, topic, pos, item="", unit="", total=None):
1354 def progress(self, topic, pos, item="", unit="", total=None):
1349 '''show a progress message
1355 '''show a progress message
1350
1356
1351 By default a textual progress bar will be displayed if an operation
1357 By default a textual progress bar will be displayed if an operation
1352 takes too long. 'topic' is the current operation, 'item' is a
1358 takes too long. 'topic' is the current operation, 'item' is a
1353 non-numeric marker of the current position (i.e. the currently
1359 non-numeric marker of the current position (i.e. the currently
1354 in-process file), 'pos' is the current numeric position (i.e.
1360 in-process file), 'pos' is the current numeric position (i.e.
1355 revision, bytes, etc.), unit is a corresponding unit label,
1361 revision, bytes, etc.), unit is a corresponding unit label,
1356 and total is the highest expected pos.
1362 and total is the highest expected pos.
1357
1363
1358 Multiple nested topics may be active at a time.
1364 Multiple nested topics may be active at a time.
1359
1365
1360 All topics should be marked closed by setting pos to None at
1366 All topics should be marked closed by setting pos to None at
1361 termination.
1367 termination.
1362 '''
1368 '''
1363 if self._progbar is not None:
1369 if self._progbar is not None:
1364 self._progbar.progress(topic, pos, item=item, unit=unit,
1370 self._progbar.progress(topic, pos, item=item, unit=unit,
1365 total=total)
1371 total=total)
1366 if pos is None or not self.configbool('progress', 'debug'):
1372 if pos is None or not self.configbool('progress', 'debug'):
1367 return
1373 return
1368
1374
1369 if unit:
1375 if unit:
1370 unit = ' ' + unit
1376 unit = ' ' + unit
1371 if item:
1377 if item:
1372 item = ' ' + item
1378 item = ' ' + item
1373
1379
1374 if total:
1380 if total:
1375 pct = 100.0 * pos / total
1381 pct = 100.0 * pos / total
1376 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
1382 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
1377 % (topic, item, pos, total, unit, pct))
1383 % (topic, item, pos, total, unit, pct))
1378 else:
1384 else:
1379 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
1385 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
1380
1386
1381 def log(self, service, *msg, **opts):
1387 def log(self, service, *msg, **opts):
1382 '''hook for logging facility extensions
1388 '''hook for logging facility extensions
1383
1389
1384 service should be a readily-identifiable subsystem, which will
1390 service should be a readily-identifiable subsystem, which will
1385 allow filtering.
1391 allow filtering.
1386
1392
1387 *msg should be a newline-terminated format string to log, and
1393 *msg should be a newline-terminated format string to log, and
1388 then any values to %-format into that format string.
1394 then any values to %-format into that format string.
1389
1395
1390 **opts currently has no defined meanings.
1396 **opts currently has no defined meanings.
1391 '''
1397 '''
1392
1398
1393 def label(self, msg, label):
1399 def label(self, msg, label):
1394 '''style msg based on supplied label
1400 '''style msg based on supplied label
1395
1401
1396 If some color mode is enabled, this will add the necessary control
1402 If some color mode is enabled, this will add the necessary control
1397 characters to apply such color. In addition, 'debug' color mode adds
1403 characters to apply such color. In addition, 'debug' color mode adds
1398 markup showing which label affects a piece of text.
1404 markup showing which label affects a piece of text.
1399
1405
1400 ui.write(s, 'label') is equivalent to
1406 ui.write(s, 'label') is equivalent to
1401 ui.write(ui.label(s, 'label')).
1407 ui.write(ui.label(s, 'label')).
1402 '''
1408 '''
1403 if self._colormode is not None:
1409 if self._colormode is not None:
1404 return color.colorlabel(self, msg, label)
1410 return color.colorlabel(self, msg, label)
1405 return msg
1411 return msg
1406
1412
1407 def develwarn(self, msg, stacklevel=1, config=None):
1413 def develwarn(self, msg, stacklevel=1, config=None):
1408 """issue a developer warning message
1414 """issue a developer warning message
1409
1415
1410 Use 'stacklevel' to report the offender some layers further up in the
1416 Use 'stacklevel' to report the offender some layers further up in the
1411 stack.
1417 stack.
1412 """
1418 """
1413 if not self.configbool('devel', 'all-warnings'):
1419 if not self.configbool('devel', 'all-warnings'):
1414 if config is not None and not self.configbool('devel', config):
1420 if config is not None and not self.configbool('devel', config):
1415 return
1421 return
1416 msg = 'devel-warn: ' + msg
1422 msg = 'devel-warn: ' + msg
1417 stacklevel += 1 # get in develwarn
1423 stacklevel += 1 # get in develwarn
1418 if self.tracebackflag:
1424 if self.tracebackflag:
1419 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1425 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1420 self.log('develwarn', '%s at:\n%s' %
1426 self.log('develwarn', '%s at:\n%s' %
1421 (msg, ''.join(util.getstackframes(stacklevel))))
1427 (msg, ''.join(util.getstackframes(stacklevel))))
1422 else:
1428 else:
1423 curframe = inspect.currentframe()
1429 curframe = inspect.currentframe()
1424 calframe = inspect.getouterframes(curframe, 2)
1430 calframe = inspect.getouterframes(curframe, 2)
1425 self.write_err('%s at: %s:%s (%s)\n'
1431 self.write_err('%s at: %s:%s (%s)\n'
1426 % ((msg,) + calframe[stacklevel][1:4]))
1432 % ((msg,) + calframe[stacklevel][1:4]))
1427 self.log('develwarn', '%s at: %s:%s (%s)\n',
1433 self.log('develwarn', '%s at: %s:%s (%s)\n',
1428 msg, *calframe[stacklevel][1:4])
1434 msg, *calframe[stacklevel][1:4])
1429 curframe = calframe = None # avoid cycles
1435 curframe = calframe = None # avoid cycles
1430
1436
1431 def deprecwarn(self, msg, version):
1437 def deprecwarn(self, msg, version):
1432 """issue a deprecation warning
1438 """issue a deprecation warning
1433
1439
1434 - msg: message explaining what is deprecated and how to upgrade,
1440 - msg: message explaining what is deprecated and how to upgrade,
1435 - version: last version where the API will be supported,
1441 - version: last version where the API will be supported,
1436 """
1442 """
1437 if not (self.configbool('devel', 'all-warnings')
1443 if not (self.configbool('devel', 'all-warnings')
1438 or self.configbool('devel', 'deprec-warn')):
1444 or self.configbool('devel', 'deprec-warn')):
1439 return
1445 return
1440 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1446 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1441 " update your code.)") % version
1447 " update your code.)") % version
1442 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1448 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1443
1449
1444 def exportableenviron(self):
1450 def exportableenviron(self):
1445 """The environment variables that are safe to export, e.g. through
1451 """The environment variables that are safe to export, e.g. through
1446 hgweb.
1452 hgweb.
1447 """
1453 """
1448 return self._exportableenviron
1454 return self._exportableenviron
1449
1455
1450 @contextlib.contextmanager
1456 @contextlib.contextmanager
1451 def configoverride(self, overrides, source=""):
1457 def configoverride(self, overrides, source=""):
1452 """Context manager for temporary config overrides
1458 """Context manager for temporary config overrides
1453 `overrides` must be a dict of the following structure:
1459 `overrides` must be a dict of the following structure:
1454 {(section, name) : value}"""
1460 {(section, name) : value}"""
1455 backups = {}
1461 backups = {}
1456 try:
1462 try:
1457 for (section, name), value in overrides.items():
1463 for (section, name), value in overrides.items():
1458 backups[(section, name)] = self.backupconfig(section, name)
1464 backups[(section, name)] = self.backupconfig(section, name)
1459 self.setconfig(section, name, value, source)
1465 self.setconfig(section, name, value, source)
1460 yield
1466 yield
1461 finally:
1467 finally:
1462 for __, backup in backups.items():
1468 for __, backup in backups.items():
1463 self.restoreconfig(backup)
1469 self.restoreconfig(backup)
1464 # just restoring ui.quiet config to the previous value is not enough
1470 # just restoring ui.quiet config to the previous value is not enough
1465 # as it does not update ui.quiet class member
1471 # as it does not update ui.quiet class member
1466 if ('ui', 'quiet') in overrides:
1472 if ('ui', 'quiet') in overrides:
1467 self.fixconfig(section='ui')
1473 self.fixconfig(section='ui')
1468
1474
1469 class paths(dict):
1475 class paths(dict):
1470 """Represents a collection of paths and their configs.
1476 """Represents a collection of paths and their configs.
1471
1477
1472 Data is initially derived from ui instances and the config files they have
1478 Data is initially derived from ui instances and the config files they have
1473 loaded.
1479 loaded.
1474 """
1480 """
1475 def __init__(self, ui):
1481 def __init__(self, ui):
1476 dict.__init__(self)
1482 dict.__init__(self)
1477
1483
1478 for name, loc in ui.configitems('paths', ignoresub=True):
1484 for name, loc in ui.configitems('paths', ignoresub=True):
1479 # No location is the same as not existing.
1485 # No location is the same as not existing.
1480 if not loc:
1486 if not loc:
1481 continue
1487 continue
1482 loc, sub = ui.configsuboptions('paths', name)
1488 loc, sub = ui.configsuboptions('paths', name)
1483 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1489 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1484
1490
1485 def getpath(self, name, default=None):
1491 def getpath(self, name, default=None):
1486 """Return a ``path`` from a string, falling back to default.
1492 """Return a ``path`` from a string, falling back to default.
1487
1493
1488 ``name`` can be a named path or locations. Locations are filesystem
1494 ``name`` can be a named path or locations. Locations are filesystem
1489 paths or URIs.
1495 paths or URIs.
1490
1496
1491 Returns None if ``name`` is not a registered path, a URI, or a local
1497 Returns None if ``name`` is not a registered path, a URI, or a local
1492 path to a repo.
1498 path to a repo.
1493 """
1499 """
1494 # Only fall back to default if no path was requested.
1500 # Only fall back to default if no path was requested.
1495 if name is None:
1501 if name is None:
1496 if not default:
1502 if not default:
1497 default = ()
1503 default = ()
1498 elif not isinstance(default, (tuple, list)):
1504 elif not isinstance(default, (tuple, list)):
1499 default = (default,)
1505 default = (default,)
1500 for k in default:
1506 for k in default:
1501 try:
1507 try:
1502 return self[k]
1508 return self[k]
1503 except KeyError:
1509 except KeyError:
1504 continue
1510 continue
1505 return None
1511 return None
1506
1512
1507 # Most likely empty string.
1513 # Most likely empty string.
1508 # This may need to raise in the future.
1514 # This may need to raise in the future.
1509 if not name:
1515 if not name:
1510 return None
1516 return None
1511
1517
1512 try:
1518 try:
1513 return self[name]
1519 return self[name]
1514 except KeyError:
1520 except KeyError:
1515 # Try to resolve as a local path or URI.
1521 # Try to resolve as a local path or URI.
1516 try:
1522 try:
1517 # We don't pass sub-options in, so no need to pass ui instance.
1523 # We don't pass sub-options in, so no need to pass ui instance.
1518 return path(None, None, rawloc=name)
1524 return path(None, None, rawloc=name)
1519 except ValueError:
1525 except ValueError:
1520 raise error.RepoError(_('repository %s does not exist') %
1526 raise error.RepoError(_('repository %s does not exist') %
1521 name)
1527 name)
1522
1528
1523 _pathsuboptions = {}
1529 _pathsuboptions = {}
1524
1530
1525 def pathsuboption(option, attr):
1531 def pathsuboption(option, attr):
1526 """Decorator used to declare a path sub-option.
1532 """Decorator used to declare a path sub-option.
1527
1533
1528 Arguments are the sub-option name and the attribute it should set on
1534 Arguments are the sub-option name and the attribute it should set on
1529 ``path`` instances.
1535 ``path`` instances.
1530
1536
1531 The decorated function will receive as arguments a ``ui`` instance,
1537 The decorated function will receive as arguments a ``ui`` instance,
1532 ``path`` instance, and the string value of this option from the config.
1538 ``path`` instance, and the string value of this option from the config.
1533 The function should return the value that will be set on the ``path``
1539 The function should return the value that will be set on the ``path``
1534 instance.
1540 instance.
1535
1541
1536 This decorator can be used to perform additional verification of
1542 This decorator can be used to perform additional verification of
1537 sub-options and to change the type of sub-options.
1543 sub-options and to change the type of sub-options.
1538 """
1544 """
1539 def register(func):
1545 def register(func):
1540 _pathsuboptions[option] = (attr, func)
1546 _pathsuboptions[option] = (attr, func)
1541 return func
1547 return func
1542 return register
1548 return register
1543
1549
1544 @pathsuboption('pushurl', 'pushloc')
1550 @pathsuboption('pushurl', 'pushloc')
1545 def pushurlpathoption(ui, path, value):
1551 def pushurlpathoption(ui, path, value):
1546 u = util.url(value)
1552 u = util.url(value)
1547 # Actually require a URL.
1553 # Actually require a URL.
1548 if not u.scheme:
1554 if not u.scheme:
1549 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1555 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1550 return None
1556 return None
1551
1557
1552 # Don't support the #foo syntax in the push URL to declare branch to
1558 # Don't support the #foo syntax in the push URL to declare branch to
1553 # push.
1559 # push.
1554 if u.fragment:
1560 if u.fragment:
1555 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1561 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1556 'ignoring)\n') % path.name)
1562 'ignoring)\n') % path.name)
1557 u.fragment = None
1563 u.fragment = None
1558
1564
1559 return str(u)
1565 return str(u)
1560
1566
1561 @pathsuboption('pushrev', 'pushrev')
1567 @pathsuboption('pushrev', 'pushrev')
1562 def pushrevpathoption(ui, path, value):
1568 def pushrevpathoption(ui, path, value):
1563 return value
1569 return value
1564
1570
1565 class path(object):
1571 class path(object):
1566 """Represents an individual path and its configuration."""
1572 """Represents an individual path and its configuration."""
1567
1573
1568 def __init__(self, ui, name, rawloc=None, suboptions=None):
1574 def __init__(self, ui, name, rawloc=None, suboptions=None):
1569 """Construct a path from its config options.
1575 """Construct a path from its config options.
1570
1576
1571 ``ui`` is the ``ui`` instance the path is coming from.
1577 ``ui`` is the ``ui`` instance the path is coming from.
1572 ``name`` is the symbolic name of the path.
1578 ``name`` is the symbolic name of the path.
1573 ``rawloc`` is the raw location, as defined in the config.
1579 ``rawloc`` is the raw location, as defined in the config.
1574 ``pushloc`` is the raw locations pushes should be made to.
1580 ``pushloc`` is the raw locations pushes should be made to.
1575
1581
1576 If ``name`` is not defined, we require that the location be a) a local
1582 If ``name`` is not defined, we require that the location be a) a local
1577 filesystem path with a .hg directory or b) a URL. If not,
1583 filesystem path with a .hg directory or b) a URL. If not,
1578 ``ValueError`` is raised.
1584 ``ValueError`` is raised.
1579 """
1585 """
1580 if not rawloc:
1586 if not rawloc:
1581 raise ValueError('rawloc must be defined')
1587 raise ValueError('rawloc must be defined')
1582
1588
1583 # Locations may define branches via syntax <base>#<branch>.
1589 # Locations may define branches via syntax <base>#<branch>.
1584 u = util.url(rawloc)
1590 u = util.url(rawloc)
1585 branch = None
1591 branch = None
1586 if u.fragment:
1592 if u.fragment:
1587 branch = u.fragment
1593 branch = u.fragment
1588 u.fragment = None
1594 u.fragment = None
1589
1595
1590 self.url = u
1596 self.url = u
1591 self.branch = branch
1597 self.branch = branch
1592
1598
1593 self.name = name
1599 self.name = name
1594 self.rawloc = rawloc
1600 self.rawloc = rawloc
1595 self.loc = str(u)
1601 self.loc = str(u)
1596
1602
1597 # When given a raw location but not a symbolic name, validate the
1603 # When given a raw location but not a symbolic name, validate the
1598 # location is valid.
1604 # location is valid.
1599 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1605 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1600 raise ValueError('location is not a URL or path to a local '
1606 raise ValueError('location is not a URL or path to a local '
1601 'repo: %s' % rawloc)
1607 'repo: %s' % rawloc)
1602
1608
1603 suboptions = suboptions or {}
1609 suboptions = suboptions or {}
1604
1610
1605 # Now process the sub-options. If a sub-option is registered, its
1611 # Now process the sub-options. If a sub-option is registered, its
1606 # attribute will always be present. The value will be None if there
1612 # attribute will always be present. The value will be None if there
1607 # was no valid sub-option.
1613 # was no valid sub-option.
1608 for suboption, (attr, func) in _pathsuboptions.iteritems():
1614 for suboption, (attr, func) in _pathsuboptions.iteritems():
1609 if suboption not in suboptions:
1615 if suboption not in suboptions:
1610 setattr(self, attr, None)
1616 setattr(self, attr, None)
1611 continue
1617 continue
1612
1618
1613 value = func(ui, self, suboptions[suboption])
1619 value = func(ui, self, suboptions[suboption])
1614 setattr(self, attr, value)
1620 setattr(self, attr, value)
1615
1621
1616 def _isvalidlocalpath(self, path):
1622 def _isvalidlocalpath(self, path):
1617 """Returns True if the given path is a potentially valid repository.
1623 """Returns True if the given path is a potentially valid repository.
1618 This is its own function so that extensions can change the definition of
1624 This is its own function so that extensions can change the definition of
1619 'valid' in this case (like when pulling from a git repo into a hg
1625 'valid' in this case (like when pulling from a git repo into a hg
1620 one)."""
1626 one)."""
1621 return os.path.isdir(os.path.join(path, '.hg'))
1627 return os.path.isdir(os.path.join(path, '.hg'))
1622
1628
1623 @property
1629 @property
1624 def suboptions(self):
1630 def suboptions(self):
1625 """Return sub-options and their values for this path.
1631 """Return sub-options and their values for this path.
1626
1632
1627 This is intended to be used for presentation purposes.
1633 This is intended to be used for presentation purposes.
1628 """
1634 """
1629 d = {}
1635 d = {}
1630 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1636 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1631 value = getattr(self, attr)
1637 value = getattr(self, attr)
1632 if value is not None:
1638 if value is not None:
1633 d[subopt] = value
1639 d[subopt] = value
1634 return d
1640 return d
1635
1641
1636 # we instantiate one globally shared progress bar to avoid
1642 # we instantiate one globally shared progress bar to avoid
1637 # competing progress bars when multiple UI objects get created
1643 # competing progress bars when multiple UI objects get created
1638 _progresssingleton = None
1644 _progresssingleton = None
1639
1645
1640 def getprogbar(ui):
1646 def getprogbar(ui):
1641 global _progresssingleton
1647 global _progresssingleton
1642 if _progresssingleton is None:
1648 if _progresssingleton is None:
1643 # passing 'ui' object to the singleton is fishy,
1649 # passing 'ui' object to the singleton is fishy,
1644 # this is how the extension used to work but feel free to rework it.
1650 # this is how the extension used to work but feel free to rework it.
1645 _progresssingleton = progress.progbar(ui)
1651 _progresssingleton = progress.progbar(ui)
1646 return _progresssingleton
1652 return _progresssingleton
@@ -1,132 +1,172 b''
1 #require chg
1 #require chg
2
2
3 $ cp $HGRCPATH $HGRCPATH.orig
3 $ cp $HGRCPATH $HGRCPATH.orig
4
4
5 init repo
5 init repo
6
6
7 $ chg init foo
7 $ chg init foo
8 $ cd foo
8 $ cd foo
9
9
10 ill-formed config
10 ill-formed config
11
11
12 $ chg status
12 $ chg status
13 $ echo '=brokenconfig' >> $HGRCPATH
13 $ echo '=brokenconfig' >> $HGRCPATH
14 $ chg status
14 $ chg status
15 hg: parse error at * (glob)
15 hg: parse error at * (glob)
16 [255]
16 [255]
17
17
18 $ cp $HGRCPATH.orig $HGRCPATH
18 $ cp $HGRCPATH.orig $HGRCPATH
19
19
20 long socket path
20 long socket path
21
21
22 $ sockpath=$TESTTMP/this/path/should/be/longer/than/one-hundred-and-seven/characters/where/107/is/the/typical/size/limit/of/unix-domain-socket
22 $ sockpath=$TESTTMP/this/path/should/be/longer/than/one-hundred-and-seven/characters/where/107/is/the/typical/size/limit/of/unix-domain-socket
23 $ mkdir -p $sockpath
23 $ mkdir -p $sockpath
24 $ bakchgsockname=$CHGSOCKNAME
24 $ bakchgsockname=$CHGSOCKNAME
25 $ CHGSOCKNAME=$sockpath/server
25 $ CHGSOCKNAME=$sockpath/server
26 $ export CHGSOCKNAME
26 $ export CHGSOCKNAME
27 $ chg root
27 $ chg root
28 $TESTTMP/foo
28 $TESTTMP/foo
29 $ rm -rf $sockpath
29 $ rm -rf $sockpath
30 $ CHGSOCKNAME=$bakchgsockname
30 $ CHGSOCKNAME=$bakchgsockname
31 $ export CHGSOCKNAME
31 $ export CHGSOCKNAME
32
32
33 $ cd ..
33 $ cd ..
34
34
35 editor
36 ------
37
38 $ cat >> pushbuffer.py <<EOF
39 > def reposetup(ui, repo):
40 > repo.ui.pushbuffer(subproc=True)
41 > EOF
42
43 $ chg init editor
44 $ cd editor
45
46 by default, system() should be redirected to the client:
47
48 $ touch foo
49 $ CHGDEBUG= HGEDITOR=cat chg ci -Am channeled --edit 2>&1 \
50 > | egrep "HG:|run 'cat"
51 chg: debug: run 'cat "*"' at '$TESTTMP/editor' (glob)
52 HG: Enter commit message. Lines beginning with 'HG:' are removed.
53 HG: Leave message empty to abort commit.
54 HG: --
55 HG: user: test
56 HG: branch 'default'
57 HG: added foo
58
59 but no redirection should be made if output is captured:
60
61 $ touch bar
62 $ CHGDEBUG= HGEDITOR=cat chg ci -Am bufferred --edit \
63 > --config extensions.pushbuffer="$TESTTMP/pushbuffer.py" 2>&1 \
64 > | egrep "HG:|run 'cat"
65 [1]
66
67 check that commit commands succeeded:
68
69 $ hg log -T '{rev}:{desc}\n'
70 1:bufferred
71 0:channeled
72
73 $ cd ..
74
35 pager
75 pager
36 -----
76 -----
37
77
38 $ cat >> fakepager.py <<EOF
78 $ cat >> fakepager.py <<EOF
39 > import sys
79 > import sys
40 > for line in sys.stdin:
80 > for line in sys.stdin:
41 > sys.stdout.write('paged! %r\n' % line)
81 > sys.stdout.write('paged! %r\n' % line)
42 > EOF
82 > EOF
43
83
44 enable pager extension globally, but spawns the master server with no tty:
84 enable pager extension globally, but spawns the master server with no tty:
45
85
46 $ chg init pager
86 $ chg init pager
47 $ cd pager
87 $ cd pager
48 $ cat >> $HGRCPATH <<EOF
88 $ cat >> $HGRCPATH <<EOF
49 > [extensions]
89 > [extensions]
50 > pager =
90 > pager =
51 > [pager]
91 > [pager]
52 > pager = python $TESTTMP/fakepager.py
92 > pager = python $TESTTMP/fakepager.py
53 > EOF
93 > EOF
54 $ chg version > /dev/null
94 $ chg version > /dev/null
55 $ touch foo
95 $ touch foo
56 $ chg ci -qAm foo
96 $ chg ci -qAm foo
57
97
58 pager should be enabled if the attached client has a tty:
98 pager should be enabled if the attached client has a tty:
59
99
60 $ chg log -l1 -q --config ui.formatted=True
100 $ chg log -l1 -q --config ui.formatted=True
61 paged! '0:1f7b0de80e11\n'
101 paged! '0:1f7b0de80e11\n'
62 $ chg log -l1 -q --config ui.formatted=False
102 $ chg log -l1 -q --config ui.formatted=False
63 0:1f7b0de80e11
103 0:1f7b0de80e11
64
104
65 $ cd ..
105 $ cd ..
66
106
67 server lifecycle
107 server lifecycle
68 ----------------
108 ----------------
69
109
70 chg server should be restarted on code change, and old server will shut down
110 chg server should be restarted on code change, and old server will shut down
71 automatically. In this test, we use the following time parameters:
111 automatically. In this test, we use the following time parameters:
72
112
73 - "sleep 1" to make mtime different
113 - "sleep 1" to make mtime different
74 - "sleep 2" to notice mtime change (polling interval is 1 sec)
114 - "sleep 2" to notice mtime change (polling interval is 1 sec)
75
115
76 set up repository with an extension:
116 set up repository with an extension:
77
117
78 $ chg init extreload
118 $ chg init extreload
79 $ cd extreload
119 $ cd extreload
80 $ touch dummyext.py
120 $ touch dummyext.py
81 $ cat <<EOF >> .hg/hgrc
121 $ cat <<EOF >> .hg/hgrc
82 > [extensions]
122 > [extensions]
83 > dummyext = dummyext.py
123 > dummyext = dummyext.py
84 > EOF
124 > EOF
85
125
86 isolate socket directory for stable result:
126 isolate socket directory for stable result:
87
127
88 $ OLDCHGSOCKNAME=$CHGSOCKNAME
128 $ OLDCHGSOCKNAME=$CHGSOCKNAME
89 $ mkdir chgsock
129 $ mkdir chgsock
90 $ CHGSOCKNAME=`pwd`/chgsock/server
130 $ CHGSOCKNAME=`pwd`/chgsock/server
91
131
92 warm up server:
132 warm up server:
93
133
94 $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
134 $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
95 chg: debug: start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
135 chg: debug: start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
96
136
97 new server should be started if extension modified:
137 new server should be started if extension modified:
98
138
99 $ sleep 1
139 $ sleep 1
100 $ touch dummyext.py
140 $ touch dummyext.py
101 $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
141 $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
102 chg: debug: instruction: unlink $TESTTMP/extreload/chgsock/server-* (glob)
142 chg: debug: instruction: unlink $TESTTMP/extreload/chgsock/server-* (glob)
103 chg: debug: instruction: reconnect
143 chg: debug: instruction: reconnect
104 chg: debug: start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
144 chg: debug: start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
105
145
106 old server will shut down, while new server should still be reachable:
146 old server will shut down, while new server should still be reachable:
107
147
108 $ sleep 2
148 $ sleep 2
109 $ CHGDEBUG= chg log 2>&1 | (egrep 'instruction|start' || true)
149 $ CHGDEBUG= chg log 2>&1 | (egrep 'instruction|start' || true)
110
150
111 socket file should never be unlinked by old server:
151 socket file should never be unlinked by old server:
112 (simulates unowned socket by updating mtime, which makes sure server exits
152 (simulates unowned socket by updating mtime, which makes sure server exits
113 at polling cycle)
153 at polling cycle)
114
154
115 $ ls chgsock/server-*
155 $ ls chgsock/server-*
116 chgsock/server-* (glob)
156 chgsock/server-* (glob)
117 $ touch chgsock/server-*
157 $ touch chgsock/server-*
118 $ sleep 2
158 $ sleep 2
119 $ ls chgsock/server-*
159 $ ls chgsock/server-*
120 chgsock/server-* (glob)
160 chgsock/server-* (glob)
121
161
122 since no server is reachable from socket file, new server should be started:
162 since no server is reachable from socket file, new server should be started:
123 (this test makes sure that old server shut down automatically)
163 (this test makes sure that old server shut down automatically)
124
164
125 $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
165 $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
126 chg: debug: start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
166 chg: debug: start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
127
167
128 shut down servers and restore environment:
168 shut down servers and restore environment:
129
169
130 $ rm -R chgsock
170 $ rm -R chgsock
131 $ CHGSOCKNAME=$OLDCHGSOCKNAME
171 $ CHGSOCKNAME=$OLDCHGSOCKNAME
132 $ cd ..
172 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now