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