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