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