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