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