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