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