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