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