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