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