##// END OF EJS Templates
py3: handle keyword arguments correctly in ui.py...
Pulkit Goyal -
r35374:4e377c43 default
parent child Browse files
Show More
@@ -1,1848 +1,1848 b''
1 # ui.py - user interface bits for mercurial
1 # ui.py - user interface bits for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import collections
10 import collections
11 import contextlib
11 import contextlib
12 import errno
12 import errno
13 import getpass
13 import getpass
14 import inspect
14 import inspect
15 import os
15 import os
16 import re
16 import re
17 import signal
17 import signal
18 import socket
18 import socket
19 import subprocess
19 import subprocess
20 import sys
20 import sys
21 import tempfile
21 import tempfile
22 import traceback
22 import traceback
23
23
24 from .i18n import _
24 from .i18n import _
25 from .node import hex
25 from .node import hex
26
26
27 from . import (
27 from . import (
28 color,
28 color,
29 config,
29 config,
30 configitems,
30 configitems,
31 encoding,
31 encoding,
32 error,
32 error,
33 formatter,
33 formatter,
34 progress,
34 progress,
35 pycompat,
35 pycompat,
36 rcutil,
36 rcutil,
37 scmutil,
37 scmutil,
38 util,
38 util,
39 )
39 )
40
40
41 urlreq = util.urlreq
41 urlreq = util.urlreq
42
42
43 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
43 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
44 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
44 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
45 if not c.isalnum())
45 if not c.isalnum())
46
46
47 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
47 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
48 tweakrc = """
48 tweakrc = """
49 [ui]
49 [ui]
50 # The rollback command is dangerous. As a rule, don't use it.
50 # The rollback command is dangerous. As a rule, don't use it.
51 rollback = False
51 rollback = False
52 # Make `hg status` report copy information
52 # Make `hg status` report copy information
53 statuscopies = yes
53 statuscopies = yes
54 # 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('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('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('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 for a in msgs:
920 self.fout.write(a)
920 self.fout.write(a)
921 except IOError as err:
921 except IOError as err:
922 raise error.StdioError(err)
922 raise error.StdioError(err)
923 finally:
923 finally:
924 self._blockedtimes['stdio_blocked'] += \
924 self._blockedtimes['stdio_blocked'] += \
925 (util.timer() - starttime) * 1000
925 (util.timer() - starttime) * 1000
926
926
927 def write_err(self, *args, **opts):
927 def write_err(self, *args, **opts):
928 self._progclear()
928 self._progclear()
929 if self._bufferstates and self._bufferstates[-1][0]:
929 if self._bufferstates and self._bufferstates[-1][0]:
930 self.write(*args, **opts)
930 self.write(*args, **opts)
931 elif self._colormode == 'win32':
931 elif self._colormode == 'win32':
932 # windows color printing is its own can of crab, defer to
932 # windows color printing is its own can of crab, defer to
933 # the color module and that is it.
933 # the color module and that is it.
934 color.win32print(self, self._write_err, *args, **opts)
934 color.win32print(self, self._write_err, *args, **opts)
935 else:
935 else:
936 msgs = args
936 msgs = args
937 if self._colormode is not None:
937 if self._colormode is not None:
938 label = opts.get('label', '')
938 label = opts.get(r'label', '')
939 msgs = [self.label(a, label) for a in args]
939 msgs = [self.label(a, label) for a in args]
940 self._write_err(*msgs, **opts)
940 self._write_err(*msgs, **opts)
941
941
942 def _write_err(self, *msgs, **opts):
942 def _write_err(self, *msgs, **opts):
943 try:
943 try:
944 with self.timeblockedsection('stdio'):
944 with self.timeblockedsection('stdio'):
945 if not getattr(self.fout, 'closed', False):
945 if not getattr(self.fout, 'closed', False):
946 self.fout.flush()
946 self.fout.flush()
947 for a in msgs:
947 for a in msgs:
948 self.ferr.write(a)
948 self.ferr.write(a)
949 # stderr may be buffered under win32 when redirected to files,
949 # stderr may be buffered under win32 when redirected to files,
950 # including stdout.
950 # including stdout.
951 if not getattr(self.ferr, 'closed', False):
951 if not getattr(self.ferr, 'closed', False):
952 self.ferr.flush()
952 self.ferr.flush()
953 except IOError as inst:
953 except IOError as inst:
954 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
954 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
955 raise error.StdioError(inst)
955 raise error.StdioError(inst)
956
956
957 def flush(self):
957 def flush(self):
958 # opencode timeblockedsection because this is a critical path
958 # opencode timeblockedsection because this is a critical path
959 starttime = util.timer()
959 starttime = util.timer()
960 try:
960 try:
961 try:
961 try:
962 self.fout.flush()
962 self.fout.flush()
963 except IOError as err:
963 except IOError as err:
964 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
964 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
965 raise error.StdioError(err)
965 raise error.StdioError(err)
966 finally:
966 finally:
967 try:
967 try:
968 self.ferr.flush()
968 self.ferr.flush()
969 except IOError as err:
969 except IOError as err:
970 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
970 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
971 raise error.StdioError(err)
971 raise error.StdioError(err)
972 finally:
972 finally:
973 self._blockedtimes['stdio_blocked'] += \
973 self._blockedtimes['stdio_blocked'] += \
974 (util.timer() - starttime) * 1000
974 (util.timer() - starttime) * 1000
975
975
976 def _isatty(self, fh):
976 def _isatty(self, fh):
977 if self.configbool('ui', 'nontty'):
977 if self.configbool('ui', 'nontty'):
978 return False
978 return False
979 return util.isatty(fh)
979 return util.isatty(fh)
980
980
981 def disablepager(self):
981 def disablepager(self):
982 self._disablepager = True
982 self._disablepager = True
983
983
984 def pager(self, command):
984 def pager(self, command):
985 """Start a pager for subsequent command output.
985 """Start a pager for subsequent command output.
986
986
987 Commands which produce a long stream of output should call
987 Commands which produce a long stream of output should call
988 this function to activate the user's preferred pagination
988 this function to activate the user's preferred pagination
989 mechanism (which may be no pager). Calling this function
989 mechanism (which may be no pager). Calling this function
990 precludes any future use of interactive functionality, such as
990 precludes any future use of interactive functionality, such as
991 prompting the user or activating curses.
991 prompting the user or activating curses.
992
992
993 Args:
993 Args:
994 command: The full, non-aliased name of the command. That is, "log"
994 command: The full, non-aliased name of the command. That is, "log"
995 not "history, "summary" not "summ", etc.
995 not "history, "summary" not "summ", etc.
996 """
996 """
997 if (self._disablepager
997 if (self._disablepager
998 or self.pageractive):
998 or self.pageractive):
999 # how pager should do is already determined
999 # how pager should do is already determined
1000 return
1000 return
1001
1001
1002 if not command.startswith('internal-always-') and (
1002 if not command.startswith('internal-always-') and (
1003 # explicit --pager=on (= 'internal-always-' prefix) should
1003 # explicit --pager=on (= 'internal-always-' prefix) should
1004 # take precedence over disabling factors below
1004 # take precedence over disabling factors below
1005 command in self.configlist('pager', 'ignore')
1005 command in self.configlist('pager', 'ignore')
1006 or not self.configbool('ui', 'paginate')
1006 or not self.configbool('ui', 'paginate')
1007 or not self.configbool('pager', 'attend-' + command, True)
1007 or not self.configbool('pager', 'attend-' + command, True)
1008 # TODO: if we want to allow HGPLAINEXCEPT=pager,
1008 # TODO: if we want to allow HGPLAINEXCEPT=pager,
1009 # formatted() will need some adjustment.
1009 # formatted() will need some adjustment.
1010 or not self.formatted()
1010 or not self.formatted()
1011 or self.plain()
1011 or self.plain()
1012 or self._buffers
1012 or self._buffers
1013 # TODO: expose debugger-enabled on the UI object
1013 # TODO: expose debugger-enabled on the UI object
1014 or '--debugger' in pycompat.sysargv):
1014 or '--debugger' in pycompat.sysargv):
1015 # We only want to paginate if the ui appears to be
1015 # We only want to paginate if the ui appears to be
1016 # interactive, the user didn't say HGPLAIN or
1016 # interactive, the user didn't say HGPLAIN or
1017 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
1017 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
1018 return
1018 return
1019
1019
1020 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
1020 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
1021 if not pagercmd:
1021 if not pagercmd:
1022 return
1022 return
1023
1023
1024 pagerenv = {}
1024 pagerenv = {}
1025 for name, value in rcutil.defaultpagerenv().items():
1025 for name, value in rcutil.defaultpagerenv().items():
1026 if name not in encoding.environ:
1026 if name not in encoding.environ:
1027 pagerenv[name] = value
1027 pagerenv[name] = value
1028
1028
1029 self.debug('starting pager for command %r\n' % command)
1029 self.debug('starting pager for command %r\n' % command)
1030 self.flush()
1030 self.flush()
1031
1031
1032 wasformatted = self.formatted()
1032 wasformatted = self.formatted()
1033 if util.safehasattr(signal, "SIGPIPE"):
1033 if util.safehasattr(signal, "SIGPIPE"):
1034 signal.signal(signal.SIGPIPE, _catchterm)
1034 signal.signal(signal.SIGPIPE, _catchterm)
1035 if self._runpager(pagercmd, pagerenv):
1035 if self._runpager(pagercmd, pagerenv):
1036 self.pageractive = True
1036 self.pageractive = True
1037 # Preserve the formatted-ness of the UI. This is important
1037 # Preserve the formatted-ness of the UI. This is important
1038 # because we mess with stdout, which might confuse
1038 # because we mess with stdout, which might confuse
1039 # auto-detection of things being formatted.
1039 # auto-detection of things being formatted.
1040 self.setconfig('ui', 'formatted', wasformatted, 'pager')
1040 self.setconfig('ui', 'formatted', wasformatted, 'pager')
1041 self.setconfig('ui', 'interactive', False, 'pager')
1041 self.setconfig('ui', 'interactive', False, 'pager')
1042
1042
1043 # If pagermode differs from color.mode, reconfigure color now that
1043 # If pagermode differs from color.mode, reconfigure color now that
1044 # pageractive is set.
1044 # pageractive is set.
1045 cm = self._colormode
1045 cm = self._colormode
1046 if cm != self.config('color', 'pagermode', cm):
1046 if cm != self.config('color', 'pagermode', cm):
1047 color.setup(self)
1047 color.setup(self)
1048 else:
1048 else:
1049 # If the pager can't be spawned in dispatch when --pager=on is
1049 # 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
1050 # given, don't try again when the command runs, to avoid a duplicate
1051 # warning about a missing pager command.
1051 # warning about a missing pager command.
1052 self.disablepager()
1052 self.disablepager()
1053
1053
1054 def _runpager(self, command, env=None):
1054 def _runpager(self, command, env=None):
1055 """Actually start the pager and set up file descriptors.
1055 """Actually start the pager and set up file descriptors.
1056
1056
1057 This is separate in part so that extensions (like chg) can
1057 This is separate in part so that extensions (like chg) can
1058 override how a pager is invoked.
1058 override how a pager is invoked.
1059 """
1059 """
1060 if command == 'cat':
1060 if command == 'cat':
1061 # Save ourselves some work.
1061 # Save ourselves some work.
1062 return False
1062 return False
1063 # If the command doesn't contain any of these characters, we
1063 # If the command doesn't contain any of these characters, we
1064 # assume it's a binary and exec it directly. This means for
1064 # assume it's a binary and exec it directly. This means for
1065 # simple pager command configurations, we can degrade
1065 # simple pager command configurations, we can degrade
1066 # gracefully and tell the user about their broken pager.
1066 # gracefully and tell the user about their broken pager.
1067 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
1067 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
1068
1068
1069 if pycompat.iswindows and not shell:
1069 if pycompat.iswindows and not shell:
1070 # Window's built-in `more` cannot be invoked with shell=False, but
1070 # Window's built-in `more` cannot be invoked with shell=False, but
1071 # its `more.com` can. Hide this implementation detail from the
1071 # its `more.com` can. Hide this implementation detail from the
1072 # user so we can also get sane bad PAGER behavior. MSYS has
1072 # 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
1073 # `more.exe`, so do a cmd.exe style resolution of the executable to
1074 # determine which one to use.
1074 # determine which one to use.
1075 fullcmd = util.findexe(command)
1075 fullcmd = util.findexe(command)
1076 if not fullcmd:
1076 if not fullcmd:
1077 self.warn(_("missing pager command '%s', skipping pager\n")
1077 self.warn(_("missing pager command '%s', skipping pager\n")
1078 % command)
1078 % command)
1079 return False
1079 return False
1080
1080
1081 command = fullcmd
1081 command = fullcmd
1082
1082
1083 try:
1083 try:
1084 pager = subprocess.Popen(
1084 pager = subprocess.Popen(
1085 command, shell=shell, bufsize=-1,
1085 command, shell=shell, bufsize=-1,
1086 close_fds=util.closefds, stdin=subprocess.PIPE,
1086 close_fds=util.closefds, stdin=subprocess.PIPE,
1087 stdout=util.stdout, stderr=util.stderr,
1087 stdout=util.stdout, stderr=util.stderr,
1088 env=util.shellenviron(env))
1088 env=util.shellenviron(env))
1089 except OSError as e:
1089 except OSError as e:
1090 if e.errno == errno.ENOENT and not shell:
1090 if e.errno == errno.ENOENT and not shell:
1091 self.warn(_("missing pager command '%s', skipping pager\n")
1091 self.warn(_("missing pager command '%s', skipping pager\n")
1092 % command)
1092 % command)
1093 return False
1093 return False
1094 raise
1094 raise
1095
1095
1096 # back up original file descriptors
1096 # back up original file descriptors
1097 stdoutfd = os.dup(util.stdout.fileno())
1097 stdoutfd = os.dup(util.stdout.fileno())
1098 stderrfd = os.dup(util.stderr.fileno())
1098 stderrfd = os.dup(util.stderr.fileno())
1099
1099
1100 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
1100 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
1101 if self._isatty(util.stderr):
1101 if self._isatty(util.stderr):
1102 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
1102 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
1103
1103
1104 @self.atexit
1104 @self.atexit
1105 def killpager():
1105 def killpager():
1106 if util.safehasattr(signal, "SIGINT"):
1106 if util.safehasattr(signal, "SIGINT"):
1107 signal.signal(signal.SIGINT, signal.SIG_IGN)
1107 signal.signal(signal.SIGINT, signal.SIG_IGN)
1108 # restore original fds, closing pager.stdin copies in the process
1108 # restore original fds, closing pager.stdin copies in the process
1109 os.dup2(stdoutfd, util.stdout.fileno())
1109 os.dup2(stdoutfd, util.stdout.fileno())
1110 os.dup2(stderrfd, util.stderr.fileno())
1110 os.dup2(stderrfd, util.stderr.fileno())
1111 pager.stdin.close()
1111 pager.stdin.close()
1112 pager.wait()
1112 pager.wait()
1113
1113
1114 return True
1114 return True
1115
1115
1116 @property
1116 @property
1117 def _exithandlers(self):
1117 def _exithandlers(self):
1118 return _reqexithandlers
1118 return _reqexithandlers
1119
1119
1120 def atexit(self, func, *args, **kwargs):
1120 def atexit(self, func, *args, **kwargs):
1121 '''register a function to run after dispatching a request
1121 '''register a function to run after dispatching a request
1122
1122
1123 Handlers do not stay registered across request boundaries.'''
1123 Handlers do not stay registered across request boundaries.'''
1124 self._exithandlers.append((func, args, kwargs))
1124 self._exithandlers.append((func, args, kwargs))
1125 return func
1125 return func
1126
1126
1127 def interface(self, feature):
1127 def interface(self, feature):
1128 """what interface to use for interactive console features?
1128 """what interface to use for interactive console features?
1129
1129
1130 The interface is controlled by the value of `ui.interface` but also by
1130 The interface is controlled by the value of `ui.interface` but also by
1131 the value of feature-specific configuration. For example:
1131 the value of feature-specific configuration. For example:
1132
1132
1133 ui.interface.histedit = text
1133 ui.interface.histedit = text
1134 ui.interface.chunkselector = curses
1134 ui.interface.chunkselector = curses
1135
1135
1136 Here the features are "histedit" and "chunkselector".
1136 Here the features are "histedit" and "chunkselector".
1137
1137
1138 The configuration above means that the default interfaces for commands
1138 The configuration above means that the default interfaces for commands
1139 is curses, the interface for histedit is text and the interface for
1139 is curses, the interface for histedit is text and the interface for
1140 selecting chunk is crecord (the best curses interface available).
1140 selecting chunk is crecord (the best curses interface available).
1141
1141
1142 Consider the following example:
1142 Consider the following example:
1143 ui.interface = curses
1143 ui.interface = curses
1144 ui.interface.histedit = text
1144 ui.interface.histedit = text
1145
1145
1146 Then histedit will use the text interface and chunkselector will use
1146 Then histedit will use the text interface and chunkselector will use
1147 the default curses interface (crecord at the moment).
1147 the default curses interface (crecord at the moment).
1148 """
1148 """
1149 alldefaults = frozenset(["text", "curses"])
1149 alldefaults = frozenset(["text", "curses"])
1150
1150
1151 featureinterfaces = {
1151 featureinterfaces = {
1152 "chunkselector": [
1152 "chunkselector": [
1153 "text",
1153 "text",
1154 "curses",
1154 "curses",
1155 ]
1155 ]
1156 }
1156 }
1157
1157
1158 # Feature-specific interface
1158 # Feature-specific interface
1159 if feature not in featureinterfaces.keys():
1159 if feature not in featureinterfaces.keys():
1160 # Programming error, not user error
1160 # Programming error, not user error
1161 raise ValueError("Unknown feature requested %s" % feature)
1161 raise ValueError("Unknown feature requested %s" % feature)
1162
1162
1163 availableinterfaces = frozenset(featureinterfaces[feature])
1163 availableinterfaces = frozenset(featureinterfaces[feature])
1164 if alldefaults > availableinterfaces:
1164 if alldefaults > availableinterfaces:
1165 # Programming error, not user error. We need a use case to
1165 # Programming error, not user error. We need a use case to
1166 # define the right thing to do here.
1166 # define the right thing to do here.
1167 raise ValueError(
1167 raise ValueError(
1168 "Feature %s does not handle all default interfaces" %
1168 "Feature %s does not handle all default interfaces" %
1169 feature)
1169 feature)
1170
1170
1171 if self.plain():
1171 if self.plain():
1172 return "text"
1172 return "text"
1173
1173
1174 # Default interface for all the features
1174 # Default interface for all the features
1175 defaultinterface = "text"
1175 defaultinterface = "text"
1176 i = self.config("ui", "interface")
1176 i = self.config("ui", "interface")
1177 if i in alldefaults:
1177 if i in alldefaults:
1178 defaultinterface = i
1178 defaultinterface = i
1179
1179
1180 choseninterface = defaultinterface
1180 choseninterface = defaultinterface
1181 f = self.config("ui", "interface.%s" % feature)
1181 f = self.config("ui", "interface.%s" % feature)
1182 if f in availableinterfaces:
1182 if f in availableinterfaces:
1183 choseninterface = f
1183 choseninterface = f
1184
1184
1185 if i is not None and defaultinterface != i:
1185 if i is not None and defaultinterface != i:
1186 if f is not None:
1186 if f is not None:
1187 self.warn(_("invalid value for ui.interface: %s\n") %
1187 self.warn(_("invalid value for ui.interface: %s\n") %
1188 (i,))
1188 (i,))
1189 else:
1189 else:
1190 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1190 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1191 (i, choseninterface))
1191 (i, choseninterface))
1192 if f is not None and choseninterface != f:
1192 if f is not None and choseninterface != f:
1193 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1193 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1194 (feature, f, choseninterface))
1194 (feature, f, choseninterface))
1195
1195
1196 return choseninterface
1196 return choseninterface
1197
1197
1198 def interactive(self):
1198 def interactive(self):
1199 '''is interactive input allowed?
1199 '''is interactive input allowed?
1200
1200
1201 An interactive session is a session where input can be reasonably read
1201 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
1202 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
1203 from stdin should fail with an error, unless a sensible default has been
1204 specified.
1204 specified.
1205
1205
1206 Interactiveness is triggered by the value of the `ui.interactive'
1206 Interactiveness is triggered by the value of the `ui.interactive'
1207 configuration variable or - if it is unset - when `sys.stdin' points
1207 configuration variable or - if it is unset - when `sys.stdin' points
1208 to a terminal device.
1208 to a terminal device.
1209
1209
1210 This function refers to input only; for output, see `ui.formatted()'.
1210 This function refers to input only; for output, see `ui.formatted()'.
1211 '''
1211 '''
1212 i = self.configbool("ui", "interactive")
1212 i = self.configbool("ui", "interactive")
1213 if i is None:
1213 if i is None:
1214 # some environments replace stdin without implementing isatty
1214 # some environments replace stdin without implementing isatty
1215 # usually those are non-interactive
1215 # usually those are non-interactive
1216 return self._isatty(self.fin)
1216 return self._isatty(self.fin)
1217
1217
1218 return i
1218 return i
1219
1219
1220 def termwidth(self):
1220 def termwidth(self):
1221 '''how wide is the terminal in columns?
1221 '''how wide is the terminal in columns?
1222 '''
1222 '''
1223 if 'COLUMNS' in encoding.environ:
1223 if 'COLUMNS' in encoding.environ:
1224 try:
1224 try:
1225 return int(encoding.environ['COLUMNS'])
1225 return int(encoding.environ['COLUMNS'])
1226 except ValueError:
1226 except ValueError:
1227 pass
1227 pass
1228 return scmutil.termsize(self)[0]
1228 return scmutil.termsize(self)[0]
1229
1229
1230 def formatted(self):
1230 def formatted(self):
1231 '''should formatted output be used?
1231 '''should formatted output be used?
1232
1232
1233 It is often desirable to format the output to suite the output medium.
1233 It is often desirable to format the output to suite the output medium.
1234 Examples of this are truncating long lines or colorizing messages.
1234 Examples of this are truncating long lines or colorizing messages.
1235 However, this is not often not desirable when piping output into other
1235 However, this is not often not desirable when piping output into other
1236 utilities, e.g. `grep'.
1236 utilities, e.g. `grep'.
1237
1237
1238 Formatted output is triggered by the value of the `ui.formatted'
1238 Formatted output is triggered by the value of the `ui.formatted'
1239 configuration variable or - if it is unset - when `sys.stdout' points
1239 configuration variable or - if it is unset - when `sys.stdout' points
1240 to a terminal device. Please note that `ui.formatted' should be
1240 to a terminal device. Please note that `ui.formatted' should be
1241 considered an implementation detail; it is not intended for use outside
1241 considered an implementation detail; it is not intended for use outside
1242 Mercurial or its extensions.
1242 Mercurial or its extensions.
1243
1243
1244 This function refers to output only; for input, see `ui.interactive()'.
1244 This function refers to output only; for input, see `ui.interactive()'.
1245 This function always returns false when in plain mode, see `ui.plain()'.
1245 This function always returns false when in plain mode, see `ui.plain()'.
1246 '''
1246 '''
1247 if self.plain():
1247 if self.plain():
1248 return False
1248 return False
1249
1249
1250 i = self.configbool("ui", "formatted")
1250 i = self.configbool("ui", "formatted")
1251 if i is None:
1251 if i is None:
1252 # some environments replace stdout without implementing isatty
1252 # some environments replace stdout without implementing isatty
1253 # usually those are non-interactive
1253 # usually those are non-interactive
1254 return self._isatty(self.fout)
1254 return self._isatty(self.fout)
1255
1255
1256 return i
1256 return i
1257
1257
1258 def _readline(self, prompt=''):
1258 def _readline(self, prompt=''):
1259 if self._isatty(self.fin):
1259 if self._isatty(self.fin):
1260 try:
1260 try:
1261 # magically add command line editing support, where
1261 # magically add command line editing support, where
1262 # available
1262 # available
1263 import readline
1263 import readline
1264 # force demandimport to really load the module
1264 # force demandimport to really load the module
1265 readline.read_history_file
1265 readline.read_history_file
1266 # windows sometimes raises something other than ImportError
1266 # windows sometimes raises something other than ImportError
1267 except Exception:
1267 except Exception:
1268 pass
1268 pass
1269
1269
1270 # call write() so output goes through subclassed implementation
1270 # call write() so output goes through subclassed implementation
1271 # e.g. color extension on Windows
1271 # e.g. color extension on Windows
1272 self.write(prompt, prompt=True)
1272 self.write(prompt, prompt=True)
1273 self.flush()
1273 self.flush()
1274
1274
1275 # prompt ' ' must exist; otherwise readline may delete entire line
1275 # prompt ' ' must exist; otherwise readline may delete entire line
1276 # - http://bugs.python.org/issue12833
1276 # - http://bugs.python.org/issue12833
1277 with self.timeblockedsection('stdio'):
1277 with self.timeblockedsection('stdio'):
1278 line = util.bytesinput(self.fin, self.fout, r' ')
1278 line = util.bytesinput(self.fin, self.fout, r' ')
1279
1279
1280 # When stdin is in binary mode on Windows, it can cause
1280 # When stdin is in binary mode on Windows, it can cause
1281 # raw_input() to emit an extra trailing carriage return
1281 # raw_input() to emit an extra trailing carriage return
1282 if pycompat.oslinesep == '\r\n' and line and line[-1] == '\r':
1282 if pycompat.oslinesep == '\r\n' and line and line[-1] == '\r':
1283 line = line[:-1]
1283 line = line[:-1]
1284 return line
1284 return line
1285
1285
1286 def prompt(self, msg, default="y"):
1286 def prompt(self, msg, default="y"):
1287 """Prompt user with msg, read response.
1287 """Prompt user with msg, read response.
1288 If ui is not interactive, the default is returned.
1288 If ui is not interactive, the default is returned.
1289 """
1289 """
1290 if not self.interactive():
1290 if not self.interactive():
1291 self.write(msg, ' ', default or '', "\n")
1291 self.write(msg, ' ', default or '', "\n")
1292 return default
1292 return default
1293 try:
1293 try:
1294 r = self._readline(self.label(msg, 'ui.prompt'))
1294 r = self._readline(self.label(msg, 'ui.prompt'))
1295 if not r:
1295 if not r:
1296 r = default
1296 r = default
1297 if self.configbool('ui', 'promptecho'):
1297 if self.configbool('ui', 'promptecho'):
1298 self.write(r, "\n")
1298 self.write(r, "\n")
1299 return r
1299 return r
1300 except EOFError:
1300 except EOFError:
1301 raise error.ResponseExpected()
1301 raise error.ResponseExpected()
1302
1302
1303 @staticmethod
1303 @staticmethod
1304 def extractchoices(prompt):
1304 def extractchoices(prompt):
1305 """Extract prompt message and list of choices from specified prompt.
1305 """Extract prompt message and list of choices from specified prompt.
1306
1306
1307 This returns tuple "(message, choices)", and "choices" is the
1307 This returns tuple "(message, choices)", and "choices" is the
1308 list of tuple "(response character, text without &)".
1308 list of tuple "(response character, text without &)".
1309
1309
1310 >>> ui.extractchoices(b"awake? $$ &Yes $$ &No")
1310 >>> ui.extractchoices(b"awake? $$ &Yes $$ &No")
1311 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1311 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1312 >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No")
1312 >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No")
1313 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1313 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1314 >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o")
1314 >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o")
1315 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1315 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1316 """
1316 """
1317
1317
1318 # Sadly, the prompt string may have been built with a filename
1318 # Sadly, the prompt string may have been built with a filename
1319 # containing "$$" so let's try to find the first valid-looking
1319 # containing "$$" so let's try to find the first valid-looking
1320 # prompt to start parsing. Sadly, we also can't rely on
1320 # prompt to start parsing. Sadly, we also can't rely on
1321 # choices containing spaces, ASCII, or basically anything
1321 # choices containing spaces, ASCII, or basically anything
1322 # except an ampersand followed by a character.
1322 # except an ampersand followed by a character.
1323 m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1323 m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1324 msg = m.group(1)
1324 msg = m.group(1)
1325 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1325 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1326 def choicetuple(s):
1326 def choicetuple(s):
1327 ampidx = s.index('&')
1327 ampidx = s.index('&')
1328 return s[ampidx + 1:ampidx + 2].lower(), s.replace('&', '', 1)
1328 return s[ampidx + 1:ampidx + 2].lower(), s.replace('&', '', 1)
1329 return (msg, [choicetuple(s) for s in choices])
1329 return (msg, [choicetuple(s) for s in choices])
1330
1330
1331 def promptchoice(self, prompt, default=0):
1331 def promptchoice(self, prompt, default=0):
1332 """Prompt user with a message, read response, and ensure it matches
1332 """Prompt user with a message, read response, and ensure it matches
1333 one of the provided choices. The prompt is formatted as follows:
1333 one of the provided choices. The prompt is formatted as follows:
1334
1334
1335 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1335 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1336
1336
1337 The index of the choice is returned. Responses are case
1337 The index of the choice is returned. Responses are case
1338 insensitive. If ui is not interactive, the default is
1338 insensitive. If ui is not interactive, the default is
1339 returned.
1339 returned.
1340 """
1340 """
1341
1341
1342 msg, choices = self.extractchoices(prompt)
1342 msg, choices = self.extractchoices(prompt)
1343 resps = [r for r, t in choices]
1343 resps = [r for r, t in choices]
1344 while True:
1344 while True:
1345 r = self.prompt(msg, resps[default])
1345 r = self.prompt(msg, resps[default])
1346 if r.lower() in resps:
1346 if r.lower() in resps:
1347 return resps.index(r.lower())
1347 return resps.index(r.lower())
1348 self.write(_("unrecognized response\n"))
1348 self.write(_("unrecognized response\n"))
1349
1349
1350 def getpass(self, prompt=None, default=None):
1350 def getpass(self, prompt=None, default=None):
1351 if not self.interactive():
1351 if not self.interactive():
1352 return default
1352 return default
1353 try:
1353 try:
1354 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1354 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1355 # disable getpass() only if explicitly specified. it's still valid
1355 # disable getpass() only if explicitly specified. it's still valid
1356 # to interact with tty even if fin is not a tty.
1356 # to interact with tty even if fin is not a tty.
1357 with self.timeblockedsection('stdio'):
1357 with self.timeblockedsection('stdio'):
1358 if self.configbool('ui', 'nontty'):
1358 if self.configbool('ui', 'nontty'):
1359 l = self.fin.readline()
1359 l = self.fin.readline()
1360 if not l:
1360 if not l:
1361 raise EOFError
1361 raise EOFError
1362 return l.rstrip('\n')
1362 return l.rstrip('\n')
1363 else:
1363 else:
1364 return getpass.getpass('')
1364 return getpass.getpass('')
1365 except EOFError:
1365 except EOFError:
1366 raise error.ResponseExpected()
1366 raise error.ResponseExpected()
1367 def status(self, *msg, **opts):
1367 def status(self, *msg, **opts):
1368 '''write status message to output (if ui.quiet is False)
1368 '''write status message to output (if ui.quiet is False)
1369
1369
1370 This adds an output label of "ui.status".
1370 This adds an output label of "ui.status".
1371 '''
1371 '''
1372 if not self.quiet:
1372 if not self.quiet:
1373 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1373 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1374 self.write(*msg, **opts)
1374 self.write(*msg, **opts)
1375 def warn(self, *msg, **opts):
1375 def warn(self, *msg, **opts):
1376 '''write warning message to output (stderr)
1376 '''write warning message to output (stderr)
1377
1377
1378 This adds an output label of "ui.warning".
1378 This adds an output label of "ui.warning".
1379 '''
1379 '''
1380 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1380 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1381 self.write_err(*msg, **opts)
1381 self.write_err(*msg, **opts)
1382 def note(self, *msg, **opts):
1382 def note(self, *msg, **opts):
1383 '''write note to output (if ui.verbose is True)
1383 '''write note to output (if ui.verbose is True)
1384
1384
1385 This adds an output label of "ui.note".
1385 This adds an output label of "ui.note".
1386 '''
1386 '''
1387 if self.verbose:
1387 if self.verbose:
1388 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1388 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1389 self.write(*msg, **opts)
1389 self.write(*msg, **opts)
1390 def debug(self, *msg, **opts):
1390 def debug(self, *msg, **opts):
1391 '''write debug message to output (if ui.debugflag is True)
1391 '''write debug message to output (if ui.debugflag is True)
1392
1392
1393 This adds an output label of "ui.debug".
1393 This adds an output label of "ui.debug".
1394 '''
1394 '''
1395 if self.debugflag:
1395 if self.debugflag:
1396 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1396 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1397 self.write(*msg, **opts)
1397 self.write(*msg, **opts)
1398
1398
1399 def edit(self, text, user, extra=None, editform=None, pending=None,
1399 def edit(self, text, user, extra=None, editform=None, pending=None,
1400 repopath=None, action=None):
1400 repopath=None, action=None):
1401 if action is None:
1401 if action is None:
1402 self.develwarn('action is None but will soon be a required '
1402 self.develwarn('action is None but will soon be a required '
1403 'parameter to ui.edit()')
1403 'parameter to ui.edit()')
1404 extra_defaults = {
1404 extra_defaults = {
1405 'prefix': 'editor',
1405 'prefix': 'editor',
1406 'suffix': '.txt',
1406 'suffix': '.txt',
1407 }
1407 }
1408 if extra is not None:
1408 if extra is not None:
1409 if extra.get('suffix') is not None:
1409 if extra.get('suffix') is not None:
1410 self.develwarn('extra.suffix is not None but will soon be '
1410 self.develwarn('extra.suffix is not None but will soon be '
1411 'ignored by ui.edit()')
1411 'ignored by ui.edit()')
1412 extra_defaults.update(extra)
1412 extra_defaults.update(extra)
1413 extra = extra_defaults
1413 extra = extra_defaults
1414
1414
1415 if action == 'diff':
1415 if action == 'diff':
1416 suffix = '.diff'
1416 suffix = '.diff'
1417 elif action:
1417 elif action:
1418 suffix = '.%s.hg.txt' % action
1418 suffix = '.%s.hg.txt' % action
1419 else:
1419 else:
1420 suffix = extra['suffix']
1420 suffix = extra['suffix']
1421
1421
1422 rdir = None
1422 rdir = None
1423 if self.configbool('experimental', 'editortmpinhg'):
1423 if self.configbool('experimental', 'editortmpinhg'):
1424 rdir = repopath
1424 rdir = repopath
1425 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1425 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1426 suffix=suffix,
1426 suffix=suffix,
1427 dir=rdir)
1427 dir=rdir)
1428 try:
1428 try:
1429 f = os.fdopen(fd, r'wb')
1429 f = os.fdopen(fd, r'wb')
1430 f.write(util.tonativeeol(text))
1430 f.write(util.tonativeeol(text))
1431 f.close()
1431 f.close()
1432
1432
1433 environ = {'HGUSER': user}
1433 environ = {'HGUSER': user}
1434 if 'transplant_source' in extra:
1434 if 'transplant_source' in extra:
1435 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1435 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1436 for label in ('intermediate-source', 'source', 'rebase_source'):
1436 for label in ('intermediate-source', 'source', 'rebase_source'):
1437 if label in extra:
1437 if label in extra:
1438 environ.update({'HGREVISION': extra[label]})
1438 environ.update({'HGREVISION': extra[label]})
1439 break
1439 break
1440 if editform:
1440 if editform:
1441 environ.update({'HGEDITFORM': editform})
1441 environ.update({'HGEDITFORM': editform})
1442 if pending:
1442 if pending:
1443 environ.update({'HG_PENDING': pending})
1443 environ.update({'HG_PENDING': pending})
1444
1444
1445 editor = self.geteditor()
1445 editor = self.geteditor()
1446
1446
1447 self.system("%s \"%s\"" % (editor, name),
1447 self.system("%s \"%s\"" % (editor, name),
1448 environ=environ,
1448 environ=environ,
1449 onerr=error.Abort, errprefix=_("edit failed"),
1449 onerr=error.Abort, errprefix=_("edit failed"),
1450 blockedtag='editor')
1450 blockedtag='editor')
1451
1451
1452 f = open(name, r'rb')
1452 f = open(name, r'rb')
1453 t = util.fromnativeeol(f.read())
1453 t = util.fromnativeeol(f.read())
1454 f.close()
1454 f.close()
1455 finally:
1455 finally:
1456 os.unlink(name)
1456 os.unlink(name)
1457
1457
1458 return t
1458 return t
1459
1459
1460 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1460 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1461 blockedtag=None):
1461 blockedtag=None):
1462 '''execute shell command with appropriate output stream. command
1462 '''execute shell command with appropriate output stream. command
1463 output will be redirected if fout is not stdout.
1463 output will be redirected if fout is not stdout.
1464
1464
1465 if command fails and onerr is None, return status, else raise onerr
1465 if command fails and onerr is None, return status, else raise onerr
1466 object as exception.
1466 object as exception.
1467 '''
1467 '''
1468 if blockedtag is None:
1468 if blockedtag is None:
1469 # Long cmds tend to be because of an absolute path on cmd. Keep
1469 # Long cmds tend to be because of an absolute path on cmd. Keep
1470 # the tail end instead
1470 # the tail end instead
1471 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1471 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1472 blockedtag = 'unknown_system_' + cmdsuffix
1472 blockedtag = 'unknown_system_' + cmdsuffix
1473 out = self.fout
1473 out = self.fout
1474 if any(s[1] for s in self._bufferstates):
1474 if any(s[1] for s in self._bufferstates):
1475 out = self
1475 out = self
1476 with self.timeblockedsection(blockedtag):
1476 with self.timeblockedsection(blockedtag):
1477 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1477 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1478 if rc and onerr:
1478 if rc and onerr:
1479 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1479 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1480 util.explainexit(rc)[0])
1480 util.explainexit(rc)[0])
1481 if errprefix:
1481 if errprefix:
1482 errmsg = '%s: %s' % (errprefix, errmsg)
1482 errmsg = '%s: %s' % (errprefix, errmsg)
1483 raise onerr(errmsg)
1483 raise onerr(errmsg)
1484 return rc
1484 return rc
1485
1485
1486 def _runsystem(self, cmd, environ, cwd, out):
1486 def _runsystem(self, cmd, environ, cwd, out):
1487 """actually execute the given shell command (can be overridden by
1487 """actually execute the given shell command (can be overridden by
1488 extensions like chg)"""
1488 extensions like chg)"""
1489 return util.system(cmd, environ=environ, cwd=cwd, out=out)
1489 return util.system(cmd, environ=environ, cwd=cwd, out=out)
1490
1490
1491 def traceback(self, exc=None, force=False):
1491 def traceback(self, exc=None, force=False):
1492 '''print exception traceback if traceback printing enabled or forced.
1492 '''print exception traceback if traceback printing enabled or forced.
1493 only to call in exception handler. returns true if traceback
1493 only to call in exception handler. returns true if traceback
1494 printed.'''
1494 printed.'''
1495 if self.tracebackflag or force:
1495 if self.tracebackflag or force:
1496 if exc is None:
1496 if exc is None:
1497 exc = sys.exc_info()
1497 exc = sys.exc_info()
1498 cause = getattr(exc[1], 'cause', None)
1498 cause = getattr(exc[1], 'cause', None)
1499
1499
1500 if cause is not None:
1500 if cause is not None:
1501 causetb = traceback.format_tb(cause[2])
1501 causetb = traceback.format_tb(cause[2])
1502 exctb = traceback.format_tb(exc[2])
1502 exctb = traceback.format_tb(exc[2])
1503 exconly = traceback.format_exception_only(cause[0], cause[1])
1503 exconly = traceback.format_exception_only(cause[0], cause[1])
1504
1504
1505 # exclude frame where 'exc' was chained and rethrown from exctb
1505 # exclude frame where 'exc' was chained and rethrown from exctb
1506 self.write_err('Traceback (most recent call last):\n',
1506 self.write_err('Traceback (most recent call last):\n',
1507 ''.join(exctb[:-1]),
1507 ''.join(exctb[:-1]),
1508 ''.join(causetb),
1508 ''.join(causetb),
1509 ''.join(exconly))
1509 ''.join(exconly))
1510 else:
1510 else:
1511 output = traceback.format_exception(exc[0], exc[1], exc[2])
1511 output = traceback.format_exception(exc[0], exc[1], exc[2])
1512 data = r''.join(output)
1512 data = r''.join(output)
1513 if pycompat.ispy3:
1513 if pycompat.ispy3:
1514 enc = pycompat.sysstr(encoding.encoding)
1514 enc = pycompat.sysstr(encoding.encoding)
1515 data = data.encode(enc, errors=r'replace')
1515 data = data.encode(enc, errors=r'replace')
1516 self.write_err(data)
1516 self.write_err(data)
1517 return self.tracebackflag or force
1517 return self.tracebackflag or force
1518
1518
1519 def geteditor(self):
1519 def geteditor(self):
1520 '''return editor to use'''
1520 '''return editor to use'''
1521 if pycompat.sysplatform == 'plan9':
1521 if pycompat.sysplatform == 'plan9':
1522 # vi is the MIPS instruction simulator on Plan 9. We
1522 # vi is the MIPS instruction simulator on Plan 9. We
1523 # instead default to E to plumb commit messages to
1523 # instead default to E to plumb commit messages to
1524 # avoid confusion.
1524 # avoid confusion.
1525 editor = 'E'
1525 editor = 'E'
1526 else:
1526 else:
1527 editor = 'vi'
1527 editor = 'vi'
1528 return (encoding.environ.get("HGEDITOR") or
1528 return (encoding.environ.get("HGEDITOR") or
1529 self.config("ui", "editor", editor))
1529 self.config("ui", "editor", editor))
1530
1530
1531 @util.propertycache
1531 @util.propertycache
1532 def _progbar(self):
1532 def _progbar(self):
1533 """setup the progbar singleton to the ui object"""
1533 """setup the progbar singleton to the ui object"""
1534 if (self.quiet or self.debugflag
1534 if (self.quiet or self.debugflag
1535 or self.configbool('progress', 'disable')
1535 or self.configbool('progress', 'disable')
1536 or not progress.shouldprint(self)):
1536 or not progress.shouldprint(self)):
1537 return None
1537 return None
1538 return getprogbar(self)
1538 return getprogbar(self)
1539
1539
1540 def _progclear(self):
1540 def _progclear(self):
1541 """clear progress bar output if any. use it before any output"""
1541 """clear progress bar output if any. use it before any output"""
1542 if not haveprogbar(): # nothing loaded yet
1542 if not haveprogbar(): # nothing loaded yet
1543 return
1543 return
1544 if self._progbar is not None and self._progbar.printed:
1544 if self._progbar is not None and self._progbar.printed:
1545 self._progbar.clear()
1545 self._progbar.clear()
1546
1546
1547 def progress(self, topic, pos, item="", unit="", total=None):
1547 def progress(self, topic, pos, item="", unit="", total=None):
1548 '''show a progress message
1548 '''show a progress message
1549
1549
1550 By default a textual progress bar will be displayed if an operation
1550 By default a textual progress bar will be displayed if an operation
1551 takes too long. 'topic' is the current operation, 'item' is a
1551 takes too long. 'topic' is the current operation, 'item' is a
1552 non-numeric marker of the current position (i.e. the currently
1552 non-numeric marker of the current position (i.e. the currently
1553 in-process file), 'pos' is the current numeric position (i.e.
1553 in-process file), 'pos' is the current numeric position (i.e.
1554 revision, bytes, etc.), unit is a corresponding unit label,
1554 revision, bytes, etc.), unit is a corresponding unit label,
1555 and total is the highest expected pos.
1555 and total is the highest expected pos.
1556
1556
1557 Multiple nested topics may be active at a time.
1557 Multiple nested topics may be active at a time.
1558
1558
1559 All topics should be marked closed by setting pos to None at
1559 All topics should be marked closed by setting pos to None at
1560 termination.
1560 termination.
1561 '''
1561 '''
1562 if self._progbar is not None:
1562 if self._progbar is not None:
1563 self._progbar.progress(topic, pos, item=item, unit=unit,
1563 self._progbar.progress(topic, pos, item=item, unit=unit,
1564 total=total)
1564 total=total)
1565 if pos is None or not self.configbool('progress', 'debug'):
1565 if pos is None or not self.configbool('progress', 'debug'):
1566 return
1566 return
1567
1567
1568 if unit:
1568 if unit:
1569 unit = ' ' + unit
1569 unit = ' ' + unit
1570 if item:
1570 if item:
1571 item = ' ' + item
1571 item = ' ' + item
1572
1572
1573 if total:
1573 if total:
1574 pct = 100.0 * pos / total
1574 pct = 100.0 * pos / total
1575 self.debug('%s:%s %d/%d%s (%4.2f%%)\n'
1575 self.debug('%s:%s %d/%d%s (%4.2f%%)\n'
1576 % (topic, item, pos, total, unit, pct))
1576 % (topic, item, pos, total, unit, pct))
1577 else:
1577 else:
1578 self.debug('%s:%s %d%s\n' % (topic, item, pos, unit))
1578 self.debug('%s:%s %d%s\n' % (topic, item, pos, unit))
1579
1579
1580 def log(self, service, *msg, **opts):
1580 def log(self, service, *msg, **opts):
1581 '''hook for logging facility extensions
1581 '''hook for logging facility extensions
1582
1582
1583 service should be a readily-identifiable subsystem, which will
1583 service should be a readily-identifiable subsystem, which will
1584 allow filtering.
1584 allow filtering.
1585
1585
1586 *msg should be a newline-terminated format string to log, and
1586 *msg should be a newline-terminated format string to log, and
1587 then any values to %-format into that format string.
1587 then any values to %-format into that format string.
1588
1588
1589 **opts currently has no defined meanings.
1589 **opts currently has no defined meanings.
1590 '''
1590 '''
1591
1591
1592 def label(self, msg, label):
1592 def label(self, msg, label):
1593 '''style msg based on supplied label
1593 '''style msg based on supplied label
1594
1594
1595 If some color mode is enabled, this will add the necessary control
1595 If some color mode is enabled, this will add the necessary control
1596 characters to apply such color. In addition, 'debug' color mode adds
1596 characters to apply such color. In addition, 'debug' color mode adds
1597 markup showing which label affects a piece of text.
1597 markup showing which label affects a piece of text.
1598
1598
1599 ui.write(s, 'label') is equivalent to
1599 ui.write(s, 'label') is equivalent to
1600 ui.write(ui.label(s, 'label')).
1600 ui.write(ui.label(s, 'label')).
1601 '''
1601 '''
1602 if self._colormode is not None:
1602 if self._colormode is not None:
1603 return color.colorlabel(self, msg, label)
1603 return color.colorlabel(self, msg, label)
1604 return msg
1604 return msg
1605
1605
1606 def develwarn(self, msg, stacklevel=1, config=None):
1606 def develwarn(self, msg, stacklevel=1, config=None):
1607 """issue a developer warning message
1607 """issue a developer warning message
1608
1608
1609 Use 'stacklevel' to report the offender some layers further up in the
1609 Use 'stacklevel' to report the offender some layers further up in the
1610 stack.
1610 stack.
1611 """
1611 """
1612 if not self.configbool('devel', 'all-warnings'):
1612 if not self.configbool('devel', 'all-warnings'):
1613 if config is None or not self.configbool('devel', config):
1613 if config is None or not self.configbool('devel', config):
1614 return
1614 return
1615 msg = 'devel-warn: ' + msg
1615 msg = 'devel-warn: ' + msg
1616 stacklevel += 1 # get in develwarn
1616 stacklevel += 1 # get in develwarn
1617 if self.tracebackflag:
1617 if self.tracebackflag:
1618 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1618 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1619 self.log('develwarn', '%s at:\n%s' %
1619 self.log('develwarn', '%s at:\n%s' %
1620 (msg, ''.join(util.getstackframes(stacklevel))))
1620 (msg, ''.join(util.getstackframes(stacklevel))))
1621 else:
1621 else:
1622 curframe = inspect.currentframe()
1622 curframe = inspect.currentframe()
1623 calframe = inspect.getouterframes(curframe, 2)
1623 calframe = inspect.getouterframes(curframe, 2)
1624 self.write_err('%s at: %s:%s (%s)\n'
1624 self.write_err('%s at: %s:%s (%s)\n'
1625 % ((msg,) + calframe[stacklevel][1:4]))
1625 % ((msg,) + calframe[stacklevel][1:4]))
1626 self.log('develwarn', '%s at: %s:%s (%s)\n',
1626 self.log('develwarn', '%s at: %s:%s (%s)\n',
1627 msg, *calframe[stacklevel][1:4])
1627 msg, *calframe[stacklevel][1:4])
1628 curframe = calframe = None # avoid cycles
1628 curframe = calframe = None # avoid cycles
1629
1629
1630 def deprecwarn(self, msg, version):
1630 def deprecwarn(self, msg, version):
1631 """issue a deprecation warning
1631 """issue a deprecation warning
1632
1632
1633 - msg: message explaining what is deprecated and how to upgrade,
1633 - msg: message explaining what is deprecated and how to upgrade,
1634 - version: last version where the API will be supported,
1634 - version: last version where the API will be supported,
1635 """
1635 """
1636 if not (self.configbool('devel', 'all-warnings')
1636 if not (self.configbool('devel', 'all-warnings')
1637 or self.configbool('devel', 'deprec-warn')):
1637 or self.configbool('devel', 'deprec-warn')):
1638 return
1638 return
1639 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1639 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1640 " update your code.)") % version
1640 " update your code.)") % version
1641 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1641 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1642
1642
1643 def exportableenviron(self):
1643 def exportableenviron(self):
1644 """The environment variables that are safe to export, e.g. through
1644 """The environment variables that are safe to export, e.g. through
1645 hgweb.
1645 hgweb.
1646 """
1646 """
1647 return self._exportableenviron
1647 return self._exportableenviron
1648
1648
1649 @contextlib.contextmanager
1649 @contextlib.contextmanager
1650 def configoverride(self, overrides, source=""):
1650 def configoverride(self, overrides, source=""):
1651 """Context manager for temporary config overrides
1651 """Context manager for temporary config overrides
1652 `overrides` must be a dict of the following structure:
1652 `overrides` must be a dict of the following structure:
1653 {(section, name) : value}"""
1653 {(section, name) : value}"""
1654 backups = {}
1654 backups = {}
1655 try:
1655 try:
1656 for (section, name), value in overrides.items():
1656 for (section, name), value in overrides.items():
1657 backups[(section, name)] = self.backupconfig(section, name)
1657 backups[(section, name)] = self.backupconfig(section, name)
1658 self.setconfig(section, name, value, source)
1658 self.setconfig(section, name, value, source)
1659 yield
1659 yield
1660 finally:
1660 finally:
1661 for __, backup in backups.items():
1661 for __, backup in backups.items():
1662 self.restoreconfig(backup)
1662 self.restoreconfig(backup)
1663 # just restoring ui.quiet config to the previous value is not enough
1663 # just restoring ui.quiet config to the previous value is not enough
1664 # as it does not update ui.quiet class member
1664 # as it does not update ui.quiet class member
1665 if ('ui', 'quiet') in overrides:
1665 if ('ui', 'quiet') in overrides:
1666 self.fixconfig(section='ui')
1666 self.fixconfig(section='ui')
1667
1667
1668 class paths(dict):
1668 class paths(dict):
1669 """Represents a collection of paths and their configs.
1669 """Represents a collection of paths and their configs.
1670
1670
1671 Data is initially derived from ui instances and the config files they have
1671 Data is initially derived from ui instances and the config files they have
1672 loaded.
1672 loaded.
1673 """
1673 """
1674 def __init__(self, ui):
1674 def __init__(self, ui):
1675 dict.__init__(self)
1675 dict.__init__(self)
1676
1676
1677 for name, loc in ui.configitems('paths', ignoresub=True):
1677 for name, loc in ui.configitems('paths', ignoresub=True):
1678 # No location is the same as not existing.
1678 # No location is the same as not existing.
1679 if not loc:
1679 if not loc:
1680 continue
1680 continue
1681 loc, sub = ui.configsuboptions('paths', name)
1681 loc, sub = ui.configsuboptions('paths', name)
1682 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1682 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1683
1683
1684 def getpath(self, name, default=None):
1684 def getpath(self, name, default=None):
1685 """Return a ``path`` from a string, falling back to default.
1685 """Return a ``path`` from a string, falling back to default.
1686
1686
1687 ``name`` can be a named path or locations. Locations are filesystem
1687 ``name`` can be a named path or locations. Locations are filesystem
1688 paths or URIs.
1688 paths or URIs.
1689
1689
1690 Returns None if ``name`` is not a registered path, a URI, or a local
1690 Returns None if ``name`` is not a registered path, a URI, or a local
1691 path to a repo.
1691 path to a repo.
1692 """
1692 """
1693 # Only fall back to default if no path was requested.
1693 # Only fall back to default if no path was requested.
1694 if name is None:
1694 if name is None:
1695 if not default:
1695 if not default:
1696 default = ()
1696 default = ()
1697 elif not isinstance(default, (tuple, list)):
1697 elif not isinstance(default, (tuple, list)):
1698 default = (default,)
1698 default = (default,)
1699 for k in default:
1699 for k in default:
1700 try:
1700 try:
1701 return self[k]
1701 return self[k]
1702 except KeyError:
1702 except KeyError:
1703 continue
1703 continue
1704 return None
1704 return None
1705
1705
1706 # Most likely empty string.
1706 # Most likely empty string.
1707 # This may need to raise in the future.
1707 # This may need to raise in the future.
1708 if not name:
1708 if not name:
1709 return None
1709 return None
1710
1710
1711 try:
1711 try:
1712 return self[name]
1712 return self[name]
1713 except KeyError:
1713 except KeyError:
1714 # Try to resolve as a local path or URI.
1714 # Try to resolve as a local path or URI.
1715 try:
1715 try:
1716 # We don't pass sub-options in, so no need to pass ui instance.
1716 # We don't pass sub-options in, so no need to pass ui instance.
1717 return path(None, None, rawloc=name)
1717 return path(None, None, rawloc=name)
1718 except ValueError:
1718 except ValueError:
1719 raise error.RepoError(_('repository %s does not exist') %
1719 raise error.RepoError(_('repository %s does not exist') %
1720 name)
1720 name)
1721
1721
1722 _pathsuboptions = {}
1722 _pathsuboptions = {}
1723
1723
1724 def pathsuboption(option, attr):
1724 def pathsuboption(option, attr):
1725 """Decorator used to declare a path sub-option.
1725 """Decorator used to declare a path sub-option.
1726
1726
1727 Arguments are the sub-option name and the attribute it should set on
1727 Arguments are the sub-option name and the attribute it should set on
1728 ``path`` instances.
1728 ``path`` instances.
1729
1729
1730 The decorated function will receive as arguments a ``ui`` instance,
1730 The decorated function will receive as arguments a ``ui`` instance,
1731 ``path`` instance, and the string value of this option from the config.
1731 ``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``
1732 The function should return the value that will be set on the ``path``
1733 instance.
1733 instance.
1734
1734
1735 This decorator can be used to perform additional verification of
1735 This decorator can be used to perform additional verification of
1736 sub-options and to change the type of sub-options.
1736 sub-options and to change the type of sub-options.
1737 """
1737 """
1738 def register(func):
1738 def register(func):
1739 _pathsuboptions[option] = (attr, func)
1739 _pathsuboptions[option] = (attr, func)
1740 return func
1740 return func
1741 return register
1741 return register
1742
1742
1743 @pathsuboption('pushurl', 'pushloc')
1743 @pathsuboption('pushurl', 'pushloc')
1744 def pushurlpathoption(ui, path, value):
1744 def pushurlpathoption(ui, path, value):
1745 u = util.url(value)
1745 u = util.url(value)
1746 # Actually require a URL.
1746 # Actually require a URL.
1747 if not u.scheme:
1747 if not u.scheme:
1748 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1748 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1749 return None
1749 return None
1750
1750
1751 # Don't support the #foo syntax in the push URL to declare branch to
1751 # Don't support the #foo syntax in the push URL to declare branch to
1752 # push.
1752 # push.
1753 if u.fragment:
1753 if u.fragment:
1754 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1754 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1755 'ignoring)\n') % path.name)
1755 'ignoring)\n') % path.name)
1756 u.fragment = None
1756 u.fragment = None
1757
1757
1758 return str(u)
1758 return str(u)
1759
1759
1760 @pathsuboption('pushrev', 'pushrev')
1760 @pathsuboption('pushrev', 'pushrev')
1761 def pushrevpathoption(ui, path, value):
1761 def pushrevpathoption(ui, path, value):
1762 return value
1762 return value
1763
1763
1764 class path(object):
1764 class path(object):
1765 """Represents an individual path and its configuration."""
1765 """Represents an individual path and its configuration."""
1766
1766
1767 def __init__(self, ui, name, rawloc=None, suboptions=None):
1767 def __init__(self, ui, name, rawloc=None, suboptions=None):
1768 """Construct a path from its config options.
1768 """Construct a path from its config options.
1769
1769
1770 ``ui`` is the ``ui`` instance the path is coming from.
1770 ``ui`` is the ``ui`` instance the path is coming from.
1771 ``name`` is the symbolic name of the path.
1771 ``name`` is the symbolic name of the path.
1772 ``rawloc`` is the raw location, as defined in the config.
1772 ``rawloc`` is the raw location, as defined in the config.
1773 ``pushloc`` is the raw locations pushes should be made to.
1773 ``pushloc`` is the raw locations pushes should be made to.
1774
1774
1775 If ``name`` is not defined, we require that the location be a) a local
1775 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,
1776 filesystem path with a .hg directory or b) a URL. If not,
1777 ``ValueError`` is raised.
1777 ``ValueError`` is raised.
1778 """
1778 """
1779 if not rawloc:
1779 if not rawloc:
1780 raise ValueError('rawloc must be defined')
1780 raise ValueError('rawloc must be defined')
1781
1781
1782 # Locations may define branches via syntax <base>#<branch>.
1782 # Locations may define branches via syntax <base>#<branch>.
1783 u = util.url(rawloc)
1783 u = util.url(rawloc)
1784 branch = None
1784 branch = None
1785 if u.fragment:
1785 if u.fragment:
1786 branch = u.fragment
1786 branch = u.fragment
1787 u.fragment = None
1787 u.fragment = None
1788
1788
1789 self.url = u
1789 self.url = u
1790 self.branch = branch
1790 self.branch = branch
1791
1791
1792 self.name = name
1792 self.name = name
1793 self.rawloc = rawloc
1793 self.rawloc = rawloc
1794 self.loc = '%s' % u
1794 self.loc = '%s' % u
1795
1795
1796 # When given a raw location but not a symbolic name, validate the
1796 # When given a raw location but not a symbolic name, validate the
1797 # location is valid.
1797 # location is valid.
1798 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1798 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 '
1799 raise ValueError('location is not a URL or path to a local '
1800 'repo: %s' % rawloc)
1800 'repo: %s' % rawloc)
1801
1801
1802 suboptions = suboptions or {}
1802 suboptions = suboptions or {}
1803
1803
1804 # Now process the sub-options. If a sub-option is registered, its
1804 # 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
1805 # attribute will always be present. The value will be None if there
1806 # was no valid sub-option.
1806 # was no valid sub-option.
1807 for suboption, (attr, func) in _pathsuboptions.iteritems():
1807 for suboption, (attr, func) in _pathsuboptions.iteritems():
1808 if suboption not in suboptions:
1808 if suboption not in suboptions:
1809 setattr(self, attr, None)
1809 setattr(self, attr, None)
1810 continue
1810 continue
1811
1811
1812 value = func(ui, self, suboptions[suboption])
1812 value = func(ui, self, suboptions[suboption])
1813 setattr(self, attr, value)
1813 setattr(self, attr, value)
1814
1814
1815 def _isvalidlocalpath(self, path):
1815 def _isvalidlocalpath(self, path):
1816 """Returns True if the given path is a potentially valid repository.
1816 """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
1817 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
1818 'valid' in this case (like when pulling from a git repo into a hg
1819 one)."""
1819 one)."""
1820 return os.path.isdir(os.path.join(path, '.hg'))
1820 return os.path.isdir(os.path.join(path, '.hg'))
1821
1821
1822 @property
1822 @property
1823 def suboptions(self):
1823 def suboptions(self):
1824 """Return sub-options and their values for this path.
1824 """Return sub-options and their values for this path.
1825
1825
1826 This is intended to be used for presentation purposes.
1826 This is intended to be used for presentation purposes.
1827 """
1827 """
1828 d = {}
1828 d = {}
1829 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1829 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1830 value = getattr(self, attr)
1830 value = getattr(self, attr)
1831 if value is not None:
1831 if value is not None:
1832 d[subopt] = value
1832 d[subopt] = value
1833 return d
1833 return d
1834
1834
1835 # we instantiate one globally shared progress bar to avoid
1835 # we instantiate one globally shared progress bar to avoid
1836 # competing progress bars when multiple UI objects get created
1836 # competing progress bars when multiple UI objects get created
1837 _progresssingleton = None
1837 _progresssingleton = None
1838
1838
1839 def getprogbar(ui):
1839 def getprogbar(ui):
1840 global _progresssingleton
1840 global _progresssingleton
1841 if _progresssingleton is None:
1841 if _progresssingleton is None:
1842 # passing 'ui' object to the singleton is fishy,
1842 # passing 'ui' object to the singleton is fishy,
1843 # this is how the extension used to work but feel free to rework it.
1843 # this is how the extension used to work but feel free to rework it.
1844 _progresssingleton = progress.progbar(ui)
1844 _progresssingleton = progress.progbar(ui)
1845 return _progresssingleton
1845 return _progresssingleton
1846
1846
1847 def haveprogbar():
1847 def haveprogbar():
1848 return _progresssingleton is not None
1848 return _progresssingleton is not None
General Comments 0
You need to be logged in to leave comments. Login now