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