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