##// END OF EJS Templates
ui: look before you leap on sys.stderr.closed (and look nicer)...
Thomas Arendsen Hein -
r10421:452b6195 default
parent child Browse files
Show More
@@ -1,403 +1,403 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, util, error
10 import config, util, error
11
11
12 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True,
12 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True,
13 '0': False, 'no': False, 'false': False, 'off': False}
13 '0': False, 'no': False, 'false': False, 'off': False}
14
14
15 class ui(object):
15 class ui(object):
16 def __init__(self, src=None):
16 def __init__(self, src=None):
17 self._buffers = []
17 self._buffers = []
18 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
18 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
19 self._reportuntrusted = True
19 self._reportuntrusted = True
20 self._ocfg = config.config() # overlay
20 self._ocfg = config.config() # overlay
21 self._tcfg = config.config() # trusted
21 self._tcfg = config.config() # trusted
22 self._ucfg = config.config() # untrusted
22 self._ucfg = config.config() # untrusted
23 self._trustusers = set()
23 self._trustusers = set()
24 self._trustgroups = set()
24 self._trustgroups = set()
25
25
26 if src:
26 if src:
27 self._tcfg = src._tcfg.copy()
27 self._tcfg = src._tcfg.copy()
28 self._ucfg = src._ucfg.copy()
28 self._ucfg = src._ucfg.copy()
29 self._ocfg = src._ocfg.copy()
29 self._ocfg = src._ocfg.copy()
30 self._trustusers = src._trustusers.copy()
30 self._trustusers = src._trustusers.copy()
31 self._trustgroups = src._trustgroups.copy()
31 self._trustgroups = src._trustgroups.copy()
32 self.environ = src.environ
32 self.environ = src.environ
33 self.fixconfig()
33 self.fixconfig()
34 else:
34 else:
35 # shared read-only environment
35 # shared read-only environment
36 self.environ = os.environ
36 self.environ = os.environ
37 # we always trust global config files
37 # we always trust global config files
38 for f in util.rcpath():
38 for f in util.rcpath():
39 self.readconfig(f, trust=True)
39 self.readconfig(f, trust=True)
40
40
41 def copy(self):
41 def copy(self):
42 return self.__class__(self)
42 return self.__class__(self)
43
43
44 def _is_trusted(self, fp, f):
44 def _is_trusted(self, fp, f):
45 st = util.fstat(fp)
45 st = util.fstat(fp)
46 if util.isowner(st):
46 if util.isowner(st):
47 return True
47 return True
48
48
49 tusers, tgroups = self._trustusers, self._trustgroups
49 tusers, tgroups = self._trustusers, self._trustgroups
50 if '*' in tusers or '*' in tgroups:
50 if '*' in tusers or '*' in tgroups:
51 return True
51 return True
52
52
53 user = util.username(st.st_uid)
53 user = util.username(st.st_uid)
54 group = util.groupname(st.st_gid)
54 group = util.groupname(st.st_gid)
55 if user in tusers or group in tgroups or user == util.username():
55 if user in tusers or group in tgroups or user == util.username():
56 return True
56 return True
57
57
58 if self._reportuntrusted:
58 if self._reportuntrusted:
59 self.warn(_('Not trusting file %s from untrusted '
59 self.warn(_('Not trusting file %s from untrusted '
60 'user %s, group %s\n') % (f, user, group))
60 'user %s, group %s\n') % (f, user, group))
61 return False
61 return False
62
62
63 def readconfig(self, filename, root=None, trust=False,
63 def readconfig(self, filename, root=None, trust=False,
64 sections=None, remap=None):
64 sections=None, remap=None):
65 try:
65 try:
66 fp = open(filename)
66 fp = open(filename)
67 except IOError:
67 except IOError:
68 if not sections: # ignore unless we were looking for something
68 if not sections: # ignore unless we were looking for something
69 return
69 return
70 raise
70 raise
71
71
72 cfg = config.config()
72 cfg = config.config()
73 trusted = sections or trust or self._is_trusted(fp, filename)
73 trusted = sections or trust or self._is_trusted(fp, filename)
74
74
75 try:
75 try:
76 cfg.read(filename, fp, sections=sections, remap=remap)
76 cfg.read(filename, fp, sections=sections, remap=remap)
77 except error.ConfigError, inst:
77 except error.ConfigError, inst:
78 if trusted:
78 if trusted:
79 raise
79 raise
80 self.warn(_("Ignored: %s\n") % str(inst))
80 self.warn(_("Ignored: %s\n") % str(inst))
81
81
82 if trusted:
82 if trusted:
83 self._tcfg.update(cfg)
83 self._tcfg.update(cfg)
84 self._tcfg.update(self._ocfg)
84 self._tcfg.update(self._ocfg)
85 self._ucfg.update(cfg)
85 self._ucfg.update(cfg)
86 self._ucfg.update(self._ocfg)
86 self._ucfg.update(self._ocfg)
87
87
88 if root is None:
88 if root is None:
89 root = os.path.expanduser('~')
89 root = os.path.expanduser('~')
90 self.fixconfig(root=root)
90 self.fixconfig(root=root)
91
91
92 def fixconfig(self, root=None):
92 def fixconfig(self, root=None):
93 # translate paths relative to root (or home) into absolute paths
93 # translate paths relative to root (or home) into absolute paths
94 root = root or os.getcwd()
94 root = root or os.getcwd()
95 for c in self._tcfg, self._ucfg, self._ocfg:
95 for c in self._tcfg, self._ucfg, self._ocfg:
96 for n, p in c.items('paths'):
96 for n, p in c.items('paths'):
97 if p and "://" not in p and not os.path.isabs(p):
97 if p and "://" not in p and not os.path.isabs(p):
98 c.set("paths", n, os.path.normpath(os.path.join(root, p)))
98 c.set("paths", n, os.path.normpath(os.path.join(root, p)))
99
99
100 # update ui options
100 # update ui options
101 self.debugflag = self.configbool('ui', 'debug')
101 self.debugflag = self.configbool('ui', 'debug')
102 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
102 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
103 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
103 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
104 if self.verbose and self.quiet:
104 if self.verbose and self.quiet:
105 self.quiet = self.verbose = False
105 self.quiet = self.verbose = False
106 self._reportuntrusted = self.configbool("ui", "report_untrusted", True)
106 self._reportuntrusted = self.configbool("ui", "report_untrusted", True)
107 self.tracebackflag = self.configbool('ui', 'traceback', False)
107 self.tracebackflag = self.configbool('ui', 'traceback', False)
108
108
109 # update trust information
109 # update trust information
110 self._trustusers.update(self.configlist('trusted', 'users'))
110 self._trustusers.update(self.configlist('trusted', 'users'))
111 self._trustgroups.update(self.configlist('trusted', 'groups'))
111 self._trustgroups.update(self.configlist('trusted', 'groups'))
112
112
113 def setconfig(self, section, name, value):
113 def setconfig(self, section, name, value):
114 for cfg in (self._ocfg, self._tcfg, self._ucfg):
114 for cfg in (self._ocfg, self._tcfg, self._ucfg):
115 cfg.set(section, name, value)
115 cfg.set(section, name, value)
116 self.fixconfig()
116 self.fixconfig()
117
117
118 def _data(self, untrusted):
118 def _data(self, untrusted):
119 return untrusted and self._ucfg or self._tcfg
119 return untrusted and self._ucfg or self._tcfg
120
120
121 def configsource(self, section, name, untrusted=False):
121 def configsource(self, section, name, untrusted=False):
122 return self._data(untrusted).source(section, name) or 'none'
122 return self._data(untrusted).source(section, name) or 'none'
123
123
124 def config(self, section, name, default=None, untrusted=False):
124 def config(self, section, name, default=None, untrusted=False):
125 value = self._data(untrusted).get(section, name, default)
125 value = self._data(untrusted).get(section, name, default)
126 if self.debugflag and not untrusted and self._reportuntrusted:
126 if self.debugflag and not untrusted and self._reportuntrusted:
127 uvalue = self._ucfg.get(section, name)
127 uvalue = self._ucfg.get(section, name)
128 if uvalue is not None and uvalue != value:
128 if uvalue is not None and uvalue != value:
129 self.debug(_("ignoring untrusted configuration option "
129 self.debug(_("ignoring untrusted configuration option "
130 "%s.%s = %s\n") % (section, name, uvalue))
130 "%s.%s = %s\n") % (section, name, uvalue))
131 return value
131 return value
132
132
133 def configbool(self, section, name, default=False, untrusted=False):
133 def configbool(self, section, name, default=False, untrusted=False):
134 v = self.config(section, name, None, untrusted)
134 v = self.config(section, name, None, untrusted)
135 if v is None:
135 if v is None:
136 return default
136 return default
137 if isinstance(v, bool):
137 if isinstance(v, bool):
138 return v
138 return v
139 if v.lower() not in _booleans:
139 if v.lower() not in _booleans:
140 raise error.ConfigError(_("%s.%s not a boolean ('%s')")
140 raise error.ConfigError(_("%s.%s not a boolean ('%s')")
141 % (section, name, v))
141 % (section, name, v))
142 return _booleans[v.lower()]
142 return _booleans[v.lower()]
143
143
144 def configlist(self, section, name, default=None, untrusted=False):
144 def configlist(self, section, name, default=None, untrusted=False):
145 """Return a list of comma/space separated strings"""
145 """Return a list of comma/space separated strings"""
146 result = self.config(section, name, untrusted=untrusted)
146 result = self.config(section, name, untrusted=untrusted)
147 if result is None:
147 if result is None:
148 result = default or []
148 result = default or []
149 if isinstance(result, basestring):
149 if isinstance(result, basestring):
150 result = result.replace(",", " ").split()
150 result = result.replace(",", " ").split()
151 return result
151 return result
152
152
153 def has_section(self, section, untrusted=False):
153 def has_section(self, section, untrusted=False):
154 '''tell whether section exists in config.'''
154 '''tell whether section exists in config.'''
155 return section in self._data(untrusted)
155 return section in self._data(untrusted)
156
156
157 def configitems(self, section, untrusted=False):
157 def configitems(self, section, untrusted=False):
158 items = self._data(untrusted).items(section)
158 items = self._data(untrusted).items(section)
159 if self.debugflag and not untrusted and self._reportuntrusted:
159 if self.debugflag and not untrusted and self._reportuntrusted:
160 for k, v in self._ucfg.items(section):
160 for k, v in self._ucfg.items(section):
161 if self._tcfg.get(section, k) != v:
161 if self._tcfg.get(section, k) != v:
162 self.debug(_("ignoring untrusted configuration option "
162 self.debug(_("ignoring untrusted configuration option "
163 "%s.%s = %s\n") % (section, k, v))
163 "%s.%s = %s\n") % (section, k, v))
164 return items
164 return items
165
165
166 def walkconfig(self, untrusted=False):
166 def walkconfig(self, untrusted=False):
167 cfg = self._data(untrusted)
167 cfg = self._data(untrusted)
168 for section in cfg.sections():
168 for section in cfg.sections():
169 for name, value in self.configitems(section, untrusted):
169 for name, value in self.configitems(section, untrusted):
170 yield section, name, str(value).replace('\n', '\\n')
170 yield section, name, str(value).replace('\n', '\\n')
171
171
172 def username(self):
172 def username(self):
173 """Return default username to be used in commits.
173 """Return default username to be used in commits.
174
174
175 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
175 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
176 and stop searching if one of these is set.
176 and stop searching if one of these is set.
177 If not found and ui.askusername is True, ask the user, else use
177 If not found and ui.askusername is True, ask the user, else use
178 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
178 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
179 """
179 """
180 user = os.environ.get("HGUSER")
180 user = os.environ.get("HGUSER")
181 if user is None:
181 if user is None:
182 user = self.config("ui", "username")
182 user = self.config("ui", "username")
183 if user is None:
183 if user is None:
184 user = os.environ.get("EMAIL")
184 user = os.environ.get("EMAIL")
185 if user is None and self.configbool("ui", "askusername"):
185 if user is None and self.configbool("ui", "askusername"):
186 user = self.prompt(_("enter a commit username:"), default=None)
186 user = self.prompt(_("enter a commit username:"), default=None)
187 if user is None and not self.interactive():
187 if user is None and not self.interactive():
188 try:
188 try:
189 user = '%s@%s' % (util.getuser(), socket.getfqdn())
189 user = '%s@%s' % (util.getuser(), socket.getfqdn())
190 self.warn(_("No username found, using '%s' instead\n") % user)
190 self.warn(_("No username found, using '%s' instead\n") % user)
191 except KeyError:
191 except KeyError:
192 pass
192 pass
193 if not user:
193 if not user:
194 raise util.Abort(_('no username supplied (see "hg help config")'))
194 raise util.Abort(_('no username supplied (see "hg help config")'))
195 if "\n" in user:
195 if "\n" in user:
196 raise util.Abort(_("username %s contains a newline\n") % repr(user))
196 raise util.Abort(_("username %s contains a newline\n") % repr(user))
197 return user
197 return user
198
198
199 def shortuser(self, user):
199 def shortuser(self, user):
200 """Return a short representation of a user name or email address."""
200 """Return a short representation of a user name or email address."""
201 if not self.verbose:
201 if not self.verbose:
202 user = util.shortuser(user)
202 user = util.shortuser(user)
203 return user
203 return user
204
204
205 def _path(self, loc):
205 def _path(self, loc):
206 p = self.config('paths', loc)
206 p = self.config('paths', loc)
207 if p:
207 if p:
208 if '%%' in p:
208 if '%%' in p:
209 self.warn("(deprecated '%%' in path %s=%s from %s)\n" %
209 self.warn("(deprecated '%%' in path %s=%s from %s)\n" %
210 (loc, p, self.configsource('paths', loc)))
210 (loc, p, self.configsource('paths', loc)))
211 p = p.replace('%%', '%')
211 p = p.replace('%%', '%')
212 p = util.expandpath(p)
212 p = util.expandpath(p)
213 return p
213 return p
214
214
215 def expandpath(self, loc, default=None):
215 def expandpath(self, loc, default=None):
216 """Return repository location relative to cwd or from [paths]"""
216 """Return repository location relative to cwd or from [paths]"""
217 if "://" in loc or os.path.isdir(os.path.join(loc, '.hg')):
217 if "://" in loc or os.path.isdir(os.path.join(loc, '.hg')):
218 return loc
218 return loc
219
219
220 path = self._path(loc)
220 path = self._path(loc)
221 if not path and default is not None:
221 if not path and default is not None:
222 path = self._path(default)
222 path = self._path(default)
223 return path or loc
223 return path or loc
224
224
225 def pushbuffer(self):
225 def pushbuffer(self):
226 self._buffers.append([])
226 self._buffers.append([])
227
227
228 def popbuffer(self):
228 def popbuffer(self):
229 return "".join(self._buffers.pop())
229 return "".join(self._buffers.pop())
230
230
231 def write(self, *args):
231 def write(self, *args):
232 if self._buffers:
232 if self._buffers:
233 self._buffers[-1].extend([str(a) for a in args])
233 self._buffers[-1].extend([str(a) for a in args])
234 else:
234 else:
235 for a in args:
235 for a in args:
236 sys.stdout.write(str(a))
236 sys.stdout.write(str(a))
237
237
238 def write_err(self, *args):
238 def write_err(self, *args):
239 try:
239 try:
240 if not hasattr(sys.stdout, 'closed') or not sys.stdout.closed:
240 if not getattr(sys.stdout, 'closed', False):
241 sys.stdout.flush()
241 sys.stdout.flush()
242 for a in args:
242 for a in args:
243 sys.stderr.write(str(a))
243 sys.stderr.write(str(a))
244 # stderr may be buffered under win32 when redirected to files,
244 # stderr may be buffered under win32 when redirected to files,
245 # including stdout.
245 # including stdout.
246 if not sys.stderr.closed:
246 if not getattr(sys.stderr, 'closed', False):
247 sys.stderr.flush()
247 sys.stderr.flush()
248 except IOError, inst:
248 except IOError, inst:
249 if inst.errno != errno.EPIPE:
249 if inst.errno != errno.EPIPE:
250 raise
250 raise
251
251
252 def flush(self):
252 def flush(self):
253 try: sys.stdout.flush()
253 try: sys.stdout.flush()
254 except: pass
254 except: pass
255 try: sys.stderr.flush()
255 try: sys.stderr.flush()
256 except: pass
256 except: pass
257
257
258 def interactive(self):
258 def interactive(self):
259 i = self.configbool("ui", "interactive", None)
259 i = self.configbool("ui", "interactive", None)
260 if i is None:
260 if i is None:
261 try:
261 try:
262 return sys.stdin.isatty()
262 return sys.stdin.isatty()
263 except AttributeError:
263 except AttributeError:
264 # some environments replace stdin without implementing isatty
264 # some environments replace stdin without implementing isatty
265 # usually those are non-interactive
265 # usually those are non-interactive
266 return False
266 return False
267
267
268 return i
268 return i
269
269
270 def _readline(self, prompt=''):
270 def _readline(self, prompt=''):
271 if sys.stdin.isatty():
271 if sys.stdin.isatty():
272 try:
272 try:
273 # magically add command line editing support, where
273 # magically add command line editing support, where
274 # available
274 # available
275 import readline
275 import readline
276 # force demandimport to really load the module
276 # force demandimport to really load the module
277 readline.read_history_file
277 readline.read_history_file
278 # windows sometimes raises something other than ImportError
278 # windows sometimes raises something other than ImportError
279 except Exception:
279 except Exception:
280 pass
280 pass
281 line = raw_input(prompt)
281 line = raw_input(prompt)
282 # When stdin is in binary mode on Windows, it can cause
282 # When stdin is in binary mode on Windows, it can cause
283 # raw_input() to emit an extra trailing carriage return
283 # raw_input() to emit an extra trailing carriage return
284 if os.linesep == '\r\n' and line and line[-1] == '\r':
284 if os.linesep == '\r\n' and line and line[-1] == '\r':
285 line = line[:-1]
285 line = line[:-1]
286 return line
286 return line
287
287
288 def prompt(self, msg, default="y"):
288 def prompt(self, msg, default="y"):
289 """Prompt user with msg, read response.
289 """Prompt user with msg, read response.
290 If ui is not interactive, the default is returned.
290 If ui is not interactive, the default is returned.
291 """
291 """
292 if not self.interactive():
292 if not self.interactive():
293 self.write(msg, ' ', default, "\n")
293 self.write(msg, ' ', default, "\n")
294 return default
294 return default
295 try:
295 try:
296 r = self._readline(msg + ' ')
296 r = self._readline(msg + ' ')
297 if not r:
297 if not r:
298 return default
298 return default
299 return r
299 return r
300 except EOFError:
300 except EOFError:
301 raise util.Abort(_('response expected'))
301 raise util.Abort(_('response expected'))
302
302
303 def promptchoice(self, msg, choices, default=0):
303 def promptchoice(self, msg, choices, default=0):
304 """Prompt user with msg, read response, and ensure it matches
304 """Prompt user with msg, read response, and ensure it matches
305 one of the provided choices. The index of the choice is returned.
305 one of the provided choices. The index of the choice is returned.
306 choices is a sequence of acceptable responses with the format:
306 choices is a sequence of acceptable responses with the format:
307 ('&None', 'E&xec', 'Sym&link') Responses are case insensitive.
307 ('&None', 'E&xec', 'Sym&link') Responses are case insensitive.
308 If ui is not interactive, the default is returned.
308 If ui is not interactive, the default is returned.
309 """
309 """
310 resps = [s[s.index('&')+1].lower() for s in choices]
310 resps = [s[s.index('&')+1].lower() for s in choices]
311 while True:
311 while True:
312 r = self.prompt(msg, resps[default])
312 r = self.prompt(msg, resps[default])
313 if r.lower() in resps:
313 if r.lower() in resps:
314 return resps.index(r.lower())
314 return resps.index(r.lower())
315 self.write(_("unrecognized response\n"))
315 self.write(_("unrecognized response\n"))
316
316
317 def getpass(self, prompt=None, default=None):
317 def getpass(self, prompt=None, default=None):
318 if not self.interactive():
318 if not self.interactive():
319 return default
319 return default
320 try:
320 try:
321 return getpass.getpass(prompt or _('password: '))
321 return getpass.getpass(prompt or _('password: '))
322 except EOFError:
322 except EOFError:
323 raise util.Abort(_('response expected'))
323 raise util.Abort(_('response expected'))
324 def status(self, *msg):
324 def status(self, *msg):
325 if not self.quiet:
325 if not self.quiet:
326 self.write(*msg)
326 self.write(*msg)
327 def warn(self, *msg):
327 def warn(self, *msg):
328 self.write_err(*msg)
328 self.write_err(*msg)
329 def note(self, *msg):
329 def note(self, *msg):
330 if self.verbose:
330 if self.verbose:
331 self.write(*msg)
331 self.write(*msg)
332 def debug(self, *msg):
332 def debug(self, *msg):
333 if self.debugflag:
333 if self.debugflag:
334 self.write(*msg)
334 self.write(*msg)
335 def edit(self, text, user):
335 def edit(self, text, user):
336 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
336 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
337 text=True)
337 text=True)
338 try:
338 try:
339 f = os.fdopen(fd, "w")
339 f = os.fdopen(fd, "w")
340 f.write(text)
340 f.write(text)
341 f.close()
341 f.close()
342
342
343 editor = self.geteditor()
343 editor = self.geteditor()
344
344
345 util.system("%s \"%s\"" % (editor, name),
345 util.system("%s \"%s\"" % (editor, name),
346 environ={'HGUSER': user},
346 environ={'HGUSER': user},
347 onerr=util.Abort, errprefix=_("edit failed"))
347 onerr=util.Abort, errprefix=_("edit failed"))
348
348
349 f = open(name)
349 f = open(name)
350 t = f.read()
350 t = f.read()
351 f.close()
351 f.close()
352 finally:
352 finally:
353 os.unlink(name)
353 os.unlink(name)
354
354
355 return t
355 return t
356
356
357 def traceback(self, exc=None):
357 def traceback(self, exc=None):
358 '''print exception traceback if traceback printing enabled.
358 '''print exception traceback if traceback printing enabled.
359 only to call in exception handler. returns true if traceback
359 only to call in exception handler. returns true if traceback
360 printed.'''
360 printed.'''
361 if self.tracebackflag:
361 if self.tracebackflag:
362 if exc:
362 if exc:
363 traceback.print_exception(exc[0], exc[1], exc[2])
363 traceback.print_exception(exc[0], exc[1], exc[2])
364 else:
364 else:
365 traceback.print_exc()
365 traceback.print_exc()
366 return self.tracebackflag
366 return self.tracebackflag
367
367
368 def geteditor(self):
368 def geteditor(self):
369 '''return editor to use'''
369 '''return editor to use'''
370 return (os.environ.get("HGEDITOR") or
370 return (os.environ.get("HGEDITOR") or
371 self.config("ui", "editor") or
371 self.config("ui", "editor") or
372 os.environ.get("VISUAL") or
372 os.environ.get("VISUAL") or
373 os.environ.get("EDITOR", "vi"))
373 os.environ.get("EDITOR", "vi"))
374
374
375 def progress(self, topic, pos, item="", unit="", total=None):
375 def progress(self, topic, pos, item="", unit="", total=None):
376 '''show a progress message
376 '''show a progress message
377
377
378 With stock hg, this is simply a debug message that is hidden
378 With stock hg, this is simply a debug message that is hidden
379 by default, but with extensions or GUI tools it may be
379 by default, but with extensions or GUI tools it may be
380 visible. 'topic' is the current operation, 'item' is a
380 visible. 'topic' is the current operation, 'item' is a
381 non-numeric marker of the current position (ie the currently
381 non-numeric marker of the current position (ie the currently
382 in-process file), 'pos' is the current numeric position (ie
382 in-process file), 'pos' is the current numeric position (ie
383 revision, bytes, etc.), unit is a corresponding unit label,
383 revision, bytes, etc.), unit is a corresponding unit label,
384 and total is the highest expected pos.
384 and total is the highest expected pos.
385
385
386 Multiple nested topics may be active at a time. All topics
386 Multiple nested topics may be active at a time. All topics
387 should be marked closed by setting pos to None at termination.
387 should be marked closed by setting pos to None at termination.
388 '''
388 '''
389
389
390 if pos == None or not self.debugflag:
390 if pos == None or not self.debugflag:
391 return
391 return
392
392
393 if unit:
393 if unit:
394 unit = ' ' + unit
394 unit = ' ' + unit
395 if item:
395 if item:
396 item = ' ' + item
396 item = ' ' + item
397
397
398 if total:
398 if total:
399 pct = 100.0 * pos / total
399 pct = 100.0 * pos / total
400 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
400 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
401 % (topic, item, pos, total, unit, pct))
401 % (topic, item, pos, total, unit, pct))
402 else:
402 else:
403 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
403 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
General Comments 0
You need to be logged in to leave comments. Login now