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