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