##// END OF EJS Templates
editor: file created for diff action should have .diff suffix...
Michael Bolin -
r34056:3c82b14d default
parent child Browse files
Show More
@@ -1,1792 +1,1794
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 b"""# example user config (see 'hg help config' for more info)
63 b"""# 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 b"""# example repository config (see 'hg help config' for more info)
85 b"""# 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 b"""# example repository config (see 'hg help config' for more info)
102 b"""# 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 b"""# example system-wide hg config (see 'hg help config' for more info)
118 b"""# 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 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
908 raise error.StdioError(inst)
908 raise error.StdioError(inst)
909
909
910 def flush(self):
910 def flush(self):
911 # opencode timeblockedsection because this is a critical path
911 # opencode timeblockedsection because this is a critical path
912 starttime = util.timer()
912 starttime = util.timer()
913 try:
913 try:
914 try:
914 try:
915 self.fout.flush()
915 self.fout.flush()
916 except IOError as err:
916 except IOError as err:
917 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
917 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
918 raise error.StdioError(err)
918 raise error.StdioError(err)
919 finally:
919 finally:
920 try:
920 try:
921 self.ferr.flush()
921 self.ferr.flush()
922 except IOError as err:
922 except IOError as err:
923 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
923 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
924 raise error.StdioError(err)
924 raise error.StdioError(err)
925 finally:
925 finally:
926 self._blockedtimes['stdio_blocked'] += \
926 self._blockedtimes['stdio_blocked'] += \
927 (util.timer() - starttime) * 1000
927 (util.timer() - starttime) * 1000
928
928
929 def _isatty(self, fh):
929 def _isatty(self, fh):
930 if self.configbool('ui', 'nontty'):
930 if self.configbool('ui', 'nontty'):
931 return False
931 return False
932 return util.isatty(fh)
932 return util.isatty(fh)
933
933
934 def disablepager(self):
934 def disablepager(self):
935 self._disablepager = True
935 self._disablepager = True
936
936
937 def pager(self, command):
937 def pager(self, command):
938 """Start a pager for subsequent command output.
938 """Start a pager for subsequent command output.
939
939
940 Commands which produce a long stream of output should call
940 Commands which produce a long stream of output should call
941 this function to activate the user's preferred pagination
941 this function to activate the user's preferred pagination
942 mechanism (which may be no pager). Calling this function
942 mechanism (which may be no pager). Calling this function
943 precludes any future use of interactive functionality, such as
943 precludes any future use of interactive functionality, such as
944 prompting the user or activating curses.
944 prompting the user or activating curses.
945
945
946 Args:
946 Args:
947 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"
948 not "history, "summary" not "summ", etc.
948 not "history, "summary" not "summ", etc.
949 """
949 """
950 if (self._disablepager
950 if (self._disablepager
951 or self.pageractive):
951 or self.pageractive):
952 # how pager should do is already determined
952 # how pager should do is already determined
953 return
953 return
954
954
955 if not command.startswith('internal-always-') and (
955 if not command.startswith('internal-always-') and (
956 # explicit --pager=on (= 'internal-always-' prefix) should
956 # explicit --pager=on (= 'internal-always-' prefix) should
957 # take precedence over disabling factors below
957 # take precedence over disabling factors below
958 command in self.configlist('pager', 'ignore')
958 command in self.configlist('pager', 'ignore')
959 or not self.configbool('ui', 'paginate')
959 or not self.configbool('ui', 'paginate')
960 or not self.configbool('pager', 'attend-' + command, True)
960 or not self.configbool('pager', 'attend-' + command, True)
961 # TODO: if we want to allow HGPLAINEXCEPT=pager,
961 # TODO: if we want to allow HGPLAINEXCEPT=pager,
962 # formatted() will need some adjustment.
962 # formatted() will need some adjustment.
963 or not self.formatted()
963 or not self.formatted()
964 or self.plain()
964 or self.plain()
965 or self._buffers
965 or self._buffers
966 # TODO: expose debugger-enabled on the UI object
966 # TODO: expose debugger-enabled on the UI object
967 or '--debugger' in pycompat.sysargv):
967 or '--debugger' in pycompat.sysargv):
968 # We only want to paginate if the ui appears to be
968 # We only want to paginate if the ui appears to be
969 # interactive, the user didn't say HGPLAIN or
969 # interactive, the user didn't say HGPLAIN or
970 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
970 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
971 return
971 return
972
972
973 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
973 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
974 if not pagercmd:
974 if not pagercmd:
975 return
975 return
976
976
977 pagerenv = {}
977 pagerenv = {}
978 for name, value in rcutil.defaultpagerenv().items():
978 for name, value in rcutil.defaultpagerenv().items():
979 if name not in encoding.environ:
979 if name not in encoding.environ:
980 pagerenv[name] = value
980 pagerenv[name] = value
981
981
982 self.debug('starting pager for command %r\n' % command)
982 self.debug('starting pager for command %r\n' % command)
983 self.flush()
983 self.flush()
984
984
985 wasformatted = self.formatted()
985 wasformatted = self.formatted()
986 if util.safehasattr(signal, "SIGPIPE"):
986 if util.safehasattr(signal, "SIGPIPE"):
987 signal.signal(signal.SIGPIPE, _catchterm)
987 signal.signal(signal.SIGPIPE, _catchterm)
988 if self._runpager(pagercmd, pagerenv):
988 if self._runpager(pagercmd, pagerenv):
989 self.pageractive = True
989 self.pageractive = True
990 # Preserve the formatted-ness of the UI. This is important
990 # Preserve the formatted-ness of the UI. This is important
991 # because we mess with stdout, which might confuse
991 # because we mess with stdout, which might confuse
992 # auto-detection of things being formatted.
992 # auto-detection of things being formatted.
993 self.setconfig('ui', 'formatted', wasformatted, 'pager')
993 self.setconfig('ui', 'formatted', wasformatted, 'pager')
994 self.setconfig('ui', 'interactive', False, 'pager')
994 self.setconfig('ui', 'interactive', False, 'pager')
995
995
996 # If pagermode differs from color.mode, reconfigure color now that
996 # If pagermode differs from color.mode, reconfigure color now that
997 # pageractive is set.
997 # pageractive is set.
998 cm = self._colormode
998 cm = self._colormode
999 if cm != self.config('color', 'pagermode', cm):
999 if cm != self.config('color', 'pagermode', cm):
1000 color.setup(self)
1000 color.setup(self)
1001 else:
1001 else:
1002 # If the pager can't be spawned in dispatch when --pager=on is
1002 # If the pager can't be spawned in dispatch when --pager=on is
1003 # given, don't try again when the command runs, to avoid a duplicate
1003 # given, don't try again when the command runs, to avoid a duplicate
1004 # warning about a missing pager command.
1004 # warning about a missing pager command.
1005 self.disablepager()
1005 self.disablepager()
1006
1006
1007 def _runpager(self, command, env=None):
1007 def _runpager(self, command, env=None):
1008 """Actually start the pager and set up file descriptors.
1008 """Actually start the pager and set up file descriptors.
1009
1009
1010 This is separate in part so that extensions (like chg) can
1010 This is separate in part so that extensions (like chg) can
1011 override how a pager is invoked.
1011 override how a pager is invoked.
1012 """
1012 """
1013 if command == 'cat':
1013 if command == 'cat':
1014 # Save ourselves some work.
1014 # Save ourselves some work.
1015 return False
1015 return False
1016 # If the command doesn't contain any of these characters, we
1016 # If the command doesn't contain any of these characters, we
1017 # assume it's a binary and exec it directly. This means for
1017 # assume it's a binary and exec it directly. This means for
1018 # simple pager command configurations, we can degrade
1018 # simple pager command configurations, we can degrade
1019 # gracefully and tell the user about their broken pager.
1019 # gracefully and tell the user about their broken pager.
1020 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
1020 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
1021
1021
1022 if pycompat.osname == 'nt' and not shell:
1022 if pycompat.osname == 'nt' and not shell:
1023 # Window's built-in `more` cannot be invoked with shell=False, but
1023 # Window's built-in `more` cannot be invoked with shell=False, but
1024 # its `more.com` can. Hide this implementation detail from the
1024 # its `more.com` can. Hide this implementation detail from the
1025 # user so we can also get sane bad PAGER behavior. MSYS has
1025 # user so we can also get sane bad PAGER behavior. MSYS has
1026 # `more.exe`, so do a cmd.exe style resolution of the executable to
1026 # `more.exe`, so do a cmd.exe style resolution of the executable to
1027 # determine which one to use.
1027 # determine which one to use.
1028 fullcmd = util.findexe(command)
1028 fullcmd = util.findexe(command)
1029 if not fullcmd:
1029 if not fullcmd:
1030 self.warn(_("missing pager command '%s', skipping pager\n")
1030 self.warn(_("missing pager command '%s', skipping pager\n")
1031 % command)
1031 % command)
1032 return False
1032 return False
1033
1033
1034 command = fullcmd
1034 command = fullcmd
1035
1035
1036 try:
1036 try:
1037 pager = subprocess.Popen(
1037 pager = subprocess.Popen(
1038 command, shell=shell, bufsize=-1,
1038 command, shell=shell, bufsize=-1,
1039 close_fds=util.closefds, stdin=subprocess.PIPE,
1039 close_fds=util.closefds, stdin=subprocess.PIPE,
1040 stdout=util.stdout, stderr=util.stderr,
1040 stdout=util.stdout, stderr=util.stderr,
1041 env=util.shellenviron(env))
1041 env=util.shellenviron(env))
1042 except OSError as e:
1042 except OSError as e:
1043 if e.errno == errno.ENOENT and not shell:
1043 if e.errno == errno.ENOENT and not shell:
1044 self.warn(_("missing pager command '%s', skipping pager\n")
1044 self.warn(_("missing pager command '%s', skipping pager\n")
1045 % command)
1045 % command)
1046 return False
1046 return False
1047 raise
1047 raise
1048
1048
1049 # back up original file descriptors
1049 # back up original file descriptors
1050 stdoutfd = os.dup(util.stdout.fileno())
1050 stdoutfd = os.dup(util.stdout.fileno())
1051 stderrfd = os.dup(util.stderr.fileno())
1051 stderrfd = os.dup(util.stderr.fileno())
1052
1052
1053 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
1053 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
1054 if self._isatty(util.stderr):
1054 if self._isatty(util.stderr):
1055 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
1055 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
1056
1056
1057 @self.atexit
1057 @self.atexit
1058 def killpager():
1058 def killpager():
1059 if util.safehasattr(signal, "SIGINT"):
1059 if util.safehasattr(signal, "SIGINT"):
1060 signal.signal(signal.SIGINT, signal.SIG_IGN)
1060 signal.signal(signal.SIGINT, signal.SIG_IGN)
1061 # restore original fds, closing pager.stdin copies in the process
1061 # restore original fds, closing pager.stdin copies in the process
1062 os.dup2(stdoutfd, util.stdout.fileno())
1062 os.dup2(stdoutfd, util.stdout.fileno())
1063 os.dup2(stderrfd, util.stderr.fileno())
1063 os.dup2(stderrfd, util.stderr.fileno())
1064 pager.stdin.close()
1064 pager.stdin.close()
1065 pager.wait()
1065 pager.wait()
1066
1066
1067 return True
1067 return True
1068
1068
1069 def atexit(self, func, *args, **kwargs):
1069 def atexit(self, func, *args, **kwargs):
1070 '''register a function to run after dispatching a request
1070 '''register a function to run after dispatching a request
1071
1071
1072 Handlers do not stay registered across request boundaries.'''
1072 Handlers do not stay registered across request boundaries.'''
1073 self._exithandlers.append((func, args, kwargs))
1073 self._exithandlers.append((func, args, kwargs))
1074 return func
1074 return func
1075
1075
1076 def interface(self, feature):
1076 def interface(self, feature):
1077 """what interface to use for interactive console features?
1077 """what interface to use for interactive console features?
1078
1078
1079 The interface is controlled by the value of `ui.interface` but also by
1079 The interface is controlled by the value of `ui.interface` but also by
1080 the value of feature-specific configuration. For example:
1080 the value of feature-specific configuration. For example:
1081
1081
1082 ui.interface.histedit = text
1082 ui.interface.histedit = text
1083 ui.interface.chunkselector = curses
1083 ui.interface.chunkselector = curses
1084
1084
1085 Here the features are "histedit" and "chunkselector".
1085 Here the features are "histedit" and "chunkselector".
1086
1086
1087 The configuration above means that the default interfaces for commands
1087 The configuration above means that the default interfaces for commands
1088 is curses, the interface for histedit is text and the interface for
1088 is curses, the interface for histedit is text and the interface for
1089 selecting chunk is crecord (the best curses interface available).
1089 selecting chunk is crecord (the best curses interface available).
1090
1090
1091 Consider the following example:
1091 Consider the following example:
1092 ui.interface = curses
1092 ui.interface = curses
1093 ui.interface.histedit = text
1093 ui.interface.histedit = text
1094
1094
1095 Then histedit will use the text interface and chunkselector will use
1095 Then histedit will use the text interface and chunkselector will use
1096 the default curses interface (crecord at the moment).
1096 the default curses interface (crecord at the moment).
1097 """
1097 """
1098 alldefaults = frozenset(["text", "curses"])
1098 alldefaults = frozenset(["text", "curses"])
1099
1099
1100 featureinterfaces = {
1100 featureinterfaces = {
1101 "chunkselector": [
1101 "chunkselector": [
1102 "text",
1102 "text",
1103 "curses",
1103 "curses",
1104 ]
1104 ]
1105 }
1105 }
1106
1106
1107 # Feature-specific interface
1107 # Feature-specific interface
1108 if feature not in featureinterfaces.keys():
1108 if feature not in featureinterfaces.keys():
1109 # Programming error, not user error
1109 # Programming error, not user error
1110 raise ValueError("Unknown feature requested %s" % feature)
1110 raise ValueError("Unknown feature requested %s" % feature)
1111
1111
1112 availableinterfaces = frozenset(featureinterfaces[feature])
1112 availableinterfaces = frozenset(featureinterfaces[feature])
1113 if alldefaults > availableinterfaces:
1113 if alldefaults > availableinterfaces:
1114 # Programming error, not user error. We need a use case to
1114 # Programming error, not user error. We need a use case to
1115 # define the right thing to do here.
1115 # define the right thing to do here.
1116 raise ValueError(
1116 raise ValueError(
1117 "Feature %s does not handle all default interfaces" %
1117 "Feature %s does not handle all default interfaces" %
1118 feature)
1118 feature)
1119
1119
1120 if self.plain():
1120 if self.plain():
1121 return "text"
1121 return "text"
1122
1122
1123 # Default interface for all the features
1123 # Default interface for all the features
1124 defaultinterface = "text"
1124 defaultinterface = "text"
1125 i = self.config("ui", "interface")
1125 i = self.config("ui", "interface")
1126 if i in alldefaults:
1126 if i in alldefaults:
1127 defaultinterface = i
1127 defaultinterface = i
1128
1128
1129 choseninterface = defaultinterface
1129 choseninterface = defaultinterface
1130 f = self.config("ui", "interface.%s" % feature, None)
1130 f = self.config("ui", "interface.%s" % feature, None)
1131 if f in availableinterfaces:
1131 if f in availableinterfaces:
1132 choseninterface = f
1132 choseninterface = f
1133
1133
1134 if i is not None and defaultinterface != i:
1134 if i is not None and defaultinterface != i:
1135 if f is not None:
1135 if f is not None:
1136 self.warn(_("invalid value for ui.interface: %s\n") %
1136 self.warn(_("invalid value for ui.interface: %s\n") %
1137 (i,))
1137 (i,))
1138 else:
1138 else:
1139 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1139 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1140 (i, choseninterface))
1140 (i, choseninterface))
1141 if f is not None and choseninterface != f:
1141 if f is not None and choseninterface != f:
1142 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1142 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1143 (feature, f, choseninterface))
1143 (feature, f, choseninterface))
1144
1144
1145 return choseninterface
1145 return choseninterface
1146
1146
1147 def interactive(self):
1147 def interactive(self):
1148 '''is interactive input allowed?
1148 '''is interactive input allowed?
1149
1149
1150 An interactive session is a session where input can be reasonably read
1150 An interactive session is a session where input can be reasonably read
1151 from `sys.stdin'. If this function returns false, any attempt to read
1151 from `sys.stdin'. If this function returns false, any attempt to read
1152 from stdin should fail with an error, unless a sensible default has been
1152 from stdin should fail with an error, unless a sensible default has been
1153 specified.
1153 specified.
1154
1154
1155 Interactiveness is triggered by the value of the `ui.interactive'
1155 Interactiveness is triggered by the value of the `ui.interactive'
1156 configuration variable or - if it is unset - when `sys.stdin' points
1156 configuration variable or - if it is unset - when `sys.stdin' points
1157 to a terminal device.
1157 to a terminal device.
1158
1158
1159 This function refers to input only; for output, see `ui.formatted()'.
1159 This function refers to input only; for output, see `ui.formatted()'.
1160 '''
1160 '''
1161 i = self.configbool("ui", "interactive")
1161 i = self.configbool("ui", "interactive")
1162 if i is None:
1162 if i is None:
1163 # some environments replace stdin without implementing isatty
1163 # some environments replace stdin without implementing isatty
1164 # usually those are non-interactive
1164 # usually those are non-interactive
1165 return self._isatty(self.fin)
1165 return self._isatty(self.fin)
1166
1166
1167 return i
1167 return i
1168
1168
1169 def termwidth(self):
1169 def termwidth(self):
1170 '''how wide is the terminal in columns?
1170 '''how wide is the terminal in columns?
1171 '''
1171 '''
1172 if 'COLUMNS' in encoding.environ:
1172 if 'COLUMNS' in encoding.environ:
1173 try:
1173 try:
1174 return int(encoding.environ['COLUMNS'])
1174 return int(encoding.environ['COLUMNS'])
1175 except ValueError:
1175 except ValueError:
1176 pass
1176 pass
1177 return scmutil.termsize(self)[0]
1177 return scmutil.termsize(self)[0]
1178
1178
1179 def formatted(self):
1179 def formatted(self):
1180 '''should formatted output be used?
1180 '''should formatted output be used?
1181
1181
1182 It is often desirable to format the output to suite the output medium.
1182 It is often desirable to format the output to suite the output medium.
1183 Examples of this are truncating long lines or colorizing messages.
1183 Examples of this are truncating long lines or colorizing messages.
1184 However, this is not often not desirable when piping output into other
1184 However, this is not often not desirable when piping output into other
1185 utilities, e.g. `grep'.
1185 utilities, e.g. `grep'.
1186
1186
1187 Formatted output is triggered by the value of the `ui.formatted'
1187 Formatted output is triggered by the value of the `ui.formatted'
1188 configuration variable or - if it is unset - when `sys.stdout' points
1188 configuration variable or - if it is unset - when `sys.stdout' points
1189 to a terminal device. Please note that `ui.formatted' should be
1189 to a terminal device. Please note that `ui.formatted' should be
1190 considered an implementation detail; it is not intended for use outside
1190 considered an implementation detail; it is not intended for use outside
1191 Mercurial or its extensions.
1191 Mercurial or its extensions.
1192
1192
1193 This function refers to output only; for input, see `ui.interactive()'.
1193 This function refers to output only; for input, see `ui.interactive()'.
1194 This function always returns false when in plain mode, see `ui.plain()'.
1194 This function always returns false when in plain mode, see `ui.plain()'.
1195 '''
1195 '''
1196 if self.plain():
1196 if self.plain():
1197 return False
1197 return False
1198
1198
1199 i = self.configbool("ui", "formatted")
1199 i = self.configbool("ui", "formatted")
1200 if i is None:
1200 if i is None:
1201 # some environments replace stdout without implementing isatty
1201 # some environments replace stdout without implementing isatty
1202 # usually those are non-interactive
1202 # usually those are non-interactive
1203 return self._isatty(self.fout)
1203 return self._isatty(self.fout)
1204
1204
1205 return i
1205 return i
1206
1206
1207 def _readline(self, prompt=''):
1207 def _readline(self, prompt=''):
1208 if self._isatty(self.fin):
1208 if self._isatty(self.fin):
1209 try:
1209 try:
1210 # magically add command line editing support, where
1210 # magically add command line editing support, where
1211 # available
1211 # available
1212 import readline
1212 import readline
1213 # force demandimport to really load the module
1213 # force demandimport to really load the module
1214 readline.read_history_file
1214 readline.read_history_file
1215 # windows sometimes raises something other than ImportError
1215 # windows sometimes raises something other than ImportError
1216 except Exception:
1216 except Exception:
1217 pass
1217 pass
1218
1218
1219 # call write() so output goes through subclassed implementation
1219 # call write() so output goes through subclassed implementation
1220 # e.g. color extension on Windows
1220 # e.g. color extension on Windows
1221 self.write(prompt, prompt=True)
1221 self.write(prompt, prompt=True)
1222 self.flush()
1222 self.flush()
1223
1223
1224 # prompt ' ' must exist; otherwise readline may delete entire line
1224 # prompt ' ' must exist; otherwise readline may delete entire line
1225 # - http://bugs.python.org/issue12833
1225 # - http://bugs.python.org/issue12833
1226 with self.timeblockedsection('stdio'):
1226 with self.timeblockedsection('stdio'):
1227 line = util.bytesinput(self.fin, self.fout, r' ')
1227 line = util.bytesinput(self.fin, self.fout, r' ')
1228
1228
1229 # When stdin is in binary mode on Windows, it can cause
1229 # When stdin is in binary mode on Windows, it can cause
1230 # raw_input() to emit an extra trailing carriage return
1230 # raw_input() to emit an extra trailing carriage return
1231 if pycompat.oslinesep == '\r\n' and line and line[-1] == '\r':
1231 if pycompat.oslinesep == '\r\n' and line and line[-1] == '\r':
1232 line = line[:-1]
1232 line = line[:-1]
1233 return line
1233 return line
1234
1234
1235 def prompt(self, msg, default="y"):
1235 def prompt(self, msg, default="y"):
1236 """Prompt user with msg, read response.
1236 """Prompt user with msg, read response.
1237 If ui is not interactive, the default is returned.
1237 If ui is not interactive, the default is returned.
1238 """
1238 """
1239 if not self.interactive():
1239 if not self.interactive():
1240 self.write(msg, ' ', default or '', "\n")
1240 self.write(msg, ' ', default or '', "\n")
1241 return default
1241 return default
1242 try:
1242 try:
1243 r = self._readline(self.label(msg, 'ui.prompt'))
1243 r = self._readline(self.label(msg, 'ui.prompt'))
1244 if not r:
1244 if not r:
1245 r = default
1245 r = default
1246 if self.configbool('ui', 'promptecho'):
1246 if self.configbool('ui', 'promptecho'):
1247 self.write(r, "\n")
1247 self.write(r, "\n")
1248 return r
1248 return r
1249 except EOFError:
1249 except EOFError:
1250 raise error.ResponseExpected()
1250 raise error.ResponseExpected()
1251
1251
1252 @staticmethod
1252 @staticmethod
1253 def extractchoices(prompt):
1253 def extractchoices(prompt):
1254 """Extract prompt message and list of choices from specified prompt.
1254 """Extract prompt message and list of choices from specified prompt.
1255
1255
1256 This returns tuple "(message, choices)", and "choices" is the
1256 This returns tuple "(message, choices)", and "choices" is the
1257 list of tuple "(response character, text without &)".
1257 list of tuple "(response character, text without &)".
1258
1258
1259 >>> ui.extractchoices("awake? $$ &Yes $$ &No")
1259 >>> ui.extractchoices("awake? $$ &Yes $$ &No")
1260 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1260 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1261 >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
1261 >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
1262 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1262 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1263 >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
1263 >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
1264 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1264 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1265 """
1265 """
1266
1266
1267 # Sadly, the prompt string may have been built with a filename
1267 # Sadly, the prompt string may have been built with a filename
1268 # containing "$$" so let's try to find the first valid-looking
1268 # containing "$$" so let's try to find the first valid-looking
1269 # prompt to start parsing. Sadly, we also can't rely on
1269 # prompt to start parsing. Sadly, we also can't rely on
1270 # choices containing spaces, ASCII, or basically anything
1270 # choices containing spaces, ASCII, or basically anything
1271 # except an ampersand followed by a character.
1271 # except an ampersand followed by a character.
1272 m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1272 m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1273 msg = m.group(1)
1273 msg = m.group(1)
1274 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1274 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1275 def choicetuple(s):
1275 def choicetuple(s):
1276 ampidx = s.index('&')
1276 ampidx = s.index('&')
1277 return s[ampidx + 1:ampidx + 2].lower(), s.replace('&', '', 1)
1277 return s[ampidx + 1:ampidx + 2].lower(), s.replace('&', '', 1)
1278 return (msg, [choicetuple(s) for s in choices])
1278 return (msg, [choicetuple(s) for s in choices])
1279
1279
1280 def promptchoice(self, prompt, default=0):
1280 def promptchoice(self, prompt, default=0):
1281 """Prompt user with a message, read response, and ensure it matches
1281 """Prompt user with a message, read response, and ensure it matches
1282 one of the provided choices. The prompt is formatted as follows:
1282 one of the provided choices. The prompt is formatted as follows:
1283
1283
1284 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1284 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1285
1285
1286 The index of the choice is returned. Responses are case
1286 The index of the choice is returned. Responses are case
1287 insensitive. If ui is not interactive, the default is
1287 insensitive. If ui is not interactive, the default is
1288 returned.
1288 returned.
1289 """
1289 """
1290
1290
1291 msg, choices = self.extractchoices(prompt)
1291 msg, choices = self.extractchoices(prompt)
1292 resps = [r for r, t in choices]
1292 resps = [r for r, t in choices]
1293 while True:
1293 while True:
1294 r = self.prompt(msg, resps[default])
1294 r = self.prompt(msg, resps[default])
1295 if r.lower() in resps:
1295 if r.lower() in resps:
1296 return resps.index(r.lower())
1296 return resps.index(r.lower())
1297 self.write(_("unrecognized response\n"))
1297 self.write(_("unrecognized response\n"))
1298
1298
1299 def getpass(self, prompt=None, default=None):
1299 def getpass(self, prompt=None, default=None):
1300 if not self.interactive():
1300 if not self.interactive():
1301 return default
1301 return default
1302 try:
1302 try:
1303 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1303 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1304 # disable getpass() only if explicitly specified. it's still valid
1304 # disable getpass() only if explicitly specified. it's still valid
1305 # to interact with tty even if fin is not a tty.
1305 # to interact with tty even if fin is not a tty.
1306 with self.timeblockedsection('stdio'):
1306 with self.timeblockedsection('stdio'):
1307 if self.configbool('ui', 'nontty'):
1307 if self.configbool('ui', 'nontty'):
1308 l = self.fin.readline()
1308 l = self.fin.readline()
1309 if not l:
1309 if not l:
1310 raise EOFError
1310 raise EOFError
1311 return l.rstrip('\n')
1311 return l.rstrip('\n')
1312 else:
1312 else:
1313 return getpass.getpass('')
1313 return getpass.getpass('')
1314 except EOFError:
1314 except EOFError:
1315 raise error.ResponseExpected()
1315 raise error.ResponseExpected()
1316 def status(self, *msg, **opts):
1316 def status(self, *msg, **opts):
1317 '''write status message to output (if ui.quiet is False)
1317 '''write status message to output (if ui.quiet is False)
1318
1318
1319 This adds an output label of "ui.status".
1319 This adds an output label of "ui.status".
1320 '''
1320 '''
1321 if not self.quiet:
1321 if not self.quiet:
1322 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1322 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1323 self.write(*msg, **opts)
1323 self.write(*msg, **opts)
1324 def warn(self, *msg, **opts):
1324 def warn(self, *msg, **opts):
1325 '''write warning message to output (stderr)
1325 '''write warning message to output (stderr)
1326
1326
1327 This adds an output label of "ui.warning".
1327 This adds an output label of "ui.warning".
1328 '''
1328 '''
1329 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1329 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1330 self.write_err(*msg, **opts)
1330 self.write_err(*msg, **opts)
1331 def note(self, *msg, **opts):
1331 def note(self, *msg, **opts):
1332 '''write note to output (if ui.verbose is True)
1332 '''write note to output (if ui.verbose is True)
1333
1333
1334 This adds an output label of "ui.note".
1334 This adds an output label of "ui.note".
1335 '''
1335 '''
1336 if self.verbose:
1336 if self.verbose:
1337 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1337 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1338 self.write(*msg, **opts)
1338 self.write(*msg, **opts)
1339 def debug(self, *msg, **opts):
1339 def debug(self, *msg, **opts):
1340 '''write debug message to output (if ui.debugflag is True)
1340 '''write debug message to output (if ui.debugflag is True)
1341
1341
1342 This adds an output label of "ui.debug".
1342 This adds an output label of "ui.debug".
1343 '''
1343 '''
1344 if self.debugflag:
1344 if self.debugflag:
1345 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1345 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1346 self.write(*msg, **opts)
1346 self.write(*msg, **opts)
1347
1347
1348 def edit(self, text, user, extra=None, editform=None, pending=None,
1348 def edit(self, text, user, extra=None, editform=None, pending=None,
1349 repopath=None, action=None):
1349 repopath=None, action=None):
1350 if action is None:
1350 if action is None:
1351 self.develwarn('action is None but will soon be a required '
1351 self.develwarn('action is None but will soon be a required '
1352 'parameter to ui.edit()')
1352 'parameter to ui.edit()')
1353 extra_defaults = {
1353 extra_defaults = {
1354 'prefix': 'editor',
1354 'prefix': 'editor',
1355 'suffix': '.txt',
1355 'suffix': '.txt',
1356 }
1356 }
1357 if extra is not None:
1357 if extra is not None:
1358 if extra.get('suffix') is not None:
1358 if extra.get('suffix') is not None:
1359 self.develwarn('extra.suffix is not None but will soon be '
1359 self.develwarn('extra.suffix is not None but will soon be '
1360 'ignored by ui.edit()')
1360 'ignored by ui.edit()')
1361 extra_defaults.update(extra)
1361 extra_defaults.update(extra)
1362 extra = extra_defaults
1362 extra = extra_defaults
1363
1363
1364 if action:
1364 if action == 'diff':
1365 suffix = '.diff'
1366 elif action:
1365 suffix = '.%s.hg.txt' % action
1367 suffix = '.%s.hg.txt' % action
1366 else:
1368 else:
1367 suffix = extra['suffix']
1369 suffix = extra['suffix']
1368
1370
1369 rdir = None
1371 rdir = None
1370 if self.configbool('experimental', 'editortmpinhg'):
1372 if self.configbool('experimental', 'editortmpinhg'):
1371 rdir = repopath
1373 rdir = repopath
1372 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1374 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1373 suffix=suffix,
1375 suffix=suffix,
1374 dir=rdir)
1376 dir=rdir)
1375 try:
1377 try:
1376 f = os.fdopen(fd, r'wb')
1378 f = os.fdopen(fd, r'wb')
1377 f.write(util.tonativeeol(text))
1379 f.write(util.tonativeeol(text))
1378 f.close()
1380 f.close()
1379
1381
1380 environ = {'HGUSER': user}
1382 environ = {'HGUSER': user}
1381 if 'transplant_source' in extra:
1383 if 'transplant_source' in extra:
1382 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1384 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1383 for label in ('intermediate-source', 'source', 'rebase_source'):
1385 for label in ('intermediate-source', 'source', 'rebase_source'):
1384 if label in extra:
1386 if label in extra:
1385 environ.update({'HGREVISION': extra[label]})
1387 environ.update({'HGREVISION': extra[label]})
1386 break
1388 break
1387 if editform:
1389 if editform:
1388 environ.update({'HGEDITFORM': editform})
1390 environ.update({'HGEDITFORM': editform})
1389 if pending:
1391 if pending:
1390 environ.update({'HG_PENDING': pending})
1392 environ.update({'HG_PENDING': pending})
1391
1393
1392 editor = self.geteditor()
1394 editor = self.geteditor()
1393
1395
1394 self.system("%s \"%s\"" % (editor, name),
1396 self.system("%s \"%s\"" % (editor, name),
1395 environ=environ,
1397 environ=environ,
1396 onerr=error.Abort, errprefix=_("edit failed"),
1398 onerr=error.Abort, errprefix=_("edit failed"),
1397 blockedtag='editor')
1399 blockedtag='editor')
1398
1400
1399 f = open(name, r'rb')
1401 f = open(name, r'rb')
1400 t = util.fromnativeeol(f.read())
1402 t = util.fromnativeeol(f.read())
1401 f.close()
1403 f.close()
1402 finally:
1404 finally:
1403 os.unlink(name)
1405 os.unlink(name)
1404
1406
1405 return t
1407 return t
1406
1408
1407 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1409 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1408 blockedtag=None):
1410 blockedtag=None):
1409 '''execute shell command with appropriate output stream. command
1411 '''execute shell command with appropriate output stream. command
1410 output will be redirected if fout is not stdout.
1412 output will be redirected if fout is not stdout.
1411
1413
1412 if command fails and onerr is None, return status, else raise onerr
1414 if command fails and onerr is None, return status, else raise onerr
1413 object as exception.
1415 object as exception.
1414 '''
1416 '''
1415 if blockedtag is None:
1417 if blockedtag is None:
1416 # Long cmds tend to be because of an absolute path on cmd. Keep
1418 # Long cmds tend to be because of an absolute path on cmd. Keep
1417 # the tail end instead
1419 # the tail end instead
1418 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1420 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1419 blockedtag = 'unknown_system_' + cmdsuffix
1421 blockedtag = 'unknown_system_' + cmdsuffix
1420 out = self.fout
1422 out = self.fout
1421 if any(s[1] for s in self._bufferstates):
1423 if any(s[1] for s in self._bufferstates):
1422 out = self
1424 out = self
1423 with self.timeblockedsection(blockedtag):
1425 with self.timeblockedsection(blockedtag):
1424 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1426 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1425 if rc and onerr:
1427 if rc and onerr:
1426 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1428 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1427 util.explainexit(rc)[0])
1429 util.explainexit(rc)[0])
1428 if errprefix:
1430 if errprefix:
1429 errmsg = '%s: %s' % (errprefix, errmsg)
1431 errmsg = '%s: %s' % (errprefix, errmsg)
1430 raise onerr(errmsg)
1432 raise onerr(errmsg)
1431 return rc
1433 return rc
1432
1434
1433 def _runsystem(self, cmd, environ, cwd, out):
1435 def _runsystem(self, cmd, environ, cwd, out):
1434 """actually execute the given shell command (can be overridden by
1436 """actually execute the given shell command (can be overridden by
1435 extensions like chg)"""
1437 extensions like chg)"""
1436 return util.system(cmd, environ=environ, cwd=cwd, out=out)
1438 return util.system(cmd, environ=environ, cwd=cwd, out=out)
1437
1439
1438 def traceback(self, exc=None, force=False):
1440 def traceback(self, exc=None, force=False):
1439 '''print exception traceback if traceback printing enabled or forced.
1441 '''print exception traceback if traceback printing enabled or forced.
1440 only to call in exception handler. returns true if traceback
1442 only to call in exception handler. returns true if traceback
1441 printed.'''
1443 printed.'''
1442 if self.tracebackflag or force:
1444 if self.tracebackflag or force:
1443 if exc is None:
1445 if exc is None:
1444 exc = sys.exc_info()
1446 exc = sys.exc_info()
1445 cause = getattr(exc[1], 'cause', None)
1447 cause = getattr(exc[1], 'cause', None)
1446
1448
1447 if cause is not None:
1449 if cause is not None:
1448 causetb = traceback.format_tb(cause[2])
1450 causetb = traceback.format_tb(cause[2])
1449 exctb = traceback.format_tb(exc[2])
1451 exctb = traceback.format_tb(exc[2])
1450 exconly = traceback.format_exception_only(cause[0], cause[1])
1452 exconly = traceback.format_exception_only(cause[0], cause[1])
1451
1453
1452 # exclude frame where 'exc' was chained and rethrown from exctb
1454 # exclude frame where 'exc' was chained and rethrown from exctb
1453 self.write_err('Traceback (most recent call last):\n',
1455 self.write_err('Traceback (most recent call last):\n',
1454 ''.join(exctb[:-1]),
1456 ''.join(exctb[:-1]),
1455 ''.join(causetb),
1457 ''.join(causetb),
1456 ''.join(exconly))
1458 ''.join(exconly))
1457 else:
1459 else:
1458 output = traceback.format_exception(exc[0], exc[1], exc[2])
1460 output = traceback.format_exception(exc[0], exc[1], exc[2])
1459 data = r''.join(output)
1461 data = r''.join(output)
1460 if pycompat.ispy3:
1462 if pycompat.ispy3:
1461 enc = pycompat.sysstr(encoding.encoding)
1463 enc = pycompat.sysstr(encoding.encoding)
1462 data = data.encode(enc, errors=r'replace')
1464 data = data.encode(enc, errors=r'replace')
1463 self.write_err(data)
1465 self.write_err(data)
1464 return self.tracebackflag or force
1466 return self.tracebackflag or force
1465
1467
1466 def geteditor(self):
1468 def geteditor(self):
1467 '''return editor to use'''
1469 '''return editor to use'''
1468 if pycompat.sysplatform == 'plan9':
1470 if pycompat.sysplatform == 'plan9':
1469 # vi is the MIPS instruction simulator on Plan 9. We
1471 # vi is the MIPS instruction simulator on Plan 9. We
1470 # instead default to E to plumb commit messages to
1472 # instead default to E to plumb commit messages to
1471 # avoid confusion.
1473 # avoid confusion.
1472 editor = 'E'
1474 editor = 'E'
1473 else:
1475 else:
1474 editor = 'vi'
1476 editor = 'vi'
1475 return (encoding.environ.get("HGEDITOR") or
1477 return (encoding.environ.get("HGEDITOR") or
1476 self.config("ui", "editor", editor))
1478 self.config("ui", "editor", editor))
1477
1479
1478 @util.propertycache
1480 @util.propertycache
1479 def _progbar(self):
1481 def _progbar(self):
1480 """setup the progbar singleton to the ui object"""
1482 """setup the progbar singleton to the ui object"""
1481 if (self.quiet or self.debugflag
1483 if (self.quiet or self.debugflag
1482 or self.configbool('progress', 'disable')
1484 or self.configbool('progress', 'disable')
1483 or not progress.shouldprint(self)):
1485 or not progress.shouldprint(self)):
1484 return None
1486 return None
1485 return getprogbar(self)
1487 return getprogbar(self)
1486
1488
1487 def _progclear(self):
1489 def _progclear(self):
1488 """clear progress bar output if any. use it before any output"""
1490 """clear progress bar output if any. use it before any output"""
1489 if '_progbar' not in vars(self): # nothing loaded yet
1491 if '_progbar' not in vars(self): # nothing loaded yet
1490 return
1492 return
1491 if self._progbar is not None and self._progbar.printed:
1493 if self._progbar is not None and self._progbar.printed:
1492 self._progbar.clear()
1494 self._progbar.clear()
1493
1495
1494 def progress(self, topic, pos, item="", unit="", total=None):
1496 def progress(self, topic, pos, item="", unit="", total=None):
1495 '''show a progress message
1497 '''show a progress message
1496
1498
1497 By default a textual progress bar will be displayed if an operation
1499 By default a textual progress bar will be displayed if an operation
1498 takes too long. 'topic' is the current operation, 'item' is a
1500 takes too long. 'topic' is the current operation, 'item' is a
1499 non-numeric marker of the current position (i.e. the currently
1501 non-numeric marker of the current position (i.e. the currently
1500 in-process file), 'pos' is the current numeric position (i.e.
1502 in-process file), 'pos' is the current numeric position (i.e.
1501 revision, bytes, etc.), unit is a corresponding unit label,
1503 revision, bytes, etc.), unit is a corresponding unit label,
1502 and total is the highest expected pos.
1504 and total is the highest expected pos.
1503
1505
1504 Multiple nested topics may be active at a time.
1506 Multiple nested topics may be active at a time.
1505
1507
1506 All topics should be marked closed by setting pos to None at
1508 All topics should be marked closed by setting pos to None at
1507 termination.
1509 termination.
1508 '''
1510 '''
1509 if self._progbar is not None:
1511 if self._progbar is not None:
1510 self._progbar.progress(topic, pos, item=item, unit=unit,
1512 self._progbar.progress(topic, pos, item=item, unit=unit,
1511 total=total)
1513 total=total)
1512 if pos is None or not self.configbool('progress', 'debug'):
1514 if pos is None or not self.configbool('progress', 'debug'):
1513 return
1515 return
1514
1516
1515 if unit:
1517 if unit:
1516 unit = ' ' + unit
1518 unit = ' ' + unit
1517 if item:
1519 if item:
1518 item = ' ' + item
1520 item = ' ' + item
1519
1521
1520 if total:
1522 if total:
1521 pct = 100.0 * pos / total
1523 pct = 100.0 * pos / total
1522 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
1524 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
1523 % (topic, item, pos, total, unit, pct))
1525 % (topic, item, pos, total, unit, pct))
1524 else:
1526 else:
1525 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
1527 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
1526
1528
1527 def log(self, service, *msg, **opts):
1529 def log(self, service, *msg, **opts):
1528 '''hook for logging facility extensions
1530 '''hook for logging facility extensions
1529
1531
1530 service should be a readily-identifiable subsystem, which will
1532 service should be a readily-identifiable subsystem, which will
1531 allow filtering.
1533 allow filtering.
1532
1534
1533 *msg should be a newline-terminated format string to log, and
1535 *msg should be a newline-terminated format string to log, and
1534 then any values to %-format into that format string.
1536 then any values to %-format into that format string.
1535
1537
1536 **opts currently has no defined meanings.
1538 **opts currently has no defined meanings.
1537 '''
1539 '''
1538
1540
1539 def label(self, msg, label):
1541 def label(self, msg, label):
1540 '''style msg based on supplied label
1542 '''style msg based on supplied label
1541
1543
1542 If some color mode is enabled, this will add the necessary control
1544 If some color mode is enabled, this will add the necessary control
1543 characters to apply such color. In addition, 'debug' color mode adds
1545 characters to apply such color. In addition, 'debug' color mode adds
1544 markup showing which label affects a piece of text.
1546 markup showing which label affects a piece of text.
1545
1547
1546 ui.write(s, 'label') is equivalent to
1548 ui.write(s, 'label') is equivalent to
1547 ui.write(ui.label(s, 'label')).
1549 ui.write(ui.label(s, 'label')).
1548 '''
1550 '''
1549 if self._colormode is not None:
1551 if self._colormode is not None:
1550 return color.colorlabel(self, msg, label)
1552 return color.colorlabel(self, msg, label)
1551 return msg
1553 return msg
1552
1554
1553 def develwarn(self, msg, stacklevel=1, config=None):
1555 def develwarn(self, msg, stacklevel=1, config=None):
1554 """issue a developer warning message
1556 """issue a developer warning message
1555
1557
1556 Use 'stacklevel' to report the offender some layers further up in the
1558 Use 'stacklevel' to report the offender some layers further up in the
1557 stack.
1559 stack.
1558 """
1560 """
1559 if not self.configbool('devel', 'all-warnings'):
1561 if not self.configbool('devel', 'all-warnings'):
1560 if config is not None and not self.configbool('devel', config):
1562 if config is not None and not self.configbool('devel', config):
1561 return
1563 return
1562 msg = 'devel-warn: ' + msg
1564 msg = 'devel-warn: ' + msg
1563 stacklevel += 1 # get in develwarn
1565 stacklevel += 1 # get in develwarn
1564 if self.tracebackflag:
1566 if self.tracebackflag:
1565 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1567 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1566 self.log('develwarn', '%s at:\n%s' %
1568 self.log('develwarn', '%s at:\n%s' %
1567 (msg, ''.join(util.getstackframes(stacklevel))))
1569 (msg, ''.join(util.getstackframes(stacklevel))))
1568 else:
1570 else:
1569 curframe = inspect.currentframe()
1571 curframe = inspect.currentframe()
1570 calframe = inspect.getouterframes(curframe, 2)
1572 calframe = inspect.getouterframes(curframe, 2)
1571 self.write_err('%s at: %s:%s (%s)\n'
1573 self.write_err('%s at: %s:%s (%s)\n'
1572 % ((msg,) + calframe[stacklevel][1:4]))
1574 % ((msg,) + calframe[stacklevel][1:4]))
1573 self.log('develwarn', '%s at: %s:%s (%s)\n',
1575 self.log('develwarn', '%s at: %s:%s (%s)\n',
1574 msg, *calframe[stacklevel][1:4])
1576 msg, *calframe[stacklevel][1:4])
1575 curframe = calframe = None # avoid cycles
1577 curframe = calframe = None # avoid cycles
1576
1578
1577 def deprecwarn(self, msg, version):
1579 def deprecwarn(self, msg, version):
1578 """issue a deprecation warning
1580 """issue a deprecation warning
1579
1581
1580 - msg: message explaining what is deprecated and how to upgrade,
1582 - msg: message explaining what is deprecated and how to upgrade,
1581 - version: last version where the API will be supported,
1583 - version: last version where the API will be supported,
1582 """
1584 """
1583 if not (self.configbool('devel', 'all-warnings')
1585 if not (self.configbool('devel', 'all-warnings')
1584 or self.configbool('devel', 'deprec-warn')):
1586 or self.configbool('devel', 'deprec-warn')):
1585 return
1587 return
1586 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1588 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1587 " update your code.)") % version
1589 " update your code.)") % version
1588 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1590 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1589
1591
1590 def exportableenviron(self):
1592 def exportableenviron(self):
1591 """The environment variables that are safe to export, e.g. through
1593 """The environment variables that are safe to export, e.g. through
1592 hgweb.
1594 hgweb.
1593 """
1595 """
1594 return self._exportableenviron
1596 return self._exportableenviron
1595
1597
1596 @contextlib.contextmanager
1598 @contextlib.contextmanager
1597 def configoverride(self, overrides, source=""):
1599 def configoverride(self, overrides, source=""):
1598 """Context manager for temporary config overrides
1600 """Context manager for temporary config overrides
1599 `overrides` must be a dict of the following structure:
1601 `overrides` must be a dict of the following structure:
1600 {(section, name) : value}"""
1602 {(section, name) : value}"""
1601 backups = {}
1603 backups = {}
1602 try:
1604 try:
1603 for (section, name), value in overrides.items():
1605 for (section, name), value in overrides.items():
1604 backups[(section, name)] = self.backupconfig(section, name)
1606 backups[(section, name)] = self.backupconfig(section, name)
1605 self.setconfig(section, name, value, source)
1607 self.setconfig(section, name, value, source)
1606 yield
1608 yield
1607 finally:
1609 finally:
1608 for __, backup in backups.items():
1610 for __, backup in backups.items():
1609 self.restoreconfig(backup)
1611 self.restoreconfig(backup)
1610 # just restoring ui.quiet config to the previous value is not enough
1612 # just restoring ui.quiet config to the previous value is not enough
1611 # as it does not update ui.quiet class member
1613 # as it does not update ui.quiet class member
1612 if ('ui', 'quiet') in overrides:
1614 if ('ui', 'quiet') in overrides:
1613 self.fixconfig(section='ui')
1615 self.fixconfig(section='ui')
1614
1616
1615 class paths(dict):
1617 class paths(dict):
1616 """Represents a collection of paths and their configs.
1618 """Represents a collection of paths and their configs.
1617
1619
1618 Data is initially derived from ui instances and the config files they have
1620 Data is initially derived from ui instances and the config files they have
1619 loaded.
1621 loaded.
1620 """
1622 """
1621 def __init__(self, ui):
1623 def __init__(self, ui):
1622 dict.__init__(self)
1624 dict.__init__(self)
1623
1625
1624 for name, loc in ui.configitems('paths', ignoresub=True):
1626 for name, loc in ui.configitems('paths', ignoresub=True):
1625 # No location is the same as not existing.
1627 # No location is the same as not existing.
1626 if not loc:
1628 if not loc:
1627 continue
1629 continue
1628 loc, sub = ui.configsuboptions('paths', name)
1630 loc, sub = ui.configsuboptions('paths', name)
1629 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1631 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1630
1632
1631 def getpath(self, name, default=None):
1633 def getpath(self, name, default=None):
1632 """Return a ``path`` from a string, falling back to default.
1634 """Return a ``path`` from a string, falling back to default.
1633
1635
1634 ``name`` can be a named path or locations. Locations are filesystem
1636 ``name`` can be a named path or locations. Locations are filesystem
1635 paths or URIs.
1637 paths or URIs.
1636
1638
1637 Returns None if ``name`` is not a registered path, a URI, or a local
1639 Returns None if ``name`` is not a registered path, a URI, or a local
1638 path to a repo.
1640 path to a repo.
1639 """
1641 """
1640 # Only fall back to default if no path was requested.
1642 # Only fall back to default if no path was requested.
1641 if name is None:
1643 if name is None:
1642 if not default:
1644 if not default:
1643 default = ()
1645 default = ()
1644 elif not isinstance(default, (tuple, list)):
1646 elif not isinstance(default, (tuple, list)):
1645 default = (default,)
1647 default = (default,)
1646 for k in default:
1648 for k in default:
1647 try:
1649 try:
1648 return self[k]
1650 return self[k]
1649 except KeyError:
1651 except KeyError:
1650 continue
1652 continue
1651 return None
1653 return None
1652
1654
1653 # Most likely empty string.
1655 # Most likely empty string.
1654 # This may need to raise in the future.
1656 # This may need to raise in the future.
1655 if not name:
1657 if not name:
1656 return None
1658 return None
1657
1659
1658 try:
1660 try:
1659 return self[name]
1661 return self[name]
1660 except KeyError:
1662 except KeyError:
1661 # Try to resolve as a local path or URI.
1663 # Try to resolve as a local path or URI.
1662 try:
1664 try:
1663 # We don't pass sub-options in, so no need to pass ui instance.
1665 # We don't pass sub-options in, so no need to pass ui instance.
1664 return path(None, None, rawloc=name)
1666 return path(None, None, rawloc=name)
1665 except ValueError:
1667 except ValueError:
1666 raise error.RepoError(_('repository %s does not exist') %
1668 raise error.RepoError(_('repository %s does not exist') %
1667 name)
1669 name)
1668
1670
1669 _pathsuboptions = {}
1671 _pathsuboptions = {}
1670
1672
1671 def pathsuboption(option, attr):
1673 def pathsuboption(option, attr):
1672 """Decorator used to declare a path sub-option.
1674 """Decorator used to declare a path sub-option.
1673
1675
1674 Arguments are the sub-option name and the attribute it should set on
1676 Arguments are the sub-option name and the attribute it should set on
1675 ``path`` instances.
1677 ``path`` instances.
1676
1678
1677 The decorated function will receive as arguments a ``ui`` instance,
1679 The decorated function will receive as arguments a ``ui`` instance,
1678 ``path`` instance, and the string value of this option from the config.
1680 ``path`` instance, and the string value of this option from the config.
1679 The function should return the value that will be set on the ``path``
1681 The function should return the value that will be set on the ``path``
1680 instance.
1682 instance.
1681
1683
1682 This decorator can be used to perform additional verification of
1684 This decorator can be used to perform additional verification of
1683 sub-options and to change the type of sub-options.
1685 sub-options and to change the type of sub-options.
1684 """
1686 """
1685 def register(func):
1687 def register(func):
1686 _pathsuboptions[option] = (attr, func)
1688 _pathsuboptions[option] = (attr, func)
1687 return func
1689 return func
1688 return register
1690 return register
1689
1691
1690 @pathsuboption('pushurl', 'pushloc')
1692 @pathsuboption('pushurl', 'pushloc')
1691 def pushurlpathoption(ui, path, value):
1693 def pushurlpathoption(ui, path, value):
1692 u = util.url(value)
1694 u = util.url(value)
1693 # Actually require a URL.
1695 # Actually require a URL.
1694 if not u.scheme:
1696 if not u.scheme:
1695 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1697 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1696 return None
1698 return None
1697
1699
1698 # Don't support the #foo syntax in the push URL to declare branch to
1700 # Don't support the #foo syntax in the push URL to declare branch to
1699 # push.
1701 # push.
1700 if u.fragment:
1702 if u.fragment:
1701 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1703 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1702 'ignoring)\n') % path.name)
1704 'ignoring)\n') % path.name)
1703 u.fragment = None
1705 u.fragment = None
1704
1706
1705 return str(u)
1707 return str(u)
1706
1708
1707 @pathsuboption('pushrev', 'pushrev')
1709 @pathsuboption('pushrev', 'pushrev')
1708 def pushrevpathoption(ui, path, value):
1710 def pushrevpathoption(ui, path, value):
1709 return value
1711 return value
1710
1712
1711 class path(object):
1713 class path(object):
1712 """Represents an individual path and its configuration."""
1714 """Represents an individual path and its configuration."""
1713
1715
1714 def __init__(self, ui, name, rawloc=None, suboptions=None):
1716 def __init__(self, ui, name, rawloc=None, suboptions=None):
1715 """Construct a path from its config options.
1717 """Construct a path from its config options.
1716
1718
1717 ``ui`` is the ``ui`` instance the path is coming from.
1719 ``ui`` is the ``ui`` instance the path is coming from.
1718 ``name`` is the symbolic name of the path.
1720 ``name`` is the symbolic name of the path.
1719 ``rawloc`` is the raw location, as defined in the config.
1721 ``rawloc`` is the raw location, as defined in the config.
1720 ``pushloc`` is the raw locations pushes should be made to.
1722 ``pushloc`` is the raw locations pushes should be made to.
1721
1723
1722 If ``name`` is not defined, we require that the location be a) a local
1724 If ``name`` is not defined, we require that the location be a) a local
1723 filesystem path with a .hg directory or b) a URL. If not,
1725 filesystem path with a .hg directory or b) a URL. If not,
1724 ``ValueError`` is raised.
1726 ``ValueError`` is raised.
1725 """
1727 """
1726 if not rawloc:
1728 if not rawloc:
1727 raise ValueError('rawloc must be defined')
1729 raise ValueError('rawloc must be defined')
1728
1730
1729 # Locations may define branches via syntax <base>#<branch>.
1731 # Locations may define branches via syntax <base>#<branch>.
1730 u = util.url(rawloc)
1732 u = util.url(rawloc)
1731 branch = None
1733 branch = None
1732 if u.fragment:
1734 if u.fragment:
1733 branch = u.fragment
1735 branch = u.fragment
1734 u.fragment = None
1736 u.fragment = None
1735
1737
1736 self.url = u
1738 self.url = u
1737 self.branch = branch
1739 self.branch = branch
1738
1740
1739 self.name = name
1741 self.name = name
1740 self.rawloc = rawloc
1742 self.rawloc = rawloc
1741 self.loc = '%s' % u
1743 self.loc = '%s' % u
1742
1744
1743 # When given a raw location but not a symbolic name, validate the
1745 # When given a raw location but not a symbolic name, validate the
1744 # location is valid.
1746 # location is valid.
1745 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1747 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1746 raise ValueError('location is not a URL or path to a local '
1748 raise ValueError('location is not a URL or path to a local '
1747 'repo: %s' % rawloc)
1749 'repo: %s' % rawloc)
1748
1750
1749 suboptions = suboptions or {}
1751 suboptions = suboptions or {}
1750
1752
1751 # Now process the sub-options. If a sub-option is registered, its
1753 # Now process the sub-options. If a sub-option is registered, its
1752 # attribute will always be present. The value will be None if there
1754 # attribute will always be present. The value will be None if there
1753 # was no valid sub-option.
1755 # was no valid sub-option.
1754 for suboption, (attr, func) in _pathsuboptions.iteritems():
1756 for suboption, (attr, func) in _pathsuboptions.iteritems():
1755 if suboption not in suboptions:
1757 if suboption not in suboptions:
1756 setattr(self, attr, None)
1758 setattr(self, attr, None)
1757 continue
1759 continue
1758
1760
1759 value = func(ui, self, suboptions[suboption])
1761 value = func(ui, self, suboptions[suboption])
1760 setattr(self, attr, value)
1762 setattr(self, attr, value)
1761
1763
1762 def _isvalidlocalpath(self, path):
1764 def _isvalidlocalpath(self, path):
1763 """Returns True if the given path is a potentially valid repository.
1765 """Returns True if the given path is a potentially valid repository.
1764 This is its own function so that extensions can change the definition of
1766 This is its own function so that extensions can change the definition of
1765 'valid' in this case (like when pulling from a git repo into a hg
1767 'valid' in this case (like when pulling from a git repo into a hg
1766 one)."""
1768 one)."""
1767 return os.path.isdir(os.path.join(path, '.hg'))
1769 return os.path.isdir(os.path.join(path, '.hg'))
1768
1770
1769 @property
1771 @property
1770 def suboptions(self):
1772 def suboptions(self):
1771 """Return sub-options and their values for this path.
1773 """Return sub-options and their values for this path.
1772
1774
1773 This is intended to be used for presentation purposes.
1775 This is intended to be used for presentation purposes.
1774 """
1776 """
1775 d = {}
1777 d = {}
1776 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1778 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1777 value = getattr(self, attr)
1779 value = getattr(self, attr)
1778 if value is not None:
1780 if value is not None:
1779 d[subopt] = value
1781 d[subopt] = value
1780 return d
1782 return d
1781
1783
1782 # we instantiate one globally shared progress bar to avoid
1784 # we instantiate one globally shared progress bar to avoid
1783 # competing progress bars when multiple UI objects get created
1785 # competing progress bars when multiple UI objects get created
1784 _progresssingleton = None
1786 _progresssingleton = None
1785
1787
1786 def getprogbar(ui):
1788 def getprogbar(ui):
1787 global _progresssingleton
1789 global _progresssingleton
1788 if _progresssingleton is None:
1790 if _progresssingleton is None:
1789 # passing 'ui' object to the singleton is fishy,
1791 # passing 'ui' object to the singleton is fishy,
1790 # this is how the extension used to work but feel free to rework it.
1792 # this is how the extension used to work but feel free to rework it.
1791 _progresssingleton = progress.progbar(ui)
1793 _progresssingleton = progress.progbar(ui)
1792 return _progresssingleton
1794 return _progresssingleton
@@ -1,35 +1,63
1 Test temp file used with an editor has the expected suffix.
1 Test temp file used with an editor has the expected suffix.
2
2
3 $ hg init
3 $ hg init
4
4
5 Create an editor that writes its arguments to stdout and set it to $HGEDITOR.
5 Create an editor that writes its arguments to stdout and set it to $HGEDITOR.
6
6
7 $ cat > editor.sh << EOF
7 $ cat > editor.sh << EOF
8 > #!/bin/bash
8 > #!/bin/bash
9 > echo "\$@"
9 > echo "\$@"
10 > exit 1
10 > exit 1
11 > EOF
11 > EOF
12 $ chmod +x editor.sh
12 $ chmod +x editor.sh
13 $ hg add editor.sh
13 $ hg add editor.sh
14 $ HGEDITOR=$TESTTMP/editor.sh
14 $ HGEDITOR=$TESTTMP/editor.sh
15 $ export HGEDITOR
15 $ export HGEDITOR
16
16
17 Verify that the path for a commit editor has the expected suffix.
17 Verify that the path for a commit editor has the expected suffix.
18
18
19 $ hg commit
19 $ hg commit
20 *.commit.hg.txt (glob)
20 *.commit.hg.txt (glob)
21 abort: edit failed: editor.sh exited with status 1
21 abort: edit failed: editor.sh exited with status 1
22 [255]
22 [255]
23
23
24 Verify that the path for a histedit editor has the expected suffix.
24 Verify that the path for a histedit editor has the expected suffix.
25
25
26 $ cat >> $HGRCPATH <<EOF
26 $ cat >> $HGRCPATH <<EOF
27 > [extensions]
27 > [extensions]
28 > rebase=
28 > rebase=
29 > histedit=
29 > histedit=
30 > EOF
30 > EOF
31 $ hg commit --message 'At least one commit for histedit.'
31 $ hg commit --message 'At least one commit for histedit.'
32 $ hg histedit
32 $ hg histedit
33 *.histedit.hg.txt (glob)
33 *.histedit.hg.txt (glob)
34 abort: edit failed: editor.sh exited with status 1
34 abort: edit failed: editor.sh exited with status 1
35 [255]
35 [255]
36
37 Verify that when performing an action that has the side-effect of creating an
38 editor for a diff, the file ends in .diff.
39
40 $ echo 1 > one
41 $ echo 2 > two
42 $ hg add
43 adding one
44 adding two
45 $ hg commit --interactive --config ui.interactive=true --config ui.interface=text << EOF
46 > y
47 > e
48 > q
49 > EOF
50 diff --git a/one b/one
51 new file mode 100644
52 examine changes to 'one'? [Ynesfdaq?] y
53
54 @@ -0,0 +1,1 @@
55 +1
56 record change 1/2 to 'one'? [Ynesfdaq?] e
57
58 *.diff (glob)
59 editor exited with exit code 1
60 record change 1/2 to 'one'? [Ynesfdaq?] q
61
62 abort: user quit
63 [255]
General Comments 0
You need to be logged in to leave comments. Login now