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