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