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