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