##// END OF EJS Templates
pycompat: provide bytes os.linesep
Yuya Nishihara -
r31775:8181f378 default
parent child Browse files
Show More
@@ -1,392 +1,393 b''
1 1 """automatically manage newlines in repository files
2 2
3 3 This extension allows you to manage the type of line endings (CRLF or
4 4 LF) that are used in the repository and in the local working
5 5 directory. That way you can get CRLF line endings on Windows and LF on
6 6 Unix/Mac, thereby letting everybody use their OS native line endings.
7 7
8 8 The extension reads its configuration from a versioned ``.hgeol``
9 9 configuration file found in the root of the working directory. The
10 10 ``.hgeol`` file use the same syntax as all other Mercurial
11 11 configuration files. It uses two sections, ``[patterns]`` and
12 12 ``[repository]``.
13 13
14 14 The ``[patterns]`` section specifies how line endings should be
15 15 converted between the working directory and the repository. The format is
16 16 specified by a file pattern. The first match is used, so put more
17 17 specific patterns first. The available line endings are ``LF``,
18 18 ``CRLF``, and ``BIN``.
19 19
20 20 Files with the declared format of ``CRLF`` or ``LF`` are always
21 21 checked out and stored in the repository in that format and files
22 22 declared to be binary (``BIN``) are left unchanged. Additionally,
23 23 ``native`` is an alias for checking out in the platform's default line
24 24 ending: ``LF`` on Unix (including Mac OS X) and ``CRLF`` on
25 25 Windows. Note that ``BIN`` (do nothing to line endings) is Mercurial's
26 26 default behavior; it is only needed if you need to override a later,
27 27 more general pattern.
28 28
29 29 The optional ``[repository]`` section specifies the line endings to
30 30 use for files stored in the repository. It has a single setting,
31 31 ``native``, which determines the storage line endings for files
32 32 declared as ``native`` in the ``[patterns]`` section. It can be set to
33 33 ``LF`` or ``CRLF``. The default is ``LF``. For example, this means
34 34 that on Windows, files configured as ``native`` (``CRLF`` by default)
35 35 will be converted to ``LF`` when stored in the repository. Files
36 36 declared as ``LF``, ``CRLF``, or ``BIN`` in the ``[patterns]`` section
37 37 are always stored as-is in the repository.
38 38
39 39 Example versioned ``.hgeol`` file::
40 40
41 41 [patterns]
42 42 **.py = native
43 43 **.vcproj = CRLF
44 44 **.txt = native
45 45 Makefile = LF
46 46 **.jpg = BIN
47 47
48 48 [repository]
49 49 native = LF
50 50
51 51 .. note::
52 52
53 53 The rules will first apply when files are touched in the working
54 54 directory, e.g. by updating to null and back to tip to touch all files.
55 55
56 56 The extension uses an optional ``[eol]`` section read from both the
57 57 normal Mercurial configuration files and the ``.hgeol`` file, with the
58 58 latter overriding the former. You can use that section to control the
59 59 overall behavior. There are three settings:
60 60
61 61 - ``eol.native`` (default ``os.linesep``) can be set to ``LF`` or
62 62 ``CRLF`` to override the default interpretation of ``native`` for
63 63 checkout. This can be used with :hg:`archive` on Unix, say, to
64 64 generate an archive where files have line endings for Windows.
65 65
66 66 - ``eol.only-consistent`` (default True) can be set to False to make
67 67 the extension convert files with inconsistent EOLs. Inconsistent
68 68 means that there is both ``CRLF`` and ``LF`` present in the file.
69 69 Such files are normally not touched under the assumption that they
70 70 have mixed EOLs on purpose.
71 71
72 72 - ``eol.fix-trailing-newline`` (default False) can be set to True to
73 73 ensure that converted files end with a EOL character (either ``\\n``
74 74 or ``\\r\\n`` as per the configured patterns).
75 75
76 76 The extension provides ``cleverencode:`` and ``cleverdecode:`` filters
77 77 like the deprecated win32text extension does. This means that you can
78 78 disable win32text and enable eol and your filters will still work. You
79 79 only need to these filters until you have prepared a ``.hgeol`` file.
80 80
81 81 The ``win32text.forbid*`` hooks provided by the win32text extension
82 82 have been unified into a single hook named ``eol.checkheadshook``. The
83 83 hook will lookup the expected line endings from the ``.hgeol`` file,
84 84 which means you must migrate to a ``.hgeol`` file first before using
85 85 the hook. ``eol.checkheadshook`` only checks heads, intermediate
86 86 invalid revisions will be pushed. To forbid them completely, use the
87 87 ``eol.checkallhook`` hook. These hooks are best used as
88 88 ``pretxnchangegroup`` hooks.
89 89
90 90 See :hg:`help patterns` for more information about the glob patterns
91 91 used.
92 92 """
93 93
94 94 from __future__ import absolute_import
95 95
96 96 import os
97 97 import re
98 98 from mercurial.i18n import _
99 99 from mercurial import (
100 100 config,
101 101 error,
102 102 extensions,
103 103 match,
104 pycompat,
104 105 util,
105 106 )
106 107
107 108 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
108 109 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
109 110 # be specifying the version(s) of Mercurial they are tested with, or
110 111 # leave the attribute unspecified.
111 112 testedwith = 'ships-with-hg-core'
112 113
113 114 # Matches a lone LF, i.e., one that is not part of CRLF.
114 115 singlelf = re.compile('(^|[^\r])\n')
115 116 # Matches a single EOL which can either be a CRLF where repeated CR
116 117 # are removed or a LF. We do not care about old Macintosh files, so a
117 118 # stray CR is an error.
118 119 eolre = re.compile('\r*\n')
119 120
120 121
121 122 def inconsistenteol(data):
122 123 return '\r\n' in data and singlelf.search(data)
123 124
124 125 def tolf(s, params, ui, **kwargs):
125 126 """Filter to convert to LF EOLs."""
126 127 if util.binary(s):
127 128 return s
128 129 if ui.configbool('eol', 'only-consistent', True) and inconsistenteol(s):
129 130 return s
130 131 if (ui.configbool('eol', 'fix-trailing-newline', False)
131 132 and s and s[-1] != '\n'):
132 133 s = s + '\n'
133 134 return eolre.sub('\n', s)
134 135
135 136 def tocrlf(s, params, ui, **kwargs):
136 137 """Filter to convert to CRLF EOLs."""
137 138 if util.binary(s):
138 139 return s
139 140 if ui.configbool('eol', 'only-consistent', True) and inconsistenteol(s):
140 141 return s
141 142 if (ui.configbool('eol', 'fix-trailing-newline', False)
142 143 and s and s[-1] != '\n'):
143 144 s = s + '\n'
144 145 return eolre.sub('\r\n', s)
145 146
146 147 def isbinary(s, params):
147 148 """Filter to do nothing with the file."""
148 149 return s
149 150
150 151 filters = {
151 152 'to-lf': tolf,
152 153 'to-crlf': tocrlf,
153 154 'is-binary': isbinary,
154 155 # The following provide backwards compatibility with win32text
155 156 'cleverencode:': tolf,
156 157 'cleverdecode:': tocrlf
157 158 }
158 159
159 160 class eolfile(object):
160 161 def __init__(self, ui, root, data):
161 162 self._decode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'}
162 163 self._encode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'}
163 164
164 165 self.cfg = config.config()
165 166 # Our files should not be touched. The pattern must be
166 167 # inserted first override a '** = native' pattern.
167 168 self.cfg.set('patterns', '.hg*', 'BIN', 'eol')
168 169 # We can then parse the user's patterns.
169 170 self.cfg.parse('.hgeol', data)
170 171
171 172 isrepolf = self.cfg.get('repository', 'native') != 'CRLF'
172 173 self._encode['NATIVE'] = isrepolf and 'to-lf' or 'to-crlf'
173 iswdlf = ui.config('eol', 'native', os.linesep) in ('LF', '\n')
174 iswdlf = ui.config('eol', 'native', pycompat.oslinesep) in ('LF', '\n')
174 175 self._decode['NATIVE'] = iswdlf and 'to-lf' or 'to-crlf'
175 176
176 177 include = []
177 178 exclude = []
178 179 self.patterns = []
179 180 for pattern, style in self.cfg.items('patterns'):
180 181 key = style.upper()
181 182 if key == 'BIN':
182 183 exclude.append(pattern)
183 184 else:
184 185 include.append(pattern)
185 186 m = match.match(root, '', [pattern])
186 187 self.patterns.append((pattern, key, m))
187 188 # This will match the files for which we need to care
188 189 # about inconsistent newlines.
189 190 self.match = match.match(root, '', [], include, exclude)
190 191
191 192 def copytoui(self, ui):
192 193 for pattern, key, m in self.patterns:
193 194 try:
194 195 ui.setconfig('decode', pattern, self._decode[key], 'eol')
195 196 ui.setconfig('encode', pattern, self._encode[key], 'eol')
196 197 except KeyError:
197 198 ui.warn(_("ignoring unknown EOL style '%s' from %s\n")
198 199 % (key, self.cfg.source('patterns', pattern)))
199 200 # eol.only-consistent can be specified in ~/.hgrc or .hgeol
200 201 for k, v in self.cfg.items('eol'):
201 202 ui.setconfig('eol', k, v, 'eol')
202 203
203 204 def checkrev(self, repo, ctx, files):
204 205 failed = []
205 206 for f in (files or ctx.files()):
206 207 if f not in ctx:
207 208 continue
208 209 for pattern, key, m in self.patterns:
209 210 if not m(f):
210 211 continue
211 212 target = self._encode[key]
212 213 data = ctx[f].data()
213 214 if (target == "to-lf" and "\r\n" in data
214 215 or target == "to-crlf" and singlelf.search(data)):
215 216 failed.append((f, target, str(ctx)))
216 217 break
217 218 return failed
218 219
219 220 def parseeol(ui, repo, nodes):
220 221 try:
221 222 for node in nodes:
222 223 try:
223 224 if node is None:
224 225 # Cannot use workingctx.data() since it would load
225 226 # and cache the filters before we configure them.
226 227 data = repo.wvfs('.hgeol').read()
227 228 else:
228 229 data = repo[node]['.hgeol'].data()
229 230 return eolfile(ui, repo.root, data)
230 231 except (IOError, LookupError):
231 232 pass
232 233 except error.ParseError as inst:
233 234 ui.warn(_("warning: ignoring .hgeol file due to parse error "
234 235 "at %s: %s\n") % (inst.args[1], inst.args[0]))
235 236 return None
236 237
237 238 def _checkhook(ui, repo, node, headsonly):
238 239 # Get revisions to check and touched files at the same time
239 240 files = set()
240 241 revs = set()
241 242 for rev in xrange(repo[node].rev(), len(repo)):
242 243 revs.add(rev)
243 244 if headsonly:
244 245 ctx = repo[rev]
245 246 files.update(ctx.files())
246 247 for pctx in ctx.parents():
247 248 revs.discard(pctx.rev())
248 249 failed = []
249 250 for rev in revs:
250 251 ctx = repo[rev]
251 252 eol = parseeol(ui, repo, [ctx.node()])
252 253 if eol:
253 254 failed.extend(eol.checkrev(repo, ctx, files))
254 255
255 256 if failed:
256 257 eols = {'to-lf': 'CRLF', 'to-crlf': 'LF'}
257 258 msgs = []
258 259 for f, target, node in sorted(failed):
259 260 msgs.append(_(" %s in %s should not have %s line endings") %
260 261 (f, node, eols[target]))
261 262 raise error.Abort(_("end-of-line check failed:\n") + "\n".join(msgs))
262 263
263 264 def checkallhook(ui, repo, node, hooktype, **kwargs):
264 265 """verify that files have expected EOLs"""
265 266 _checkhook(ui, repo, node, False)
266 267
267 268 def checkheadshook(ui, repo, node, hooktype, **kwargs):
268 269 """verify that files have expected EOLs"""
269 270 _checkhook(ui, repo, node, True)
270 271
271 272 # "checkheadshook" used to be called "hook"
272 273 hook = checkheadshook
273 274
274 275 def preupdate(ui, repo, hooktype, parent1, parent2):
275 276 repo.loadeol([parent1])
276 277 return False
277 278
278 279 def uisetup(ui):
279 280 ui.setconfig('hooks', 'preupdate.eol', preupdate, 'eol')
280 281
281 282 def extsetup(ui):
282 283 try:
283 284 extensions.find('win32text')
284 285 ui.warn(_("the eol extension is incompatible with the "
285 286 "win32text extension\n"))
286 287 except KeyError:
287 288 pass
288 289
289 290
290 291 def reposetup(ui, repo):
291 292 uisetup(repo.ui)
292 293
293 294 if not repo.local():
294 295 return
295 296 for name, fn in filters.iteritems():
296 297 repo.adddatafilter(name, fn)
297 298
298 299 ui.setconfig('patch', 'eol', 'auto', 'eol')
299 300
300 301 class eolrepo(repo.__class__):
301 302
302 303 def loadeol(self, nodes):
303 304 eol = parseeol(self.ui, self, nodes)
304 305 if eol is None:
305 306 return None
306 307 eol.copytoui(self.ui)
307 308 return eol.match
308 309
309 310 def _hgcleardirstate(self):
310 311 self._eolmatch = self.loadeol([None, 'tip'])
311 312 if not self._eolmatch:
312 313 self._eolmatch = util.never
313 314 return
314 315
315 316 oldeol = None
316 317 try:
317 318 cachemtime = os.path.getmtime(self.vfs.join("eol.cache"))
318 319 except OSError:
319 320 cachemtime = 0
320 321 else:
321 322 olddata = self.vfs.read("eol.cache")
322 323 if olddata:
323 324 oldeol = eolfile(self.ui, self.root, olddata)
324 325
325 326 try:
326 327 eolmtime = os.path.getmtime(self.wjoin(".hgeol"))
327 328 except OSError:
328 329 eolmtime = 0
329 330
330 331 if eolmtime > cachemtime:
331 332 self.ui.debug("eol: detected change in .hgeol\n")
332 333
333 334 hgeoldata = self.wvfs.read('.hgeol')
334 335 neweol = eolfile(self.ui, self.root, hgeoldata)
335 336
336 337 wlock = None
337 338 try:
338 339 wlock = self.wlock()
339 340 for f in self.dirstate:
340 341 if self.dirstate[f] != 'n':
341 342 continue
342 343 if oldeol is not None:
343 344 if not oldeol.match(f) and not neweol.match(f):
344 345 continue
345 346 oldkey = None
346 347 for pattern, key, m in oldeol.patterns:
347 348 if m(f):
348 349 oldkey = key
349 350 break
350 351 newkey = None
351 352 for pattern, key, m in neweol.patterns:
352 353 if m(f):
353 354 newkey = key
354 355 break
355 356 if oldkey == newkey:
356 357 continue
357 358 # all normal files need to be looked at again since
358 359 # the new .hgeol file specify a different filter
359 360 self.dirstate.normallookup(f)
360 361 # Write the cache to update mtime and cache .hgeol
361 362 with self.vfs("eol.cache", "w") as f:
362 363 f.write(hgeoldata)
363 364 except error.LockUnavailable:
364 365 # If we cannot lock the repository and clear the
365 366 # dirstate, then a commit might not see all files
366 367 # as modified. But if we cannot lock the
367 368 # repository, then we can also not make a commit,
368 369 # so ignore the error.
369 370 pass
370 371 finally:
371 372 if wlock is not None:
372 373 wlock.release()
373 374
374 375 def commitctx(self, ctx, haserror=False):
375 376 for f in sorted(ctx.added() + ctx.modified()):
376 377 if not self._eolmatch(f):
377 378 continue
378 379 fctx = ctx[f]
379 380 if fctx is None:
380 381 continue
381 382 data = fctx.data()
382 383 if util.binary(data):
383 384 # We should not abort here, since the user should
384 385 # be able to say "** = native" to automatically
385 386 # have all non-binary files taken care of.
386 387 continue
387 388 if inconsistenteol(data):
388 389 raise error.Abort(_("inconsistent newline style "
389 390 "in %s\n") % f)
390 391 return super(eolrepo, self).commitctx(ctx, haserror)
391 392 repo.__class__ = eolrepo
392 393 repo._hgcleardirstate()
@@ -1,391 +1,393 b''
1 1 # pycompat.py - portability shim for python 3
2 2 #
3 3 # This software may be used and distributed according to the terms of the
4 4 # GNU General Public License version 2 or any later version.
5 5
6 6 """Mercurial portability shim for python 3.
7 7
8 8 This contains aliases to hide python version-specific details from the core.
9 9 """
10 10
11 11 from __future__ import absolute_import
12 12
13 13 import getopt
14 14 import os
15 15 import shlex
16 16 import sys
17 17
18 18 ispy3 = (sys.version_info[0] >= 3)
19 19
20 20 if not ispy3:
21 21 import cPickle as pickle
22 22 import httplib
23 23 import Queue as _queue
24 24 import SocketServer as socketserver
25 25 import xmlrpclib
26 26 else:
27 27 import http.client as httplib
28 28 import pickle
29 29 import queue as _queue
30 30 import socketserver
31 31 import xmlrpc.client as xmlrpclib
32 32
33 33 def identity(a):
34 34 return a
35 35
36 36 if ispy3:
37 37 import builtins
38 38 import functools
39 39 import io
40 40 import struct
41 41
42 42 fsencode = os.fsencode
43 43 fsdecode = os.fsdecode
44 44 # A bytes version of os.name.
45 oslinesep = os.linesep.encode('ascii')
45 46 osname = os.name.encode('ascii')
46 47 ospathsep = os.pathsep.encode('ascii')
47 48 ossep = os.sep.encode('ascii')
48 49 osaltsep = os.altsep
49 50 if osaltsep:
50 51 osaltsep = osaltsep.encode('ascii')
51 52 # os.getcwd() on Python 3 returns string, but it has os.getcwdb() which
52 53 # returns bytes.
53 54 getcwd = os.getcwdb
54 55 sysplatform = sys.platform.encode('ascii')
55 56 sysexecutable = sys.executable
56 57 if sysexecutable:
57 58 sysexecutable = os.fsencode(sysexecutable)
58 59 stringio = io.BytesIO
59 60 maplist = lambda *args: list(map(*args))
60 61
61 62 # TODO: .buffer might not exist if std streams were replaced; we'll need
62 63 # a silly wrapper to make a bytes stream backed by a unicode one.
63 64 stdin = sys.stdin.buffer
64 65 stdout = sys.stdout.buffer
65 66 stderr = sys.stderr.buffer
66 67
67 68 # Since Python 3 converts argv to wchar_t type by Py_DecodeLocale() on Unix,
68 69 # we can use os.fsencode() to get back bytes argv.
69 70 #
70 71 # https://hg.python.org/cpython/file/v3.5.1/Programs/python.c#l55
71 72 #
72 73 # TODO: On Windows, the native argv is wchar_t, so we'll need a different
73 74 # workaround to simulate the Python 2 (i.e. ANSI Win32 API) behavior.
74 75 if getattr(sys, 'argv', None) is not None:
75 76 sysargv = list(map(os.fsencode, sys.argv))
76 77
77 78 bytechr = struct.Struct('>B').pack
78 79
79 80 class bytestr(bytes):
80 81 """A bytes which mostly acts as a Python 2 str
81 82
82 83 >>> bytestr(), bytestr(bytearray(b'foo')), bytestr(u'ascii'), bytestr(1)
83 84 (b'', b'foo', b'ascii', b'1')
84 85 >>> s = bytestr(b'foo')
85 86 >>> assert s is bytestr(s)
86 87
87 88 There's no implicit conversion from non-ascii str as its encoding is
88 89 unknown:
89 90
90 91 >>> bytestr(chr(0x80)) # doctest: +ELLIPSIS
91 92 Traceback (most recent call last):
92 93 ...
93 94 UnicodeEncodeError: ...
94 95
95 96 Comparison between bytestr and bytes should work:
96 97
97 98 >>> assert bytestr(b'foo') == b'foo'
98 99 >>> assert b'foo' == bytestr(b'foo')
99 100 >>> assert b'f' in bytestr(b'foo')
100 101 >>> assert bytestr(b'f') in b'foo'
101 102
102 103 Sliced elements should be bytes, not integer:
103 104
104 105 >>> s[1], s[:2]
105 106 (b'o', b'fo')
106 107 >>> list(s), list(reversed(s))
107 108 ([b'f', b'o', b'o'], [b'o', b'o', b'f'])
108 109
109 110 As bytestr type isn't propagated across operations, you need to cast
110 111 bytes to bytestr explicitly:
111 112
112 113 >>> s = bytestr(b'foo').upper()
113 114 >>> t = bytestr(s)
114 115 >>> s[0], t[0]
115 116 (70, b'F')
116 117
117 118 Be careful to not pass a bytestr object to a function which expects
118 119 bytearray-like behavior.
119 120
120 121 >>> t = bytes(t) # cast to bytes
121 122 >>> assert type(t) is bytes
122 123 """
123 124
124 125 def __new__(cls, s=b''):
125 126 if isinstance(s, bytestr):
126 127 return s
127 128 if not isinstance(s, (bytes, bytearray)):
128 129 s = str(s).encode(u'ascii')
129 130 return bytes.__new__(cls, s)
130 131
131 132 def __getitem__(self, key):
132 133 s = bytes.__getitem__(self, key)
133 134 if not isinstance(s, bytes):
134 135 s = bytechr(s)
135 136 return s
136 137
137 138 def __iter__(self):
138 139 return iterbytestr(bytes.__iter__(self))
139 140
140 141 def iterbytestr(s):
141 142 """Iterate bytes as if it were a str object of Python 2"""
142 143 return map(bytechr, s)
143 144
144 145 def sysstr(s):
145 146 """Return a keyword str to be passed to Python functions such as
146 147 getattr() and str.encode()
147 148
148 149 This never raises UnicodeDecodeError. Non-ascii characters are
149 150 considered invalid and mapped to arbitrary but unique code points
150 151 such that 'sysstr(a) != sysstr(b)' for all 'a != b'.
151 152 """
152 153 if isinstance(s, builtins.str):
153 154 return s
154 155 return s.decode(u'latin-1')
155 156
156 157 def _wrapattrfunc(f):
157 158 @functools.wraps(f)
158 159 def w(object, name, *args):
159 160 return f(object, sysstr(name), *args)
160 161 return w
161 162
162 163 # these wrappers are automagically imported by hgloader
163 164 delattr = _wrapattrfunc(builtins.delattr)
164 165 getattr = _wrapattrfunc(builtins.getattr)
165 166 hasattr = _wrapattrfunc(builtins.hasattr)
166 167 setattr = _wrapattrfunc(builtins.setattr)
167 168 xrange = builtins.range
168 169
169 170 def open(name, mode='r', buffering=-1):
170 171 return builtins.open(name, sysstr(mode), buffering)
171 172
172 173 # getopt.getopt() on Python 3 deals with unicodes internally so we cannot
173 174 # pass bytes there. Passing unicodes will result in unicodes as return
174 175 # values which we need to convert again to bytes.
175 176 def getoptb(args, shortlist, namelist):
176 177 args = [a.decode('latin-1') for a in args]
177 178 shortlist = shortlist.decode('latin-1')
178 179 namelist = [a.decode('latin-1') for a in namelist]
179 180 opts, args = getopt.getopt(args, shortlist, namelist)
180 181 opts = [(a[0].encode('latin-1'), a[1].encode('latin-1'))
181 182 for a in opts]
182 183 args = [a.encode('latin-1') for a in args]
183 184 return opts, args
184 185
185 186 # keys of keyword arguments in Python need to be strings which are unicodes
186 187 # Python 3. This function takes keyword arguments, convert the keys to str.
187 188 def strkwargs(dic):
188 189 dic = dict((k.decode('latin-1'), v) for k, v in dic.iteritems())
189 190 return dic
190 191
191 192 # keys of keyword arguments need to be unicode while passing into
192 193 # a function. This function helps us to convert those keys back to bytes
193 194 # again as we need to deal with bytes.
194 195 def byteskwargs(dic):
195 196 dic = dict((k.encode('latin-1'), v) for k, v in dic.iteritems())
196 197 return dic
197 198
198 199 # shlex.split() accepts unicodes on Python 3. This function takes bytes
199 200 # argument, convert it into unicodes, pass into shlex.split(), convert the
200 201 # returned value to bytes and return that.
201 202 # TODO: handle shlex.shlex().
202 203 def shlexsplit(s):
203 204 ret = shlex.split(s.decode('latin-1'))
204 205 return [a.encode('latin-1') for a in ret]
205 206
206 207 else:
207 208 import cStringIO
208 209
209 210 bytechr = chr
210 211 bytestr = str
211 212 iterbytestr = iter
212 213 sysstr = identity
213 214
214 215 # Partial backport from os.py in Python 3, which only accepts bytes.
215 216 # In Python 2, our paths should only ever be bytes, a unicode path
216 217 # indicates a bug.
217 218 def fsencode(filename):
218 219 if isinstance(filename, str):
219 220 return filename
220 221 else:
221 222 raise TypeError(
222 223 "expect str, not %s" % type(filename).__name__)
223 224
224 225 # In Python 2, fsdecode() has a very chance to receive bytes. So it's
225 226 # better not to touch Python 2 part as it's already working fine.
226 227 fsdecode = identity
227 228
228 229 def getoptb(args, shortlist, namelist):
229 230 return getopt.getopt(args, shortlist, namelist)
230 231
231 232 strkwargs = identity
232 233 byteskwargs = identity
233 234
235 oslinesep = os.linesep
234 236 osname = os.name
235 237 ospathsep = os.pathsep
236 238 ossep = os.sep
237 239 osaltsep = os.altsep
238 240 stdin = sys.stdin
239 241 stdout = sys.stdout
240 242 stderr = sys.stderr
241 243 if getattr(sys, 'argv', None) is not None:
242 244 sysargv = sys.argv
243 245 sysplatform = sys.platform
244 246 getcwd = os.getcwd
245 247 sysexecutable = sys.executable
246 248 shlexsplit = shlex.split
247 249 stringio = cStringIO.StringIO
248 250 maplist = map
249 251
250 252 empty = _queue.Empty
251 253 queue = _queue.Queue
252 254
253 255 class _pycompatstub(object):
254 256 def __init__(self):
255 257 self._aliases = {}
256 258
257 259 def _registeraliases(self, origin, items):
258 260 """Add items that will be populated at the first access"""
259 261 items = map(sysstr, items)
260 262 self._aliases.update(
261 263 (item.replace(sysstr('_'), sysstr('')).lower(), (origin, item))
262 264 for item in items)
263 265
264 266 def _registeralias(self, origin, attr, name):
265 267 """Alias ``origin``.``attr`` as ``name``"""
266 268 self._aliases[sysstr(name)] = (origin, sysstr(attr))
267 269
268 270 def __getattr__(self, name):
269 271 try:
270 272 origin, item = self._aliases[name]
271 273 except KeyError:
272 274 raise AttributeError(name)
273 275 self.__dict__[name] = obj = getattr(origin, item)
274 276 return obj
275 277
276 278 httpserver = _pycompatstub()
277 279 urlreq = _pycompatstub()
278 280 urlerr = _pycompatstub()
279 281 if not ispy3:
280 282 import BaseHTTPServer
281 283 import CGIHTTPServer
282 284 import SimpleHTTPServer
283 285 import urllib2
284 286 import urllib
285 287 import urlparse
286 288 urlreq._registeraliases(urllib, (
287 289 "addclosehook",
288 290 "addinfourl",
289 291 "ftpwrapper",
290 292 "pathname2url",
291 293 "quote",
292 294 "splitattr",
293 295 "splitpasswd",
294 296 "splitport",
295 297 "splituser",
296 298 "unquote",
297 299 "url2pathname",
298 300 "urlencode",
299 301 ))
300 302 urlreq._registeraliases(urllib2, (
301 303 "AbstractHTTPHandler",
302 304 "BaseHandler",
303 305 "build_opener",
304 306 "FileHandler",
305 307 "FTPHandler",
306 308 "HTTPBasicAuthHandler",
307 309 "HTTPDigestAuthHandler",
308 310 "HTTPHandler",
309 311 "HTTPPasswordMgrWithDefaultRealm",
310 312 "HTTPSHandler",
311 313 "install_opener",
312 314 "ProxyHandler",
313 315 "Request",
314 316 "urlopen",
315 317 ))
316 318 urlreq._registeraliases(urlparse, (
317 319 "urlparse",
318 320 "urlunparse",
319 321 ))
320 322 urlerr._registeraliases(urllib2, (
321 323 "HTTPError",
322 324 "URLError",
323 325 ))
324 326 httpserver._registeraliases(BaseHTTPServer, (
325 327 "HTTPServer",
326 328 "BaseHTTPRequestHandler",
327 329 ))
328 330 httpserver._registeraliases(SimpleHTTPServer, (
329 331 "SimpleHTTPRequestHandler",
330 332 ))
331 333 httpserver._registeraliases(CGIHTTPServer, (
332 334 "CGIHTTPRequestHandler",
333 335 ))
334 336
335 337 else:
336 338 import urllib.parse
337 339 urlreq._registeraliases(urllib.parse, (
338 340 "splitattr",
339 341 "splitpasswd",
340 342 "splitport",
341 343 "splituser",
342 344 "urlparse",
343 345 "urlunparse",
344 346 ))
345 347 urlreq._registeralias(urllib.parse, "unquote_to_bytes", "unquote")
346 348 import urllib.request
347 349 urlreq._registeraliases(urllib.request, (
348 350 "AbstractHTTPHandler",
349 351 "BaseHandler",
350 352 "build_opener",
351 353 "FileHandler",
352 354 "FTPHandler",
353 355 "ftpwrapper",
354 356 "HTTPHandler",
355 357 "HTTPSHandler",
356 358 "install_opener",
357 359 "pathname2url",
358 360 "HTTPBasicAuthHandler",
359 361 "HTTPDigestAuthHandler",
360 362 "HTTPPasswordMgrWithDefaultRealm",
361 363 "ProxyHandler",
362 364 "Request",
363 365 "url2pathname",
364 366 "urlopen",
365 367 ))
366 368 import urllib.response
367 369 urlreq._registeraliases(urllib.response, (
368 370 "addclosehook",
369 371 "addinfourl",
370 372 ))
371 373 import urllib.error
372 374 urlerr._registeraliases(urllib.error, (
373 375 "HTTPError",
374 376 "URLError",
375 377 ))
376 378 import http.server
377 379 httpserver._registeraliases(http.server, (
378 380 "HTTPServer",
379 381 "BaseHTTPRequestHandler",
380 382 "SimpleHTTPRequestHandler",
381 383 "CGIHTTPRequestHandler",
382 384 ))
383 385
384 386 # urllib.parse.quote() accepts both str and bytes, decodes bytes
385 387 # (if necessary), and returns str. This is wonky. We provide a custom
386 388 # implementation that only accepts bytes and emits bytes.
387 389 def quote(s, safe=r'/'):
388 390 s = urllib.parse.quote_from_bytes(s, safe=safe)
389 391 return s.encode('ascii', 'strict')
390 392
391 393 urlreq.quote = quote
@@ -1,1654 +1,1654 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 atexit
11 11 import collections
12 12 import contextlib
13 13 import errno
14 14 import getpass
15 15 import inspect
16 16 import os
17 17 import re
18 18 import signal
19 19 import socket
20 20 import subprocess
21 21 import sys
22 22 import tempfile
23 23 import traceback
24 24
25 25 from .i18n import _
26 26 from .node import hex
27 27
28 28 from . import (
29 29 color,
30 30 config,
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 samplehgrcs = {
48 48 'user':
49 49 """# example user config (see 'hg help config' for more info)
50 50 [ui]
51 51 # name and email, e.g.
52 52 # username = Jane Doe <jdoe@example.com>
53 53 username =
54 54
55 55 # uncomment to colorize command output
56 56 # color = auto
57 57
58 58 [extensions]
59 59 # uncomment these lines to enable some popular extensions
60 60 # (see 'hg help extensions' for more info)
61 61 #
62 62 # pager =""",
63 63
64 64 'cloned':
65 65 """# example repository config (see 'hg help config' for more info)
66 66 [paths]
67 67 default = %s
68 68
69 69 # path aliases to other clones of this repo in URLs or filesystem paths
70 70 # (see 'hg help config.paths' for more info)
71 71 #
72 72 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
73 73 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
74 74 # my-clone = /home/jdoe/jdoes-clone
75 75
76 76 [ui]
77 77 # name and email (local to this repository, optional), e.g.
78 78 # username = Jane Doe <jdoe@example.com>
79 79 """,
80 80
81 81 'local':
82 82 """# example repository config (see 'hg help config' for more info)
83 83 [paths]
84 84 # path aliases to other clones of this repo in URLs or filesystem paths
85 85 # (see 'hg help config.paths' for more info)
86 86 #
87 87 # default = http://example.com/hg/example-repo
88 88 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
89 89 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
90 90 # my-clone = /home/jdoe/jdoes-clone
91 91
92 92 [ui]
93 93 # name and email (local to this repository, optional), e.g.
94 94 # username = Jane Doe <jdoe@example.com>
95 95 """,
96 96
97 97 'global':
98 98 """# example system-wide hg config (see 'hg help config' for more info)
99 99
100 100 [ui]
101 101 # uncomment to colorize command output
102 102 # color = auto
103 103
104 104 [extensions]
105 105 # uncomment these lines to enable some popular extensions
106 106 # (see 'hg help extensions' for more info)
107 107 #
108 108 # blackbox =
109 109 # pager =""",
110 110 }
111 111
112 112
113 113 class httppasswordmgrdbproxy(object):
114 114 """Delays loading urllib2 until it's needed."""
115 115 def __init__(self):
116 116 self._mgr = None
117 117
118 118 def _get_mgr(self):
119 119 if self._mgr is None:
120 120 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
121 121 return self._mgr
122 122
123 123 def add_password(self, *args, **kwargs):
124 124 return self._get_mgr().add_password(*args, **kwargs)
125 125
126 126 def find_user_password(self, *args, **kwargs):
127 127 return self._get_mgr().find_user_password(*args, **kwargs)
128 128
129 129 def _catchterm(*args):
130 130 raise error.SignalInterrupt
131 131
132 132 class ui(object):
133 133 def __init__(self, src=None):
134 134 """Create a fresh new ui object if no src given
135 135
136 136 Use uimod.ui.load() to create a ui which knows global and user configs.
137 137 In most cases, you should use ui.copy() to create a copy of an existing
138 138 ui object.
139 139 """
140 140 # _buffers: used for temporary capture of output
141 141 self._buffers = []
142 142 # 3-tuple describing how each buffer in the stack behaves.
143 143 # Values are (capture stderr, capture subprocesses, apply labels).
144 144 self._bufferstates = []
145 145 # When a buffer is active, defines whether we are expanding labels.
146 146 # This exists to prevent an extra list lookup.
147 147 self._bufferapplylabels = None
148 148 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
149 149 self._reportuntrusted = True
150 150 self._ocfg = config.config() # overlay
151 151 self._tcfg = config.config() # trusted
152 152 self._ucfg = config.config() # untrusted
153 153 self._trustusers = set()
154 154 self._trustgroups = set()
155 155 self.callhooks = True
156 156 # Insecure server connections requested.
157 157 self.insecureconnections = False
158 158 # Blocked time
159 159 self.logblockedtimes = False
160 160 # color mode: see mercurial/color.py for possible value
161 161 self._colormode = None
162 162 self._terminfoparams = {}
163 163 self._styles = {}
164 164
165 165 if src:
166 166 self.fout = src.fout
167 167 self.ferr = src.ferr
168 168 self.fin = src.fin
169 169 self.pageractive = src.pageractive
170 170 self._disablepager = src._disablepager
171 171
172 172 self._tcfg = src._tcfg.copy()
173 173 self._ucfg = src._ucfg.copy()
174 174 self._ocfg = src._ocfg.copy()
175 175 self._trustusers = src._trustusers.copy()
176 176 self._trustgroups = src._trustgroups.copy()
177 177 self.environ = src.environ
178 178 self.callhooks = src.callhooks
179 179 self.insecureconnections = src.insecureconnections
180 180 self._colormode = src._colormode
181 181 self._terminfoparams = src._terminfoparams.copy()
182 182 self._styles = src._styles.copy()
183 183
184 184 self.fixconfig()
185 185
186 186 self.httppasswordmgrdb = src.httppasswordmgrdb
187 187 self._blockedtimes = src._blockedtimes
188 188 else:
189 189 self.fout = util.stdout
190 190 self.ferr = util.stderr
191 191 self.fin = util.stdin
192 192 self.pageractive = False
193 193 self._disablepager = False
194 194
195 195 # shared read-only environment
196 196 self.environ = encoding.environ
197 197
198 198 self.httppasswordmgrdb = httppasswordmgrdbproxy()
199 199 self._blockedtimes = collections.defaultdict(int)
200 200
201 201 allowed = self.configlist('experimental', 'exportableenviron')
202 202 if '*' in allowed:
203 203 self._exportableenviron = self.environ
204 204 else:
205 205 self._exportableenviron = {}
206 206 for k in allowed:
207 207 if k in self.environ:
208 208 self._exportableenviron[k] = self.environ[k]
209 209
210 210 @classmethod
211 211 def load(cls):
212 212 """Create a ui and load global and user configs"""
213 213 u = cls()
214 214 # we always trust global config files and environment variables
215 215 for t, f in rcutil.rccomponents():
216 216 if t == 'path':
217 217 u.readconfig(f, trust=True)
218 218 elif t == 'items':
219 219 sections = set()
220 220 for section, name, value, source in f:
221 221 # do not set u._ocfg
222 222 # XXX clean this up once immutable config object is a thing
223 223 u._tcfg.set(section, name, value, source)
224 224 u._ucfg.set(section, name, value, source)
225 225 sections.add(section)
226 226 for section in sections:
227 227 u.fixconfig(section=section)
228 228 else:
229 229 raise error.ProgrammingError('unknown rctype: %s' % t)
230 230 return u
231 231
232 232 def copy(self):
233 233 return self.__class__(self)
234 234
235 235 def resetstate(self):
236 236 """Clear internal state that shouldn't persist across commands"""
237 237 if self._progbar:
238 238 self._progbar.resetstate() # reset last-print time of progress bar
239 239 self.httppasswordmgrdb = httppasswordmgrdbproxy()
240 240
241 241 @contextlib.contextmanager
242 242 def timeblockedsection(self, key):
243 243 # this is open-coded below - search for timeblockedsection to find them
244 244 starttime = util.timer()
245 245 try:
246 246 yield
247 247 finally:
248 248 self._blockedtimes[key + '_blocked'] += \
249 249 (util.timer() - starttime) * 1000
250 250
251 251 def formatter(self, topic, opts):
252 252 return formatter.formatter(self, topic, opts)
253 253
254 254 def _trusted(self, fp, f):
255 255 st = util.fstat(fp)
256 256 if util.isowner(st):
257 257 return True
258 258
259 259 tusers, tgroups = self._trustusers, self._trustgroups
260 260 if '*' in tusers or '*' in tgroups:
261 261 return True
262 262
263 263 user = util.username(st.st_uid)
264 264 group = util.groupname(st.st_gid)
265 265 if user in tusers or group in tgroups or user == util.username():
266 266 return True
267 267
268 268 if self._reportuntrusted:
269 269 self.warn(_('not trusting file %s from untrusted '
270 270 'user %s, group %s\n') % (f, user, group))
271 271 return False
272 272
273 273 def readconfig(self, filename, root=None, trust=False,
274 274 sections=None, remap=None):
275 275 try:
276 276 fp = open(filename, u'rb')
277 277 except IOError:
278 278 if not sections: # ignore unless we were looking for something
279 279 return
280 280 raise
281 281
282 282 cfg = config.config()
283 283 trusted = sections or trust or self._trusted(fp, filename)
284 284
285 285 try:
286 286 cfg.read(filename, fp, sections=sections, remap=remap)
287 287 fp.close()
288 288 except error.ConfigError as inst:
289 289 if trusted:
290 290 raise
291 291 self.warn(_("ignored: %s\n") % str(inst))
292 292
293 293 if self.plain():
294 294 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
295 295 'logtemplate', 'statuscopies', 'style',
296 296 'traceback', 'verbose'):
297 297 if k in cfg['ui']:
298 298 del cfg['ui'][k]
299 299 for k, v in cfg.items('defaults'):
300 300 del cfg['defaults'][k]
301 301 for k, v in cfg.items('commands'):
302 302 del cfg['commands'][k]
303 303 # Don't remove aliases from the configuration if in the exceptionlist
304 304 if self.plain('alias'):
305 305 for k, v in cfg.items('alias'):
306 306 del cfg['alias'][k]
307 307 if self.plain('revsetalias'):
308 308 for k, v in cfg.items('revsetalias'):
309 309 del cfg['revsetalias'][k]
310 310 if self.plain('templatealias'):
311 311 for k, v in cfg.items('templatealias'):
312 312 del cfg['templatealias'][k]
313 313
314 314 if trusted:
315 315 self._tcfg.update(cfg)
316 316 self._tcfg.update(self._ocfg)
317 317 self._ucfg.update(cfg)
318 318 self._ucfg.update(self._ocfg)
319 319
320 320 if root is None:
321 321 root = os.path.expanduser('~')
322 322 self.fixconfig(root=root)
323 323
324 324 def fixconfig(self, root=None, section=None):
325 325 if section in (None, 'paths'):
326 326 # expand vars and ~
327 327 # translate paths relative to root (or home) into absolute paths
328 328 root = root or pycompat.getcwd()
329 329 for c in self._tcfg, self._ucfg, self._ocfg:
330 330 for n, p in c.items('paths'):
331 331 # Ignore sub-options.
332 332 if ':' in n:
333 333 continue
334 334 if not p:
335 335 continue
336 336 if '%%' in p:
337 337 s = self.configsource('paths', n) or 'none'
338 338 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
339 339 % (n, p, s))
340 340 p = p.replace('%%', '%')
341 341 p = util.expandpath(p)
342 342 if not util.hasscheme(p) and not os.path.isabs(p):
343 343 p = os.path.normpath(os.path.join(root, p))
344 344 c.set("paths", n, p)
345 345
346 346 if section in (None, 'ui'):
347 347 # update ui options
348 348 self.debugflag = self.configbool('ui', 'debug')
349 349 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
350 350 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
351 351 if self.verbose and self.quiet:
352 352 self.quiet = self.verbose = False
353 353 self._reportuntrusted = self.debugflag or self.configbool("ui",
354 354 "report_untrusted", True)
355 355 self.tracebackflag = self.configbool('ui', 'traceback', False)
356 356 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
357 357
358 358 if section in (None, 'trusted'):
359 359 # update trust information
360 360 self._trustusers.update(self.configlist('trusted', 'users'))
361 361 self._trustgroups.update(self.configlist('trusted', 'groups'))
362 362
363 363 def backupconfig(self, section, item):
364 364 return (self._ocfg.backup(section, item),
365 365 self._tcfg.backup(section, item),
366 366 self._ucfg.backup(section, item),)
367 367 def restoreconfig(self, data):
368 368 self._ocfg.restore(data[0])
369 369 self._tcfg.restore(data[1])
370 370 self._ucfg.restore(data[2])
371 371
372 372 def setconfig(self, section, name, value, source=''):
373 373 for cfg in (self._ocfg, self._tcfg, self._ucfg):
374 374 cfg.set(section, name, value, source)
375 375 self.fixconfig(section=section)
376 376
377 377 def _data(self, untrusted):
378 378 return untrusted and self._ucfg or self._tcfg
379 379
380 380 def configsource(self, section, name, untrusted=False):
381 381 return self._data(untrusted).source(section, name)
382 382
383 383 def config(self, section, name, default=None, untrusted=False):
384 384 if isinstance(name, list):
385 385 alternates = name
386 386 else:
387 387 alternates = [name]
388 388
389 389 for n in alternates:
390 390 value = self._data(untrusted).get(section, n, None)
391 391 if value is not None:
392 392 name = n
393 393 break
394 394 else:
395 395 value = default
396 396
397 397 if self.debugflag and not untrusted and self._reportuntrusted:
398 398 for n in alternates:
399 399 uvalue = self._ucfg.get(section, n)
400 400 if uvalue is not None and uvalue != value:
401 401 self.debug("ignoring untrusted configuration option "
402 402 "%s.%s = %s\n" % (section, n, uvalue))
403 403 return value
404 404
405 405 def configsuboptions(self, section, name, default=None, untrusted=False):
406 406 """Get a config option and all sub-options.
407 407
408 408 Some config options have sub-options that are declared with the
409 409 format "key:opt = value". This method is used to return the main
410 410 option and all its declared sub-options.
411 411
412 412 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
413 413 is a dict of defined sub-options where keys and values are strings.
414 414 """
415 415 data = self._data(untrusted)
416 416 main = data.get(section, name, default)
417 417 if self.debugflag and not untrusted and self._reportuntrusted:
418 418 uvalue = self._ucfg.get(section, name)
419 419 if uvalue is not None and uvalue != main:
420 420 self.debug('ignoring untrusted configuration option '
421 421 '%s.%s = %s\n' % (section, name, uvalue))
422 422
423 423 sub = {}
424 424 prefix = '%s:' % name
425 425 for k, v in data.items(section):
426 426 if k.startswith(prefix):
427 427 sub[k[len(prefix):]] = v
428 428
429 429 if self.debugflag and not untrusted and self._reportuntrusted:
430 430 for k, v in sub.items():
431 431 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
432 432 if uvalue is not None and uvalue != v:
433 433 self.debug('ignoring untrusted configuration option '
434 434 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
435 435
436 436 return main, sub
437 437
438 438 def configpath(self, section, name, default=None, untrusted=False):
439 439 'get a path config item, expanded relative to repo root or config file'
440 440 v = self.config(section, name, default, untrusted)
441 441 if v is None:
442 442 return None
443 443 if not os.path.isabs(v) or "://" not in v:
444 444 src = self.configsource(section, name, untrusted)
445 445 if ':' in src:
446 446 base = os.path.dirname(src.rsplit(':')[0])
447 447 v = os.path.join(base, os.path.expanduser(v))
448 448 return v
449 449
450 450 def configbool(self, section, name, default=False, untrusted=False):
451 451 """parse a configuration element as a boolean
452 452
453 453 >>> u = ui(); s = 'foo'
454 454 >>> u.setconfig(s, 'true', 'yes')
455 455 >>> u.configbool(s, 'true')
456 456 True
457 457 >>> u.setconfig(s, 'false', 'no')
458 458 >>> u.configbool(s, 'false')
459 459 False
460 460 >>> u.configbool(s, 'unknown')
461 461 False
462 462 >>> u.configbool(s, 'unknown', True)
463 463 True
464 464 >>> u.setconfig(s, 'invalid', 'somevalue')
465 465 >>> u.configbool(s, 'invalid')
466 466 Traceback (most recent call last):
467 467 ...
468 468 ConfigError: foo.invalid is not a boolean ('somevalue')
469 469 """
470 470
471 471 v = self.config(section, name, None, untrusted)
472 472 if v is None:
473 473 return default
474 474 if isinstance(v, bool):
475 475 return v
476 476 b = util.parsebool(v)
477 477 if b is None:
478 478 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
479 479 % (section, name, v))
480 480 return b
481 481
482 482 def configwith(self, convert, section, name, default=None,
483 483 desc=None, untrusted=False):
484 484 """parse a configuration element with a conversion function
485 485
486 486 >>> u = ui(); s = 'foo'
487 487 >>> u.setconfig(s, 'float1', '42')
488 488 >>> u.configwith(float, s, 'float1')
489 489 42.0
490 490 >>> u.setconfig(s, 'float2', '-4.25')
491 491 >>> u.configwith(float, s, 'float2')
492 492 -4.25
493 493 >>> u.configwith(float, s, 'unknown', 7)
494 494 7
495 495 >>> u.setconfig(s, 'invalid', 'somevalue')
496 496 >>> u.configwith(float, s, 'invalid')
497 497 Traceback (most recent call last):
498 498 ...
499 499 ConfigError: foo.invalid is not a valid float ('somevalue')
500 500 >>> u.configwith(float, s, 'invalid', desc='womble')
501 501 Traceback (most recent call last):
502 502 ...
503 503 ConfigError: foo.invalid is not a valid womble ('somevalue')
504 504 """
505 505
506 506 v = self.config(section, name, None, untrusted)
507 507 if v is None:
508 508 return default
509 509 try:
510 510 return convert(v)
511 511 except ValueError:
512 512 if desc is None:
513 513 desc = convert.__name__
514 514 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
515 515 % (section, name, desc, v))
516 516
517 517 def configint(self, section, name, default=None, untrusted=False):
518 518 """parse a configuration element as an integer
519 519
520 520 >>> u = ui(); s = 'foo'
521 521 >>> u.setconfig(s, 'int1', '42')
522 522 >>> u.configint(s, 'int1')
523 523 42
524 524 >>> u.setconfig(s, 'int2', '-42')
525 525 >>> u.configint(s, 'int2')
526 526 -42
527 527 >>> u.configint(s, 'unknown', 7)
528 528 7
529 529 >>> u.setconfig(s, 'invalid', 'somevalue')
530 530 >>> u.configint(s, 'invalid')
531 531 Traceback (most recent call last):
532 532 ...
533 533 ConfigError: foo.invalid is not a valid integer ('somevalue')
534 534 """
535 535
536 536 return self.configwith(int, section, name, default, 'integer',
537 537 untrusted)
538 538
539 539 def configbytes(self, section, name, default=0, untrusted=False):
540 540 """parse a configuration element as a quantity in bytes
541 541
542 542 Units can be specified as b (bytes), k or kb (kilobytes), m or
543 543 mb (megabytes), g or gb (gigabytes).
544 544
545 545 >>> u = ui(); s = 'foo'
546 546 >>> u.setconfig(s, 'val1', '42')
547 547 >>> u.configbytes(s, 'val1')
548 548 42
549 549 >>> u.setconfig(s, 'val2', '42.5 kb')
550 550 >>> u.configbytes(s, 'val2')
551 551 43520
552 552 >>> u.configbytes(s, 'unknown', '7 MB')
553 553 7340032
554 554 >>> u.setconfig(s, 'invalid', 'somevalue')
555 555 >>> u.configbytes(s, 'invalid')
556 556 Traceback (most recent call last):
557 557 ...
558 558 ConfigError: foo.invalid is not a byte quantity ('somevalue')
559 559 """
560 560
561 561 value = self.config(section, name, None, untrusted)
562 562 if value is None:
563 563 if not isinstance(default, str):
564 564 return default
565 565 value = default
566 566 try:
567 567 return util.sizetoint(value)
568 568 except error.ParseError:
569 569 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
570 570 % (section, name, value))
571 571
572 572 def configlist(self, section, name, default=None, untrusted=False):
573 573 """parse a configuration element as a list of comma/space separated
574 574 strings
575 575
576 576 >>> u = ui(); s = 'foo'
577 577 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
578 578 >>> u.configlist(s, 'list1')
579 579 ['this', 'is', 'a small', 'test']
580 580 """
581 581 # default is not always a list
582 582 if isinstance(default, bytes):
583 583 default = config.parselist(default)
584 584 return self.configwith(config.parselist, section, name, default or [],
585 585 'list', untrusted)
586 586
587 587 def hasconfig(self, section, name, untrusted=False):
588 588 return self._data(untrusted).hasitem(section, name)
589 589
590 590 def has_section(self, section, untrusted=False):
591 591 '''tell whether section exists in config.'''
592 592 return section in self._data(untrusted)
593 593
594 594 def configitems(self, section, untrusted=False, ignoresub=False):
595 595 items = self._data(untrusted).items(section)
596 596 if ignoresub:
597 597 newitems = {}
598 598 for k, v in items:
599 599 if ':' not in k:
600 600 newitems[k] = v
601 601 items = newitems.items()
602 602 if self.debugflag and not untrusted and self._reportuntrusted:
603 603 for k, v in self._ucfg.items(section):
604 604 if self._tcfg.get(section, k) != v:
605 605 self.debug("ignoring untrusted configuration option "
606 606 "%s.%s = %s\n" % (section, k, v))
607 607 return items
608 608
609 609 def walkconfig(self, untrusted=False):
610 610 cfg = self._data(untrusted)
611 611 for section in cfg.sections():
612 612 for name, value in self.configitems(section, untrusted):
613 613 yield section, name, value
614 614
615 615 def plain(self, feature=None):
616 616 '''is plain mode active?
617 617
618 618 Plain mode means that all configuration variables which affect
619 619 the behavior and output of Mercurial should be
620 620 ignored. Additionally, the output should be stable,
621 621 reproducible and suitable for use in scripts or applications.
622 622
623 623 The only way to trigger plain mode is by setting either the
624 624 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
625 625
626 626 The return value can either be
627 627 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
628 628 - True otherwise
629 629 '''
630 630 if ('HGPLAIN' not in encoding.environ and
631 631 'HGPLAINEXCEPT' not in encoding.environ):
632 632 return False
633 633 exceptions = encoding.environ.get('HGPLAINEXCEPT',
634 634 '').strip().split(',')
635 635 if feature and exceptions:
636 636 return feature not in exceptions
637 637 return True
638 638
639 639 def username(self):
640 640 """Return default username to be used in commits.
641 641
642 642 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
643 643 and stop searching if one of these is set.
644 644 If not found and ui.askusername is True, ask the user, else use
645 645 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
646 646 """
647 647 user = encoding.environ.get("HGUSER")
648 648 if user is None:
649 649 user = self.config("ui", ["username", "user"])
650 650 if user is not None:
651 651 user = os.path.expandvars(user)
652 652 if user is None:
653 653 user = encoding.environ.get("EMAIL")
654 654 if user is None and self.configbool("ui", "askusername"):
655 655 user = self.prompt(_("enter a commit username:"), default=None)
656 656 if user is None and not self.interactive():
657 657 try:
658 658 user = '%s@%s' % (util.getuser(), socket.getfqdn())
659 659 self.warn(_("no username found, using '%s' instead\n") % user)
660 660 except KeyError:
661 661 pass
662 662 if not user:
663 663 raise error.Abort(_('no username supplied'),
664 664 hint=_("use 'hg config --edit' "
665 665 'to set your username'))
666 666 if "\n" in user:
667 667 raise error.Abort(_("username %s contains a newline\n")
668 668 % repr(user))
669 669 return user
670 670
671 671 def shortuser(self, user):
672 672 """Return a short representation of a user name or email address."""
673 673 if not self.verbose:
674 674 user = util.shortuser(user)
675 675 return user
676 676
677 677 def expandpath(self, loc, default=None):
678 678 """Return repository location relative to cwd or from [paths]"""
679 679 try:
680 680 p = self.paths.getpath(loc)
681 681 if p:
682 682 return p.rawloc
683 683 except error.RepoError:
684 684 pass
685 685
686 686 if default:
687 687 try:
688 688 p = self.paths.getpath(default)
689 689 if p:
690 690 return p.rawloc
691 691 except error.RepoError:
692 692 pass
693 693
694 694 return loc
695 695
696 696 @util.propertycache
697 697 def paths(self):
698 698 return paths(self)
699 699
700 700 def pushbuffer(self, error=False, subproc=False, labeled=False):
701 701 """install a buffer to capture standard output of the ui object
702 702
703 703 If error is True, the error output will be captured too.
704 704
705 705 If subproc is True, output from subprocesses (typically hooks) will be
706 706 captured too.
707 707
708 708 If labeled is True, any labels associated with buffered
709 709 output will be handled. By default, this has no effect
710 710 on the output returned, but extensions and GUI tools may
711 711 handle this argument and returned styled output. If output
712 712 is being buffered so it can be captured and parsed or
713 713 processed, labeled should not be set to True.
714 714 """
715 715 self._buffers.append([])
716 716 self._bufferstates.append((error, subproc, labeled))
717 717 self._bufferapplylabels = labeled
718 718
719 719 def popbuffer(self):
720 720 '''pop the last buffer and return the buffered output'''
721 721 self._bufferstates.pop()
722 722 if self._bufferstates:
723 723 self._bufferapplylabels = self._bufferstates[-1][2]
724 724 else:
725 725 self._bufferapplylabels = None
726 726
727 727 return "".join(self._buffers.pop())
728 728
729 729 def write(self, *args, **opts):
730 730 '''write args to output
731 731
732 732 By default, this method simply writes to the buffer or stdout.
733 733 Color mode can be set on the UI class to have the output decorated
734 734 with color modifier before being written to stdout.
735 735
736 736 The color used is controlled by an optional keyword argument, "label".
737 737 This should be a string containing label names separated by space.
738 738 Label names take the form of "topic.type". For example, ui.debug()
739 739 issues a label of "ui.debug".
740 740
741 741 When labeling output for a specific command, a label of
742 742 "cmdname.type" is recommended. For example, status issues
743 743 a label of "status.modified" for modified files.
744 744 '''
745 745 if self._buffers and not opts.get('prompt', False):
746 746 if self._bufferapplylabels:
747 747 label = opts.get('label', '')
748 748 self._buffers[-1].extend(self.label(a, label) for a in args)
749 749 else:
750 750 self._buffers[-1].extend(args)
751 751 elif self._colormode == 'win32':
752 752 # windows color printing is its own can of crab, defer to
753 753 # the color module and that is it.
754 754 color.win32print(self, self._write, *args, **opts)
755 755 else:
756 756 msgs = args
757 757 if self._colormode is not None:
758 758 label = opts.get('label', '')
759 759 msgs = [self.label(a, label) for a in args]
760 760 self._write(*msgs, **opts)
761 761
762 762 def _write(self, *msgs, **opts):
763 763 self._progclear()
764 764 # opencode timeblockedsection because this is a critical path
765 765 starttime = util.timer()
766 766 try:
767 767 for a in msgs:
768 768 self.fout.write(a)
769 769 finally:
770 770 self._blockedtimes['stdio_blocked'] += \
771 771 (util.timer() - starttime) * 1000
772 772
773 773 def write_err(self, *args, **opts):
774 774 self._progclear()
775 775 if self._bufferstates and self._bufferstates[-1][0]:
776 776 self.write(*args, **opts)
777 777 elif self._colormode == 'win32':
778 778 # windows color printing is its own can of crab, defer to
779 779 # the color module and that is it.
780 780 color.win32print(self, self._write_err, *args, **opts)
781 781 else:
782 782 msgs = args
783 783 if self._colormode is not None:
784 784 label = opts.get('label', '')
785 785 msgs = [self.label(a, label) for a in args]
786 786 self._write_err(*msgs, **opts)
787 787
788 788 def _write_err(self, *msgs, **opts):
789 789 try:
790 790 with self.timeblockedsection('stdio'):
791 791 if not getattr(self.fout, 'closed', False):
792 792 self.fout.flush()
793 793 for a in msgs:
794 794 self.ferr.write(a)
795 795 # stderr may be buffered under win32 when redirected to files,
796 796 # including stdout.
797 797 if not getattr(self.ferr, 'closed', False):
798 798 self.ferr.flush()
799 799 except IOError as inst:
800 800 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
801 801 raise
802 802
803 803 def flush(self):
804 804 # opencode timeblockedsection because this is a critical path
805 805 starttime = util.timer()
806 806 try:
807 807 try: self.fout.flush()
808 808 except (IOError, ValueError): pass
809 809 try: self.ferr.flush()
810 810 except (IOError, ValueError): pass
811 811 finally:
812 812 self._blockedtimes['stdio_blocked'] += \
813 813 (util.timer() - starttime) * 1000
814 814
815 815 def _isatty(self, fh):
816 816 if self.configbool('ui', 'nontty', False):
817 817 return False
818 818 return util.isatty(fh)
819 819
820 820 def disablepager(self):
821 821 self._disablepager = True
822 822
823 823 def pager(self, command):
824 824 """Start a pager for subsequent command output.
825 825
826 826 Commands which produce a long stream of output should call
827 827 this function to activate the user's preferred pagination
828 828 mechanism (which may be no pager). Calling this function
829 829 precludes any future use of interactive functionality, such as
830 830 prompting the user or activating curses.
831 831
832 832 Args:
833 833 command: The full, non-aliased name of the command. That is, "log"
834 834 not "history, "summary" not "summ", etc.
835 835 """
836 836 if (self._disablepager
837 837 or self.pageractive
838 838 or command in self.configlist('pager', 'ignore')
839 839 or not self.configbool('pager', 'enable', True)
840 840 or not self.configbool('pager', 'attend-' + command, True)
841 841 # TODO: if we want to allow HGPLAINEXCEPT=pager,
842 842 # formatted() will need some adjustment.
843 843 or not self.formatted()
844 844 or self.plain()
845 845 # TODO: expose debugger-enabled on the UI object
846 846 or '--debugger' in pycompat.sysargv):
847 847 # We only want to paginate if the ui appears to be
848 848 # interactive, the user didn't say HGPLAIN or
849 849 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
850 850 return
851 851
852 852 fallbackpager = 'more'
853 853 pagercmd = self.config('pager', 'pager', fallbackpager)
854 854 if not pagercmd:
855 855 return
856 856
857 857 self.debug('starting pager for command %r\n' % command)
858 858 self.flush()
859 859
860 860 wasformatted = self.formatted()
861 861 if util.safehasattr(signal, "SIGPIPE"):
862 862 signal.signal(signal.SIGPIPE, _catchterm)
863 863 if self._runpager(pagercmd):
864 864 self.pageractive = True
865 865 # Preserve the formatted-ness of the UI. This is important
866 866 # because we mess with stdout, which might confuse
867 867 # auto-detection of things being formatted.
868 868 self.setconfig('ui', 'formatted', wasformatted, 'pager')
869 869 self.setconfig('ui', 'interactive', False, 'pager')
870 870
871 871 # If pagermode differs from color.mode, reconfigure color now that
872 872 # pageractive is set.
873 873 cm = self._colormode
874 874 if cm != self.config('color', 'pagermode', cm):
875 875 color.setup(self)
876 876 else:
877 877 # If the pager can't be spawned in dispatch when --pager=on is
878 878 # given, don't try again when the command runs, to avoid a duplicate
879 879 # warning about a missing pager command.
880 880 self.disablepager()
881 881
882 882 def _runpager(self, command):
883 883 """Actually start the pager and set up file descriptors.
884 884
885 885 This is separate in part so that extensions (like chg) can
886 886 override how a pager is invoked.
887 887 """
888 888 if command == 'cat':
889 889 # Save ourselves some work.
890 890 return False
891 891 # If the command doesn't contain any of these characters, we
892 892 # assume it's a binary and exec it directly. This means for
893 893 # simple pager command configurations, we can degrade
894 894 # gracefully and tell the user about their broken pager.
895 895 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
896 896
897 897 if pycompat.osname == 'nt' and not shell:
898 898 # Window's built-in `more` cannot be invoked with shell=False, but
899 899 # its `more.com` can. Hide this implementation detail from the
900 900 # user so we can also get sane bad PAGER behavior. MSYS has
901 901 # `more.exe`, so do a cmd.exe style resolution of the executable to
902 902 # determine which one to use.
903 903 fullcmd = util.findexe(command)
904 904 if not fullcmd:
905 905 self.warn(_("missing pager command '%s', skipping pager\n")
906 906 % command)
907 907 return False
908 908
909 909 command = fullcmd
910 910
911 911 try:
912 912 pager = subprocess.Popen(
913 913 command, shell=shell, bufsize=-1,
914 914 close_fds=util.closefds, stdin=subprocess.PIPE,
915 915 stdout=util.stdout, stderr=util.stderr)
916 916 except OSError as e:
917 917 if e.errno == errno.ENOENT and not shell:
918 918 self.warn(_("missing pager command '%s', skipping pager\n")
919 919 % command)
920 920 return False
921 921 raise
922 922
923 923 # back up original file descriptors
924 924 stdoutfd = os.dup(util.stdout.fileno())
925 925 stderrfd = os.dup(util.stderr.fileno())
926 926
927 927 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
928 928 if self._isatty(util.stderr):
929 929 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
930 930
931 931 @atexit.register
932 932 def killpager():
933 933 if util.safehasattr(signal, "SIGINT"):
934 934 signal.signal(signal.SIGINT, signal.SIG_IGN)
935 935 # restore original fds, closing pager.stdin copies in the process
936 936 os.dup2(stdoutfd, util.stdout.fileno())
937 937 os.dup2(stderrfd, util.stderr.fileno())
938 938 pager.stdin.close()
939 939 pager.wait()
940 940
941 941 return True
942 942
943 943 def interface(self, feature):
944 944 """what interface to use for interactive console features?
945 945
946 946 The interface is controlled by the value of `ui.interface` but also by
947 947 the value of feature-specific configuration. For example:
948 948
949 949 ui.interface.histedit = text
950 950 ui.interface.chunkselector = curses
951 951
952 952 Here the features are "histedit" and "chunkselector".
953 953
954 954 The configuration above means that the default interfaces for commands
955 955 is curses, the interface for histedit is text and the interface for
956 956 selecting chunk is crecord (the best curses interface available).
957 957
958 958 Consider the following example:
959 959 ui.interface = curses
960 960 ui.interface.histedit = text
961 961
962 962 Then histedit will use the text interface and chunkselector will use
963 963 the default curses interface (crecord at the moment).
964 964 """
965 965 alldefaults = frozenset(["text", "curses"])
966 966
967 967 featureinterfaces = {
968 968 "chunkselector": [
969 969 "text",
970 970 "curses",
971 971 ]
972 972 }
973 973
974 974 # Feature-specific interface
975 975 if feature not in featureinterfaces.keys():
976 976 # Programming error, not user error
977 977 raise ValueError("Unknown feature requested %s" % feature)
978 978
979 979 availableinterfaces = frozenset(featureinterfaces[feature])
980 980 if alldefaults > availableinterfaces:
981 981 # Programming error, not user error. We need a use case to
982 982 # define the right thing to do here.
983 983 raise ValueError(
984 984 "Feature %s does not handle all default interfaces" %
985 985 feature)
986 986
987 987 if self.plain():
988 988 return "text"
989 989
990 990 # Default interface for all the features
991 991 defaultinterface = "text"
992 992 i = self.config("ui", "interface", None)
993 993 if i in alldefaults:
994 994 defaultinterface = i
995 995
996 996 choseninterface = defaultinterface
997 997 f = self.config("ui", "interface.%s" % feature, None)
998 998 if f in availableinterfaces:
999 999 choseninterface = f
1000 1000
1001 1001 if i is not None and defaultinterface != i:
1002 1002 if f is not None:
1003 1003 self.warn(_("invalid value for ui.interface: %s\n") %
1004 1004 (i,))
1005 1005 else:
1006 1006 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1007 1007 (i, choseninterface))
1008 1008 if f is not None and choseninterface != f:
1009 1009 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1010 1010 (feature, f, choseninterface))
1011 1011
1012 1012 return choseninterface
1013 1013
1014 1014 def interactive(self):
1015 1015 '''is interactive input allowed?
1016 1016
1017 1017 An interactive session is a session where input can be reasonably read
1018 1018 from `sys.stdin'. If this function returns false, any attempt to read
1019 1019 from stdin should fail with an error, unless a sensible default has been
1020 1020 specified.
1021 1021
1022 1022 Interactiveness is triggered by the value of the `ui.interactive'
1023 1023 configuration variable or - if it is unset - when `sys.stdin' points
1024 1024 to a terminal device.
1025 1025
1026 1026 This function refers to input only; for output, see `ui.formatted()'.
1027 1027 '''
1028 1028 i = self.configbool("ui", "interactive", None)
1029 1029 if i is None:
1030 1030 # some environments replace stdin without implementing isatty
1031 1031 # usually those are non-interactive
1032 1032 return self._isatty(self.fin)
1033 1033
1034 1034 return i
1035 1035
1036 1036 def termwidth(self):
1037 1037 '''how wide is the terminal in columns?
1038 1038 '''
1039 1039 if 'COLUMNS' in encoding.environ:
1040 1040 try:
1041 1041 return int(encoding.environ['COLUMNS'])
1042 1042 except ValueError:
1043 1043 pass
1044 1044 return scmutil.termsize(self)[0]
1045 1045
1046 1046 def formatted(self):
1047 1047 '''should formatted output be used?
1048 1048
1049 1049 It is often desirable to format the output to suite the output medium.
1050 1050 Examples of this are truncating long lines or colorizing messages.
1051 1051 However, this is not often not desirable when piping output into other
1052 1052 utilities, e.g. `grep'.
1053 1053
1054 1054 Formatted output is triggered by the value of the `ui.formatted'
1055 1055 configuration variable or - if it is unset - when `sys.stdout' points
1056 1056 to a terminal device. Please note that `ui.formatted' should be
1057 1057 considered an implementation detail; it is not intended for use outside
1058 1058 Mercurial or its extensions.
1059 1059
1060 1060 This function refers to output only; for input, see `ui.interactive()'.
1061 1061 This function always returns false when in plain mode, see `ui.plain()'.
1062 1062 '''
1063 1063 if self.plain():
1064 1064 return False
1065 1065
1066 1066 i = self.configbool("ui", "formatted", None)
1067 1067 if i is None:
1068 1068 # some environments replace stdout without implementing isatty
1069 1069 # usually those are non-interactive
1070 1070 return self._isatty(self.fout)
1071 1071
1072 1072 return i
1073 1073
1074 1074 def _readline(self, prompt=''):
1075 1075 if self._isatty(self.fin):
1076 1076 try:
1077 1077 # magically add command line editing support, where
1078 1078 # available
1079 1079 import readline
1080 1080 # force demandimport to really load the module
1081 1081 readline.read_history_file
1082 1082 # windows sometimes raises something other than ImportError
1083 1083 except Exception:
1084 1084 pass
1085 1085
1086 1086 # call write() so output goes through subclassed implementation
1087 1087 # e.g. color extension on Windows
1088 1088 self.write(prompt, prompt=True)
1089 1089
1090 1090 # instead of trying to emulate raw_input, swap (self.fin,
1091 1091 # self.fout) with (sys.stdin, sys.stdout)
1092 1092 oldin = sys.stdin
1093 1093 oldout = sys.stdout
1094 1094 sys.stdin = self.fin
1095 1095 sys.stdout = self.fout
1096 1096 # prompt ' ' must exist; otherwise readline may delete entire line
1097 1097 # - http://bugs.python.org/issue12833
1098 1098 with self.timeblockedsection('stdio'):
1099 1099 line = raw_input(' ')
1100 1100 sys.stdin = oldin
1101 1101 sys.stdout = oldout
1102 1102
1103 1103 # When stdin is in binary mode on Windows, it can cause
1104 1104 # raw_input() to emit an extra trailing carriage return
1105 if os.linesep == '\r\n' and line and line[-1] == '\r':
1105 if pycompat.oslinesep == '\r\n' and line and line[-1] == '\r':
1106 1106 line = line[:-1]
1107 1107 return line
1108 1108
1109 1109 def prompt(self, msg, default="y"):
1110 1110 """Prompt user with msg, read response.
1111 1111 If ui is not interactive, the default is returned.
1112 1112 """
1113 1113 if not self.interactive():
1114 1114 self.write(msg, ' ', default or '', "\n")
1115 1115 return default
1116 1116 try:
1117 1117 r = self._readline(self.label(msg, 'ui.prompt'))
1118 1118 if not r:
1119 1119 r = default
1120 1120 if self.configbool('ui', 'promptecho'):
1121 1121 self.write(r, "\n")
1122 1122 return r
1123 1123 except EOFError:
1124 1124 raise error.ResponseExpected()
1125 1125
1126 1126 @staticmethod
1127 1127 def extractchoices(prompt):
1128 1128 """Extract prompt message and list of choices from specified prompt.
1129 1129
1130 1130 This returns tuple "(message, choices)", and "choices" is the
1131 1131 list of tuple "(response character, text without &)".
1132 1132
1133 1133 >>> ui.extractchoices("awake? $$ &Yes $$ &No")
1134 1134 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1135 1135 >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
1136 1136 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1137 1137 >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
1138 1138 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1139 1139 """
1140 1140
1141 1141 # Sadly, the prompt string may have been built with a filename
1142 1142 # containing "$$" so let's try to find the first valid-looking
1143 1143 # prompt to start parsing. Sadly, we also can't rely on
1144 1144 # choices containing spaces, ASCII, or basically anything
1145 1145 # except an ampersand followed by a character.
1146 1146 m = re.match(r'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1147 1147 msg = m.group(1)
1148 1148 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1149 1149 return (msg,
1150 1150 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
1151 1151 for s in choices])
1152 1152
1153 1153 def promptchoice(self, prompt, default=0):
1154 1154 """Prompt user with a message, read response, and ensure it matches
1155 1155 one of the provided choices. The prompt is formatted as follows:
1156 1156
1157 1157 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1158 1158
1159 1159 The index of the choice is returned. Responses are case
1160 1160 insensitive. If ui is not interactive, the default is
1161 1161 returned.
1162 1162 """
1163 1163
1164 1164 msg, choices = self.extractchoices(prompt)
1165 1165 resps = [r for r, t in choices]
1166 1166 while True:
1167 1167 r = self.prompt(msg, resps[default])
1168 1168 if r.lower() in resps:
1169 1169 return resps.index(r.lower())
1170 1170 self.write(_("unrecognized response\n"))
1171 1171
1172 1172 def getpass(self, prompt=None, default=None):
1173 1173 if not self.interactive():
1174 1174 return default
1175 1175 try:
1176 1176 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1177 1177 # disable getpass() only if explicitly specified. it's still valid
1178 1178 # to interact with tty even if fin is not a tty.
1179 1179 with self.timeblockedsection('stdio'):
1180 1180 if self.configbool('ui', 'nontty'):
1181 1181 l = self.fin.readline()
1182 1182 if not l:
1183 1183 raise EOFError
1184 1184 return l.rstrip('\n')
1185 1185 else:
1186 1186 return getpass.getpass('')
1187 1187 except EOFError:
1188 1188 raise error.ResponseExpected()
1189 1189 def status(self, *msg, **opts):
1190 1190 '''write status message to output (if ui.quiet is False)
1191 1191
1192 1192 This adds an output label of "ui.status".
1193 1193 '''
1194 1194 if not self.quiet:
1195 1195 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1196 1196 self.write(*msg, **opts)
1197 1197 def warn(self, *msg, **opts):
1198 1198 '''write warning message to output (stderr)
1199 1199
1200 1200 This adds an output label of "ui.warning".
1201 1201 '''
1202 1202 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1203 1203 self.write_err(*msg, **opts)
1204 1204 def note(self, *msg, **opts):
1205 1205 '''write note to output (if ui.verbose is True)
1206 1206
1207 1207 This adds an output label of "ui.note".
1208 1208 '''
1209 1209 if self.verbose:
1210 1210 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1211 1211 self.write(*msg, **opts)
1212 1212 def debug(self, *msg, **opts):
1213 1213 '''write debug message to output (if ui.debugflag is True)
1214 1214
1215 1215 This adds an output label of "ui.debug".
1216 1216 '''
1217 1217 if self.debugflag:
1218 1218 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1219 1219 self.write(*msg, **opts)
1220 1220
1221 1221 def edit(self, text, user, extra=None, editform=None, pending=None,
1222 1222 repopath=None):
1223 1223 extra_defaults = {
1224 1224 'prefix': 'editor',
1225 1225 'suffix': '.txt',
1226 1226 }
1227 1227 if extra is not None:
1228 1228 extra_defaults.update(extra)
1229 1229 extra = extra_defaults
1230 1230
1231 1231 rdir = None
1232 1232 if self.configbool('experimental', 'editortmpinhg'):
1233 1233 rdir = repopath
1234 1234 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1235 1235 suffix=extra['suffix'], text=True,
1236 1236 dir=rdir)
1237 1237 try:
1238 1238 f = os.fdopen(fd, pycompat.sysstr("w"))
1239 1239 f.write(encoding.strfromlocal(text))
1240 1240 f.close()
1241 1241
1242 1242 environ = {'HGUSER': user}
1243 1243 if 'transplant_source' in extra:
1244 1244 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1245 1245 for label in ('intermediate-source', 'source', 'rebase_source'):
1246 1246 if label in extra:
1247 1247 environ.update({'HGREVISION': extra[label]})
1248 1248 break
1249 1249 if editform:
1250 1250 environ.update({'HGEDITFORM': editform})
1251 1251 if pending:
1252 1252 environ.update({'HG_PENDING': pending})
1253 1253
1254 1254 editor = self.geteditor()
1255 1255
1256 1256 self.system("%s \"%s\"" % (editor, name),
1257 1257 environ=environ,
1258 1258 onerr=error.Abort, errprefix=_("edit failed"),
1259 1259 blockedtag='editor')
1260 1260
1261 1261 f = open(name)
1262 1262 t = encoding.strtolocal(f.read())
1263 1263 f.close()
1264 1264 finally:
1265 1265 os.unlink(name)
1266 1266
1267 1267 return t
1268 1268
1269 1269 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1270 1270 blockedtag=None):
1271 1271 '''execute shell command with appropriate output stream. command
1272 1272 output will be redirected if fout is not stdout.
1273 1273
1274 1274 if command fails and onerr is None, return status, else raise onerr
1275 1275 object as exception.
1276 1276 '''
1277 1277 if blockedtag is None:
1278 1278 # Long cmds tend to be because of an absolute path on cmd. Keep
1279 1279 # the tail end instead
1280 1280 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1281 1281 blockedtag = 'unknown_system_' + cmdsuffix
1282 1282 out = self.fout
1283 1283 if any(s[1] for s in self._bufferstates):
1284 1284 out = self
1285 1285 with self.timeblockedsection(blockedtag):
1286 1286 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1287 1287 if rc and onerr:
1288 1288 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1289 1289 util.explainexit(rc)[0])
1290 1290 if errprefix:
1291 1291 errmsg = '%s: %s' % (errprefix, errmsg)
1292 1292 raise onerr(errmsg)
1293 1293 return rc
1294 1294
1295 1295 def _runsystem(self, cmd, environ, cwd, out):
1296 1296 """actually execute the given shell command (can be overridden by
1297 1297 extensions like chg)"""
1298 1298 return util.system(cmd, environ=environ, cwd=cwd, out=out)
1299 1299
1300 1300 def traceback(self, exc=None, force=False):
1301 1301 '''print exception traceback if traceback printing enabled or forced.
1302 1302 only to call in exception handler. returns true if traceback
1303 1303 printed.'''
1304 1304 if self.tracebackflag or force:
1305 1305 if exc is None:
1306 1306 exc = sys.exc_info()
1307 1307 cause = getattr(exc[1], 'cause', None)
1308 1308
1309 1309 if cause is not None:
1310 1310 causetb = traceback.format_tb(cause[2])
1311 1311 exctb = traceback.format_tb(exc[2])
1312 1312 exconly = traceback.format_exception_only(cause[0], cause[1])
1313 1313
1314 1314 # exclude frame where 'exc' was chained and rethrown from exctb
1315 1315 self.write_err('Traceback (most recent call last):\n',
1316 1316 ''.join(exctb[:-1]),
1317 1317 ''.join(causetb),
1318 1318 ''.join(exconly))
1319 1319 else:
1320 1320 output = traceback.format_exception(exc[0], exc[1], exc[2])
1321 1321 data = r''.join(output)
1322 1322 if pycompat.ispy3:
1323 1323 enc = pycompat.sysstr(encoding.encoding)
1324 1324 data = data.encode(enc, errors=r'replace')
1325 1325 self.write_err(data)
1326 1326 return self.tracebackflag or force
1327 1327
1328 1328 def geteditor(self):
1329 1329 '''return editor to use'''
1330 1330 if pycompat.sysplatform == 'plan9':
1331 1331 # vi is the MIPS instruction simulator on Plan 9. We
1332 1332 # instead default to E to plumb commit messages to
1333 1333 # avoid confusion.
1334 1334 editor = 'E'
1335 1335 else:
1336 1336 editor = 'vi'
1337 1337 return (encoding.environ.get("HGEDITOR") or
1338 1338 self.config("ui", "editor", editor))
1339 1339
1340 1340 @util.propertycache
1341 1341 def _progbar(self):
1342 1342 """setup the progbar singleton to the ui object"""
1343 1343 if (self.quiet or self.debugflag
1344 1344 or self.configbool('progress', 'disable', False)
1345 1345 or not progress.shouldprint(self)):
1346 1346 return None
1347 1347 return getprogbar(self)
1348 1348
1349 1349 def _progclear(self):
1350 1350 """clear progress bar output if any. use it before any output"""
1351 1351 if '_progbar' not in vars(self): # nothing loaded yet
1352 1352 return
1353 1353 if self._progbar is not None and self._progbar.printed:
1354 1354 self._progbar.clear()
1355 1355
1356 1356 def progress(self, topic, pos, item="", unit="", total=None):
1357 1357 '''show a progress message
1358 1358
1359 1359 By default a textual progress bar will be displayed if an operation
1360 1360 takes too long. 'topic' is the current operation, 'item' is a
1361 1361 non-numeric marker of the current position (i.e. the currently
1362 1362 in-process file), 'pos' is the current numeric position (i.e.
1363 1363 revision, bytes, etc.), unit is a corresponding unit label,
1364 1364 and total is the highest expected pos.
1365 1365
1366 1366 Multiple nested topics may be active at a time.
1367 1367
1368 1368 All topics should be marked closed by setting pos to None at
1369 1369 termination.
1370 1370 '''
1371 1371 if self._progbar is not None:
1372 1372 self._progbar.progress(topic, pos, item=item, unit=unit,
1373 1373 total=total)
1374 1374 if pos is None or not self.configbool('progress', 'debug'):
1375 1375 return
1376 1376
1377 1377 if unit:
1378 1378 unit = ' ' + unit
1379 1379 if item:
1380 1380 item = ' ' + item
1381 1381
1382 1382 if total:
1383 1383 pct = 100.0 * pos / total
1384 1384 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
1385 1385 % (topic, item, pos, total, unit, pct))
1386 1386 else:
1387 1387 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
1388 1388
1389 1389 def log(self, service, *msg, **opts):
1390 1390 '''hook for logging facility extensions
1391 1391
1392 1392 service should be a readily-identifiable subsystem, which will
1393 1393 allow filtering.
1394 1394
1395 1395 *msg should be a newline-terminated format string to log, and
1396 1396 then any values to %-format into that format string.
1397 1397
1398 1398 **opts currently has no defined meanings.
1399 1399 '''
1400 1400
1401 1401 def label(self, msg, label):
1402 1402 '''style msg based on supplied label
1403 1403
1404 1404 If some color mode is enabled, this will add the necessary control
1405 1405 characters to apply such color. In addition, 'debug' color mode adds
1406 1406 markup showing which label affects a piece of text.
1407 1407
1408 1408 ui.write(s, 'label') is equivalent to
1409 1409 ui.write(ui.label(s, 'label')).
1410 1410 '''
1411 1411 if self._colormode is not None:
1412 1412 return color.colorlabel(self, msg, label)
1413 1413 return msg
1414 1414
1415 1415 def develwarn(self, msg, stacklevel=1, config=None):
1416 1416 """issue a developer warning message
1417 1417
1418 1418 Use 'stacklevel' to report the offender some layers further up in the
1419 1419 stack.
1420 1420 """
1421 1421 if not self.configbool('devel', 'all-warnings'):
1422 1422 if config is not None and not self.configbool('devel', config):
1423 1423 return
1424 1424 msg = 'devel-warn: ' + msg
1425 1425 stacklevel += 1 # get in develwarn
1426 1426 if self.tracebackflag:
1427 1427 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1428 1428 self.log('develwarn', '%s at:\n%s' %
1429 1429 (msg, ''.join(util.getstackframes(stacklevel))))
1430 1430 else:
1431 1431 curframe = inspect.currentframe()
1432 1432 calframe = inspect.getouterframes(curframe, 2)
1433 1433 self.write_err('%s at: %s:%s (%s)\n'
1434 1434 % ((msg,) + calframe[stacklevel][1:4]))
1435 1435 self.log('develwarn', '%s at: %s:%s (%s)\n',
1436 1436 msg, *calframe[stacklevel][1:4])
1437 1437 curframe = calframe = None # avoid cycles
1438 1438
1439 1439 def deprecwarn(self, msg, version):
1440 1440 """issue a deprecation warning
1441 1441
1442 1442 - msg: message explaining what is deprecated and how to upgrade,
1443 1443 - version: last version where the API will be supported,
1444 1444 """
1445 1445 if not (self.configbool('devel', 'all-warnings')
1446 1446 or self.configbool('devel', 'deprec-warn')):
1447 1447 return
1448 1448 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1449 1449 " update your code.)") % version
1450 1450 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1451 1451
1452 1452 def exportableenviron(self):
1453 1453 """The environment variables that are safe to export, e.g. through
1454 1454 hgweb.
1455 1455 """
1456 1456 return self._exportableenviron
1457 1457
1458 1458 @contextlib.contextmanager
1459 1459 def configoverride(self, overrides, source=""):
1460 1460 """Context manager for temporary config overrides
1461 1461 `overrides` must be a dict of the following structure:
1462 1462 {(section, name) : value}"""
1463 1463 backups = {}
1464 1464 try:
1465 1465 for (section, name), value in overrides.items():
1466 1466 backups[(section, name)] = self.backupconfig(section, name)
1467 1467 self.setconfig(section, name, value, source)
1468 1468 yield
1469 1469 finally:
1470 1470 for __, backup in backups.items():
1471 1471 self.restoreconfig(backup)
1472 1472 # just restoring ui.quiet config to the previous value is not enough
1473 1473 # as it does not update ui.quiet class member
1474 1474 if ('ui', 'quiet') in overrides:
1475 1475 self.fixconfig(section='ui')
1476 1476
1477 1477 class paths(dict):
1478 1478 """Represents a collection of paths and their configs.
1479 1479
1480 1480 Data is initially derived from ui instances and the config files they have
1481 1481 loaded.
1482 1482 """
1483 1483 def __init__(self, ui):
1484 1484 dict.__init__(self)
1485 1485
1486 1486 for name, loc in ui.configitems('paths', ignoresub=True):
1487 1487 # No location is the same as not existing.
1488 1488 if not loc:
1489 1489 continue
1490 1490 loc, sub = ui.configsuboptions('paths', name)
1491 1491 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1492 1492
1493 1493 def getpath(self, name, default=None):
1494 1494 """Return a ``path`` from a string, falling back to default.
1495 1495
1496 1496 ``name`` can be a named path or locations. Locations are filesystem
1497 1497 paths or URIs.
1498 1498
1499 1499 Returns None if ``name`` is not a registered path, a URI, or a local
1500 1500 path to a repo.
1501 1501 """
1502 1502 # Only fall back to default if no path was requested.
1503 1503 if name is None:
1504 1504 if not default:
1505 1505 default = ()
1506 1506 elif not isinstance(default, (tuple, list)):
1507 1507 default = (default,)
1508 1508 for k in default:
1509 1509 try:
1510 1510 return self[k]
1511 1511 except KeyError:
1512 1512 continue
1513 1513 return None
1514 1514
1515 1515 # Most likely empty string.
1516 1516 # This may need to raise in the future.
1517 1517 if not name:
1518 1518 return None
1519 1519
1520 1520 try:
1521 1521 return self[name]
1522 1522 except KeyError:
1523 1523 # Try to resolve as a local path or URI.
1524 1524 try:
1525 1525 # We don't pass sub-options in, so no need to pass ui instance.
1526 1526 return path(None, None, rawloc=name)
1527 1527 except ValueError:
1528 1528 raise error.RepoError(_('repository %s does not exist') %
1529 1529 name)
1530 1530
1531 1531 _pathsuboptions = {}
1532 1532
1533 1533 def pathsuboption(option, attr):
1534 1534 """Decorator used to declare a path sub-option.
1535 1535
1536 1536 Arguments are the sub-option name and the attribute it should set on
1537 1537 ``path`` instances.
1538 1538
1539 1539 The decorated function will receive as arguments a ``ui`` instance,
1540 1540 ``path`` instance, and the string value of this option from the config.
1541 1541 The function should return the value that will be set on the ``path``
1542 1542 instance.
1543 1543
1544 1544 This decorator can be used to perform additional verification of
1545 1545 sub-options and to change the type of sub-options.
1546 1546 """
1547 1547 def register(func):
1548 1548 _pathsuboptions[option] = (attr, func)
1549 1549 return func
1550 1550 return register
1551 1551
1552 1552 @pathsuboption('pushurl', 'pushloc')
1553 1553 def pushurlpathoption(ui, path, value):
1554 1554 u = util.url(value)
1555 1555 # Actually require a URL.
1556 1556 if not u.scheme:
1557 1557 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1558 1558 return None
1559 1559
1560 1560 # Don't support the #foo syntax in the push URL to declare branch to
1561 1561 # push.
1562 1562 if u.fragment:
1563 1563 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1564 1564 'ignoring)\n') % path.name)
1565 1565 u.fragment = None
1566 1566
1567 1567 return str(u)
1568 1568
1569 1569 @pathsuboption('pushrev', 'pushrev')
1570 1570 def pushrevpathoption(ui, path, value):
1571 1571 return value
1572 1572
1573 1573 class path(object):
1574 1574 """Represents an individual path and its configuration."""
1575 1575
1576 1576 def __init__(self, ui, name, rawloc=None, suboptions=None):
1577 1577 """Construct a path from its config options.
1578 1578
1579 1579 ``ui`` is the ``ui`` instance the path is coming from.
1580 1580 ``name`` is the symbolic name of the path.
1581 1581 ``rawloc`` is the raw location, as defined in the config.
1582 1582 ``pushloc`` is the raw locations pushes should be made to.
1583 1583
1584 1584 If ``name`` is not defined, we require that the location be a) a local
1585 1585 filesystem path with a .hg directory or b) a URL. If not,
1586 1586 ``ValueError`` is raised.
1587 1587 """
1588 1588 if not rawloc:
1589 1589 raise ValueError('rawloc must be defined')
1590 1590
1591 1591 # Locations may define branches via syntax <base>#<branch>.
1592 1592 u = util.url(rawloc)
1593 1593 branch = None
1594 1594 if u.fragment:
1595 1595 branch = u.fragment
1596 1596 u.fragment = None
1597 1597
1598 1598 self.url = u
1599 1599 self.branch = branch
1600 1600
1601 1601 self.name = name
1602 1602 self.rawloc = rawloc
1603 1603 self.loc = '%s' % u
1604 1604
1605 1605 # When given a raw location but not a symbolic name, validate the
1606 1606 # location is valid.
1607 1607 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1608 1608 raise ValueError('location is not a URL or path to a local '
1609 1609 'repo: %s' % rawloc)
1610 1610
1611 1611 suboptions = suboptions or {}
1612 1612
1613 1613 # Now process the sub-options. If a sub-option is registered, its
1614 1614 # attribute will always be present. The value will be None if there
1615 1615 # was no valid sub-option.
1616 1616 for suboption, (attr, func) in _pathsuboptions.iteritems():
1617 1617 if suboption not in suboptions:
1618 1618 setattr(self, attr, None)
1619 1619 continue
1620 1620
1621 1621 value = func(ui, self, suboptions[suboption])
1622 1622 setattr(self, attr, value)
1623 1623
1624 1624 def _isvalidlocalpath(self, path):
1625 1625 """Returns True if the given path is a potentially valid repository.
1626 1626 This is its own function so that extensions can change the definition of
1627 1627 'valid' in this case (like when pulling from a git repo into a hg
1628 1628 one)."""
1629 1629 return os.path.isdir(os.path.join(path, '.hg'))
1630 1630
1631 1631 @property
1632 1632 def suboptions(self):
1633 1633 """Return sub-options and their values for this path.
1634 1634
1635 1635 This is intended to be used for presentation purposes.
1636 1636 """
1637 1637 d = {}
1638 1638 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1639 1639 value = getattr(self, attr)
1640 1640 if value is not None:
1641 1641 d[subopt] = value
1642 1642 return d
1643 1643
1644 1644 # we instantiate one globally shared progress bar to avoid
1645 1645 # competing progress bars when multiple UI objects get created
1646 1646 _progresssingleton = None
1647 1647
1648 1648 def getprogbar(ui):
1649 1649 global _progresssingleton
1650 1650 if _progresssingleton is None:
1651 1651 # passing 'ui' object to the singleton is fishy,
1652 1652 # this is how the extension used to work but feel free to rework it.
1653 1653 _progresssingleton = progress.progbar(ui)
1654 1654 return _progresssingleton
General Comments 0
You need to be logged in to leave comments. Login now