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