##// END OF EJS Templates
blackbox: adds a blackbox extension...
Durham Goode -
r18669:18242716 default
parent child Browse files
Show More
@@ -0,0 +1,77 b''
1 # worker.py - master-slave parallelism support
2 #
3 # Copyright 2013 Facebook, Inc.
4 #
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
7
8 """log repository events to a blackbox for debugging
9
10 Logs event information to .hg/blackbox.log to help debug and diagnose problems.
11 The events that get logged can be configured via the blackbox.track config key.
12 Examples:
13
14 [blackbox]
15 track = *
16
17 [blackbox]
18 track = command, commandfinish, commandexception, exthook, pythonhook
19
20 [blackbox]
21 track = incoming
22
23 """
24
25 from mercurial import util, cmdutil
26 from mercurial.i18n import _
27 import os, getpass, re, string
28
29 cmdtable = {}
30 command = cmdutil.command(cmdtable)
31 testedwith = 'internal'
32 lastblackbox = None
33
34 def wrapui(ui):
35 class blackboxui(ui.__class__):
36 @util.propertycache
37 def track(self):
38 return ui.configlist('blackbox', 'track', ['*'])
39
40 def log(self, event, *msg, **opts):
41 global lastblackbox
42 super(blackboxui, self).log(event, *msg, **opts)
43
44 if not '*' in self.track and not event in self.track:
45 return
46
47 if util.safehasattr(self, '_blackbox'):
48 blackbox = self._blackbox
49 else:
50 # certain ui instances exist outside the context of
51 # a repo, so just default to the last blackbox that
52 # was seen.
53 blackbox = lastblackbox
54
55 if blackbox:
56 date = util.datestr(None, '%Y/%m/%d %H:%M:%S')
57 user = getpass.getuser()
58 formattedmsg = msg[0] % msg[1:]
59 blackbox.write('%s %s> %s' % (date, user, formattedmsg))
60 lastblackbox = blackbox
61
62 def setrepo(self, repo):
63 self._blackbox = repo.opener('blackbox.log', 'a')
64
65 ui.__class__ = blackboxui
66
67 def uisetup(ui):
68 wrapui(ui)
69
70 def reposetup(ui, repo):
71 # During 'hg pull' a httppeer repo is created to represent the remote repo.
72 # It doesn't have a .hg directory to put a blackbox in, so we don't do
73 # the blackbox setup for it.
74 if not repo.local():
75 return
76
77 ui.setrepo(repo)
@@ -1,761 +1,761 b''
1 # ui.py - user interface bits for mercurial
1 # ui.py - user interface bits for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from i18n import _
8 from i18n import _
9 import errno, getpass, os, socket, sys, tempfile, traceback
9 import errno, getpass, os, socket, sys, tempfile, traceback
10 import config, scmutil, util, error, formatter
10 import config, scmutil, util, error, formatter
11
11
12 class ui(object):
12 class ui(object):
13 def __init__(self, src=None):
13 def __init__(self, src=None):
14 self._buffers = []
14 self._buffers = []
15 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
15 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
16 self._reportuntrusted = True
16 self._reportuntrusted = True
17 self._ocfg = config.config() # overlay
17 self._ocfg = config.config() # overlay
18 self._tcfg = config.config() # trusted
18 self._tcfg = config.config() # trusted
19 self._ucfg = config.config() # untrusted
19 self._ucfg = config.config() # untrusted
20 self._trustusers = set()
20 self._trustusers = set()
21 self._trustgroups = set()
21 self._trustgroups = set()
22 self.callhooks = True
22 self.callhooks = True
23
23
24 if src:
24 if src:
25 self.fout = src.fout
25 self.fout = src.fout
26 self.ferr = src.ferr
26 self.ferr = src.ferr
27 self.fin = src.fin
27 self.fin = src.fin
28
28
29 self._tcfg = src._tcfg.copy()
29 self._tcfg = src._tcfg.copy()
30 self._ucfg = src._ucfg.copy()
30 self._ucfg = src._ucfg.copy()
31 self._ocfg = src._ocfg.copy()
31 self._ocfg = src._ocfg.copy()
32 self._trustusers = src._trustusers.copy()
32 self._trustusers = src._trustusers.copy()
33 self._trustgroups = src._trustgroups.copy()
33 self._trustgroups = src._trustgroups.copy()
34 self.environ = src.environ
34 self.environ = src.environ
35 self.callhooks = src.callhooks
35 self.callhooks = src.callhooks
36 self.fixconfig()
36 self.fixconfig()
37 else:
37 else:
38 self.fout = sys.stdout
38 self.fout = sys.stdout
39 self.ferr = sys.stderr
39 self.ferr = sys.stderr
40 self.fin = sys.stdin
40 self.fin = sys.stdin
41
41
42 # shared read-only environment
42 # shared read-only environment
43 self.environ = os.environ
43 self.environ = os.environ
44 # we always trust global config files
44 # we always trust global config files
45 for f in scmutil.rcpath():
45 for f in scmutil.rcpath():
46 self.readconfig(f, trust=True)
46 self.readconfig(f, trust=True)
47
47
48 def copy(self):
48 def copy(self):
49 return self.__class__(self)
49 return self.__class__(self)
50
50
51 def formatter(self, topic, opts):
51 def formatter(self, topic, opts):
52 return formatter.formatter(self, topic, opts)
52 return formatter.formatter(self, topic, opts)
53
53
54 def _trusted(self, fp, f):
54 def _trusted(self, fp, f):
55 st = util.fstat(fp)
55 st = util.fstat(fp)
56 if util.isowner(st):
56 if util.isowner(st):
57 return True
57 return True
58
58
59 tusers, tgroups = self._trustusers, self._trustgroups
59 tusers, tgroups = self._trustusers, self._trustgroups
60 if '*' in tusers or '*' in tgroups:
60 if '*' in tusers or '*' in tgroups:
61 return True
61 return True
62
62
63 user = util.username(st.st_uid)
63 user = util.username(st.st_uid)
64 group = util.groupname(st.st_gid)
64 group = util.groupname(st.st_gid)
65 if user in tusers or group in tgroups or user == util.username():
65 if user in tusers or group in tgroups or user == util.username():
66 return True
66 return True
67
67
68 if self._reportuntrusted:
68 if self._reportuntrusted:
69 self.warn(_('not trusting file %s from untrusted '
69 self.warn(_('not trusting file %s from untrusted '
70 'user %s, group %s\n') % (f, user, group))
70 'user %s, group %s\n') % (f, user, group))
71 return False
71 return False
72
72
73 def readconfig(self, filename, root=None, trust=False,
73 def readconfig(self, filename, root=None, trust=False,
74 sections=None, remap=None):
74 sections=None, remap=None):
75 try:
75 try:
76 fp = open(filename)
76 fp = open(filename)
77 except IOError:
77 except IOError:
78 if not sections: # ignore unless we were looking for something
78 if not sections: # ignore unless we were looking for something
79 return
79 return
80 raise
80 raise
81
81
82 cfg = config.config()
82 cfg = config.config()
83 trusted = sections or trust or self._trusted(fp, filename)
83 trusted = sections or trust or self._trusted(fp, filename)
84
84
85 try:
85 try:
86 cfg.read(filename, fp, sections=sections, remap=remap)
86 cfg.read(filename, fp, sections=sections, remap=remap)
87 fp.close()
87 fp.close()
88 except error.ConfigError, inst:
88 except error.ConfigError, inst:
89 if trusted:
89 if trusted:
90 raise
90 raise
91 self.warn(_("ignored: %s\n") % str(inst))
91 self.warn(_("ignored: %s\n") % str(inst))
92
92
93 if self.plain():
93 if self.plain():
94 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
94 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
95 'logtemplate', 'style',
95 'logtemplate', 'style',
96 'traceback', 'verbose'):
96 'traceback', 'verbose'):
97 if k in cfg['ui']:
97 if k in cfg['ui']:
98 del cfg['ui'][k]
98 del cfg['ui'][k]
99 for k, v in cfg.items('defaults'):
99 for k, v in cfg.items('defaults'):
100 del cfg['defaults'][k]
100 del cfg['defaults'][k]
101 # Don't remove aliases from the configuration if in the exceptionlist
101 # Don't remove aliases from the configuration if in the exceptionlist
102 if self.plain('alias'):
102 if self.plain('alias'):
103 for k, v in cfg.items('alias'):
103 for k, v in cfg.items('alias'):
104 del cfg['alias'][k]
104 del cfg['alias'][k]
105
105
106 if trusted:
106 if trusted:
107 self._tcfg.update(cfg)
107 self._tcfg.update(cfg)
108 self._tcfg.update(self._ocfg)
108 self._tcfg.update(self._ocfg)
109 self._ucfg.update(cfg)
109 self._ucfg.update(cfg)
110 self._ucfg.update(self._ocfg)
110 self._ucfg.update(self._ocfg)
111
111
112 if root is None:
112 if root is None:
113 root = os.path.expanduser('~')
113 root = os.path.expanduser('~')
114 self.fixconfig(root=root)
114 self.fixconfig(root=root)
115
115
116 def fixconfig(self, root=None, section=None):
116 def fixconfig(self, root=None, section=None):
117 if section in (None, 'paths'):
117 if section in (None, 'paths'):
118 # expand vars and ~
118 # expand vars and ~
119 # translate paths relative to root (or home) into absolute paths
119 # translate paths relative to root (or home) into absolute paths
120 root = root or os.getcwd()
120 root = root or os.getcwd()
121 for c in self._tcfg, self._ucfg, self._ocfg:
121 for c in self._tcfg, self._ucfg, self._ocfg:
122 for n, p in c.items('paths'):
122 for n, p in c.items('paths'):
123 if not p:
123 if not p:
124 continue
124 continue
125 if '%%' in p:
125 if '%%' in p:
126 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
126 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
127 % (n, p, self.configsource('paths', n)))
127 % (n, p, self.configsource('paths', n)))
128 p = p.replace('%%', '%')
128 p = p.replace('%%', '%')
129 p = util.expandpath(p)
129 p = util.expandpath(p)
130 if not util.hasscheme(p) and not os.path.isabs(p):
130 if not util.hasscheme(p) and not os.path.isabs(p):
131 p = os.path.normpath(os.path.join(root, p))
131 p = os.path.normpath(os.path.join(root, p))
132 c.set("paths", n, p)
132 c.set("paths", n, p)
133
133
134 if section in (None, 'ui'):
134 if section in (None, 'ui'):
135 # update ui options
135 # update ui options
136 self.debugflag = self.configbool('ui', 'debug')
136 self.debugflag = self.configbool('ui', 'debug')
137 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
137 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
138 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
138 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
139 if self.verbose and self.quiet:
139 if self.verbose and self.quiet:
140 self.quiet = self.verbose = False
140 self.quiet = self.verbose = False
141 self._reportuntrusted = self.debugflag or self.configbool("ui",
141 self._reportuntrusted = self.debugflag or self.configbool("ui",
142 "report_untrusted", True)
142 "report_untrusted", True)
143 self.tracebackflag = self.configbool('ui', 'traceback', False)
143 self.tracebackflag = self.configbool('ui', 'traceback', False)
144
144
145 if section in (None, 'trusted'):
145 if section in (None, 'trusted'):
146 # update trust information
146 # update trust information
147 self._trustusers.update(self.configlist('trusted', 'users'))
147 self._trustusers.update(self.configlist('trusted', 'users'))
148 self._trustgroups.update(self.configlist('trusted', 'groups'))
148 self._trustgroups.update(self.configlist('trusted', 'groups'))
149
149
150 def backupconfig(self, section, item):
150 def backupconfig(self, section, item):
151 return (self._ocfg.backup(section, item),
151 return (self._ocfg.backup(section, item),
152 self._tcfg.backup(section, item),
152 self._tcfg.backup(section, item),
153 self._ucfg.backup(section, item),)
153 self._ucfg.backup(section, item),)
154 def restoreconfig(self, data):
154 def restoreconfig(self, data):
155 self._ocfg.restore(data[0])
155 self._ocfg.restore(data[0])
156 self._tcfg.restore(data[1])
156 self._tcfg.restore(data[1])
157 self._ucfg.restore(data[2])
157 self._ucfg.restore(data[2])
158
158
159 def setconfig(self, section, name, value, overlay=True):
159 def setconfig(self, section, name, value, overlay=True):
160 if overlay:
160 if overlay:
161 self._ocfg.set(section, name, value)
161 self._ocfg.set(section, name, value)
162 self._tcfg.set(section, name, value)
162 self._tcfg.set(section, name, value)
163 self._ucfg.set(section, name, value)
163 self._ucfg.set(section, name, value)
164 self.fixconfig(section=section)
164 self.fixconfig(section=section)
165
165
166 def _data(self, untrusted):
166 def _data(self, untrusted):
167 return untrusted and self._ucfg or self._tcfg
167 return untrusted and self._ucfg or self._tcfg
168
168
169 def configsource(self, section, name, untrusted=False):
169 def configsource(self, section, name, untrusted=False):
170 return self._data(untrusted).source(section, name) or 'none'
170 return self._data(untrusted).source(section, name) or 'none'
171
171
172 def config(self, section, name, default=None, untrusted=False):
172 def config(self, section, name, default=None, untrusted=False):
173 if isinstance(name, list):
173 if isinstance(name, list):
174 alternates = name
174 alternates = name
175 else:
175 else:
176 alternates = [name]
176 alternates = [name]
177
177
178 for n in alternates:
178 for n in alternates:
179 value = self._data(untrusted).get(section, name, None)
179 value = self._data(untrusted).get(section, name, None)
180 if value is not None:
180 if value is not None:
181 name = n
181 name = n
182 break
182 break
183 else:
183 else:
184 value = default
184 value = default
185
185
186 if self.debugflag and not untrusted and self._reportuntrusted:
186 if self.debugflag and not untrusted and self._reportuntrusted:
187 uvalue = self._ucfg.get(section, name)
187 uvalue = self._ucfg.get(section, name)
188 if uvalue is not None and uvalue != value:
188 if uvalue is not None and uvalue != value:
189 self.debug("ignoring untrusted configuration option "
189 self.debug("ignoring untrusted configuration option "
190 "%s.%s = %s\n" % (section, name, uvalue))
190 "%s.%s = %s\n" % (section, name, uvalue))
191 return value
191 return value
192
192
193 def configpath(self, section, name, default=None, untrusted=False):
193 def configpath(self, section, name, default=None, untrusted=False):
194 'get a path config item, expanded relative to repo root or config file'
194 'get a path config item, expanded relative to repo root or config file'
195 v = self.config(section, name, default, untrusted)
195 v = self.config(section, name, default, untrusted)
196 if v is None:
196 if v is None:
197 return None
197 return None
198 if not os.path.isabs(v) or "://" not in v:
198 if not os.path.isabs(v) or "://" not in v:
199 src = self.configsource(section, name, untrusted)
199 src = self.configsource(section, name, untrusted)
200 if ':' in src:
200 if ':' in src:
201 base = os.path.dirname(src.rsplit(':')[0])
201 base = os.path.dirname(src.rsplit(':')[0])
202 v = os.path.join(base, os.path.expanduser(v))
202 v = os.path.join(base, os.path.expanduser(v))
203 return v
203 return v
204
204
205 def configbool(self, section, name, default=False, untrusted=False):
205 def configbool(self, section, name, default=False, untrusted=False):
206 """parse a configuration element as a boolean
206 """parse a configuration element as a boolean
207
207
208 >>> u = ui(); s = 'foo'
208 >>> u = ui(); s = 'foo'
209 >>> u.setconfig(s, 'true', 'yes')
209 >>> u.setconfig(s, 'true', 'yes')
210 >>> u.configbool(s, 'true')
210 >>> u.configbool(s, 'true')
211 True
211 True
212 >>> u.setconfig(s, 'false', 'no')
212 >>> u.setconfig(s, 'false', 'no')
213 >>> u.configbool(s, 'false')
213 >>> u.configbool(s, 'false')
214 False
214 False
215 >>> u.configbool(s, 'unknown')
215 >>> u.configbool(s, 'unknown')
216 False
216 False
217 >>> u.configbool(s, 'unknown', True)
217 >>> u.configbool(s, 'unknown', True)
218 True
218 True
219 >>> u.setconfig(s, 'invalid', 'somevalue')
219 >>> u.setconfig(s, 'invalid', 'somevalue')
220 >>> u.configbool(s, 'invalid')
220 >>> u.configbool(s, 'invalid')
221 Traceback (most recent call last):
221 Traceback (most recent call last):
222 ...
222 ...
223 ConfigError: foo.invalid is not a boolean ('somevalue')
223 ConfigError: foo.invalid is not a boolean ('somevalue')
224 """
224 """
225
225
226 v = self.config(section, name, None, untrusted)
226 v = self.config(section, name, None, untrusted)
227 if v is None:
227 if v is None:
228 return default
228 return default
229 if isinstance(v, bool):
229 if isinstance(v, bool):
230 return v
230 return v
231 b = util.parsebool(v)
231 b = util.parsebool(v)
232 if b is None:
232 if b is None:
233 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
233 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
234 % (section, name, v))
234 % (section, name, v))
235 return b
235 return b
236
236
237 def configint(self, section, name, default=None, untrusted=False):
237 def configint(self, section, name, default=None, untrusted=False):
238 """parse a configuration element as an integer
238 """parse a configuration element as an integer
239
239
240 >>> u = ui(); s = 'foo'
240 >>> u = ui(); s = 'foo'
241 >>> u.setconfig(s, 'int1', '42')
241 >>> u.setconfig(s, 'int1', '42')
242 >>> u.configint(s, 'int1')
242 >>> u.configint(s, 'int1')
243 42
243 42
244 >>> u.setconfig(s, 'int2', '-42')
244 >>> u.setconfig(s, 'int2', '-42')
245 >>> u.configint(s, 'int2')
245 >>> u.configint(s, 'int2')
246 -42
246 -42
247 >>> u.configint(s, 'unknown', 7)
247 >>> u.configint(s, 'unknown', 7)
248 7
248 7
249 >>> u.setconfig(s, 'invalid', 'somevalue')
249 >>> u.setconfig(s, 'invalid', 'somevalue')
250 >>> u.configint(s, 'invalid')
250 >>> u.configint(s, 'invalid')
251 Traceback (most recent call last):
251 Traceback (most recent call last):
252 ...
252 ...
253 ConfigError: foo.invalid is not an integer ('somevalue')
253 ConfigError: foo.invalid is not an integer ('somevalue')
254 """
254 """
255
255
256 v = self.config(section, name, None, untrusted)
256 v = self.config(section, name, None, untrusted)
257 if v is None:
257 if v is None:
258 return default
258 return default
259 try:
259 try:
260 return int(v)
260 return int(v)
261 except ValueError:
261 except ValueError:
262 raise error.ConfigError(_("%s.%s is not an integer ('%s')")
262 raise error.ConfigError(_("%s.%s is not an integer ('%s')")
263 % (section, name, v))
263 % (section, name, v))
264
264
265 def configlist(self, section, name, default=None, untrusted=False):
265 def configlist(self, section, name, default=None, untrusted=False):
266 """parse a configuration element as a list of comma/space separated
266 """parse a configuration element as a list of comma/space separated
267 strings
267 strings
268
268
269 >>> u = ui(); s = 'foo'
269 >>> u = ui(); s = 'foo'
270 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
270 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
271 >>> u.configlist(s, 'list1')
271 >>> u.configlist(s, 'list1')
272 ['this', 'is', 'a small', 'test']
272 ['this', 'is', 'a small', 'test']
273 """
273 """
274
274
275 def _parse_plain(parts, s, offset):
275 def _parse_plain(parts, s, offset):
276 whitespace = False
276 whitespace = False
277 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
277 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
278 whitespace = True
278 whitespace = True
279 offset += 1
279 offset += 1
280 if offset >= len(s):
280 if offset >= len(s):
281 return None, parts, offset
281 return None, parts, offset
282 if whitespace:
282 if whitespace:
283 parts.append('')
283 parts.append('')
284 if s[offset] == '"' and not parts[-1]:
284 if s[offset] == '"' and not parts[-1]:
285 return _parse_quote, parts, offset + 1
285 return _parse_quote, parts, offset + 1
286 elif s[offset] == '"' and parts[-1][-1] == '\\':
286 elif s[offset] == '"' and parts[-1][-1] == '\\':
287 parts[-1] = parts[-1][:-1] + s[offset]
287 parts[-1] = parts[-1][:-1] + s[offset]
288 return _parse_plain, parts, offset + 1
288 return _parse_plain, parts, offset + 1
289 parts[-1] += s[offset]
289 parts[-1] += s[offset]
290 return _parse_plain, parts, offset + 1
290 return _parse_plain, parts, offset + 1
291
291
292 def _parse_quote(parts, s, offset):
292 def _parse_quote(parts, s, offset):
293 if offset < len(s) and s[offset] == '"': # ""
293 if offset < len(s) and s[offset] == '"': # ""
294 parts.append('')
294 parts.append('')
295 offset += 1
295 offset += 1
296 while offset < len(s) and (s[offset].isspace() or
296 while offset < len(s) and (s[offset].isspace() or
297 s[offset] == ','):
297 s[offset] == ','):
298 offset += 1
298 offset += 1
299 return _parse_plain, parts, offset
299 return _parse_plain, parts, offset
300
300
301 while offset < len(s) and s[offset] != '"':
301 while offset < len(s) and s[offset] != '"':
302 if (s[offset] == '\\' and offset + 1 < len(s)
302 if (s[offset] == '\\' and offset + 1 < len(s)
303 and s[offset + 1] == '"'):
303 and s[offset + 1] == '"'):
304 offset += 1
304 offset += 1
305 parts[-1] += '"'
305 parts[-1] += '"'
306 else:
306 else:
307 parts[-1] += s[offset]
307 parts[-1] += s[offset]
308 offset += 1
308 offset += 1
309
309
310 if offset >= len(s):
310 if offset >= len(s):
311 real_parts = _configlist(parts[-1])
311 real_parts = _configlist(parts[-1])
312 if not real_parts:
312 if not real_parts:
313 parts[-1] = '"'
313 parts[-1] = '"'
314 else:
314 else:
315 real_parts[0] = '"' + real_parts[0]
315 real_parts[0] = '"' + real_parts[0]
316 parts = parts[:-1]
316 parts = parts[:-1]
317 parts.extend(real_parts)
317 parts.extend(real_parts)
318 return None, parts, offset
318 return None, parts, offset
319
319
320 offset += 1
320 offset += 1
321 while offset < len(s) and s[offset] in [' ', ',']:
321 while offset < len(s) and s[offset] in [' ', ',']:
322 offset += 1
322 offset += 1
323
323
324 if offset < len(s):
324 if offset < len(s):
325 if offset + 1 == len(s) and s[offset] == '"':
325 if offset + 1 == len(s) and s[offset] == '"':
326 parts[-1] += '"'
326 parts[-1] += '"'
327 offset += 1
327 offset += 1
328 else:
328 else:
329 parts.append('')
329 parts.append('')
330 else:
330 else:
331 return None, parts, offset
331 return None, parts, offset
332
332
333 return _parse_plain, parts, offset
333 return _parse_plain, parts, offset
334
334
335 def _configlist(s):
335 def _configlist(s):
336 s = s.rstrip(' ,')
336 s = s.rstrip(' ,')
337 if not s:
337 if not s:
338 return []
338 return []
339 parser, parts, offset = _parse_plain, [''], 0
339 parser, parts, offset = _parse_plain, [''], 0
340 while parser:
340 while parser:
341 parser, parts, offset = parser(parts, s, offset)
341 parser, parts, offset = parser(parts, s, offset)
342 return parts
342 return parts
343
343
344 result = self.config(section, name, untrusted=untrusted)
344 result = self.config(section, name, untrusted=untrusted)
345 if result is None:
345 if result is None:
346 result = default or []
346 result = default or []
347 if isinstance(result, basestring):
347 if isinstance(result, basestring):
348 result = _configlist(result.lstrip(' ,\n'))
348 result = _configlist(result.lstrip(' ,\n'))
349 if result is None:
349 if result is None:
350 result = default or []
350 result = default or []
351 return result
351 return result
352
352
353 def has_section(self, section, untrusted=False):
353 def has_section(self, section, untrusted=False):
354 '''tell whether section exists in config.'''
354 '''tell whether section exists in config.'''
355 return section in self._data(untrusted)
355 return section in self._data(untrusted)
356
356
357 def configitems(self, section, untrusted=False):
357 def configitems(self, section, untrusted=False):
358 items = self._data(untrusted).items(section)
358 items = self._data(untrusted).items(section)
359 if self.debugflag and not untrusted and self._reportuntrusted:
359 if self.debugflag and not untrusted and self._reportuntrusted:
360 for k, v in self._ucfg.items(section):
360 for k, v in self._ucfg.items(section):
361 if self._tcfg.get(section, k) != v:
361 if self._tcfg.get(section, k) != v:
362 self.debug("ignoring untrusted configuration option "
362 self.debug("ignoring untrusted configuration option "
363 "%s.%s = %s\n" % (section, k, v))
363 "%s.%s = %s\n" % (section, k, v))
364 return items
364 return items
365
365
366 def walkconfig(self, untrusted=False):
366 def walkconfig(self, untrusted=False):
367 cfg = self._data(untrusted)
367 cfg = self._data(untrusted)
368 for section in cfg.sections():
368 for section in cfg.sections():
369 for name, value in self.configitems(section, untrusted):
369 for name, value in self.configitems(section, untrusted):
370 yield section, name, value
370 yield section, name, value
371
371
372 def plain(self, feature=None):
372 def plain(self, feature=None):
373 '''is plain mode active?
373 '''is plain mode active?
374
374
375 Plain mode means that all configuration variables which affect
375 Plain mode means that all configuration variables which affect
376 the behavior and output of Mercurial should be
376 the behavior and output of Mercurial should be
377 ignored. Additionally, the output should be stable,
377 ignored. Additionally, the output should be stable,
378 reproducible and suitable for use in scripts or applications.
378 reproducible and suitable for use in scripts or applications.
379
379
380 The only way to trigger plain mode is by setting either the
380 The only way to trigger plain mode is by setting either the
381 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
381 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
382
382
383 The return value can either be
383 The return value can either be
384 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
384 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
385 - True otherwise
385 - True otherwise
386 '''
386 '''
387 if 'HGPLAIN' not in os.environ and 'HGPLAINEXCEPT' not in os.environ:
387 if 'HGPLAIN' not in os.environ and 'HGPLAINEXCEPT' not in os.environ:
388 return False
388 return False
389 exceptions = os.environ.get('HGPLAINEXCEPT', '').strip().split(',')
389 exceptions = os.environ.get('HGPLAINEXCEPT', '').strip().split(',')
390 if feature and exceptions:
390 if feature and exceptions:
391 return feature not in exceptions
391 return feature not in exceptions
392 return True
392 return True
393
393
394 def username(self):
394 def username(self):
395 """Return default username to be used in commits.
395 """Return default username to be used in commits.
396
396
397 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
397 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
398 and stop searching if one of these is set.
398 and stop searching if one of these is set.
399 If not found and ui.askusername is True, ask the user, else use
399 If not found and ui.askusername is True, ask the user, else use
400 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
400 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
401 """
401 """
402 user = os.environ.get("HGUSER")
402 user = os.environ.get("HGUSER")
403 if user is None:
403 if user is None:
404 user = self.config("ui", "username")
404 user = self.config("ui", "username")
405 if user is not None:
405 if user is not None:
406 user = os.path.expandvars(user)
406 user = os.path.expandvars(user)
407 if user is None:
407 if user is None:
408 user = os.environ.get("EMAIL")
408 user = os.environ.get("EMAIL")
409 if user is None and self.configbool("ui", "askusername"):
409 if user is None and self.configbool("ui", "askusername"):
410 user = self.prompt(_("enter a commit username:"), default=None)
410 user = self.prompt(_("enter a commit username:"), default=None)
411 if user is None and not self.interactive():
411 if user is None and not self.interactive():
412 try:
412 try:
413 user = '%s@%s' % (util.getuser(), socket.getfqdn())
413 user = '%s@%s' % (util.getuser(), socket.getfqdn())
414 self.warn(_("no username found, using '%s' instead\n") % user)
414 self.warn(_("no username found, using '%s' instead\n") % user)
415 except KeyError:
415 except KeyError:
416 pass
416 pass
417 if not user:
417 if not user:
418 raise util.Abort(_('no username supplied (see "hg help config")'))
418 raise util.Abort(_('no username supplied (see "hg help config")'))
419 if "\n" in user:
419 if "\n" in user:
420 raise util.Abort(_("username %s contains a newline\n") % repr(user))
420 raise util.Abort(_("username %s contains a newline\n") % repr(user))
421 return user
421 return user
422
422
423 def shortuser(self, user):
423 def shortuser(self, user):
424 """Return a short representation of a user name or email address."""
424 """Return a short representation of a user name or email address."""
425 if not self.verbose:
425 if not self.verbose:
426 user = util.shortuser(user)
426 user = util.shortuser(user)
427 return user
427 return user
428
428
429 def expandpath(self, loc, default=None):
429 def expandpath(self, loc, default=None):
430 """Return repository location relative to cwd or from [paths]"""
430 """Return repository location relative to cwd or from [paths]"""
431 if util.hasscheme(loc) or os.path.isdir(os.path.join(loc, '.hg')):
431 if util.hasscheme(loc) or os.path.isdir(os.path.join(loc, '.hg')):
432 return loc
432 return loc
433
433
434 path = self.config('paths', loc)
434 path = self.config('paths', loc)
435 if not path and default is not None:
435 if not path and default is not None:
436 path = self.config('paths', default)
436 path = self.config('paths', default)
437 return path or loc
437 return path or loc
438
438
439 def pushbuffer(self):
439 def pushbuffer(self):
440 self._buffers.append([])
440 self._buffers.append([])
441
441
442 def popbuffer(self, labeled=False):
442 def popbuffer(self, labeled=False):
443 '''pop the last buffer and return the buffered output
443 '''pop the last buffer and return the buffered output
444
444
445 If labeled is True, any labels associated with buffered
445 If labeled is True, any labels associated with buffered
446 output will be handled. By default, this has no effect
446 output will be handled. By default, this has no effect
447 on the output returned, but extensions and GUI tools may
447 on the output returned, but extensions and GUI tools may
448 handle this argument and returned styled output. If output
448 handle this argument and returned styled output. If output
449 is being buffered so it can be captured and parsed or
449 is being buffered so it can be captured and parsed or
450 processed, labeled should not be set to True.
450 processed, labeled should not be set to True.
451 '''
451 '''
452 return "".join(self._buffers.pop())
452 return "".join(self._buffers.pop())
453
453
454 def write(self, *args, **opts):
454 def write(self, *args, **opts):
455 '''write args to output
455 '''write args to output
456
456
457 By default, this method simply writes to the buffer or stdout,
457 By default, this method simply writes to the buffer or stdout,
458 but extensions or GUI tools may override this method,
458 but extensions or GUI tools may override this method,
459 write_err(), popbuffer(), and label() to style output from
459 write_err(), popbuffer(), and label() to style output from
460 various parts of hg.
460 various parts of hg.
461
461
462 An optional keyword argument, "label", can be passed in.
462 An optional keyword argument, "label", can be passed in.
463 This should be a string containing label names separated by
463 This should be a string containing label names separated by
464 space. Label names take the form of "topic.type". For example,
464 space. Label names take the form of "topic.type". For example,
465 ui.debug() issues a label of "ui.debug".
465 ui.debug() issues a label of "ui.debug".
466
466
467 When labeling output for a specific command, a label of
467 When labeling output for a specific command, a label of
468 "cmdname.type" is recommended. For example, status issues
468 "cmdname.type" is recommended. For example, status issues
469 a label of "status.modified" for modified files.
469 a label of "status.modified" for modified files.
470 '''
470 '''
471 if self._buffers:
471 if self._buffers:
472 self._buffers[-1].extend([str(a) for a in args])
472 self._buffers[-1].extend([str(a) for a in args])
473 else:
473 else:
474 for a in args:
474 for a in args:
475 self.fout.write(str(a))
475 self.fout.write(str(a))
476
476
477 def write_err(self, *args, **opts):
477 def write_err(self, *args, **opts):
478 try:
478 try:
479 if not getattr(self.fout, 'closed', False):
479 if not getattr(self.fout, 'closed', False):
480 self.fout.flush()
480 self.fout.flush()
481 for a in args:
481 for a in args:
482 self.ferr.write(str(a))
482 self.ferr.write(str(a))
483 # stderr may be buffered under win32 when redirected to files,
483 # stderr may be buffered under win32 when redirected to files,
484 # including stdout.
484 # including stdout.
485 if not getattr(self.ferr, 'closed', False):
485 if not getattr(self.ferr, 'closed', False):
486 self.ferr.flush()
486 self.ferr.flush()
487 except IOError, inst:
487 except IOError, inst:
488 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
488 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
489 raise
489 raise
490
490
491 def flush(self):
491 def flush(self):
492 try: self.fout.flush()
492 try: self.fout.flush()
493 except (IOError, ValueError): pass
493 except (IOError, ValueError): pass
494 try: self.ferr.flush()
494 try: self.ferr.flush()
495 except (IOError, ValueError): pass
495 except (IOError, ValueError): pass
496
496
497 def _isatty(self, fh):
497 def _isatty(self, fh):
498 if self.configbool('ui', 'nontty', False):
498 if self.configbool('ui', 'nontty', False):
499 return False
499 return False
500 return util.isatty(fh)
500 return util.isatty(fh)
501
501
502 def interactive(self):
502 def interactive(self):
503 '''is interactive input allowed?
503 '''is interactive input allowed?
504
504
505 An interactive session is a session where input can be reasonably read
505 An interactive session is a session where input can be reasonably read
506 from `sys.stdin'. If this function returns false, any attempt to read
506 from `sys.stdin'. If this function returns false, any attempt to read
507 from stdin should fail with an error, unless a sensible default has been
507 from stdin should fail with an error, unless a sensible default has been
508 specified.
508 specified.
509
509
510 Interactiveness is triggered by the value of the `ui.interactive'
510 Interactiveness is triggered by the value of the `ui.interactive'
511 configuration variable or - if it is unset - when `sys.stdin' points
511 configuration variable or - if it is unset - when `sys.stdin' points
512 to a terminal device.
512 to a terminal device.
513
513
514 This function refers to input only; for output, see `ui.formatted()'.
514 This function refers to input only; for output, see `ui.formatted()'.
515 '''
515 '''
516 i = self.configbool("ui", "interactive", None)
516 i = self.configbool("ui", "interactive", None)
517 if i is None:
517 if i is None:
518 # some environments replace stdin without implementing isatty
518 # some environments replace stdin without implementing isatty
519 # usually those are non-interactive
519 # usually those are non-interactive
520 return self._isatty(self.fin)
520 return self._isatty(self.fin)
521
521
522 return i
522 return i
523
523
524 def termwidth(self):
524 def termwidth(self):
525 '''how wide is the terminal in columns?
525 '''how wide is the terminal in columns?
526 '''
526 '''
527 if 'COLUMNS' in os.environ:
527 if 'COLUMNS' in os.environ:
528 try:
528 try:
529 return int(os.environ['COLUMNS'])
529 return int(os.environ['COLUMNS'])
530 except ValueError:
530 except ValueError:
531 pass
531 pass
532 return util.termwidth()
532 return util.termwidth()
533
533
534 def formatted(self):
534 def formatted(self):
535 '''should formatted output be used?
535 '''should formatted output be used?
536
536
537 It is often desirable to format the output to suite the output medium.
537 It is often desirable to format the output to suite the output medium.
538 Examples of this are truncating long lines or colorizing messages.
538 Examples of this are truncating long lines or colorizing messages.
539 However, this is not often not desirable when piping output into other
539 However, this is not often not desirable when piping output into other
540 utilities, e.g. `grep'.
540 utilities, e.g. `grep'.
541
541
542 Formatted output is triggered by the value of the `ui.formatted'
542 Formatted output is triggered by the value of the `ui.formatted'
543 configuration variable or - if it is unset - when `sys.stdout' points
543 configuration variable or - if it is unset - when `sys.stdout' points
544 to a terminal device. Please note that `ui.formatted' should be
544 to a terminal device. Please note that `ui.formatted' should be
545 considered an implementation detail; it is not intended for use outside
545 considered an implementation detail; it is not intended for use outside
546 Mercurial or its extensions.
546 Mercurial or its extensions.
547
547
548 This function refers to output only; for input, see `ui.interactive()'.
548 This function refers to output only; for input, see `ui.interactive()'.
549 This function always returns false when in plain mode, see `ui.plain()'.
549 This function always returns false when in plain mode, see `ui.plain()'.
550 '''
550 '''
551 if self.plain():
551 if self.plain():
552 return False
552 return False
553
553
554 i = self.configbool("ui", "formatted", None)
554 i = self.configbool("ui", "formatted", None)
555 if i is None:
555 if i is None:
556 # some environments replace stdout without implementing isatty
556 # some environments replace stdout without implementing isatty
557 # usually those are non-interactive
557 # usually those are non-interactive
558 return self._isatty(self.fout)
558 return self._isatty(self.fout)
559
559
560 return i
560 return i
561
561
562 def _readline(self, prompt=''):
562 def _readline(self, prompt=''):
563 if self._isatty(self.fin):
563 if self._isatty(self.fin):
564 try:
564 try:
565 # magically add command line editing support, where
565 # magically add command line editing support, where
566 # available
566 # available
567 import readline
567 import readline
568 # force demandimport to really load the module
568 # force demandimport to really load the module
569 readline.read_history_file
569 readline.read_history_file
570 # windows sometimes raises something other than ImportError
570 # windows sometimes raises something other than ImportError
571 except Exception:
571 except Exception:
572 pass
572 pass
573
573
574 # call write() so output goes through subclassed implementation
574 # call write() so output goes through subclassed implementation
575 # e.g. color extension on Windows
575 # e.g. color extension on Windows
576 self.write(prompt)
576 self.write(prompt)
577
577
578 # instead of trying to emulate raw_input, swap (self.fin,
578 # instead of trying to emulate raw_input, swap (self.fin,
579 # self.fout) with (sys.stdin, sys.stdout)
579 # self.fout) with (sys.stdin, sys.stdout)
580 oldin = sys.stdin
580 oldin = sys.stdin
581 oldout = sys.stdout
581 oldout = sys.stdout
582 sys.stdin = self.fin
582 sys.stdin = self.fin
583 sys.stdout = self.fout
583 sys.stdout = self.fout
584 line = raw_input(' ')
584 line = raw_input(' ')
585 sys.stdin = oldin
585 sys.stdin = oldin
586 sys.stdout = oldout
586 sys.stdout = oldout
587
587
588 # When stdin is in binary mode on Windows, it can cause
588 # When stdin is in binary mode on Windows, it can cause
589 # raw_input() to emit an extra trailing carriage return
589 # raw_input() to emit an extra trailing carriage return
590 if os.linesep == '\r\n' and line and line[-1] == '\r':
590 if os.linesep == '\r\n' and line and line[-1] == '\r':
591 line = line[:-1]
591 line = line[:-1]
592 return line
592 return line
593
593
594 def prompt(self, msg, default="y"):
594 def prompt(self, msg, default="y"):
595 """Prompt user with msg, read response.
595 """Prompt user with msg, read response.
596 If ui is not interactive, the default is returned.
596 If ui is not interactive, the default is returned.
597 """
597 """
598 if not self.interactive():
598 if not self.interactive():
599 self.write(msg, ' ', default, "\n")
599 self.write(msg, ' ', default, "\n")
600 return default
600 return default
601 try:
601 try:
602 r = self._readline(self.label(msg, 'ui.prompt'))
602 r = self._readline(self.label(msg, 'ui.prompt'))
603 if not r:
603 if not r:
604 return default
604 return default
605 return r
605 return r
606 except EOFError:
606 except EOFError:
607 raise util.Abort(_('response expected'))
607 raise util.Abort(_('response expected'))
608
608
609 def promptchoice(self, msg, choices, default=0):
609 def promptchoice(self, msg, choices, default=0):
610 """Prompt user with msg, read response, and ensure it matches
610 """Prompt user with msg, read response, and ensure it matches
611 one of the provided choices. The index of the choice is returned.
611 one of the provided choices. The index of the choice is returned.
612 choices is a sequence of acceptable responses with the format:
612 choices is a sequence of acceptable responses with the format:
613 ('&None', 'E&xec', 'Sym&link') Responses are case insensitive.
613 ('&None', 'E&xec', 'Sym&link') Responses are case insensitive.
614 If ui is not interactive, the default is returned.
614 If ui is not interactive, the default is returned.
615 """
615 """
616 resps = [s[s.index('&') + 1].lower() for s in choices]
616 resps = [s[s.index('&') + 1].lower() for s in choices]
617 while True:
617 while True:
618 r = self.prompt(msg, resps[default])
618 r = self.prompt(msg, resps[default])
619 if r.lower() in resps:
619 if r.lower() in resps:
620 return resps.index(r.lower())
620 return resps.index(r.lower())
621 self.write(_("unrecognized response\n"))
621 self.write(_("unrecognized response\n"))
622
622
623 def getpass(self, prompt=None, default=None):
623 def getpass(self, prompt=None, default=None):
624 if not self.interactive():
624 if not self.interactive():
625 return default
625 return default
626 try:
626 try:
627 return getpass.getpass(prompt or _('password: '))
627 return getpass.getpass(prompt or _('password: '))
628 except EOFError:
628 except EOFError:
629 raise util.Abort(_('response expected'))
629 raise util.Abort(_('response expected'))
630 def status(self, *msg, **opts):
630 def status(self, *msg, **opts):
631 '''write status message to output (if ui.quiet is False)
631 '''write status message to output (if ui.quiet is False)
632
632
633 This adds an output label of "ui.status".
633 This adds an output label of "ui.status".
634 '''
634 '''
635 if not self.quiet:
635 if not self.quiet:
636 opts['label'] = opts.get('label', '') + ' ui.status'
636 opts['label'] = opts.get('label', '') + ' ui.status'
637 self.write(*msg, **opts)
637 self.write(*msg, **opts)
638 def warn(self, *msg, **opts):
638 def warn(self, *msg, **opts):
639 '''write warning message to output (stderr)
639 '''write warning message to output (stderr)
640
640
641 This adds an output label of "ui.warning".
641 This adds an output label of "ui.warning".
642 '''
642 '''
643 opts['label'] = opts.get('label', '') + ' ui.warning'
643 opts['label'] = opts.get('label', '') + ' ui.warning'
644 self.write_err(*msg, **opts)
644 self.write_err(*msg, **opts)
645 def note(self, *msg, **opts):
645 def note(self, *msg, **opts):
646 '''write note to output (if ui.verbose is True)
646 '''write note to output (if ui.verbose is True)
647
647
648 This adds an output label of "ui.note".
648 This adds an output label of "ui.note".
649 '''
649 '''
650 if self.verbose:
650 if self.verbose:
651 opts['label'] = opts.get('label', '') + ' ui.note'
651 opts['label'] = opts.get('label', '') + ' ui.note'
652 self.write(*msg, **opts)
652 self.write(*msg, **opts)
653 def debug(self, *msg, **opts):
653 def debug(self, *msg, **opts):
654 '''write debug message to output (if ui.debugflag is True)
654 '''write debug message to output (if ui.debugflag is True)
655
655
656 This adds an output label of "ui.debug".
656 This adds an output label of "ui.debug".
657 '''
657 '''
658 if self.debugflag:
658 if self.debugflag:
659 opts['label'] = opts.get('label', '') + ' ui.debug'
659 opts['label'] = opts.get('label', '') + ' ui.debug'
660 self.write(*msg, **opts)
660 self.write(*msg, **opts)
661 def edit(self, text, user):
661 def edit(self, text, user):
662 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
662 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
663 text=True)
663 text=True)
664 try:
664 try:
665 f = os.fdopen(fd, "w")
665 f = os.fdopen(fd, "w")
666 f.write(text)
666 f.write(text)
667 f.close()
667 f.close()
668
668
669 editor = self.geteditor()
669 editor = self.geteditor()
670
670
671 util.system("%s \"%s\"" % (editor, name),
671 util.system("%s \"%s\"" % (editor, name),
672 environ={'HGUSER': user},
672 environ={'HGUSER': user},
673 onerr=util.Abort, errprefix=_("edit failed"),
673 onerr=util.Abort, errprefix=_("edit failed"),
674 out=self.fout)
674 out=self.fout)
675
675
676 f = open(name)
676 f = open(name)
677 t = f.read()
677 t = f.read()
678 f.close()
678 f.close()
679 finally:
679 finally:
680 os.unlink(name)
680 os.unlink(name)
681
681
682 return t
682 return t
683
683
684 def traceback(self, exc=None):
684 def traceback(self, exc=None):
685 '''print exception traceback if traceback printing enabled.
685 '''print exception traceback if traceback printing enabled.
686 only to call in exception handler. returns true if traceback
686 only to call in exception handler. returns true if traceback
687 printed.'''
687 printed.'''
688 if self.tracebackflag:
688 if self.tracebackflag:
689 if exc:
689 if exc:
690 traceback.print_exception(exc[0], exc[1], exc[2],
690 traceback.print_exception(exc[0], exc[1], exc[2],
691 file=self.ferr)
691 file=self.ferr)
692 else:
692 else:
693 traceback.print_exc(file=self.ferr)
693 traceback.print_exc(file=self.ferr)
694 return self.tracebackflag
694 return self.tracebackflag
695
695
696 def geteditor(self):
696 def geteditor(self):
697 '''return editor to use'''
697 '''return editor to use'''
698 if sys.platform == 'plan9':
698 if sys.platform == 'plan9':
699 # vi is the MIPS instruction simulator on Plan 9. We
699 # vi is the MIPS instruction simulator on Plan 9. We
700 # instead default to E to plumb commit messages to
700 # instead default to E to plumb commit messages to
701 # avoid confusion.
701 # avoid confusion.
702 editor = 'E'
702 editor = 'E'
703 else:
703 else:
704 editor = 'vi'
704 editor = 'vi'
705 return (os.environ.get("HGEDITOR") or
705 return (os.environ.get("HGEDITOR") or
706 self.config("ui", "editor") or
706 self.config("ui", "editor") or
707 os.environ.get("VISUAL") or
707 os.environ.get("VISUAL") or
708 os.environ.get("EDITOR", editor))
708 os.environ.get("EDITOR", editor))
709
709
710 def progress(self, topic, pos, item="", unit="", total=None):
710 def progress(self, topic, pos, item="", unit="", total=None):
711 '''show a progress message
711 '''show a progress message
712
712
713 With stock hg, this is simply a debug message that is hidden
713 With stock hg, this is simply a debug message that is hidden
714 by default, but with extensions or GUI tools it may be
714 by default, but with extensions or GUI tools it may be
715 visible. 'topic' is the current operation, 'item' is a
715 visible. 'topic' is the current operation, 'item' is a
716 non-numeric marker of the current position (i.e. the currently
716 non-numeric marker of the current position (i.e. the currently
717 in-process file), 'pos' is the current numeric position (i.e.
717 in-process file), 'pos' is the current numeric position (i.e.
718 revision, bytes, etc.), unit is a corresponding unit label,
718 revision, bytes, etc.), unit is a corresponding unit label,
719 and total is the highest expected pos.
719 and total is the highest expected pos.
720
720
721 Multiple nested topics may be active at a time.
721 Multiple nested topics may be active at a time.
722
722
723 All topics should be marked closed by setting pos to None at
723 All topics should be marked closed by setting pos to None at
724 termination.
724 termination.
725 '''
725 '''
726
726
727 if pos is None or not self.debugflag:
727 if pos is None or not self.debugflag:
728 return
728 return
729
729
730 if unit:
730 if unit:
731 unit = ' ' + unit
731 unit = ' ' + unit
732 if item:
732 if item:
733 item = ' ' + item
733 item = ' ' + item
734
734
735 if total:
735 if total:
736 pct = 100.0 * pos / total
736 pct = 100.0 * pos / total
737 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
737 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
738 % (topic, item, pos, total, unit, pct))
738 % (topic, item, pos, total, unit, pct))
739 else:
739 else:
740 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
740 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
741
741
742 def log(self, service, message):
742 def log(self, service, *msg, **opts):
743 '''hook for logging facility extensions
743 '''hook for logging facility extensions
744
744
745 service should be a readily-identifiable subsystem, which will
745 service should be a readily-identifiable subsystem, which will
746 allow filtering.
746 allow filtering.
747 message should be a newline-terminated string to log.
747 message should be a newline-terminated string to log.
748 '''
748 '''
749 pass
749 pass
750
750
751 def label(self, msg, label):
751 def label(self, msg, label):
752 '''style msg based on supplied label
752 '''style msg based on supplied label
753
753
754 Like ui.write(), this just returns msg unchanged, but extensions
754 Like ui.write(), this just returns msg unchanged, but extensions
755 and GUI tools can override it to allow styling output without
755 and GUI tools can override it to allow styling output without
756 writing it.
756 writing it.
757
757
758 ui.write(s, 'label') is equivalent to
758 ui.write(s, 'label') is equivalent to
759 ui.write(ui.label(s, 'label')).
759 ui.write(ui.label(s, 'label')).
760 '''
760 '''
761 return msg
761 return msg
General Comments 0
You need to be logged in to leave comments. Login now