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