##// END OF EJS Templates
progress: write ui.progress() in terms of ui.makeprogress()...
Martin von Zweigbergk -
r41178:8cf92ca9 default
parent child Browse files
Show More
@@ -1,1810 +1,1845
1 1 # scmutil.py - Mercurial core utility functions
2 2 #
3 3 # Copyright 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 errno
11 11 import glob
12 12 import hashlib
13 13 import os
14 14 import re
15 15 import socket
16 16 import subprocess
17 17 import weakref
18 18
19 19 from .i18n import _
20 20 from .node import (
21 21 bin,
22 22 hex,
23 23 nullid,
24 24 nullrev,
25 25 short,
26 26 wdirid,
27 27 wdirrev,
28 28 )
29 29
30 30 from . import (
31 31 encoding,
32 32 error,
33 33 match as matchmod,
34 34 obsolete,
35 35 obsutil,
36 36 pathutil,
37 37 phases,
38 38 policy,
39 39 pycompat,
40 40 revsetlang,
41 41 similar,
42 42 smartset,
43 43 url,
44 44 util,
45 45 vfs,
46 46 )
47 47
48 48 from .utils import (
49 49 procutil,
50 50 stringutil,
51 51 )
52 52
53 53 if pycompat.iswindows:
54 54 from . import scmwindows as scmplatform
55 55 else:
56 56 from . import scmposix as scmplatform
57 57
58 58 parsers = policy.importmod(r'parsers')
59 59
60 60 termsize = scmplatform.termsize
61 61
62 62 class status(tuple):
63 63 '''Named tuple with a list of files per status. The 'deleted', 'unknown'
64 64 and 'ignored' properties are only relevant to the working copy.
65 65 '''
66 66
67 67 __slots__ = ()
68 68
69 69 def __new__(cls, modified, added, removed, deleted, unknown, ignored,
70 70 clean):
71 71 return tuple.__new__(cls, (modified, added, removed, deleted, unknown,
72 72 ignored, clean))
73 73
74 74 @property
75 75 def modified(self):
76 76 '''files that have been modified'''
77 77 return self[0]
78 78
79 79 @property
80 80 def added(self):
81 81 '''files that have been added'''
82 82 return self[1]
83 83
84 84 @property
85 85 def removed(self):
86 86 '''files that have been removed'''
87 87 return self[2]
88 88
89 89 @property
90 90 def deleted(self):
91 91 '''files that are in the dirstate, but have been deleted from the
92 92 working copy (aka "missing")
93 93 '''
94 94 return self[3]
95 95
96 96 @property
97 97 def unknown(self):
98 98 '''files not in the dirstate that are not ignored'''
99 99 return self[4]
100 100
101 101 @property
102 102 def ignored(self):
103 103 '''files not in the dirstate that are ignored (by _dirignore())'''
104 104 return self[5]
105 105
106 106 @property
107 107 def clean(self):
108 108 '''files that have not been modified'''
109 109 return self[6]
110 110
111 111 def __repr__(self, *args, **kwargs):
112 112 return ((r'<status modified=%s, added=%s, removed=%s, deleted=%s, '
113 113 r'unknown=%s, ignored=%s, clean=%s>') %
114 114 tuple(pycompat.sysstr(stringutil.pprint(v)) for v in self))
115 115
116 116 def itersubrepos(ctx1, ctx2):
117 117 """find subrepos in ctx1 or ctx2"""
118 118 # Create a (subpath, ctx) mapping where we prefer subpaths from
119 119 # ctx1. The subpaths from ctx2 are important when the .hgsub file
120 120 # has been modified (in ctx2) but not yet committed (in ctx1).
121 121 subpaths = dict.fromkeys(ctx2.substate, ctx2)
122 122 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
123 123
124 124 missing = set()
125 125
126 126 for subpath in ctx2.substate:
127 127 if subpath not in ctx1.substate:
128 128 del subpaths[subpath]
129 129 missing.add(subpath)
130 130
131 131 for subpath, ctx in sorted(subpaths.iteritems()):
132 132 yield subpath, ctx.sub(subpath)
133 133
134 134 # Yield an empty subrepo based on ctx1 for anything only in ctx2. That way,
135 135 # status and diff will have an accurate result when it does
136 136 # 'sub.{status|diff}(rev2)'. Otherwise, the ctx2 subrepo is compared
137 137 # against itself.
138 138 for subpath in missing:
139 139 yield subpath, ctx2.nullsub(subpath, ctx1)
140 140
141 141 def nochangesfound(ui, repo, excluded=None):
142 142 '''Report no changes for push/pull, excluded is None or a list of
143 143 nodes excluded from the push/pull.
144 144 '''
145 145 secretlist = []
146 146 if excluded:
147 147 for n in excluded:
148 148 ctx = repo[n]
149 149 if ctx.phase() >= phases.secret and not ctx.extinct():
150 150 secretlist.append(n)
151 151
152 152 if secretlist:
153 153 ui.status(_("no changes found (ignored %d secret changesets)\n")
154 154 % len(secretlist))
155 155 else:
156 156 ui.status(_("no changes found\n"))
157 157
158 158 def callcatch(ui, func):
159 159 """call func() with global exception handling
160 160
161 161 return func() if no exception happens. otherwise do some error handling
162 162 and return an exit code accordingly. does not handle all exceptions.
163 163 """
164 164 try:
165 165 try:
166 166 return func()
167 167 except: # re-raises
168 168 ui.traceback()
169 169 raise
170 170 # Global exception handling, alphabetically
171 171 # Mercurial-specific first, followed by built-in and library exceptions
172 172 except error.LockHeld as inst:
173 173 if inst.errno == errno.ETIMEDOUT:
174 174 reason = _('timed out waiting for lock held by %r') % (
175 175 pycompat.bytestr(inst.locker))
176 176 else:
177 177 reason = _('lock held by %r') % inst.locker
178 178 ui.error(_("abort: %s: %s\n") % (
179 179 inst.desc or stringutil.forcebytestr(inst.filename), reason))
180 180 if not inst.locker:
181 181 ui.error(_("(lock might be very busy)\n"))
182 182 except error.LockUnavailable as inst:
183 183 ui.error(_("abort: could not lock %s: %s\n") %
184 184 (inst.desc or stringutil.forcebytestr(inst.filename),
185 185 encoding.strtolocal(inst.strerror)))
186 186 except error.OutOfBandError as inst:
187 187 if inst.args:
188 188 msg = _("abort: remote error:\n")
189 189 else:
190 190 msg = _("abort: remote error\n")
191 191 ui.error(msg)
192 192 if inst.args:
193 193 ui.error(''.join(inst.args))
194 194 if inst.hint:
195 195 ui.error('(%s)\n' % inst.hint)
196 196 except error.RepoError as inst:
197 197 ui.error(_("abort: %s!\n") % inst)
198 198 if inst.hint:
199 199 ui.error(_("(%s)\n") % inst.hint)
200 200 except error.ResponseError as inst:
201 201 ui.error(_("abort: %s") % inst.args[0])
202 202 msg = inst.args[1]
203 203 if isinstance(msg, type(u'')):
204 204 msg = pycompat.sysbytes(msg)
205 205 if not isinstance(msg, bytes):
206 206 ui.error(" %r\n" % (msg,))
207 207 elif not msg:
208 208 ui.error(_(" empty string\n"))
209 209 else:
210 210 ui.error("\n%r\n" % pycompat.bytestr(stringutil.ellipsis(msg)))
211 211 except error.CensoredNodeError as inst:
212 212 ui.error(_("abort: file censored %s!\n") % inst)
213 213 except error.StorageError as inst:
214 214 ui.error(_("abort: %s!\n") % inst)
215 215 if inst.hint:
216 216 ui.error(_("(%s)\n") % inst.hint)
217 217 except error.InterventionRequired as inst:
218 218 ui.error("%s\n" % inst)
219 219 if inst.hint:
220 220 ui.error(_("(%s)\n") % inst.hint)
221 221 return 1
222 222 except error.WdirUnsupported:
223 223 ui.error(_("abort: working directory revision cannot be specified\n"))
224 224 except error.Abort as inst:
225 225 ui.error(_("abort: %s\n") % inst)
226 226 if inst.hint:
227 227 ui.error(_("(%s)\n") % inst.hint)
228 228 except ImportError as inst:
229 229 ui.error(_("abort: %s!\n") % stringutil.forcebytestr(inst))
230 230 m = stringutil.forcebytestr(inst).split()[-1]
231 231 if m in "mpatch bdiff".split():
232 232 ui.error(_("(did you forget to compile extensions?)\n"))
233 233 elif m in "zlib".split():
234 234 ui.error(_("(is your Python install correct?)\n"))
235 235 except IOError as inst:
236 236 if util.safehasattr(inst, "code"):
237 237 ui.error(_("abort: %s\n") % stringutil.forcebytestr(inst))
238 238 elif util.safehasattr(inst, "reason"):
239 239 try: # usually it is in the form (errno, strerror)
240 240 reason = inst.reason.args[1]
241 241 except (AttributeError, IndexError):
242 242 # it might be anything, for example a string
243 243 reason = inst.reason
244 244 if isinstance(reason, pycompat.unicode):
245 245 # SSLError of Python 2.7.9 contains a unicode
246 246 reason = encoding.unitolocal(reason)
247 247 ui.error(_("abort: error: %s\n") % reason)
248 248 elif (util.safehasattr(inst, "args")
249 249 and inst.args and inst.args[0] == errno.EPIPE):
250 250 pass
251 251 elif getattr(inst, "strerror", None):
252 252 if getattr(inst, "filename", None):
253 253 ui.error(_("abort: %s: %s\n") % (
254 254 encoding.strtolocal(inst.strerror),
255 255 stringutil.forcebytestr(inst.filename)))
256 256 else:
257 257 ui.error(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
258 258 else:
259 259 raise
260 260 except OSError as inst:
261 261 if getattr(inst, "filename", None) is not None:
262 262 ui.error(_("abort: %s: '%s'\n") % (
263 263 encoding.strtolocal(inst.strerror),
264 264 stringutil.forcebytestr(inst.filename)))
265 265 else:
266 266 ui.error(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
267 267 except MemoryError:
268 268 ui.error(_("abort: out of memory\n"))
269 269 except SystemExit as inst:
270 270 # Commands shouldn't sys.exit directly, but give a return code.
271 271 # Just in case catch this and and pass exit code to caller.
272 272 return inst.code
273 273 except socket.error as inst:
274 274 ui.error(_("abort: %s\n") % stringutil.forcebytestr(inst.args[-1]))
275 275
276 276 return -1
277 277
278 278 def checknewlabel(repo, lbl, kind):
279 279 # Do not use the "kind" parameter in ui output.
280 280 # It makes strings difficult to translate.
281 281 if lbl in ['tip', '.', 'null']:
282 282 raise error.Abort(_("the name '%s' is reserved") % lbl)
283 283 for c in (':', '\0', '\n', '\r'):
284 284 if c in lbl:
285 285 raise error.Abort(
286 286 _("%r cannot be used in a name") % pycompat.bytestr(c))
287 287 try:
288 288 int(lbl)
289 289 raise error.Abort(_("cannot use an integer as a name"))
290 290 except ValueError:
291 291 pass
292 292 if lbl.strip() != lbl:
293 293 raise error.Abort(_("leading or trailing whitespace in name %r") % lbl)
294 294
295 295 def checkfilename(f):
296 296 '''Check that the filename f is an acceptable filename for a tracked file'''
297 297 if '\r' in f or '\n' in f:
298 298 raise error.Abort(_("'\\n' and '\\r' disallowed in filenames: %r")
299 299 % pycompat.bytestr(f))
300 300
301 301 def checkportable(ui, f):
302 302 '''Check if filename f is portable and warn or abort depending on config'''
303 303 checkfilename(f)
304 304 abort, warn = checkportabilityalert(ui)
305 305 if abort or warn:
306 306 msg = util.checkwinfilename(f)
307 307 if msg:
308 308 msg = "%s: %s" % (msg, procutil.shellquote(f))
309 309 if abort:
310 310 raise error.Abort(msg)
311 311 ui.warn(_("warning: %s\n") % msg)
312 312
313 313 def checkportabilityalert(ui):
314 314 '''check if the user's config requests nothing, a warning, or abort for
315 315 non-portable filenames'''
316 316 val = ui.config('ui', 'portablefilenames')
317 317 lval = val.lower()
318 318 bval = stringutil.parsebool(val)
319 319 abort = pycompat.iswindows or lval == 'abort'
320 320 warn = bval or lval == 'warn'
321 321 if bval is None and not (warn or abort or lval == 'ignore'):
322 322 raise error.ConfigError(
323 323 _("ui.portablefilenames value is invalid ('%s')") % val)
324 324 return abort, warn
325 325
326 326 class casecollisionauditor(object):
327 327 def __init__(self, ui, abort, dirstate):
328 328 self._ui = ui
329 329 self._abort = abort
330 330 allfiles = '\0'.join(dirstate._map)
331 331 self._loweredfiles = set(encoding.lower(allfiles).split('\0'))
332 332 self._dirstate = dirstate
333 333 # The purpose of _newfiles is so that we don't complain about
334 334 # case collisions if someone were to call this object with the
335 335 # same filename twice.
336 336 self._newfiles = set()
337 337
338 338 def __call__(self, f):
339 339 if f in self._newfiles:
340 340 return
341 341 fl = encoding.lower(f)
342 342 if fl in self._loweredfiles and f not in self._dirstate:
343 343 msg = _('possible case-folding collision for %s') % f
344 344 if self._abort:
345 345 raise error.Abort(msg)
346 346 self._ui.warn(_("warning: %s\n") % msg)
347 347 self._loweredfiles.add(fl)
348 348 self._newfiles.add(f)
349 349
350 350 def filteredhash(repo, maxrev):
351 351 """build hash of filtered revisions in the current repoview.
352 352
353 353 Multiple caches perform up-to-date validation by checking that the
354 354 tiprev and tipnode stored in the cache file match the current repository.
355 355 However, this is not sufficient for validating repoviews because the set
356 356 of revisions in the view may change without the repository tiprev and
357 357 tipnode changing.
358 358
359 359 This function hashes all the revs filtered from the view and returns
360 360 that SHA-1 digest.
361 361 """
362 362 cl = repo.changelog
363 363 if not cl.filteredrevs:
364 364 return None
365 365 key = None
366 366 revs = sorted(r for r in cl.filteredrevs if r <= maxrev)
367 367 if revs:
368 368 s = hashlib.sha1()
369 369 for rev in revs:
370 370 s.update('%d;' % rev)
371 371 key = s.digest()
372 372 return key
373 373
374 374 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
375 375 '''yield every hg repository under path, always recursively.
376 376 The recurse flag will only control recursion into repo working dirs'''
377 377 def errhandler(err):
378 378 if err.filename == path:
379 379 raise err
380 380 samestat = getattr(os.path, 'samestat', None)
381 381 if followsym and samestat is not None:
382 382 def adddir(dirlst, dirname):
383 383 dirstat = os.stat(dirname)
384 384 match = any(samestat(dirstat, lstdirstat) for lstdirstat in dirlst)
385 385 if not match:
386 386 dirlst.append(dirstat)
387 387 return not match
388 388 else:
389 389 followsym = False
390 390
391 391 if (seen_dirs is None) and followsym:
392 392 seen_dirs = []
393 393 adddir(seen_dirs, path)
394 394 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
395 395 dirs.sort()
396 396 if '.hg' in dirs:
397 397 yield root # found a repository
398 398 qroot = os.path.join(root, '.hg', 'patches')
399 399 if os.path.isdir(os.path.join(qroot, '.hg')):
400 400 yield qroot # we have a patch queue repo here
401 401 if recurse:
402 402 # avoid recursing inside the .hg directory
403 403 dirs.remove('.hg')
404 404 else:
405 405 dirs[:] = [] # don't descend further
406 406 elif followsym:
407 407 newdirs = []
408 408 for d in dirs:
409 409 fname = os.path.join(root, d)
410 410 if adddir(seen_dirs, fname):
411 411 if os.path.islink(fname):
412 412 for hgname in walkrepos(fname, True, seen_dirs):
413 413 yield hgname
414 414 else:
415 415 newdirs.append(d)
416 416 dirs[:] = newdirs
417 417
418 418 def binnode(ctx):
419 419 """Return binary node id for a given basectx"""
420 420 node = ctx.node()
421 421 if node is None:
422 422 return wdirid
423 423 return node
424 424
425 425 def intrev(ctx):
426 426 """Return integer for a given basectx that can be used in comparison or
427 427 arithmetic operation"""
428 428 rev = ctx.rev()
429 429 if rev is None:
430 430 return wdirrev
431 431 return rev
432 432
433 433 def formatchangeid(ctx):
434 434 """Format changectx as '{rev}:{node|formatnode}', which is the default
435 435 template provided by logcmdutil.changesettemplater"""
436 436 repo = ctx.repo()
437 437 return formatrevnode(repo.ui, intrev(ctx), binnode(ctx))
438 438
439 439 def formatrevnode(ui, rev, node):
440 440 """Format given revision and node depending on the current verbosity"""
441 441 if ui.debugflag:
442 442 hexfunc = hex
443 443 else:
444 444 hexfunc = short
445 445 return '%d:%s' % (rev, hexfunc(node))
446 446
447 447 def resolvehexnodeidprefix(repo, prefix):
448 448 if (prefix.startswith('x') and
449 449 repo.ui.configbool('experimental', 'revisions.prefixhexnode')):
450 450 prefix = prefix[1:]
451 451 try:
452 452 # Uses unfiltered repo because it's faster when prefix is ambiguous/
453 453 # This matches the shortesthexnodeidprefix() function below.
454 454 node = repo.unfiltered().changelog._partialmatch(prefix)
455 455 except error.AmbiguousPrefixLookupError:
456 456 revset = repo.ui.config('experimental', 'revisions.disambiguatewithin')
457 457 if revset:
458 458 # Clear config to avoid infinite recursion
459 459 configoverrides = {('experimental',
460 460 'revisions.disambiguatewithin'): None}
461 461 with repo.ui.configoverride(configoverrides):
462 462 revs = repo.anyrevs([revset], user=True)
463 463 matches = []
464 464 for rev in revs:
465 465 node = repo.changelog.node(rev)
466 466 if hex(node).startswith(prefix):
467 467 matches.append(node)
468 468 if len(matches) == 1:
469 469 return matches[0]
470 470 raise
471 471 if node is None:
472 472 return
473 473 repo.changelog.rev(node) # make sure node isn't filtered
474 474 return node
475 475
476 476 def mayberevnum(repo, prefix):
477 477 """Checks if the given prefix may be mistaken for a revision number"""
478 478 try:
479 479 i = int(prefix)
480 480 # if we are a pure int, then starting with zero will not be
481 481 # confused as a rev; or, obviously, if the int is larger
482 482 # than the value of the tip rev. We still need to disambiguate if
483 483 # prefix == '0', since that *is* a valid revnum.
484 484 if (prefix != b'0' and prefix[0:1] == b'0') or i >= len(repo):
485 485 return False
486 486 return True
487 487 except ValueError:
488 488 return False
489 489
490 490 def shortesthexnodeidprefix(repo, node, minlength=1, cache=None):
491 491 """Find the shortest unambiguous prefix that matches hexnode.
492 492
493 493 If "cache" is not None, it must be a dictionary that can be used for
494 494 caching between calls to this method.
495 495 """
496 496 # _partialmatch() of filtered changelog could take O(len(repo)) time,
497 497 # which would be unacceptably slow. so we look for hash collision in
498 498 # unfiltered space, which means some hashes may be slightly longer.
499 499
500 500 minlength=max(minlength, 1)
501 501
502 502 def disambiguate(prefix):
503 503 """Disambiguate against revnums."""
504 504 if repo.ui.configbool('experimental', 'revisions.prefixhexnode'):
505 505 if mayberevnum(repo, prefix):
506 506 return 'x' + prefix
507 507 else:
508 508 return prefix
509 509
510 510 hexnode = hex(node)
511 511 for length in range(len(prefix), len(hexnode) + 1):
512 512 prefix = hexnode[:length]
513 513 if not mayberevnum(repo, prefix):
514 514 return prefix
515 515
516 516 cl = repo.unfiltered().changelog
517 517 revset = repo.ui.config('experimental', 'revisions.disambiguatewithin')
518 518 if revset:
519 519 revs = None
520 520 if cache is not None:
521 521 revs = cache.get('disambiguationrevset')
522 522 if revs is None:
523 523 revs = repo.anyrevs([revset], user=True)
524 524 if cache is not None:
525 525 cache['disambiguationrevset'] = revs
526 526 if cl.rev(node) in revs:
527 527 hexnode = hex(node)
528 528 nodetree = None
529 529 if cache is not None:
530 530 nodetree = cache.get('disambiguationnodetree')
531 531 if not nodetree:
532 532 try:
533 533 nodetree = parsers.nodetree(cl.index, len(revs))
534 534 except AttributeError:
535 535 # no native nodetree
536 536 pass
537 537 else:
538 538 for r in revs:
539 539 nodetree.insert(r)
540 540 if cache is not None:
541 541 cache['disambiguationnodetree'] = nodetree
542 542 if nodetree is not None:
543 543 length = max(nodetree.shortest(node), minlength)
544 544 prefix = hexnode[:length]
545 545 return disambiguate(prefix)
546 546 for length in range(minlength, len(hexnode) + 1):
547 547 matches = []
548 548 prefix = hexnode[:length]
549 549 for rev in revs:
550 550 otherhexnode = repo[rev].hex()
551 551 if prefix == otherhexnode[:length]:
552 552 matches.append(otherhexnode)
553 553 if len(matches) == 1:
554 554 return disambiguate(prefix)
555 555
556 556 try:
557 557 return disambiguate(cl.shortest(node, minlength))
558 558 except error.LookupError:
559 559 raise error.RepoLookupError()
560 560
561 561 def isrevsymbol(repo, symbol):
562 562 """Checks if a symbol exists in the repo.
563 563
564 564 See revsymbol() for details. Raises error.AmbiguousPrefixLookupError if the
565 565 symbol is an ambiguous nodeid prefix.
566 566 """
567 567 try:
568 568 revsymbol(repo, symbol)
569 569 return True
570 570 except error.RepoLookupError:
571 571 return False
572 572
573 573 def revsymbol(repo, symbol):
574 574 """Returns a context given a single revision symbol (as string).
575 575
576 576 This is similar to revsingle(), but accepts only a single revision symbol,
577 577 i.e. things like ".", "tip", "1234", "deadbeef", "my-bookmark" work, but
578 578 not "max(public())".
579 579 """
580 580 if not isinstance(symbol, bytes):
581 581 msg = ("symbol (%s of type %s) was not a string, did you mean "
582 582 "repo[symbol]?" % (symbol, type(symbol)))
583 583 raise error.ProgrammingError(msg)
584 584 try:
585 585 if symbol in ('.', 'tip', 'null'):
586 586 return repo[symbol]
587 587
588 588 try:
589 589 r = int(symbol)
590 590 if '%d' % r != symbol:
591 591 raise ValueError
592 592 l = len(repo.changelog)
593 593 if r < 0:
594 594 r += l
595 595 if r < 0 or r >= l and r != wdirrev:
596 596 raise ValueError
597 597 return repo[r]
598 598 except error.FilteredIndexError:
599 599 raise
600 600 except (ValueError, OverflowError, IndexError):
601 601 pass
602 602
603 603 if len(symbol) == 40:
604 604 try:
605 605 node = bin(symbol)
606 606 rev = repo.changelog.rev(node)
607 607 return repo[rev]
608 608 except error.FilteredLookupError:
609 609 raise
610 610 except (TypeError, LookupError):
611 611 pass
612 612
613 613 # look up bookmarks through the name interface
614 614 try:
615 615 node = repo.names.singlenode(repo, symbol)
616 616 rev = repo.changelog.rev(node)
617 617 return repo[rev]
618 618 except KeyError:
619 619 pass
620 620
621 621 node = resolvehexnodeidprefix(repo, symbol)
622 622 if node is not None:
623 623 rev = repo.changelog.rev(node)
624 624 return repo[rev]
625 625
626 626 raise error.RepoLookupError(_("unknown revision '%s'") % symbol)
627 627
628 628 except error.WdirUnsupported:
629 629 return repo[None]
630 630 except (error.FilteredIndexError, error.FilteredLookupError,
631 631 error.FilteredRepoLookupError):
632 632 raise _filterederror(repo, symbol)
633 633
634 634 def _filterederror(repo, changeid):
635 635 """build an exception to be raised about a filtered changeid
636 636
637 637 This is extracted in a function to help extensions (eg: evolve) to
638 638 experiment with various message variants."""
639 639 if repo.filtername.startswith('visible'):
640 640
641 641 # Check if the changeset is obsolete
642 642 unfilteredrepo = repo.unfiltered()
643 643 ctx = revsymbol(unfilteredrepo, changeid)
644 644
645 645 # If the changeset is obsolete, enrich the message with the reason
646 646 # that made this changeset not visible
647 647 if ctx.obsolete():
648 648 msg = obsutil._getfilteredreason(repo, changeid, ctx)
649 649 else:
650 650 msg = _("hidden revision '%s'") % changeid
651 651
652 652 hint = _('use --hidden to access hidden revisions')
653 653
654 654 return error.FilteredRepoLookupError(msg, hint=hint)
655 655 msg = _("filtered revision '%s' (not in '%s' subset)")
656 656 msg %= (changeid, repo.filtername)
657 657 return error.FilteredRepoLookupError(msg)
658 658
659 659 def revsingle(repo, revspec, default='.', localalias=None):
660 660 if not revspec and revspec != 0:
661 661 return repo[default]
662 662
663 663 l = revrange(repo, [revspec], localalias=localalias)
664 664 if not l:
665 665 raise error.Abort(_('empty revision set'))
666 666 return repo[l.last()]
667 667
668 668 def _pairspec(revspec):
669 669 tree = revsetlang.parse(revspec)
670 670 return tree and tree[0] in ('range', 'rangepre', 'rangepost', 'rangeall')
671 671
672 672 def revpair(repo, revs):
673 673 if not revs:
674 674 return repo['.'], repo[None]
675 675
676 676 l = revrange(repo, revs)
677 677
678 678 if not l:
679 679 first = second = None
680 680 elif l.isascending():
681 681 first = l.min()
682 682 second = l.max()
683 683 elif l.isdescending():
684 684 first = l.max()
685 685 second = l.min()
686 686 else:
687 687 first = l.first()
688 688 second = l.last()
689 689
690 690 if first is None:
691 691 raise error.Abort(_('empty revision range'))
692 692 if (first == second and len(revs) >= 2
693 693 and not all(revrange(repo, [r]) for r in revs)):
694 694 raise error.Abort(_('empty revision on one side of range'))
695 695
696 696 # if top-level is range expression, the result must always be a pair
697 697 if first == second and len(revs) == 1 and not _pairspec(revs[0]):
698 698 return repo[first], repo[None]
699 699
700 700 return repo[first], repo[second]
701 701
702 702 def revrange(repo, specs, localalias=None):
703 703 """Execute 1 to many revsets and return the union.
704 704
705 705 This is the preferred mechanism for executing revsets using user-specified
706 706 config options, such as revset aliases.
707 707
708 708 The revsets specified by ``specs`` will be executed via a chained ``OR``
709 709 expression. If ``specs`` is empty, an empty result is returned.
710 710
711 711 ``specs`` can contain integers, in which case they are assumed to be
712 712 revision numbers.
713 713
714 714 It is assumed the revsets are already formatted. If you have arguments
715 715 that need to be expanded in the revset, call ``revsetlang.formatspec()``
716 716 and pass the result as an element of ``specs``.
717 717
718 718 Specifying a single revset is allowed.
719 719
720 720 Returns a ``revset.abstractsmartset`` which is a list-like interface over
721 721 integer revisions.
722 722 """
723 723 allspecs = []
724 724 for spec in specs:
725 725 if isinstance(spec, int):
726 726 spec = revsetlang.formatspec('rev(%d)', spec)
727 727 allspecs.append(spec)
728 728 return repo.anyrevs(allspecs, user=True, localalias=localalias)
729 729
730 730 def meaningfulparents(repo, ctx):
731 731 """Return list of meaningful (or all if debug) parentrevs for rev.
732 732
733 733 For merges (two non-nullrev revisions) both parents are meaningful.
734 734 Otherwise the first parent revision is considered meaningful if it
735 735 is not the preceding revision.
736 736 """
737 737 parents = ctx.parents()
738 738 if len(parents) > 1:
739 739 return parents
740 740 if repo.ui.debugflag:
741 741 return [parents[0], repo[nullrev]]
742 742 if parents[0].rev() >= intrev(ctx) - 1:
743 743 return []
744 744 return parents
745 745
746 746 def expandpats(pats):
747 747 '''Expand bare globs when running on windows.
748 748 On posix we assume it already has already been done by sh.'''
749 749 if not util.expandglobs:
750 750 return list(pats)
751 751 ret = []
752 752 for kindpat in pats:
753 753 kind, pat = matchmod._patsplit(kindpat, None)
754 754 if kind is None:
755 755 try:
756 756 globbed = glob.glob(pat)
757 757 except re.error:
758 758 globbed = [pat]
759 759 if globbed:
760 760 ret.extend(globbed)
761 761 continue
762 762 ret.append(kindpat)
763 763 return ret
764 764
765 765 def matchandpats(ctx, pats=(), opts=None, globbed=False, default='relpath',
766 766 badfn=None):
767 767 '''Return a matcher and the patterns that were used.
768 768 The matcher will warn about bad matches, unless an alternate badfn callback
769 769 is provided.'''
770 770 if pats == ("",):
771 771 pats = []
772 772 if opts is None:
773 773 opts = {}
774 774 if not globbed and default == 'relpath':
775 775 pats = expandpats(pats or [])
776 776
777 777 def bad(f, msg):
778 778 ctx.repo().ui.warn("%s: %s\n" % (m.rel(f), msg))
779 779
780 780 if badfn is None:
781 781 badfn = bad
782 782
783 783 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
784 784 default, listsubrepos=opts.get('subrepos'), badfn=badfn)
785 785
786 786 if m.always():
787 787 pats = []
788 788 return m, pats
789 789
790 790 def match(ctx, pats=(), opts=None, globbed=False, default='relpath',
791 791 badfn=None):
792 792 '''Return a matcher that will warn about bad matches.'''
793 793 return matchandpats(ctx, pats, opts, globbed, default, badfn=badfn)[0]
794 794
795 795 def matchall(repo):
796 796 '''Return a matcher that will efficiently match everything.'''
797 797 return matchmod.always(repo.root, repo.getcwd())
798 798
799 799 def matchfiles(repo, files, badfn=None):
800 800 '''Return a matcher that will efficiently match exactly these files.'''
801 801 return matchmod.exact(repo.root, repo.getcwd(), files, badfn=badfn)
802 802
803 803 def parsefollowlinespattern(repo, rev, pat, msg):
804 804 """Return a file name from `pat` pattern suitable for usage in followlines
805 805 logic.
806 806 """
807 807 if not matchmod.patkind(pat):
808 808 return pathutil.canonpath(repo.root, repo.getcwd(), pat)
809 809 else:
810 810 ctx = repo[rev]
811 811 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=ctx)
812 812 files = [f for f in ctx if m(f)]
813 813 if len(files) != 1:
814 814 raise error.ParseError(msg)
815 815 return files[0]
816 816
817 817 def getorigvfs(ui, repo):
818 818 """return a vfs suitable to save 'orig' file
819 819
820 820 return None if no special directory is configured"""
821 821 origbackuppath = ui.config('ui', 'origbackuppath')
822 822 if not origbackuppath:
823 823 return None
824 824 return vfs.vfs(repo.wvfs.join(origbackuppath))
825 825
826 826 def origpath(ui, repo, filepath):
827 827 '''customize where .orig files are created
828 828
829 829 Fetch user defined path from config file: [ui] origbackuppath = <path>
830 830 Fall back to default (filepath with .orig suffix) if not specified
831 831 '''
832 832 origvfs = getorigvfs(ui, repo)
833 833 if origvfs is None:
834 834 return filepath + ".orig"
835 835
836 836 # Convert filepath from an absolute path into a path inside the repo.
837 837 filepathfromroot = util.normpath(os.path.relpath(filepath,
838 838 start=repo.root))
839 839
840 840 origbackupdir = origvfs.dirname(filepathfromroot)
841 841 if not origvfs.isdir(origbackupdir) or origvfs.islink(origbackupdir):
842 842 ui.note(_('creating directory: %s\n') % origvfs.join(origbackupdir))
843 843
844 844 # Remove any files that conflict with the backup file's path
845 845 for f in reversed(list(util.finddirs(filepathfromroot))):
846 846 if origvfs.isfileorlink(f):
847 847 ui.note(_('removing conflicting file: %s\n')
848 848 % origvfs.join(f))
849 849 origvfs.unlink(f)
850 850 break
851 851
852 852 origvfs.makedirs(origbackupdir)
853 853
854 854 if origvfs.isdir(filepathfromroot) and not origvfs.islink(filepathfromroot):
855 855 ui.note(_('removing conflicting directory: %s\n')
856 856 % origvfs.join(filepathfromroot))
857 857 origvfs.rmtree(filepathfromroot, forcibly=True)
858 858
859 859 return origvfs.join(filepathfromroot)
860 860
861 861 class _containsnode(object):
862 862 """proxy __contains__(node) to container.__contains__ which accepts revs"""
863 863
864 864 def __init__(self, repo, revcontainer):
865 865 self._torev = repo.changelog.rev
866 866 self._revcontains = revcontainer.__contains__
867 867
868 868 def __contains__(self, node):
869 869 return self._revcontains(self._torev(node))
870 870
871 871 def cleanupnodes(repo, replacements, operation, moves=None, metadata=None,
872 872 fixphase=False, targetphase=None, backup=True):
873 873 """do common cleanups when old nodes are replaced by new nodes
874 874
875 875 That includes writing obsmarkers or stripping nodes, and moving bookmarks.
876 876 (we might also want to move working directory parent in the future)
877 877
878 878 By default, bookmark moves are calculated automatically from 'replacements',
879 879 but 'moves' can be used to override that. Also, 'moves' may include
880 880 additional bookmark moves that should not have associated obsmarkers.
881 881
882 882 replacements is {oldnode: [newnode]} or a iterable of nodes if they do not
883 883 have replacements. operation is a string, like "rebase".
884 884
885 885 metadata is dictionary containing metadata to be stored in obsmarker if
886 886 obsolescence is enabled.
887 887 """
888 888 assert fixphase or targetphase is None
889 889 if not replacements and not moves:
890 890 return
891 891
892 892 # translate mapping's other forms
893 893 if not util.safehasattr(replacements, 'items'):
894 894 replacements = {(n,): () for n in replacements}
895 895 else:
896 896 # upgrading non tuple "source" to tuple ones for BC
897 897 repls = {}
898 898 for key, value in replacements.items():
899 899 if not isinstance(key, tuple):
900 900 key = (key,)
901 901 repls[key] = value
902 902 replacements = repls
903 903
904 904 # Unfiltered repo is needed since nodes in replacements might be hidden.
905 905 unfi = repo.unfiltered()
906 906
907 907 # Calculate bookmark movements
908 908 if moves is None:
909 909 moves = {}
910 910 for oldnodes, newnodes in replacements.items():
911 911 for oldnode in oldnodes:
912 912 if oldnode in moves:
913 913 continue
914 914 if len(newnodes) > 1:
915 915 # usually a split, take the one with biggest rev number
916 916 newnode = next(unfi.set('max(%ln)', newnodes)).node()
917 917 elif len(newnodes) == 0:
918 918 # move bookmark backwards
919 919 allreplaced = []
920 920 for rep in replacements:
921 921 allreplaced.extend(rep)
922 922 roots = list(unfi.set('max((::%n) - %ln)', oldnode,
923 923 allreplaced))
924 924 if roots:
925 925 newnode = roots[0].node()
926 926 else:
927 927 newnode = nullid
928 928 else:
929 929 newnode = newnodes[0]
930 930 moves[oldnode] = newnode
931 931
932 932 allnewnodes = [n for ns in replacements.values() for n in ns]
933 933 toretract = {}
934 934 toadvance = {}
935 935 if fixphase:
936 936 precursors = {}
937 937 for oldnodes, newnodes in replacements.items():
938 938 for oldnode in oldnodes:
939 939 for newnode in newnodes:
940 940 precursors.setdefault(newnode, []).append(oldnode)
941 941
942 942 allnewnodes.sort(key=lambda n: unfi[n].rev())
943 943 newphases = {}
944 944 def phase(ctx):
945 945 return newphases.get(ctx.node(), ctx.phase())
946 946 for newnode in allnewnodes:
947 947 ctx = unfi[newnode]
948 948 parentphase = max(phase(p) for p in ctx.parents())
949 949 if targetphase is None:
950 950 oldphase = max(unfi[oldnode].phase()
951 951 for oldnode in precursors[newnode])
952 952 newphase = max(oldphase, parentphase)
953 953 else:
954 954 newphase = max(targetphase, parentphase)
955 955 newphases[newnode] = newphase
956 956 if newphase > ctx.phase():
957 957 toretract.setdefault(newphase, []).append(newnode)
958 958 elif newphase < ctx.phase():
959 959 toadvance.setdefault(newphase, []).append(newnode)
960 960
961 961 with repo.transaction('cleanup') as tr:
962 962 # Move bookmarks
963 963 bmarks = repo._bookmarks
964 964 bmarkchanges = []
965 965 for oldnode, newnode in moves.items():
966 966 oldbmarks = repo.nodebookmarks(oldnode)
967 967 if not oldbmarks:
968 968 continue
969 969 from . import bookmarks # avoid import cycle
970 970 repo.ui.debug('moving bookmarks %r from %s to %s\n' %
971 971 (pycompat.rapply(pycompat.maybebytestr, oldbmarks),
972 972 hex(oldnode), hex(newnode)))
973 973 # Delete divergent bookmarks being parents of related newnodes
974 974 deleterevs = repo.revs('parents(roots(%ln & (::%n))) - parents(%n)',
975 975 allnewnodes, newnode, oldnode)
976 976 deletenodes = _containsnode(repo, deleterevs)
977 977 for name in oldbmarks:
978 978 bmarkchanges.append((name, newnode))
979 979 for b in bookmarks.divergent2delete(repo, deletenodes, name):
980 980 bmarkchanges.append((b, None))
981 981
982 982 if bmarkchanges:
983 983 bmarks.applychanges(repo, tr, bmarkchanges)
984 984
985 985 for phase, nodes in toretract.items():
986 986 phases.retractboundary(repo, tr, phase, nodes)
987 987 for phase, nodes in toadvance.items():
988 988 phases.advanceboundary(repo, tr, phase, nodes)
989 989
990 990 # Obsolete or strip nodes
991 991 if obsolete.isenabled(repo, obsolete.createmarkersopt):
992 992 # If a node is already obsoleted, and we want to obsolete it
993 993 # without a successor, skip that obssolete request since it's
994 994 # unnecessary. That's the "if s or not isobs(n)" check below.
995 995 # Also sort the node in topology order, that might be useful for
996 996 # some obsstore logic.
997 997 # NOTE: the sorting might belong to createmarkers.
998 998 torev = unfi.changelog.rev
999 999 sortfunc = lambda ns: torev(ns[0][0])
1000 1000 rels = []
1001 1001 for ns, s in sorted(replacements.items(), key=sortfunc):
1002 1002 rel = (tuple(unfi[n] for n in ns), tuple(unfi[m] for m in s))
1003 1003 rels.append(rel)
1004 1004 if rels:
1005 1005 obsolete.createmarkers(repo, rels, operation=operation,
1006 1006 metadata=metadata)
1007 1007 else:
1008 1008 from . import repair # avoid import cycle
1009 1009 tostrip = list(n for ns in replacements for n in ns)
1010 1010 if tostrip:
1011 1011 repair.delayedstrip(repo.ui, repo, tostrip, operation,
1012 1012 backup=backup)
1013 1013
1014 1014 def addremove(repo, matcher, prefix, opts=None):
1015 1015 if opts is None:
1016 1016 opts = {}
1017 1017 m = matcher
1018 1018 dry_run = opts.get('dry_run')
1019 1019 try:
1020 1020 similarity = float(opts.get('similarity') or 0)
1021 1021 except ValueError:
1022 1022 raise error.Abort(_('similarity must be a number'))
1023 1023 if similarity < 0 or similarity > 100:
1024 1024 raise error.Abort(_('similarity must be between 0 and 100'))
1025 1025 similarity /= 100.0
1026 1026
1027 1027 ret = 0
1028 1028 join = lambda f: os.path.join(prefix, f)
1029 1029
1030 1030 wctx = repo[None]
1031 1031 for subpath in sorted(wctx.substate):
1032 1032 submatch = matchmod.subdirmatcher(subpath, m)
1033 1033 if opts.get('subrepos') or m.exact(subpath) or any(submatch.files()):
1034 1034 sub = wctx.sub(subpath)
1035 1035 try:
1036 1036 if sub.addremove(submatch, prefix, opts):
1037 1037 ret = 1
1038 1038 except error.LookupError:
1039 1039 repo.ui.status(_("skipping missing subrepository: %s\n")
1040 1040 % join(subpath))
1041 1041
1042 1042 rejected = []
1043 1043 def badfn(f, msg):
1044 1044 if f in m.files():
1045 1045 m.bad(f, msg)
1046 1046 rejected.append(f)
1047 1047
1048 1048 badmatch = matchmod.badmatch(m, badfn)
1049 1049 added, unknown, deleted, removed, forgotten = _interestingfiles(repo,
1050 1050 badmatch)
1051 1051
1052 1052 unknownset = set(unknown + forgotten)
1053 1053 toprint = unknownset.copy()
1054 1054 toprint.update(deleted)
1055 1055 for abs in sorted(toprint):
1056 1056 if repo.ui.verbose or not m.exact(abs):
1057 1057 if abs in unknownset:
1058 1058 status = _('adding %s\n') % m.uipath(abs)
1059 1059 label = 'ui.addremove.added'
1060 1060 else:
1061 1061 status = _('removing %s\n') % m.uipath(abs)
1062 1062 label = 'ui.addremove.removed'
1063 1063 repo.ui.status(status, label=label)
1064 1064
1065 1065 renames = _findrenames(repo, m, added + unknown, removed + deleted,
1066 1066 similarity)
1067 1067
1068 1068 if not dry_run:
1069 1069 _markchanges(repo, unknown + forgotten, deleted, renames)
1070 1070
1071 1071 for f in rejected:
1072 1072 if f in m.files():
1073 1073 return 1
1074 1074 return ret
1075 1075
1076 1076 def marktouched(repo, files, similarity=0.0):
1077 1077 '''Assert that files have somehow been operated upon. files are relative to
1078 1078 the repo root.'''
1079 1079 m = matchfiles(repo, files, badfn=lambda x, y: rejected.append(x))
1080 1080 rejected = []
1081 1081
1082 1082 added, unknown, deleted, removed, forgotten = _interestingfiles(repo, m)
1083 1083
1084 1084 if repo.ui.verbose:
1085 1085 unknownset = set(unknown + forgotten)
1086 1086 toprint = unknownset.copy()
1087 1087 toprint.update(deleted)
1088 1088 for abs in sorted(toprint):
1089 1089 if abs in unknownset:
1090 1090 status = _('adding %s\n') % abs
1091 1091 else:
1092 1092 status = _('removing %s\n') % abs
1093 1093 repo.ui.status(status)
1094 1094
1095 1095 renames = _findrenames(repo, m, added + unknown, removed + deleted,
1096 1096 similarity)
1097 1097
1098 1098 _markchanges(repo, unknown + forgotten, deleted, renames)
1099 1099
1100 1100 for f in rejected:
1101 1101 if f in m.files():
1102 1102 return 1
1103 1103 return 0
1104 1104
1105 1105 def _interestingfiles(repo, matcher):
1106 1106 '''Walk dirstate with matcher, looking for files that addremove would care
1107 1107 about.
1108 1108
1109 1109 This is different from dirstate.status because it doesn't care about
1110 1110 whether files are modified or clean.'''
1111 1111 added, unknown, deleted, removed, forgotten = [], [], [], [], []
1112 1112 audit_path = pathutil.pathauditor(repo.root, cached=True)
1113 1113
1114 1114 ctx = repo[None]
1115 1115 dirstate = repo.dirstate
1116 1116 matcher = repo.narrowmatch(matcher, includeexact=True)
1117 1117 walkresults = dirstate.walk(matcher, subrepos=sorted(ctx.substate),
1118 1118 unknown=True, ignored=False, full=False)
1119 1119 for abs, st in walkresults.iteritems():
1120 1120 dstate = dirstate[abs]
1121 1121 if dstate == '?' and audit_path.check(abs):
1122 1122 unknown.append(abs)
1123 1123 elif dstate != 'r' and not st:
1124 1124 deleted.append(abs)
1125 1125 elif dstate == 'r' and st:
1126 1126 forgotten.append(abs)
1127 1127 # for finding renames
1128 1128 elif dstate == 'r' and not st:
1129 1129 removed.append(abs)
1130 1130 elif dstate == 'a':
1131 1131 added.append(abs)
1132 1132
1133 1133 return added, unknown, deleted, removed, forgotten
1134 1134
1135 1135 def _findrenames(repo, matcher, added, removed, similarity):
1136 1136 '''Find renames from removed files to added ones.'''
1137 1137 renames = {}
1138 1138 if similarity > 0:
1139 1139 for old, new, score in similar.findrenames(repo, added, removed,
1140 1140 similarity):
1141 1141 if (repo.ui.verbose or not matcher.exact(old)
1142 1142 or not matcher.exact(new)):
1143 1143 repo.ui.status(_('recording removal of %s as rename to %s '
1144 1144 '(%d%% similar)\n') %
1145 1145 (matcher.rel(old), matcher.rel(new),
1146 1146 score * 100))
1147 1147 renames[new] = old
1148 1148 return renames
1149 1149
1150 1150 def _markchanges(repo, unknown, deleted, renames):
1151 1151 '''Marks the files in unknown as added, the files in deleted as removed,
1152 1152 and the files in renames as copied.'''
1153 1153 wctx = repo[None]
1154 1154 with repo.wlock():
1155 1155 wctx.forget(deleted)
1156 1156 wctx.add(unknown)
1157 1157 for new, old in renames.iteritems():
1158 1158 wctx.copy(old, new)
1159 1159
1160 1160 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
1161 1161 """Update the dirstate to reflect the intent of copying src to dst. For
1162 1162 different reasons it might not end with dst being marked as copied from src.
1163 1163 """
1164 1164 origsrc = repo.dirstate.copied(src) or src
1165 1165 if dst == origsrc: # copying back a copy?
1166 1166 if repo.dirstate[dst] not in 'mn' and not dryrun:
1167 1167 repo.dirstate.normallookup(dst)
1168 1168 else:
1169 1169 if repo.dirstate[origsrc] == 'a' and origsrc == src:
1170 1170 if not ui.quiet:
1171 1171 ui.warn(_("%s has not been committed yet, so no copy "
1172 1172 "data will be stored for %s.\n")
1173 1173 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
1174 1174 if repo.dirstate[dst] in '?r' and not dryrun:
1175 1175 wctx.add([dst])
1176 1176 elif not dryrun:
1177 1177 wctx.copy(origsrc, dst)
1178 1178
1179 1179 def writerequires(opener, requirements):
1180 1180 with opener('requires', 'w', atomictemp=True) as fp:
1181 1181 for r in sorted(requirements):
1182 1182 fp.write("%s\n" % r)
1183 1183
1184 1184 class filecachesubentry(object):
1185 1185 def __init__(self, path, stat):
1186 1186 self.path = path
1187 1187 self.cachestat = None
1188 1188 self._cacheable = None
1189 1189
1190 1190 if stat:
1191 1191 self.cachestat = filecachesubentry.stat(self.path)
1192 1192
1193 1193 if self.cachestat:
1194 1194 self._cacheable = self.cachestat.cacheable()
1195 1195 else:
1196 1196 # None means we don't know yet
1197 1197 self._cacheable = None
1198 1198
1199 1199 def refresh(self):
1200 1200 if self.cacheable():
1201 1201 self.cachestat = filecachesubentry.stat(self.path)
1202 1202
1203 1203 def cacheable(self):
1204 1204 if self._cacheable is not None:
1205 1205 return self._cacheable
1206 1206
1207 1207 # we don't know yet, assume it is for now
1208 1208 return True
1209 1209
1210 1210 def changed(self):
1211 1211 # no point in going further if we can't cache it
1212 1212 if not self.cacheable():
1213 1213 return True
1214 1214
1215 1215 newstat = filecachesubentry.stat(self.path)
1216 1216
1217 1217 # we may not know if it's cacheable yet, check again now
1218 1218 if newstat and self._cacheable is None:
1219 1219 self._cacheable = newstat.cacheable()
1220 1220
1221 1221 # check again
1222 1222 if not self._cacheable:
1223 1223 return True
1224 1224
1225 1225 if self.cachestat != newstat:
1226 1226 self.cachestat = newstat
1227 1227 return True
1228 1228 else:
1229 1229 return False
1230 1230
1231 1231 @staticmethod
1232 1232 def stat(path):
1233 1233 try:
1234 1234 return util.cachestat(path)
1235 1235 except OSError as e:
1236 1236 if e.errno != errno.ENOENT:
1237 1237 raise
1238 1238
1239 1239 class filecacheentry(object):
1240 1240 def __init__(self, paths, stat=True):
1241 1241 self._entries = []
1242 1242 for path in paths:
1243 1243 self._entries.append(filecachesubentry(path, stat))
1244 1244
1245 1245 def changed(self):
1246 1246 '''true if any entry has changed'''
1247 1247 for entry in self._entries:
1248 1248 if entry.changed():
1249 1249 return True
1250 1250 return False
1251 1251
1252 1252 def refresh(self):
1253 1253 for entry in self._entries:
1254 1254 entry.refresh()
1255 1255
1256 1256 class filecache(object):
1257 1257 """A property like decorator that tracks files under .hg/ for updates.
1258 1258
1259 1259 On first access, the files defined as arguments are stat()ed and the
1260 1260 results cached. The decorated function is called. The results are stashed
1261 1261 away in a ``_filecache`` dict on the object whose method is decorated.
1262 1262
1263 1263 On subsequent access, the cached result is used as it is set to the
1264 1264 instance dictionary.
1265 1265
1266 1266 On external property set/delete operations, the caller must update the
1267 1267 corresponding _filecache entry appropriately. Use __class__.<attr>.set()
1268 1268 instead of directly setting <attr>.
1269 1269
1270 1270 When using the property API, the cached data is always used if available.
1271 1271 No stat() is performed to check if the file has changed.
1272 1272
1273 1273 Others can muck about with the state of the ``_filecache`` dict. e.g. they
1274 1274 can populate an entry before the property's getter is called. In this case,
1275 1275 entries in ``_filecache`` will be used during property operations,
1276 1276 if available. If the underlying file changes, it is up to external callers
1277 1277 to reflect this by e.g. calling ``delattr(obj, attr)`` to remove the cached
1278 1278 method result as well as possibly calling ``del obj._filecache[attr]`` to
1279 1279 remove the ``filecacheentry``.
1280 1280 """
1281 1281
1282 1282 def __init__(self, *paths):
1283 1283 self.paths = paths
1284 1284
1285 1285 def join(self, obj, fname):
1286 1286 """Used to compute the runtime path of a cached file.
1287 1287
1288 1288 Users should subclass filecache and provide their own version of this
1289 1289 function to call the appropriate join function on 'obj' (an instance
1290 1290 of the class that its member function was decorated).
1291 1291 """
1292 1292 raise NotImplementedError
1293 1293
1294 1294 def __call__(self, func):
1295 1295 self.func = func
1296 1296 self.sname = func.__name__
1297 1297 self.name = pycompat.sysbytes(self.sname)
1298 1298 return self
1299 1299
1300 1300 def __get__(self, obj, type=None):
1301 1301 # if accessed on the class, return the descriptor itself.
1302 1302 if obj is None:
1303 1303 return self
1304 1304
1305 1305 assert self.sname not in obj.__dict__
1306 1306
1307 1307 entry = obj._filecache.get(self.name)
1308 1308
1309 1309 if entry:
1310 1310 if entry.changed():
1311 1311 entry.obj = self.func(obj)
1312 1312 else:
1313 1313 paths = [self.join(obj, path) for path in self.paths]
1314 1314
1315 1315 # We stat -before- creating the object so our cache doesn't lie if
1316 1316 # a writer modified between the time we read and stat
1317 1317 entry = filecacheentry(paths, True)
1318 1318 entry.obj = self.func(obj)
1319 1319
1320 1320 obj._filecache[self.name] = entry
1321 1321
1322 1322 obj.__dict__[self.sname] = entry.obj
1323 1323 return entry.obj
1324 1324
1325 1325 # don't implement __set__(), which would make __dict__ lookup as slow as
1326 1326 # function call.
1327 1327
1328 1328 def set(self, obj, value):
1329 1329 if self.name not in obj._filecache:
1330 1330 # we add an entry for the missing value because X in __dict__
1331 1331 # implies X in _filecache
1332 1332 paths = [self.join(obj, path) for path in self.paths]
1333 1333 ce = filecacheentry(paths, False)
1334 1334 obj._filecache[self.name] = ce
1335 1335 else:
1336 1336 ce = obj._filecache[self.name]
1337 1337
1338 1338 ce.obj = value # update cached copy
1339 1339 obj.__dict__[self.sname] = value # update copy returned by obj.x
1340 1340
1341 1341 def extdatasource(repo, source):
1342 1342 """Gather a map of rev -> value dict from the specified source
1343 1343
1344 1344 A source spec is treated as a URL, with a special case shell: type
1345 1345 for parsing the output from a shell command.
1346 1346
1347 1347 The data is parsed as a series of newline-separated records where
1348 1348 each record is a revision specifier optionally followed by a space
1349 1349 and a freeform string value. If the revision is known locally, it
1350 1350 is converted to a rev, otherwise the record is skipped.
1351 1351
1352 1352 Note that both key and value are treated as UTF-8 and converted to
1353 1353 the local encoding. This allows uniformity between local and
1354 1354 remote data sources.
1355 1355 """
1356 1356
1357 1357 spec = repo.ui.config("extdata", source)
1358 1358 if not spec:
1359 1359 raise error.Abort(_("unknown extdata source '%s'") % source)
1360 1360
1361 1361 data = {}
1362 1362 src = proc = None
1363 1363 try:
1364 1364 if spec.startswith("shell:"):
1365 1365 # external commands should be run relative to the repo root
1366 1366 cmd = spec[6:]
1367 1367 proc = subprocess.Popen(procutil.tonativestr(cmd),
1368 1368 shell=True, bufsize=-1,
1369 1369 close_fds=procutil.closefds,
1370 1370 stdout=subprocess.PIPE,
1371 1371 cwd=procutil.tonativestr(repo.root))
1372 1372 src = proc.stdout
1373 1373 else:
1374 1374 # treat as a URL or file
1375 1375 src = url.open(repo.ui, spec)
1376 1376 for l in src:
1377 1377 if " " in l:
1378 1378 k, v = l.strip().split(" ", 1)
1379 1379 else:
1380 1380 k, v = l.strip(), ""
1381 1381
1382 1382 k = encoding.tolocal(k)
1383 1383 try:
1384 1384 data[revsingle(repo, k).rev()] = encoding.tolocal(v)
1385 1385 except (error.LookupError, error.RepoLookupError):
1386 1386 pass # we ignore data for nodes that don't exist locally
1387 1387 finally:
1388 1388 if proc:
1389 1389 proc.communicate()
1390 1390 if src:
1391 1391 src.close()
1392 1392 if proc and proc.returncode != 0:
1393 1393 raise error.Abort(_("extdata command '%s' failed: %s")
1394 1394 % (cmd, procutil.explainexit(proc.returncode)))
1395 1395
1396 1396 return data
1397 1397
1398 1398 def _locksub(repo, lock, envvar, cmd, environ=None, *args, **kwargs):
1399 1399 if lock is None:
1400 1400 raise error.LockInheritanceContractViolation(
1401 1401 'lock can only be inherited while held')
1402 1402 if environ is None:
1403 1403 environ = {}
1404 1404 with lock.inherit() as locker:
1405 1405 environ[envvar] = locker
1406 1406 return repo.ui.system(cmd, environ=environ, *args, **kwargs)
1407 1407
1408 1408 def wlocksub(repo, cmd, *args, **kwargs):
1409 1409 """run cmd as a subprocess that allows inheriting repo's wlock
1410 1410
1411 1411 This can only be called while the wlock is held. This takes all the
1412 1412 arguments that ui.system does, and returns the exit code of the
1413 1413 subprocess."""
1414 1414 return _locksub(repo, repo.currentwlock(), 'HG_WLOCK_LOCKER', cmd, *args,
1415 1415 **kwargs)
1416 1416
1417 1417 class progress(object):
1418 1418 def __init__(self, ui, topic, unit="", total=None):
1419 1419 self.ui = ui
1420 1420 self.pos = 0
1421 1421 self.topic = topic
1422 1422 self.unit = unit
1423 1423 self.total = total
1424 1424
1425 1425 def __enter__(self):
1426 1426 return self
1427 1427
1428 1428 def __exit__(self, exc_type, exc_value, exc_tb):
1429 1429 self.complete()
1430 1430
1431 1431 def update(self, pos, item="", total=None):
1432 1432 assert pos is not None
1433 1433 if total:
1434 1434 self.total = total
1435 1435 self.pos = pos
1436 1436 self._print(item)
1437 1437
1438 1438 def increment(self, step=1, item="", total=None):
1439 1439 self.update(self.pos + step, item, total)
1440 1440
1441 1441 def complete(self):
1442 self.ui.progress(self.topic, None)
1442 self.pos = None
1443 self.unit = ""
1444 self.total = None
1445 self._print("")
1443 1446
1444 1447 def _print(self, item):
1445 self.ui.progress(self.topic, self.pos, item, self.unit,
1446 self.total)
1448 if getattr(self.ui._fmsgerr, 'structured', False):
1449 # channel for machine-readable output with metadata, just send
1450 # raw information
1451 # TODO: consider porting some useful information (e.g. estimated
1452 # time) from progbar. we might want to support update delay to
1453 # reduce the cost of transferring progress messages.
1454 self.ui._fmsgerr.write(None, type=b'progress', topic=self.topic,
1455 pos=self.pos, item=item, unit=self.unit,
1456 total=self.total)
1457 elif self.ui._progbar is not None:
1458 self.ui._progbar.progress(self.topic, self.pos, item=item,
1459 unit=self.unit, total=self.total)
1460
1461 # Looking up progress.debug in tight loops is expensive. The value
1462 # is cached on the progbar object and we can avoid the lookup in
1463 # the common case where a progbar is active.
1464 if self.pos is None or not self.ui._progbar.debug:
1465 return
1466
1467 # Keep this logic in sync with above.
1468 if self.pos is None or not self.ui.configbool('progress', 'debug'):
1469 return
1470
1471 if self.unit:
1472 unit = ' ' + self.unit
1473 if item:
1474 item = ' ' + item
1475
1476 if self.total:
1477 pct = 100.0 * self.pos / self.total
1478 self.ui.debug('%s:%s %d/%d%s (%4.2f%%)\n'
1479 % (self.topic, item, self.pos, self.total, unit, pct))
1480 else:
1481 self.ui.debug('%s:%s %d%s\n' % (self.topic, item, self.pos, unit))
1447 1482
1448 1483 def gdinitconfig(ui):
1449 1484 """helper function to know if a repo should be created as general delta
1450 1485 """
1451 1486 # experimental config: format.generaldelta
1452 1487 return (ui.configbool('format', 'generaldelta')
1453 1488 or ui.configbool('format', 'usegeneraldelta'))
1454 1489
1455 1490 def gddeltaconfig(ui):
1456 1491 """helper function to know if incoming delta should be optimised
1457 1492 """
1458 1493 # experimental config: format.generaldelta
1459 1494 return ui.configbool('format', 'generaldelta')
1460 1495
1461 1496 class simplekeyvaluefile(object):
1462 1497 """A simple file with key=value lines
1463 1498
1464 1499 Keys must be alphanumerics and start with a letter, values must not
1465 1500 contain '\n' characters"""
1466 1501 firstlinekey = '__firstline'
1467 1502
1468 1503 def __init__(self, vfs, path, keys=None):
1469 1504 self.vfs = vfs
1470 1505 self.path = path
1471 1506
1472 1507 def read(self, firstlinenonkeyval=False):
1473 1508 """Read the contents of a simple key-value file
1474 1509
1475 1510 'firstlinenonkeyval' indicates whether the first line of file should
1476 1511 be treated as a key-value pair or reuturned fully under the
1477 1512 __firstline key."""
1478 1513 lines = self.vfs.readlines(self.path)
1479 1514 d = {}
1480 1515 if firstlinenonkeyval:
1481 1516 if not lines:
1482 1517 e = _("empty simplekeyvalue file")
1483 1518 raise error.CorruptedState(e)
1484 1519 # we don't want to include '\n' in the __firstline
1485 1520 d[self.firstlinekey] = lines[0][:-1]
1486 1521 del lines[0]
1487 1522
1488 1523 try:
1489 1524 # the 'if line.strip()' part prevents us from failing on empty
1490 1525 # lines which only contain '\n' therefore are not skipped
1491 1526 # by 'if line'
1492 1527 updatedict = dict(line[:-1].split('=', 1) for line in lines
1493 1528 if line.strip())
1494 1529 if self.firstlinekey in updatedict:
1495 1530 e = _("%r can't be used as a key")
1496 1531 raise error.CorruptedState(e % self.firstlinekey)
1497 1532 d.update(updatedict)
1498 1533 except ValueError as e:
1499 1534 raise error.CorruptedState(str(e))
1500 1535 return d
1501 1536
1502 1537 def write(self, data, firstline=None):
1503 1538 """Write key=>value mapping to a file
1504 1539 data is a dict. Keys must be alphanumerical and start with a letter.
1505 1540 Values must not contain newline characters.
1506 1541
1507 1542 If 'firstline' is not None, it is written to file before
1508 1543 everything else, as it is, not in a key=value form"""
1509 1544 lines = []
1510 1545 if firstline is not None:
1511 1546 lines.append('%s\n' % firstline)
1512 1547
1513 1548 for k, v in data.items():
1514 1549 if k == self.firstlinekey:
1515 1550 e = "key name '%s' is reserved" % self.firstlinekey
1516 1551 raise error.ProgrammingError(e)
1517 1552 if not k[0:1].isalpha():
1518 1553 e = "keys must start with a letter in a key-value file"
1519 1554 raise error.ProgrammingError(e)
1520 1555 if not k.isalnum():
1521 1556 e = "invalid key name in a simple key-value file"
1522 1557 raise error.ProgrammingError(e)
1523 1558 if '\n' in v:
1524 1559 e = "invalid value in a simple key-value file"
1525 1560 raise error.ProgrammingError(e)
1526 1561 lines.append("%s=%s\n" % (k, v))
1527 1562 with self.vfs(self.path, mode='wb', atomictemp=True) as fp:
1528 1563 fp.write(''.join(lines))
1529 1564
1530 1565 _reportobsoletedsource = [
1531 1566 'debugobsolete',
1532 1567 'pull',
1533 1568 'push',
1534 1569 'serve',
1535 1570 'unbundle',
1536 1571 ]
1537 1572
1538 1573 _reportnewcssource = [
1539 1574 'pull',
1540 1575 'unbundle',
1541 1576 ]
1542 1577
1543 1578 def prefetchfiles(repo, revs, match):
1544 1579 """Invokes the registered file prefetch functions, allowing extensions to
1545 1580 ensure the corresponding files are available locally, before the command
1546 1581 uses them."""
1547 1582 if match:
1548 1583 # The command itself will complain about files that don't exist, so
1549 1584 # don't duplicate the message.
1550 1585 match = matchmod.badmatch(match, lambda fn, msg: None)
1551 1586 else:
1552 1587 match = matchall(repo)
1553 1588
1554 1589 fileprefetchhooks(repo, revs, match)
1555 1590
1556 1591 # a list of (repo, revs, match) prefetch functions
1557 1592 fileprefetchhooks = util.hooks()
1558 1593
1559 1594 # A marker that tells the evolve extension to suppress its own reporting
1560 1595 _reportstroubledchangesets = True
1561 1596
1562 1597 def registersummarycallback(repo, otr, txnname=''):
1563 1598 """register a callback to issue a summary after the transaction is closed
1564 1599 """
1565 1600 def txmatch(sources):
1566 1601 return any(txnname.startswith(source) for source in sources)
1567 1602
1568 1603 categories = []
1569 1604
1570 1605 def reportsummary(func):
1571 1606 """decorator for report callbacks."""
1572 1607 # The repoview life cycle is shorter than the one of the actual
1573 1608 # underlying repository. So the filtered object can die before the
1574 1609 # weakref is used leading to troubles. We keep a reference to the
1575 1610 # unfiltered object and restore the filtering when retrieving the
1576 1611 # repository through the weakref.
1577 1612 filtername = repo.filtername
1578 1613 reporef = weakref.ref(repo.unfiltered())
1579 1614 def wrapped(tr):
1580 1615 repo = reporef()
1581 1616 if filtername:
1582 1617 repo = repo.filtered(filtername)
1583 1618 func(repo, tr)
1584 1619 newcat = '%02i-txnreport' % len(categories)
1585 1620 otr.addpostclose(newcat, wrapped)
1586 1621 categories.append(newcat)
1587 1622 return wrapped
1588 1623
1589 1624 if txmatch(_reportobsoletedsource):
1590 1625 @reportsummary
1591 1626 def reportobsoleted(repo, tr):
1592 1627 obsoleted = obsutil.getobsoleted(repo, tr)
1593 1628 if obsoleted:
1594 1629 repo.ui.status(_('obsoleted %i changesets\n')
1595 1630 % len(obsoleted))
1596 1631
1597 1632 if (obsolete.isenabled(repo, obsolete.createmarkersopt) and
1598 1633 repo.ui.configbool('experimental', 'evolution.report-instabilities')):
1599 1634 instabilitytypes = [
1600 1635 ('orphan', 'orphan'),
1601 1636 ('phase-divergent', 'phasedivergent'),
1602 1637 ('content-divergent', 'contentdivergent'),
1603 1638 ]
1604 1639
1605 1640 def getinstabilitycounts(repo):
1606 1641 filtered = repo.changelog.filteredrevs
1607 1642 counts = {}
1608 1643 for instability, revset in instabilitytypes:
1609 1644 counts[instability] = len(set(obsolete.getrevs(repo, revset)) -
1610 1645 filtered)
1611 1646 return counts
1612 1647
1613 1648 oldinstabilitycounts = getinstabilitycounts(repo)
1614 1649 @reportsummary
1615 1650 def reportnewinstabilities(repo, tr):
1616 1651 newinstabilitycounts = getinstabilitycounts(repo)
1617 1652 for instability, revset in instabilitytypes:
1618 1653 delta = (newinstabilitycounts[instability] -
1619 1654 oldinstabilitycounts[instability])
1620 1655 msg = getinstabilitymessage(delta, instability)
1621 1656 if msg:
1622 1657 repo.ui.warn(msg)
1623 1658
1624 1659 if txmatch(_reportnewcssource):
1625 1660 @reportsummary
1626 1661 def reportnewcs(repo, tr):
1627 1662 """Report the range of new revisions pulled/unbundled."""
1628 1663 origrepolen = tr.changes.get('origrepolen', len(repo))
1629 1664 unfi = repo.unfiltered()
1630 1665 if origrepolen >= len(unfi):
1631 1666 return
1632 1667
1633 1668 # Compute the bounds of new visible revisions' range.
1634 1669 revs = smartset.spanset(repo, start=origrepolen)
1635 1670 if revs:
1636 1671 minrev, maxrev = repo[revs.min()], repo[revs.max()]
1637 1672
1638 1673 if minrev == maxrev:
1639 1674 revrange = minrev
1640 1675 else:
1641 1676 revrange = '%s:%s' % (minrev, maxrev)
1642 1677 draft = len(repo.revs('%ld and draft()', revs))
1643 1678 secret = len(repo.revs('%ld and secret()', revs))
1644 1679 if not (draft or secret):
1645 1680 msg = _('new changesets %s\n') % revrange
1646 1681 elif draft and secret:
1647 1682 msg = _('new changesets %s (%d drafts, %d secrets)\n')
1648 1683 msg %= (revrange, draft, secret)
1649 1684 elif draft:
1650 1685 msg = _('new changesets %s (%d drafts)\n')
1651 1686 msg %= (revrange, draft)
1652 1687 elif secret:
1653 1688 msg = _('new changesets %s (%d secrets)\n')
1654 1689 msg %= (revrange, secret)
1655 1690 else:
1656 1691 errormsg = 'entered unreachable condition'
1657 1692 raise error.ProgrammingError(errormsg)
1658 1693 repo.ui.status(msg)
1659 1694
1660 1695 # search new changesets directly pulled as obsolete
1661 1696 duplicates = tr.changes.get('revduplicates', ())
1662 1697 obsadded = unfi.revs('(%d: + %ld) and obsolete()',
1663 1698 origrepolen, duplicates)
1664 1699 cl = repo.changelog
1665 1700 extinctadded = [r for r in obsadded if r not in cl]
1666 1701 if extinctadded:
1667 1702 # They are not just obsolete, but obsolete and invisible
1668 1703 # we call them "extinct" internally but the terms have not been
1669 1704 # exposed to users.
1670 1705 msg = '(%d other changesets obsolete on arrival)\n'
1671 1706 repo.ui.status(msg % len(extinctadded))
1672 1707
1673 1708 @reportsummary
1674 1709 def reportphasechanges(repo, tr):
1675 1710 """Report statistics of phase changes for changesets pre-existing
1676 1711 pull/unbundle.
1677 1712 """
1678 1713 origrepolen = tr.changes.get('origrepolen', len(repo))
1679 1714 phasetracking = tr.changes.get('phases', {})
1680 1715 if not phasetracking:
1681 1716 return
1682 1717 published = [
1683 1718 rev for rev, (old, new) in phasetracking.iteritems()
1684 1719 if new == phases.public and rev < origrepolen
1685 1720 ]
1686 1721 if not published:
1687 1722 return
1688 1723 repo.ui.status(_('%d local changesets published\n')
1689 1724 % len(published))
1690 1725
1691 1726 def getinstabilitymessage(delta, instability):
1692 1727 """function to return the message to show warning about new instabilities
1693 1728
1694 1729 exists as a separate function so that extension can wrap to show more
1695 1730 information like how to fix instabilities"""
1696 1731 if delta > 0:
1697 1732 return _('%i new %s changesets\n') % (delta, instability)
1698 1733
1699 1734 def nodesummaries(repo, nodes, maxnumnodes=4):
1700 1735 if len(nodes) <= maxnumnodes or repo.ui.verbose:
1701 1736 return ' '.join(short(h) for h in nodes)
1702 1737 first = ' '.join(short(h) for h in nodes[:maxnumnodes])
1703 1738 return _("%s and %d others") % (first, len(nodes) - maxnumnodes)
1704 1739
1705 1740 def enforcesinglehead(repo, tr, desc):
1706 1741 """check that no named branch has multiple heads"""
1707 1742 if desc in ('strip', 'repair'):
1708 1743 # skip the logic during strip
1709 1744 return
1710 1745 visible = repo.filtered('visible')
1711 1746 # possible improvement: we could restrict the check to affected branch
1712 1747 for name, heads in visible.branchmap().iteritems():
1713 1748 if len(heads) > 1:
1714 1749 msg = _('rejecting multiple heads on branch "%s"')
1715 1750 msg %= name
1716 1751 hint = _('%d heads: %s')
1717 1752 hint %= (len(heads), nodesummaries(repo, heads))
1718 1753 raise error.Abort(msg, hint=hint)
1719 1754
1720 1755 def wrapconvertsink(sink):
1721 1756 """Allow extensions to wrap the sink returned by convcmd.convertsink()
1722 1757 before it is used, whether or not the convert extension was formally loaded.
1723 1758 """
1724 1759 return sink
1725 1760
1726 1761 def unhidehashlikerevs(repo, specs, hiddentype):
1727 1762 """parse the user specs and unhide changesets whose hash or revision number
1728 1763 is passed.
1729 1764
1730 1765 hiddentype can be: 1) 'warn': warn while unhiding changesets
1731 1766 2) 'nowarn': don't warn while unhiding changesets
1732 1767
1733 1768 returns a repo object with the required changesets unhidden
1734 1769 """
1735 1770 if not repo.filtername or not repo.ui.configbool('experimental',
1736 1771 'directaccess'):
1737 1772 return repo
1738 1773
1739 1774 if repo.filtername not in ('visible', 'visible-hidden'):
1740 1775 return repo
1741 1776
1742 1777 symbols = set()
1743 1778 for spec in specs:
1744 1779 try:
1745 1780 tree = revsetlang.parse(spec)
1746 1781 except error.ParseError: # will be reported by scmutil.revrange()
1747 1782 continue
1748 1783
1749 1784 symbols.update(revsetlang.gethashlikesymbols(tree))
1750 1785
1751 1786 if not symbols:
1752 1787 return repo
1753 1788
1754 1789 revs = _getrevsfromsymbols(repo, symbols)
1755 1790
1756 1791 if not revs:
1757 1792 return repo
1758 1793
1759 1794 if hiddentype == 'warn':
1760 1795 unfi = repo.unfiltered()
1761 1796 revstr = ", ".join([pycompat.bytestr(unfi[l]) for l in revs])
1762 1797 repo.ui.warn(_("warning: accessing hidden changesets for write "
1763 1798 "operation: %s\n") % revstr)
1764 1799
1765 1800 # we have to use new filtername to separate branch/tags cache until we can
1766 1801 # disbale these cache when revisions are dynamically pinned.
1767 1802 return repo.filtered('visible-hidden', revs)
1768 1803
1769 1804 def _getrevsfromsymbols(repo, symbols):
1770 1805 """parse the list of symbols and returns a set of revision numbers of hidden
1771 1806 changesets present in symbols"""
1772 1807 revs = set()
1773 1808 unfi = repo.unfiltered()
1774 1809 unficl = unfi.changelog
1775 1810 cl = repo.changelog
1776 1811 tiprev = len(unficl)
1777 1812 allowrevnums = repo.ui.configbool('experimental', 'directaccess.revnums')
1778 1813 for s in symbols:
1779 1814 try:
1780 1815 n = int(s)
1781 1816 if n <= tiprev:
1782 1817 if not allowrevnums:
1783 1818 continue
1784 1819 else:
1785 1820 if n not in cl:
1786 1821 revs.add(n)
1787 1822 continue
1788 1823 except ValueError:
1789 1824 pass
1790 1825
1791 1826 try:
1792 1827 s = resolvehexnodeidprefix(unfi, s)
1793 1828 except (error.LookupError, error.WdirUnsupported):
1794 1829 s = None
1795 1830
1796 1831 if s is not None:
1797 1832 rev = unficl.rev(s)
1798 1833 if rev not in cl:
1799 1834 revs.add(rev)
1800 1835
1801 1836 return revs
1802 1837
1803 1838 def bookmarkrevs(repo, mark):
1804 1839 """
1805 1840 Select revisions reachable by a given bookmark
1806 1841 """
1807 1842 return repo.revs("ancestors(bookmark(%s)) - "
1808 1843 "ancestors(head() and not bookmark(%s)) - "
1809 1844 "ancestors(bookmark() and not bookmark(%s))",
1810 1845 mark, mark, mark)
@@ -1,2056 +1,2028
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 traceback
22 22
23 23 from .i18n import _
24 24 from .node import hex
25 25
26 26 from . import (
27 27 color,
28 28 config,
29 29 configitems,
30 30 encoding,
31 31 error,
32 32 formatter,
33 33 loggingutil,
34 34 progress,
35 35 pycompat,
36 36 rcutil,
37 37 scmutil,
38 38 util,
39 39 )
40 40 from .utils import (
41 41 dateutil,
42 42 procutil,
43 43 stringutil,
44 44 )
45 45
46 46 urlreq = util.urlreq
47 47
48 48 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
49 49 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
50 50 if not c.isalnum())
51 51
52 52 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
53 53 tweakrc = b"""
54 54 [ui]
55 55 # The rollback command is dangerous. As a rule, don't use it.
56 56 rollback = False
57 57 # Make `hg status` report copy information
58 58 statuscopies = yes
59 59 # Prefer curses UIs when available. Revert to plain-text with `text`.
60 60 interface = curses
61 61
62 62 [commands]
63 63 # Grep working directory by default.
64 64 grep.all-files = True
65 65 # Make `hg status` emit cwd-relative paths by default.
66 66 status.relative = yes
67 67 # Refuse to perform an `hg update` that would cause a file content merge
68 68 update.check = noconflict
69 69 # Show conflicts information in `hg status`
70 70 status.verbose = True
71 71
72 72 [diff]
73 73 git = 1
74 74 showfunc = 1
75 75 word-diff = 1
76 76 """
77 77
78 78 samplehgrcs = {
79 79 'user':
80 80 b"""# example user config (see 'hg help config' for more info)
81 81 [ui]
82 82 # name and email, e.g.
83 83 # username = Jane Doe <jdoe@example.com>
84 84 username =
85 85
86 86 # We recommend enabling tweakdefaults to get slight improvements to
87 87 # the UI over time. Make sure to set HGPLAIN in the environment when
88 88 # writing scripts!
89 89 # tweakdefaults = True
90 90
91 91 # uncomment to disable color in command output
92 92 # (see 'hg help color' for details)
93 93 # color = never
94 94
95 95 # uncomment to disable command output pagination
96 96 # (see 'hg help pager' for details)
97 97 # paginate = never
98 98
99 99 [extensions]
100 100 # uncomment these lines to enable some popular extensions
101 101 # (see 'hg help extensions' for more info)
102 102 #
103 103 # churn =
104 104 """,
105 105
106 106 'cloned':
107 107 b"""# example repository config (see 'hg help config' for more info)
108 108 [paths]
109 109 default = %s
110 110
111 111 # path aliases to other clones of this repo in URLs or filesystem paths
112 112 # (see 'hg help config.paths' for more info)
113 113 #
114 114 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
115 115 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
116 116 # my-clone = /home/jdoe/jdoes-clone
117 117
118 118 [ui]
119 119 # name and email (local to this repository, optional), e.g.
120 120 # username = Jane Doe <jdoe@example.com>
121 121 """,
122 122
123 123 'local':
124 124 b"""# example repository config (see 'hg help config' for more info)
125 125 [paths]
126 126 # path aliases to other clones of this repo in URLs or filesystem paths
127 127 # (see 'hg help config.paths' for more info)
128 128 #
129 129 # default = http://example.com/hg/example-repo
130 130 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
131 131 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
132 132 # my-clone = /home/jdoe/jdoes-clone
133 133
134 134 [ui]
135 135 # name and email (local to this repository, optional), e.g.
136 136 # username = Jane Doe <jdoe@example.com>
137 137 """,
138 138
139 139 'global':
140 140 b"""# example system-wide hg config (see 'hg help config' for more info)
141 141
142 142 [ui]
143 143 # uncomment to disable color in command output
144 144 # (see 'hg help color' for details)
145 145 # color = never
146 146
147 147 # uncomment to disable command output pagination
148 148 # (see 'hg help pager' for details)
149 149 # paginate = never
150 150
151 151 [extensions]
152 152 # uncomment these lines to enable some popular extensions
153 153 # (see 'hg help extensions' for more info)
154 154 #
155 155 # blackbox =
156 156 # churn =
157 157 """,
158 158 }
159 159
160 160 def _maybestrurl(maybebytes):
161 161 return pycompat.rapply(pycompat.strurl, maybebytes)
162 162
163 163 def _maybebytesurl(maybestr):
164 164 return pycompat.rapply(pycompat.bytesurl, maybestr)
165 165
166 166 class httppasswordmgrdbproxy(object):
167 167 """Delays loading urllib2 until it's needed."""
168 168 def __init__(self):
169 169 self._mgr = None
170 170
171 171 def _get_mgr(self):
172 172 if self._mgr is None:
173 173 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
174 174 return self._mgr
175 175
176 176 def add_password(self, realm, uris, user, passwd):
177 177 return self._get_mgr().add_password(
178 178 _maybestrurl(realm), _maybestrurl(uris),
179 179 _maybestrurl(user), _maybestrurl(passwd))
180 180
181 181 def find_user_password(self, realm, uri):
182 182 mgr = self._get_mgr()
183 183 return _maybebytesurl(mgr.find_user_password(_maybestrurl(realm),
184 184 _maybestrurl(uri)))
185 185
186 186 def _catchterm(*args):
187 187 raise error.SignalInterrupt
188 188
189 189 # unique object used to detect no default value has been provided when
190 190 # retrieving configuration value.
191 191 _unset = object()
192 192
193 193 # _reqexithandlers: callbacks run at the end of a request
194 194 _reqexithandlers = []
195 195
196 196 class ui(object):
197 197 def __init__(self, src=None):
198 198 """Create a fresh new ui object if no src given
199 199
200 200 Use uimod.ui.load() to create a ui which knows global and user configs.
201 201 In most cases, you should use ui.copy() to create a copy of an existing
202 202 ui object.
203 203 """
204 204 # _buffers: used for temporary capture of output
205 205 self._buffers = []
206 206 # 3-tuple describing how each buffer in the stack behaves.
207 207 # Values are (capture stderr, capture subprocesses, apply labels).
208 208 self._bufferstates = []
209 209 # When a buffer is active, defines whether we are expanding labels.
210 210 # This exists to prevent an extra list lookup.
211 211 self._bufferapplylabels = None
212 212 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
213 213 self._reportuntrusted = True
214 214 self._knownconfig = configitems.coreitems
215 215 self._ocfg = config.config() # overlay
216 216 self._tcfg = config.config() # trusted
217 217 self._ucfg = config.config() # untrusted
218 218 self._trustusers = set()
219 219 self._trustgroups = set()
220 220 self.callhooks = True
221 221 # Insecure server connections requested.
222 222 self.insecureconnections = False
223 223 # Blocked time
224 224 self.logblockedtimes = False
225 225 # color mode: see mercurial/color.py for possible value
226 226 self._colormode = None
227 227 self._terminfoparams = {}
228 228 self._styles = {}
229 229 self._uninterruptible = False
230 230
231 231 if src:
232 232 self._fout = src._fout
233 233 self._ferr = src._ferr
234 234 self._fin = src._fin
235 235 self._fmsg = src._fmsg
236 236 self._fmsgout = src._fmsgout
237 237 self._fmsgerr = src._fmsgerr
238 238 self._finoutredirected = src._finoutredirected
239 239 self._loggers = src._loggers.copy()
240 240 self.pageractive = src.pageractive
241 241 self._disablepager = src._disablepager
242 242 self._tweaked = src._tweaked
243 243
244 244 self._tcfg = src._tcfg.copy()
245 245 self._ucfg = src._ucfg.copy()
246 246 self._ocfg = src._ocfg.copy()
247 247 self._trustusers = src._trustusers.copy()
248 248 self._trustgroups = src._trustgroups.copy()
249 249 self.environ = src.environ
250 250 self.callhooks = src.callhooks
251 251 self.insecureconnections = src.insecureconnections
252 252 self._colormode = src._colormode
253 253 self._terminfoparams = src._terminfoparams.copy()
254 254 self._styles = src._styles.copy()
255 255
256 256 self.fixconfig()
257 257
258 258 self.httppasswordmgrdb = src.httppasswordmgrdb
259 259 self._blockedtimes = src._blockedtimes
260 260 else:
261 261 self._fout = procutil.stdout
262 262 self._ferr = procutil.stderr
263 263 self._fin = procutil.stdin
264 264 self._fmsg = None
265 265 self._fmsgout = self.fout # configurable
266 266 self._fmsgerr = self.ferr # configurable
267 267 self._finoutredirected = False
268 268 self._loggers = {}
269 269 self.pageractive = False
270 270 self._disablepager = False
271 271 self._tweaked = False
272 272
273 273 # shared read-only environment
274 274 self.environ = encoding.environ
275 275
276 276 self.httppasswordmgrdb = httppasswordmgrdbproxy()
277 277 self._blockedtimes = collections.defaultdict(int)
278 278
279 279 allowed = self.configlist('experimental', 'exportableenviron')
280 280 if '*' in allowed:
281 281 self._exportableenviron = self.environ
282 282 else:
283 283 self._exportableenviron = {}
284 284 for k in allowed:
285 285 if k in self.environ:
286 286 self._exportableenviron[k] = self.environ[k]
287 287
288 288 @classmethod
289 289 def load(cls):
290 290 """Create a ui and load global and user configs"""
291 291 u = cls()
292 292 # we always trust global config files and environment variables
293 293 for t, f in rcutil.rccomponents():
294 294 if t == 'path':
295 295 u.readconfig(f, trust=True)
296 296 elif t == 'items':
297 297 sections = set()
298 298 for section, name, value, source in f:
299 299 # do not set u._ocfg
300 300 # XXX clean this up once immutable config object is a thing
301 301 u._tcfg.set(section, name, value, source)
302 302 u._ucfg.set(section, name, value, source)
303 303 sections.add(section)
304 304 for section in sections:
305 305 u.fixconfig(section=section)
306 306 else:
307 307 raise error.ProgrammingError('unknown rctype: %s' % t)
308 308 u._maybetweakdefaults()
309 309 return u
310 310
311 311 def _maybetweakdefaults(self):
312 312 if not self.configbool('ui', 'tweakdefaults'):
313 313 return
314 314 if self._tweaked or self.plain('tweakdefaults'):
315 315 return
316 316
317 317 # Note: it is SUPER IMPORTANT that you set self._tweaked to
318 318 # True *before* any calls to setconfig(), otherwise you'll get
319 319 # infinite recursion between setconfig and this method.
320 320 #
321 321 # TODO: We should extract an inner method in setconfig() to
322 322 # avoid this weirdness.
323 323 self._tweaked = True
324 324 tmpcfg = config.config()
325 325 tmpcfg.parse('<tweakdefaults>', tweakrc)
326 326 for section in tmpcfg:
327 327 for name, value in tmpcfg.items(section):
328 328 if not self.hasconfig(section, name):
329 329 self.setconfig(section, name, value, "<tweakdefaults>")
330 330
331 331 def copy(self):
332 332 return self.__class__(self)
333 333
334 334 def resetstate(self):
335 335 """Clear internal state that shouldn't persist across commands"""
336 336 if self._progbar:
337 337 self._progbar.resetstate() # reset last-print time of progress bar
338 338 self.httppasswordmgrdb = httppasswordmgrdbproxy()
339 339
340 340 @contextlib.contextmanager
341 341 def timeblockedsection(self, key):
342 342 # this is open-coded below - search for timeblockedsection to find them
343 343 starttime = util.timer()
344 344 try:
345 345 yield
346 346 finally:
347 347 self._blockedtimes[key + '_blocked'] += \
348 348 (util.timer() - starttime) * 1000
349 349
350 350 @contextlib.contextmanager
351 351 def uninterruptible(self):
352 352 """Mark an operation as unsafe.
353 353
354 354 Most operations on a repository are safe to interrupt, but a
355 355 few are risky (for example repair.strip). This context manager
356 356 lets you advise Mercurial that something risky is happening so
357 357 that control-C etc can be blocked if desired.
358 358 """
359 359 enabled = self.configbool('experimental', 'nointerrupt')
360 360 if (enabled and
361 361 self.configbool('experimental', 'nointerrupt-interactiveonly')):
362 362 enabled = self.interactive()
363 363 if self._uninterruptible or not enabled:
364 364 # if nointerrupt support is turned off, the process isn't
365 365 # interactive, or we're already in an uninterruptible
366 366 # block, do nothing.
367 367 yield
368 368 return
369 369 def warn():
370 370 self.warn(_("shutting down cleanly\n"))
371 371 self.warn(
372 372 _("press ^C again to terminate immediately (dangerous)\n"))
373 373 return True
374 374 with procutil.uninterruptible(warn):
375 375 try:
376 376 self._uninterruptible = True
377 377 yield
378 378 finally:
379 379 self._uninterruptible = False
380 380
381 381 def formatter(self, topic, opts):
382 382 return formatter.formatter(self, self, topic, opts)
383 383
384 384 def _trusted(self, fp, f):
385 385 st = util.fstat(fp)
386 386 if util.isowner(st):
387 387 return True
388 388
389 389 tusers, tgroups = self._trustusers, self._trustgroups
390 390 if '*' in tusers or '*' in tgroups:
391 391 return True
392 392
393 393 user = util.username(st.st_uid)
394 394 group = util.groupname(st.st_gid)
395 395 if user in tusers or group in tgroups or user == util.username():
396 396 return True
397 397
398 398 if self._reportuntrusted:
399 399 self.warn(_('not trusting file %s from untrusted '
400 400 'user %s, group %s\n') % (f, user, group))
401 401 return False
402 402
403 403 def readconfig(self, filename, root=None, trust=False,
404 404 sections=None, remap=None):
405 405 try:
406 406 fp = open(filename, r'rb')
407 407 except IOError:
408 408 if not sections: # ignore unless we were looking for something
409 409 return
410 410 raise
411 411
412 412 cfg = config.config()
413 413 trusted = sections or trust or self._trusted(fp, filename)
414 414
415 415 try:
416 416 cfg.read(filename, fp, sections=sections, remap=remap)
417 417 fp.close()
418 418 except error.ConfigError as inst:
419 419 if trusted:
420 420 raise
421 421 self.warn(_("ignored: %s\n") % stringutil.forcebytestr(inst))
422 422
423 423 if self.plain():
424 424 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
425 425 'logtemplate', 'message-output', 'statuscopies', 'style',
426 426 'traceback', 'verbose'):
427 427 if k in cfg['ui']:
428 428 del cfg['ui'][k]
429 429 for k, v in cfg.items('defaults'):
430 430 del cfg['defaults'][k]
431 431 for k, v in cfg.items('commands'):
432 432 del cfg['commands'][k]
433 433 # Don't remove aliases from the configuration if in the exceptionlist
434 434 if self.plain('alias'):
435 435 for k, v in cfg.items('alias'):
436 436 del cfg['alias'][k]
437 437 if self.plain('revsetalias'):
438 438 for k, v in cfg.items('revsetalias'):
439 439 del cfg['revsetalias'][k]
440 440 if self.plain('templatealias'):
441 441 for k, v in cfg.items('templatealias'):
442 442 del cfg['templatealias'][k]
443 443
444 444 if trusted:
445 445 self._tcfg.update(cfg)
446 446 self._tcfg.update(self._ocfg)
447 447 self._ucfg.update(cfg)
448 448 self._ucfg.update(self._ocfg)
449 449
450 450 if root is None:
451 451 root = os.path.expanduser('~')
452 452 self.fixconfig(root=root)
453 453
454 454 def fixconfig(self, root=None, section=None):
455 455 if section in (None, 'paths'):
456 456 # expand vars and ~
457 457 # translate paths relative to root (or home) into absolute paths
458 458 root = root or encoding.getcwd()
459 459 for c in self._tcfg, self._ucfg, self._ocfg:
460 460 for n, p in c.items('paths'):
461 461 # Ignore sub-options.
462 462 if ':' in n:
463 463 continue
464 464 if not p:
465 465 continue
466 466 if '%%' in p:
467 467 s = self.configsource('paths', n) or 'none'
468 468 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
469 469 % (n, p, s))
470 470 p = p.replace('%%', '%')
471 471 p = util.expandpath(p)
472 472 if not util.hasscheme(p) and not os.path.isabs(p):
473 473 p = os.path.normpath(os.path.join(root, p))
474 474 c.set("paths", n, p)
475 475
476 476 if section in (None, 'ui'):
477 477 # update ui options
478 478 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
479 479 self.debugflag = self.configbool('ui', 'debug')
480 480 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
481 481 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
482 482 if self.verbose and self.quiet:
483 483 self.quiet = self.verbose = False
484 484 self._reportuntrusted = self.debugflag or self.configbool("ui",
485 485 "report_untrusted")
486 486 self.tracebackflag = self.configbool('ui', 'traceback')
487 487 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
488 488
489 489 if section in (None, 'trusted'):
490 490 # update trust information
491 491 self._trustusers.update(self.configlist('trusted', 'users'))
492 492 self._trustgroups.update(self.configlist('trusted', 'groups'))
493 493
494 494 if section in (None, b'devel', b'ui') and self.debugflag:
495 495 tracked = set()
496 496 if self.configbool(b'devel', b'debug.extensions'):
497 497 tracked.add(b'extension')
498 498 if tracked:
499 499 logger = loggingutil.fileobjectlogger(self._ferr, tracked)
500 500 self.setlogger(b'debug', logger)
501 501
502 502 def backupconfig(self, section, item):
503 503 return (self._ocfg.backup(section, item),
504 504 self._tcfg.backup(section, item),
505 505 self._ucfg.backup(section, item),)
506 506 def restoreconfig(self, data):
507 507 self._ocfg.restore(data[0])
508 508 self._tcfg.restore(data[1])
509 509 self._ucfg.restore(data[2])
510 510
511 511 def setconfig(self, section, name, value, source=''):
512 512 for cfg in (self._ocfg, self._tcfg, self._ucfg):
513 513 cfg.set(section, name, value, source)
514 514 self.fixconfig(section=section)
515 515 self._maybetweakdefaults()
516 516
517 517 def _data(self, untrusted):
518 518 return untrusted and self._ucfg or self._tcfg
519 519
520 520 def configsource(self, section, name, untrusted=False):
521 521 return self._data(untrusted).source(section, name)
522 522
523 523 def config(self, section, name, default=_unset, untrusted=False):
524 524 """return the plain string version of a config"""
525 525 value = self._config(section, name, default=default,
526 526 untrusted=untrusted)
527 527 if value is _unset:
528 528 return None
529 529 return value
530 530
531 531 def _config(self, section, name, default=_unset, untrusted=False):
532 532 value = itemdefault = default
533 533 item = self._knownconfig.get(section, {}).get(name)
534 534 alternates = [(section, name)]
535 535
536 536 if item is not None:
537 537 alternates.extend(item.alias)
538 538 if callable(item.default):
539 539 itemdefault = item.default()
540 540 else:
541 541 itemdefault = item.default
542 542 else:
543 543 msg = ("accessing unregistered config item: '%s.%s'")
544 544 msg %= (section, name)
545 545 self.develwarn(msg, 2, 'warn-config-unknown')
546 546
547 547 if default is _unset:
548 548 if item is None:
549 549 value = default
550 550 elif item.default is configitems.dynamicdefault:
551 551 value = None
552 552 msg = "config item requires an explicit default value: '%s.%s'"
553 553 msg %= (section, name)
554 554 self.develwarn(msg, 2, 'warn-config-default')
555 555 else:
556 556 value = itemdefault
557 557 elif (item is not None
558 558 and item.default is not configitems.dynamicdefault
559 559 and default != itemdefault):
560 560 msg = ("specifying a mismatched default value for a registered "
561 561 "config item: '%s.%s' '%s'")
562 562 msg %= (section, name, pycompat.bytestr(default))
563 563 self.develwarn(msg, 2, 'warn-config-default')
564 564
565 565 for s, n in alternates:
566 566 candidate = self._data(untrusted).get(s, n, None)
567 567 if candidate is not None:
568 568 value = candidate
569 569 section = s
570 570 name = n
571 571 break
572 572
573 573 if self.debugflag and not untrusted and self._reportuntrusted:
574 574 for s, n in alternates:
575 575 uvalue = self._ucfg.get(s, n)
576 576 if uvalue is not None and uvalue != value:
577 577 self.debug("ignoring untrusted configuration option "
578 578 "%s.%s = %s\n" % (s, n, uvalue))
579 579 return value
580 580
581 581 def configsuboptions(self, section, name, default=_unset, untrusted=False):
582 582 """Get a config option and all sub-options.
583 583
584 584 Some config options have sub-options that are declared with the
585 585 format "key:opt = value". This method is used to return the main
586 586 option and all its declared sub-options.
587 587
588 588 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
589 589 is a dict of defined sub-options where keys and values are strings.
590 590 """
591 591 main = self.config(section, name, default, untrusted=untrusted)
592 592 data = self._data(untrusted)
593 593 sub = {}
594 594 prefix = '%s:' % name
595 595 for k, v in data.items(section):
596 596 if k.startswith(prefix):
597 597 sub[k[len(prefix):]] = v
598 598
599 599 if self.debugflag and not untrusted and self._reportuntrusted:
600 600 for k, v in sub.items():
601 601 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
602 602 if uvalue is not None and uvalue != v:
603 603 self.debug('ignoring untrusted configuration option '
604 604 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
605 605
606 606 return main, sub
607 607
608 608 def configpath(self, section, name, default=_unset, untrusted=False):
609 609 'get a path config item, expanded relative to repo root or config file'
610 610 v = self.config(section, name, default, untrusted)
611 611 if v is None:
612 612 return None
613 613 if not os.path.isabs(v) or "://" not in v:
614 614 src = self.configsource(section, name, untrusted)
615 615 if ':' in src:
616 616 base = os.path.dirname(src.rsplit(':')[0])
617 617 v = os.path.join(base, os.path.expanduser(v))
618 618 return v
619 619
620 620 def configbool(self, section, name, default=_unset, untrusted=False):
621 621 """parse a configuration element as a boolean
622 622
623 623 >>> u = ui(); s = b'foo'
624 624 >>> u.setconfig(s, b'true', b'yes')
625 625 >>> u.configbool(s, b'true')
626 626 True
627 627 >>> u.setconfig(s, b'false', b'no')
628 628 >>> u.configbool(s, b'false')
629 629 False
630 630 >>> u.configbool(s, b'unknown')
631 631 False
632 632 >>> u.configbool(s, b'unknown', True)
633 633 True
634 634 >>> u.setconfig(s, b'invalid', b'somevalue')
635 635 >>> u.configbool(s, b'invalid')
636 636 Traceback (most recent call last):
637 637 ...
638 638 ConfigError: foo.invalid is not a boolean ('somevalue')
639 639 """
640 640
641 641 v = self._config(section, name, default, untrusted=untrusted)
642 642 if v is None:
643 643 return v
644 644 if v is _unset:
645 645 if default is _unset:
646 646 return False
647 647 return default
648 648 if isinstance(v, bool):
649 649 return v
650 650 b = stringutil.parsebool(v)
651 651 if b is None:
652 652 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
653 653 % (section, name, v))
654 654 return b
655 655
656 656 def configwith(self, convert, section, name, default=_unset,
657 657 desc=None, untrusted=False):
658 658 """parse a configuration element with a conversion function
659 659
660 660 >>> u = ui(); s = b'foo'
661 661 >>> u.setconfig(s, b'float1', b'42')
662 662 >>> u.configwith(float, s, b'float1')
663 663 42.0
664 664 >>> u.setconfig(s, b'float2', b'-4.25')
665 665 >>> u.configwith(float, s, b'float2')
666 666 -4.25
667 667 >>> u.configwith(float, s, b'unknown', 7)
668 668 7.0
669 669 >>> u.setconfig(s, b'invalid', b'somevalue')
670 670 >>> u.configwith(float, s, b'invalid')
671 671 Traceback (most recent call last):
672 672 ...
673 673 ConfigError: foo.invalid is not a valid float ('somevalue')
674 674 >>> u.configwith(float, s, b'invalid', desc=b'womble')
675 675 Traceback (most recent call last):
676 676 ...
677 677 ConfigError: foo.invalid is not a valid womble ('somevalue')
678 678 """
679 679
680 680 v = self.config(section, name, default, untrusted)
681 681 if v is None:
682 682 return v # do not attempt to convert None
683 683 try:
684 684 return convert(v)
685 685 except (ValueError, error.ParseError):
686 686 if desc is None:
687 687 desc = pycompat.sysbytes(convert.__name__)
688 688 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
689 689 % (section, name, desc, v))
690 690
691 691 def configint(self, section, name, default=_unset, untrusted=False):
692 692 """parse a configuration element as an integer
693 693
694 694 >>> u = ui(); s = b'foo'
695 695 >>> u.setconfig(s, b'int1', b'42')
696 696 >>> u.configint(s, b'int1')
697 697 42
698 698 >>> u.setconfig(s, b'int2', b'-42')
699 699 >>> u.configint(s, b'int2')
700 700 -42
701 701 >>> u.configint(s, b'unknown', 7)
702 702 7
703 703 >>> u.setconfig(s, b'invalid', b'somevalue')
704 704 >>> u.configint(s, b'invalid')
705 705 Traceback (most recent call last):
706 706 ...
707 707 ConfigError: foo.invalid is not a valid integer ('somevalue')
708 708 """
709 709
710 710 return self.configwith(int, section, name, default, 'integer',
711 711 untrusted)
712 712
713 713 def configbytes(self, section, name, default=_unset, untrusted=False):
714 714 """parse a configuration element as a quantity in bytes
715 715
716 716 Units can be specified as b (bytes), k or kb (kilobytes), m or
717 717 mb (megabytes), g or gb (gigabytes).
718 718
719 719 >>> u = ui(); s = b'foo'
720 720 >>> u.setconfig(s, b'val1', b'42')
721 721 >>> u.configbytes(s, b'val1')
722 722 42
723 723 >>> u.setconfig(s, b'val2', b'42.5 kb')
724 724 >>> u.configbytes(s, b'val2')
725 725 43520
726 726 >>> u.configbytes(s, b'unknown', b'7 MB')
727 727 7340032
728 728 >>> u.setconfig(s, b'invalid', b'somevalue')
729 729 >>> u.configbytes(s, b'invalid')
730 730 Traceback (most recent call last):
731 731 ...
732 732 ConfigError: foo.invalid is not a byte quantity ('somevalue')
733 733 """
734 734
735 735 value = self._config(section, name, default, untrusted)
736 736 if value is _unset:
737 737 if default is _unset:
738 738 default = 0
739 739 value = default
740 740 if not isinstance(value, bytes):
741 741 return value
742 742 try:
743 743 return util.sizetoint(value)
744 744 except error.ParseError:
745 745 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
746 746 % (section, name, value))
747 747
748 748 def configlist(self, section, name, default=_unset, untrusted=False):
749 749 """parse a configuration element as a list of comma/space separated
750 750 strings
751 751
752 752 >>> u = ui(); s = b'foo'
753 753 >>> u.setconfig(s, b'list1', b'this,is "a small" ,test')
754 754 >>> u.configlist(s, b'list1')
755 755 ['this', 'is', 'a small', 'test']
756 756 >>> u.setconfig(s, b'list2', b'this, is "a small" , test ')
757 757 >>> u.configlist(s, b'list2')
758 758 ['this', 'is', 'a small', 'test']
759 759 """
760 760 # default is not always a list
761 761 v = self.configwith(config.parselist, section, name, default,
762 762 'list', untrusted)
763 763 if isinstance(v, bytes):
764 764 return config.parselist(v)
765 765 elif v is None:
766 766 return []
767 767 return v
768 768
769 769 def configdate(self, section, name, default=_unset, untrusted=False):
770 770 """parse a configuration element as a tuple of ints
771 771
772 772 >>> u = ui(); s = b'foo'
773 773 >>> u.setconfig(s, b'date', b'0 0')
774 774 >>> u.configdate(s, b'date')
775 775 (0, 0)
776 776 """
777 777 if self.config(section, name, default, untrusted):
778 778 return self.configwith(dateutil.parsedate, section, name, default,
779 779 'date', untrusted)
780 780 if default is _unset:
781 781 return None
782 782 return default
783 783
784 784 def hasconfig(self, section, name, untrusted=False):
785 785 return self._data(untrusted).hasitem(section, name)
786 786
787 787 def has_section(self, section, untrusted=False):
788 788 '''tell whether section exists in config.'''
789 789 return section in self._data(untrusted)
790 790
791 791 def configitems(self, section, untrusted=False, ignoresub=False):
792 792 items = self._data(untrusted).items(section)
793 793 if ignoresub:
794 794 items = [i for i in items if ':' not in i[0]]
795 795 if self.debugflag and not untrusted and self._reportuntrusted:
796 796 for k, v in self._ucfg.items(section):
797 797 if self._tcfg.get(section, k) != v:
798 798 self.debug("ignoring untrusted configuration option "
799 799 "%s.%s = %s\n" % (section, k, v))
800 800 return items
801 801
802 802 def walkconfig(self, untrusted=False):
803 803 cfg = self._data(untrusted)
804 804 for section in cfg.sections():
805 805 for name, value in self.configitems(section, untrusted):
806 806 yield section, name, value
807 807
808 808 def plain(self, feature=None):
809 809 '''is plain mode active?
810 810
811 811 Plain mode means that all configuration variables which affect
812 812 the behavior and output of Mercurial should be
813 813 ignored. Additionally, the output should be stable,
814 814 reproducible and suitable for use in scripts or applications.
815 815
816 816 The only way to trigger plain mode is by setting either the
817 817 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
818 818
819 819 The return value can either be
820 820 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
821 821 - False if feature is disabled by default and not included in HGPLAIN
822 822 - True otherwise
823 823 '''
824 824 if ('HGPLAIN' not in encoding.environ and
825 825 'HGPLAINEXCEPT' not in encoding.environ):
826 826 return False
827 827 exceptions = encoding.environ.get('HGPLAINEXCEPT',
828 828 '').strip().split(',')
829 829 # TODO: add support for HGPLAIN=+feature,-feature syntax
830 830 if '+strictflags' not in encoding.environ.get('HGPLAIN', '').split(','):
831 831 exceptions.append('strictflags')
832 832 if feature and exceptions:
833 833 return feature not in exceptions
834 834 return True
835 835
836 836 def username(self, acceptempty=False):
837 837 """Return default username to be used in commits.
838 838
839 839 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
840 840 and stop searching if one of these is set.
841 841 If not found and acceptempty is True, returns None.
842 842 If not found and ui.askusername is True, ask the user, else use
843 843 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
844 844 If no username could be found, raise an Abort error.
845 845 """
846 846 user = encoding.environ.get("HGUSER")
847 847 if user is None:
848 848 user = self.config("ui", "username")
849 849 if user is not None:
850 850 user = os.path.expandvars(user)
851 851 if user is None:
852 852 user = encoding.environ.get("EMAIL")
853 853 if user is None and acceptempty:
854 854 return user
855 855 if user is None and self.configbool("ui", "askusername"):
856 856 user = self.prompt(_("enter a commit username:"), default=None)
857 857 if user is None and not self.interactive():
858 858 try:
859 859 user = '%s@%s' % (procutil.getuser(),
860 860 encoding.strtolocal(socket.getfqdn()))
861 861 self.warn(_("no username found, using '%s' instead\n") % user)
862 862 except KeyError:
863 863 pass
864 864 if not user:
865 865 raise error.Abort(_('no username supplied'),
866 866 hint=_("use 'hg config --edit' "
867 867 'to set your username'))
868 868 if "\n" in user:
869 869 raise error.Abort(_("username %r contains a newline\n")
870 870 % pycompat.bytestr(user))
871 871 return user
872 872
873 873 def shortuser(self, user):
874 874 """Return a short representation of a user name or email address."""
875 875 if not self.verbose:
876 876 user = stringutil.shortuser(user)
877 877 return user
878 878
879 879 def expandpath(self, loc, default=None):
880 880 """Return repository location relative to cwd or from [paths]"""
881 881 try:
882 882 p = self.paths.getpath(loc)
883 883 if p:
884 884 return p.rawloc
885 885 except error.RepoError:
886 886 pass
887 887
888 888 if default:
889 889 try:
890 890 p = self.paths.getpath(default)
891 891 if p:
892 892 return p.rawloc
893 893 except error.RepoError:
894 894 pass
895 895
896 896 return loc
897 897
898 898 @util.propertycache
899 899 def paths(self):
900 900 return paths(self)
901 901
902 902 @property
903 903 def fout(self):
904 904 return self._fout
905 905
906 906 @fout.setter
907 907 def fout(self, f):
908 908 self._fout = f
909 909 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
910 910
911 911 @property
912 912 def ferr(self):
913 913 return self._ferr
914 914
915 915 @ferr.setter
916 916 def ferr(self, f):
917 917 self._ferr = f
918 918 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
919 919
920 920 @property
921 921 def fin(self):
922 922 return self._fin
923 923
924 924 @fin.setter
925 925 def fin(self, f):
926 926 self._fin = f
927 927
928 928 @property
929 929 def fmsg(self):
930 930 """Stream dedicated for status/error messages; may be None if
931 931 fout/ferr are used"""
932 932 return self._fmsg
933 933
934 934 @fmsg.setter
935 935 def fmsg(self, f):
936 936 self._fmsg = f
937 937 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
938 938
939 939 def pushbuffer(self, error=False, subproc=False, labeled=False):
940 940 """install a buffer to capture standard output of the ui object
941 941
942 942 If error is True, the error output will be captured too.
943 943
944 944 If subproc is True, output from subprocesses (typically hooks) will be
945 945 captured too.
946 946
947 947 If labeled is True, any labels associated with buffered
948 948 output will be handled. By default, this has no effect
949 949 on the output returned, but extensions and GUI tools may
950 950 handle this argument and returned styled output. If output
951 951 is being buffered so it can be captured and parsed or
952 952 processed, labeled should not be set to True.
953 953 """
954 954 self._buffers.append([])
955 955 self._bufferstates.append((error, subproc, labeled))
956 956 self._bufferapplylabels = labeled
957 957
958 958 def popbuffer(self):
959 959 '''pop the last buffer and return the buffered output'''
960 960 self._bufferstates.pop()
961 961 if self._bufferstates:
962 962 self._bufferapplylabels = self._bufferstates[-1][2]
963 963 else:
964 964 self._bufferapplylabels = None
965 965
966 966 return "".join(self._buffers.pop())
967 967
968 968 def _isbuffered(self, dest):
969 969 if dest is self._fout:
970 970 return bool(self._buffers)
971 971 if dest is self._ferr:
972 972 return bool(self._bufferstates and self._bufferstates[-1][0])
973 973 return False
974 974
975 975 def canwritewithoutlabels(self):
976 976 '''check if write skips the label'''
977 977 if self._buffers and not self._bufferapplylabels:
978 978 return True
979 979 return self._colormode is None
980 980
981 981 def canbatchlabeledwrites(self):
982 982 '''check if write calls with labels are batchable'''
983 983 # Windows color printing is special, see ``write``.
984 984 return self._colormode != 'win32'
985 985
986 986 def write(self, *args, **opts):
987 987 '''write args to output
988 988
989 989 By default, this method simply writes to the buffer or stdout.
990 990 Color mode can be set on the UI class to have the output decorated
991 991 with color modifier before being written to stdout.
992 992
993 993 The color used is controlled by an optional keyword argument, "label".
994 994 This should be a string containing label names separated by space.
995 995 Label names take the form of "topic.type". For example, ui.debug()
996 996 issues a label of "ui.debug".
997 997
998 998 When labeling output for a specific command, a label of
999 999 "cmdname.type" is recommended. For example, status issues
1000 1000 a label of "status.modified" for modified files.
1001 1001 '''
1002 1002 self._write(self._fout, *args, **opts)
1003 1003
1004 1004 def write_err(self, *args, **opts):
1005 1005 self._write(self._ferr, *args, **opts)
1006 1006
1007 1007 def _write(self, dest, *args, **opts):
1008 1008 if self._isbuffered(dest):
1009 1009 if self._bufferapplylabels:
1010 1010 label = opts.get(r'label', '')
1011 1011 self._buffers[-1].extend(self.label(a, label) for a in args)
1012 1012 else:
1013 1013 self._buffers[-1].extend(args)
1014 1014 else:
1015 1015 self._writenobuf(dest, *args, **opts)
1016 1016
1017 1017 def _writenobuf(self, dest, *args, **opts):
1018 1018 self._progclear()
1019 1019 msg = b''.join(args)
1020 1020
1021 1021 # opencode timeblockedsection because this is a critical path
1022 1022 starttime = util.timer()
1023 1023 try:
1024 1024 if dest is self._ferr and not getattr(self._fout, 'closed', False):
1025 1025 self._fout.flush()
1026 1026 if getattr(dest, 'structured', False):
1027 1027 # channel for machine-readable output with metadata, where
1028 1028 # no extra colorization is necessary.
1029 1029 dest.write(msg, **opts)
1030 1030 elif self._colormode == 'win32':
1031 1031 # windows color printing is its own can of crab, defer to
1032 1032 # the color module and that is it.
1033 1033 color.win32print(self, dest.write, msg, **opts)
1034 1034 else:
1035 1035 if self._colormode is not None:
1036 1036 label = opts.get(r'label', '')
1037 1037 msg = self.label(msg, label)
1038 1038 dest.write(msg)
1039 1039 # stderr may be buffered under win32 when redirected to files,
1040 1040 # including stdout.
1041 1041 if dest is self._ferr and not getattr(self._ferr, 'closed', False):
1042 1042 dest.flush()
1043 1043 except IOError as err:
1044 1044 if (dest is self._ferr
1045 1045 and err.errno in (errno.EPIPE, errno.EIO, errno.EBADF)):
1046 1046 # no way to report the error, so ignore it
1047 1047 return
1048 1048 raise error.StdioError(err)
1049 1049 finally:
1050 1050 self._blockedtimes['stdio_blocked'] += \
1051 1051 (util.timer() - starttime) * 1000
1052 1052
1053 1053 def _writemsg(self, dest, *args, **opts):
1054 1054 _writemsgwith(self._write, dest, *args, **opts)
1055 1055
1056 1056 def _writemsgnobuf(self, dest, *args, **opts):
1057 1057 _writemsgwith(self._writenobuf, dest, *args, **opts)
1058 1058
1059 1059 def flush(self):
1060 1060 # opencode timeblockedsection because this is a critical path
1061 1061 starttime = util.timer()
1062 1062 try:
1063 1063 try:
1064 1064 self._fout.flush()
1065 1065 except IOError as err:
1066 1066 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1067 1067 raise error.StdioError(err)
1068 1068 finally:
1069 1069 try:
1070 1070 self._ferr.flush()
1071 1071 except IOError as err:
1072 1072 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1073 1073 raise error.StdioError(err)
1074 1074 finally:
1075 1075 self._blockedtimes['stdio_blocked'] += \
1076 1076 (util.timer() - starttime) * 1000
1077 1077
1078 1078 def _isatty(self, fh):
1079 1079 if self.configbool('ui', 'nontty'):
1080 1080 return False
1081 1081 return procutil.isatty(fh)
1082 1082
1083 1083 def disablepager(self):
1084 1084 self._disablepager = True
1085 1085
1086 1086 def pager(self, command):
1087 1087 """Start a pager for subsequent command output.
1088 1088
1089 1089 Commands which produce a long stream of output should call
1090 1090 this function to activate the user's preferred pagination
1091 1091 mechanism (which may be no pager). Calling this function
1092 1092 precludes any future use of interactive functionality, such as
1093 1093 prompting the user or activating curses.
1094 1094
1095 1095 Args:
1096 1096 command: The full, non-aliased name of the command. That is, "log"
1097 1097 not "history, "summary" not "summ", etc.
1098 1098 """
1099 1099 if (self._disablepager
1100 1100 or self.pageractive):
1101 1101 # how pager should do is already determined
1102 1102 return
1103 1103
1104 1104 if not command.startswith('internal-always-') and (
1105 1105 # explicit --pager=on (= 'internal-always-' prefix) should
1106 1106 # take precedence over disabling factors below
1107 1107 command in self.configlist('pager', 'ignore')
1108 1108 or not self.configbool('ui', 'paginate')
1109 1109 or not self.configbool('pager', 'attend-' + command, True)
1110 1110 or encoding.environ.get('TERM') == 'dumb'
1111 1111 # TODO: if we want to allow HGPLAINEXCEPT=pager,
1112 1112 # formatted() will need some adjustment.
1113 1113 or not self.formatted()
1114 1114 or self.plain()
1115 1115 or self._buffers
1116 1116 # TODO: expose debugger-enabled on the UI object
1117 1117 or '--debugger' in pycompat.sysargv):
1118 1118 # We only want to paginate if the ui appears to be
1119 1119 # interactive, the user didn't say HGPLAIN or
1120 1120 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
1121 1121 return
1122 1122
1123 1123 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
1124 1124 if not pagercmd:
1125 1125 return
1126 1126
1127 1127 pagerenv = {}
1128 1128 for name, value in rcutil.defaultpagerenv().items():
1129 1129 if name not in encoding.environ:
1130 1130 pagerenv[name] = value
1131 1131
1132 1132 self.debug('starting pager for command %s\n' %
1133 1133 stringutil.pprint(command))
1134 1134 self.flush()
1135 1135
1136 1136 wasformatted = self.formatted()
1137 1137 if util.safehasattr(signal, "SIGPIPE"):
1138 1138 signal.signal(signal.SIGPIPE, _catchterm)
1139 1139 if self._runpager(pagercmd, pagerenv):
1140 1140 self.pageractive = True
1141 1141 # Preserve the formatted-ness of the UI. This is important
1142 1142 # because we mess with stdout, which might confuse
1143 1143 # auto-detection of things being formatted.
1144 1144 self.setconfig('ui', 'formatted', wasformatted, 'pager')
1145 1145 self.setconfig('ui', 'interactive', False, 'pager')
1146 1146
1147 1147 # If pagermode differs from color.mode, reconfigure color now that
1148 1148 # pageractive is set.
1149 1149 cm = self._colormode
1150 1150 if cm != self.config('color', 'pagermode', cm):
1151 1151 color.setup(self)
1152 1152 else:
1153 1153 # If the pager can't be spawned in dispatch when --pager=on is
1154 1154 # given, don't try again when the command runs, to avoid a duplicate
1155 1155 # warning about a missing pager command.
1156 1156 self.disablepager()
1157 1157
1158 1158 def _runpager(self, command, env=None):
1159 1159 """Actually start the pager and set up file descriptors.
1160 1160
1161 1161 This is separate in part so that extensions (like chg) can
1162 1162 override how a pager is invoked.
1163 1163 """
1164 1164 if command == 'cat':
1165 1165 # Save ourselves some work.
1166 1166 return False
1167 1167 # If the command doesn't contain any of these characters, we
1168 1168 # assume it's a binary and exec it directly. This means for
1169 1169 # simple pager command configurations, we can degrade
1170 1170 # gracefully and tell the user about their broken pager.
1171 1171 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
1172 1172
1173 1173 if pycompat.iswindows and not shell:
1174 1174 # Window's built-in `more` cannot be invoked with shell=False, but
1175 1175 # its `more.com` can. Hide this implementation detail from the
1176 1176 # user so we can also get sane bad PAGER behavior. MSYS has
1177 1177 # `more.exe`, so do a cmd.exe style resolution of the executable to
1178 1178 # determine which one to use.
1179 1179 fullcmd = procutil.findexe(command)
1180 1180 if not fullcmd:
1181 1181 self.warn(_("missing pager command '%s', skipping pager\n")
1182 1182 % command)
1183 1183 return False
1184 1184
1185 1185 command = fullcmd
1186 1186
1187 1187 try:
1188 1188 pager = subprocess.Popen(
1189 1189 procutil.tonativestr(command), shell=shell, bufsize=-1,
1190 1190 close_fds=procutil.closefds, stdin=subprocess.PIPE,
1191 1191 stdout=procutil.stdout, stderr=procutil.stderr,
1192 1192 env=procutil.tonativeenv(procutil.shellenviron(env)))
1193 1193 except OSError as e:
1194 1194 if e.errno == errno.ENOENT and not shell:
1195 1195 self.warn(_("missing pager command '%s', skipping pager\n")
1196 1196 % command)
1197 1197 return False
1198 1198 raise
1199 1199
1200 1200 # back up original file descriptors
1201 1201 stdoutfd = os.dup(procutil.stdout.fileno())
1202 1202 stderrfd = os.dup(procutil.stderr.fileno())
1203 1203
1204 1204 os.dup2(pager.stdin.fileno(), procutil.stdout.fileno())
1205 1205 if self._isatty(procutil.stderr):
1206 1206 os.dup2(pager.stdin.fileno(), procutil.stderr.fileno())
1207 1207
1208 1208 @self.atexit
1209 1209 def killpager():
1210 1210 if util.safehasattr(signal, "SIGINT"):
1211 1211 signal.signal(signal.SIGINT, signal.SIG_IGN)
1212 1212 # restore original fds, closing pager.stdin copies in the process
1213 1213 os.dup2(stdoutfd, procutil.stdout.fileno())
1214 1214 os.dup2(stderrfd, procutil.stderr.fileno())
1215 1215 pager.stdin.close()
1216 1216 pager.wait()
1217 1217
1218 1218 return True
1219 1219
1220 1220 @property
1221 1221 def _exithandlers(self):
1222 1222 return _reqexithandlers
1223 1223
1224 1224 def atexit(self, func, *args, **kwargs):
1225 1225 '''register a function to run after dispatching a request
1226 1226
1227 1227 Handlers do not stay registered across request boundaries.'''
1228 1228 self._exithandlers.append((func, args, kwargs))
1229 1229 return func
1230 1230
1231 1231 def interface(self, feature):
1232 1232 """what interface to use for interactive console features?
1233 1233
1234 1234 The interface is controlled by the value of `ui.interface` but also by
1235 1235 the value of feature-specific configuration. For example:
1236 1236
1237 1237 ui.interface.histedit = text
1238 1238 ui.interface.chunkselector = curses
1239 1239
1240 1240 Here the features are "histedit" and "chunkselector".
1241 1241
1242 1242 The configuration above means that the default interfaces for commands
1243 1243 is curses, the interface for histedit is text and the interface for
1244 1244 selecting chunk is crecord (the best curses interface available).
1245 1245
1246 1246 Consider the following example:
1247 1247 ui.interface = curses
1248 1248 ui.interface.histedit = text
1249 1249
1250 1250 Then histedit will use the text interface and chunkselector will use
1251 1251 the default curses interface (crecord at the moment).
1252 1252 """
1253 1253 alldefaults = frozenset(["text", "curses"])
1254 1254
1255 1255 featureinterfaces = {
1256 1256 "chunkselector": [
1257 1257 "text",
1258 1258 "curses",
1259 1259 ],
1260 1260 "histedit": [
1261 1261 "text",
1262 1262 "curses",
1263 1263 ],
1264 1264 }
1265 1265
1266 1266 # Feature-specific interface
1267 1267 if feature not in featureinterfaces.keys():
1268 1268 # Programming error, not user error
1269 1269 raise ValueError("Unknown feature requested %s" % feature)
1270 1270
1271 1271 availableinterfaces = frozenset(featureinterfaces[feature])
1272 1272 if alldefaults > availableinterfaces:
1273 1273 # Programming error, not user error. We need a use case to
1274 1274 # define the right thing to do here.
1275 1275 raise ValueError(
1276 1276 "Feature %s does not handle all default interfaces" %
1277 1277 feature)
1278 1278
1279 1279 if self.plain() or encoding.environ.get('TERM') == 'dumb':
1280 1280 return "text"
1281 1281
1282 1282 # Default interface for all the features
1283 1283 defaultinterface = "text"
1284 1284 i = self.config("ui", "interface")
1285 1285 if i in alldefaults:
1286 1286 defaultinterface = i
1287 1287
1288 1288 choseninterface = defaultinterface
1289 1289 f = self.config("ui", "interface.%s" % feature)
1290 1290 if f in availableinterfaces:
1291 1291 choseninterface = f
1292 1292
1293 1293 if i is not None and defaultinterface != i:
1294 1294 if f is not None:
1295 1295 self.warn(_("invalid value for ui.interface: %s\n") %
1296 1296 (i,))
1297 1297 else:
1298 1298 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1299 1299 (i, choseninterface))
1300 1300 if f is not None and choseninterface != f:
1301 1301 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1302 1302 (feature, f, choseninterface))
1303 1303
1304 1304 return choseninterface
1305 1305
1306 1306 def interactive(self):
1307 1307 '''is interactive input allowed?
1308 1308
1309 1309 An interactive session is a session where input can be reasonably read
1310 1310 from `sys.stdin'. If this function returns false, any attempt to read
1311 1311 from stdin should fail with an error, unless a sensible default has been
1312 1312 specified.
1313 1313
1314 1314 Interactiveness is triggered by the value of the `ui.interactive'
1315 1315 configuration variable or - if it is unset - when `sys.stdin' points
1316 1316 to a terminal device.
1317 1317
1318 1318 This function refers to input only; for output, see `ui.formatted()'.
1319 1319 '''
1320 1320 i = self.configbool("ui", "interactive")
1321 1321 if i is None:
1322 1322 # some environments replace stdin without implementing isatty
1323 1323 # usually those are non-interactive
1324 1324 return self._isatty(self._fin)
1325 1325
1326 1326 return i
1327 1327
1328 1328 def termwidth(self):
1329 1329 '''how wide is the terminal in columns?
1330 1330 '''
1331 1331 if 'COLUMNS' in encoding.environ:
1332 1332 try:
1333 1333 return int(encoding.environ['COLUMNS'])
1334 1334 except ValueError:
1335 1335 pass
1336 1336 return scmutil.termsize(self)[0]
1337 1337
1338 1338 def formatted(self):
1339 1339 '''should formatted output be used?
1340 1340
1341 1341 It is often desirable to format the output to suite the output medium.
1342 1342 Examples of this are truncating long lines or colorizing messages.
1343 1343 However, this is not often not desirable when piping output into other
1344 1344 utilities, e.g. `grep'.
1345 1345
1346 1346 Formatted output is triggered by the value of the `ui.formatted'
1347 1347 configuration variable or - if it is unset - when `sys.stdout' points
1348 1348 to a terminal device. Please note that `ui.formatted' should be
1349 1349 considered an implementation detail; it is not intended for use outside
1350 1350 Mercurial or its extensions.
1351 1351
1352 1352 This function refers to output only; for input, see `ui.interactive()'.
1353 1353 This function always returns false when in plain mode, see `ui.plain()'.
1354 1354 '''
1355 1355 if self.plain():
1356 1356 return False
1357 1357
1358 1358 i = self.configbool("ui", "formatted")
1359 1359 if i is None:
1360 1360 # some environments replace stdout without implementing isatty
1361 1361 # usually those are non-interactive
1362 1362 return self._isatty(self._fout)
1363 1363
1364 1364 return i
1365 1365
1366 1366 def _readline(self):
1367 1367 # Replacing stdin/stdout temporarily is a hard problem on Python 3
1368 1368 # because they have to be text streams with *no buffering*. Instead,
1369 1369 # we use rawinput() only if call_readline() will be invoked by
1370 1370 # PyOS_Readline(), so no I/O will be made at Python layer.
1371 1371 usereadline = (self._isatty(self._fin) and self._isatty(self._fout)
1372 1372 and procutil.isstdin(self._fin)
1373 1373 and procutil.isstdout(self._fout))
1374 1374 if usereadline:
1375 1375 try:
1376 1376 # magically add command line editing support, where
1377 1377 # available
1378 1378 import readline
1379 1379 # force demandimport to really load the module
1380 1380 readline.read_history_file
1381 1381 # windows sometimes raises something other than ImportError
1382 1382 except Exception:
1383 1383 usereadline = False
1384 1384
1385 1385 # prompt ' ' must exist; otherwise readline may delete entire line
1386 1386 # - http://bugs.python.org/issue12833
1387 1387 with self.timeblockedsection('stdio'):
1388 1388 if usereadline:
1389 1389 line = encoding.strtolocal(pycompat.rawinput(r' '))
1390 1390 # When stdin is in binary mode on Windows, it can cause
1391 1391 # raw_input() to emit an extra trailing carriage return
1392 1392 if pycompat.oslinesep == b'\r\n' and line.endswith(b'\r'):
1393 1393 line = line[:-1]
1394 1394 else:
1395 1395 self._fout.write(b' ')
1396 1396 self._fout.flush()
1397 1397 line = self._fin.readline()
1398 1398 if not line:
1399 1399 raise EOFError
1400 1400 line = line.rstrip(pycompat.oslinesep)
1401 1401
1402 1402 return line
1403 1403
1404 1404 def prompt(self, msg, default="y"):
1405 1405 """Prompt user with msg, read response.
1406 1406 If ui is not interactive, the default is returned.
1407 1407 """
1408 1408 return self._prompt(msg, default=default)
1409 1409
1410 1410 def _prompt(self, msg, **opts):
1411 1411 default = opts[r'default']
1412 1412 if not self.interactive():
1413 1413 self._writemsg(self._fmsgout, msg, ' ', type='prompt', **opts)
1414 1414 self._writemsg(self._fmsgout, default or '', "\n",
1415 1415 type='promptecho')
1416 1416 return default
1417 1417 self._writemsgnobuf(self._fmsgout, msg, type='prompt', **opts)
1418 1418 self.flush()
1419 1419 try:
1420 1420 r = self._readline()
1421 1421 if not r:
1422 1422 r = default
1423 1423 if self.configbool('ui', 'promptecho'):
1424 1424 self._writemsg(self._fmsgout, r, "\n", type='promptecho')
1425 1425 return r
1426 1426 except EOFError:
1427 1427 raise error.ResponseExpected()
1428 1428
1429 1429 @staticmethod
1430 1430 def extractchoices(prompt):
1431 1431 """Extract prompt message and list of choices from specified prompt.
1432 1432
1433 1433 This returns tuple "(message, choices)", and "choices" is the
1434 1434 list of tuple "(response character, text without &)".
1435 1435
1436 1436 >>> ui.extractchoices(b"awake? $$ &Yes $$ &No")
1437 1437 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1438 1438 >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No")
1439 1439 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1440 1440 >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o")
1441 1441 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1442 1442 """
1443 1443
1444 1444 # Sadly, the prompt string may have been built with a filename
1445 1445 # containing "$$" so let's try to find the first valid-looking
1446 1446 # prompt to start parsing. Sadly, we also can't rely on
1447 1447 # choices containing spaces, ASCII, or basically anything
1448 1448 # except an ampersand followed by a character.
1449 1449 m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1450 1450 msg = m.group(1)
1451 1451 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1452 1452 def choicetuple(s):
1453 1453 ampidx = s.index('&')
1454 1454 return s[ampidx + 1:ampidx + 2].lower(), s.replace('&', '', 1)
1455 1455 return (msg, [choicetuple(s) for s in choices])
1456 1456
1457 1457 def promptchoice(self, prompt, default=0):
1458 1458 """Prompt user with a message, read response, and ensure it matches
1459 1459 one of the provided choices. The prompt is formatted as follows:
1460 1460
1461 1461 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1462 1462
1463 1463 The index of the choice is returned. Responses are case
1464 1464 insensitive. If ui is not interactive, the default is
1465 1465 returned.
1466 1466 """
1467 1467
1468 1468 msg, choices = self.extractchoices(prompt)
1469 1469 resps = [r for r, t in choices]
1470 1470 while True:
1471 1471 r = self._prompt(msg, default=resps[default], choices=choices)
1472 1472 if r.lower() in resps:
1473 1473 return resps.index(r.lower())
1474 1474 # TODO: shouldn't it be a warning?
1475 1475 self._writemsg(self._fmsgout, _("unrecognized response\n"))
1476 1476
1477 1477 def getpass(self, prompt=None, default=None):
1478 1478 if not self.interactive():
1479 1479 return default
1480 1480 try:
1481 1481 self._writemsg(self._fmsgerr, prompt or _('password: '),
1482 1482 type='prompt', password=True)
1483 1483 # disable getpass() only if explicitly specified. it's still valid
1484 1484 # to interact with tty even if fin is not a tty.
1485 1485 with self.timeblockedsection('stdio'):
1486 1486 if self.configbool('ui', 'nontty'):
1487 1487 l = self._fin.readline()
1488 1488 if not l:
1489 1489 raise EOFError
1490 1490 return l.rstrip('\n')
1491 1491 else:
1492 1492 return getpass.getpass('')
1493 1493 except EOFError:
1494 1494 raise error.ResponseExpected()
1495 1495
1496 1496 def status(self, *msg, **opts):
1497 1497 '''write status message to output (if ui.quiet is False)
1498 1498
1499 1499 This adds an output label of "ui.status".
1500 1500 '''
1501 1501 if not self.quiet:
1502 1502 self._writemsg(self._fmsgout, type='status', *msg, **opts)
1503 1503
1504 1504 def warn(self, *msg, **opts):
1505 1505 '''write warning message to output (stderr)
1506 1506
1507 1507 This adds an output label of "ui.warning".
1508 1508 '''
1509 1509 self._writemsg(self._fmsgerr, type='warning', *msg, **opts)
1510 1510
1511 1511 def error(self, *msg, **opts):
1512 1512 '''write error message to output (stderr)
1513 1513
1514 1514 This adds an output label of "ui.error".
1515 1515 '''
1516 1516 self._writemsg(self._fmsgerr, type='error', *msg, **opts)
1517 1517
1518 1518 def note(self, *msg, **opts):
1519 1519 '''write note to output (if ui.verbose is True)
1520 1520
1521 1521 This adds an output label of "ui.note".
1522 1522 '''
1523 1523 if self.verbose:
1524 1524 self._writemsg(self._fmsgout, type='note', *msg, **opts)
1525 1525
1526 1526 def debug(self, *msg, **opts):
1527 1527 '''write debug message to output (if ui.debugflag is True)
1528 1528
1529 1529 This adds an output label of "ui.debug".
1530 1530 '''
1531 1531 if self.debugflag:
1532 1532 self._writemsg(self._fmsgout, type='debug', *msg, **opts)
1533 1533 self.log(b'debug', b'%s', b''.join(msg))
1534 1534
1535 1535 def edit(self, text, user, extra=None, editform=None, pending=None,
1536 1536 repopath=None, action=None):
1537 1537 if action is None:
1538 1538 self.develwarn('action is None but will soon be a required '
1539 1539 'parameter to ui.edit()')
1540 1540 extra_defaults = {
1541 1541 'prefix': 'editor',
1542 1542 'suffix': '.txt',
1543 1543 }
1544 1544 if extra is not None:
1545 1545 if extra.get('suffix') is not None:
1546 1546 self.develwarn('extra.suffix is not None but will soon be '
1547 1547 'ignored by ui.edit()')
1548 1548 extra_defaults.update(extra)
1549 1549 extra = extra_defaults
1550 1550
1551 1551 if action == 'diff':
1552 1552 suffix = '.diff'
1553 1553 elif action:
1554 1554 suffix = '.%s.hg.txt' % action
1555 1555 else:
1556 1556 suffix = extra['suffix']
1557 1557
1558 1558 rdir = None
1559 1559 if self.configbool('experimental', 'editortmpinhg'):
1560 1560 rdir = repopath
1561 1561 (fd, name) = pycompat.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1562 1562 suffix=suffix,
1563 1563 dir=rdir)
1564 1564 try:
1565 1565 f = os.fdopen(fd, r'wb')
1566 1566 f.write(util.tonativeeol(text))
1567 1567 f.close()
1568 1568
1569 1569 environ = {'HGUSER': user}
1570 1570 if 'transplant_source' in extra:
1571 1571 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1572 1572 for label in ('intermediate-source', 'source', 'rebase_source'):
1573 1573 if label in extra:
1574 1574 environ.update({'HGREVISION': extra[label]})
1575 1575 break
1576 1576 if editform:
1577 1577 environ.update({'HGEDITFORM': editform})
1578 1578 if pending:
1579 1579 environ.update({'HG_PENDING': pending})
1580 1580
1581 1581 editor = self.geteditor()
1582 1582
1583 1583 self.system("%s \"%s\"" % (editor, name),
1584 1584 environ=environ,
1585 1585 onerr=error.Abort, errprefix=_("edit failed"),
1586 1586 blockedtag='editor')
1587 1587
1588 1588 f = open(name, r'rb')
1589 1589 t = util.fromnativeeol(f.read())
1590 1590 f.close()
1591 1591 finally:
1592 1592 os.unlink(name)
1593 1593
1594 1594 return t
1595 1595
1596 1596 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1597 1597 blockedtag=None):
1598 1598 '''execute shell command with appropriate output stream. command
1599 1599 output will be redirected if fout is not stdout.
1600 1600
1601 1601 if command fails and onerr is None, return status, else raise onerr
1602 1602 object as exception.
1603 1603 '''
1604 1604 if blockedtag is None:
1605 1605 # Long cmds tend to be because of an absolute path on cmd. Keep
1606 1606 # the tail end instead
1607 1607 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1608 1608 blockedtag = 'unknown_system_' + cmdsuffix
1609 1609 out = self._fout
1610 1610 if any(s[1] for s in self._bufferstates):
1611 1611 out = self
1612 1612 with self.timeblockedsection(blockedtag):
1613 1613 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1614 1614 if rc and onerr:
1615 1615 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1616 1616 procutil.explainexit(rc))
1617 1617 if errprefix:
1618 1618 errmsg = '%s: %s' % (errprefix, errmsg)
1619 1619 raise onerr(errmsg)
1620 1620 return rc
1621 1621
1622 1622 def _runsystem(self, cmd, environ, cwd, out):
1623 1623 """actually execute the given shell command (can be overridden by
1624 1624 extensions like chg)"""
1625 1625 return procutil.system(cmd, environ=environ, cwd=cwd, out=out)
1626 1626
1627 1627 def traceback(self, exc=None, force=False):
1628 1628 '''print exception traceback if traceback printing enabled or forced.
1629 1629 only to call in exception handler. returns true if traceback
1630 1630 printed.'''
1631 1631 if self.tracebackflag or force:
1632 1632 if exc is None:
1633 1633 exc = sys.exc_info()
1634 1634 cause = getattr(exc[1], 'cause', None)
1635 1635
1636 1636 if cause is not None:
1637 1637 causetb = traceback.format_tb(cause[2])
1638 1638 exctb = traceback.format_tb(exc[2])
1639 1639 exconly = traceback.format_exception_only(cause[0], cause[1])
1640 1640
1641 1641 # exclude frame where 'exc' was chained and rethrown from exctb
1642 1642 self.write_err('Traceback (most recent call last):\n',
1643 1643 ''.join(exctb[:-1]),
1644 1644 ''.join(causetb),
1645 1645 ''.join(exconly))
1646 1646 else:
1647 1647 output = traceback.format_exception(exc[0], exc[1], exc[2])
1648 1648 self.write_err(encoding.strtolocal(r''.join(output)))
1649 1649 return self.tracebackflag or force
1650 1650
1651 1651 def geteditor(self):
1652 1652 '''return editor to use'''
1653 1653 if pycompat.sysplatform == 'plan9':
1654 1654 # vi is the MIPS instruction simulator on Plan 9. We
1655 1655 # instead default to E to plumb commit messages to
1656 1656 # avoid confusion.
1657 1657 editor = 'E'
1658 1658 else:
1659 1659 editor = 'vi'
1660 1660 return (encoding.environ.get("HGEDITOR") or
1661 1661 self.config("ui", "editor", editor))
1662 1662
1663 1663 @util.propertycache
1664 1664 def _progbar(self):
1665 1665 """setup the progbar singleton to the ui object"""
1666 1666 if (self.quiet or self.debugflag
1667 1667 or self.configbool('progress', 'disable')
1668 1668 or not progress.shouldprint(self)):
1669 1669 return None
1670 1670 return getprogbar(self)
1671 1671
1672 1672 def _progclear(self):
1673 1673 """clear progress bar output if any. use it before any output"""
1674 1674 if not haveprogbar(): # nothing loaded yet
1675 1675 return
1676 1676 if self._progbar is not None and self._progbar.printed:
1677 1677 self._progbar.clear()
1678 1678
1679 1679 def progress(self, topic, pos, item="", unit="", total=None):
1680 1680 '''show a progress message
1681 1681
1682 1682 By default a textual progress bar will be displayed if an operation
1683 1683 takes too long. 'topic' is the current operation, 'item' is a
1684 1684 non-numeric marker of the current position (i.e. the currently
1685 1685 in-process file), 'pos' is the current numeric position (i.e.
1686 1686 revision, bytes, etc.), unit is a corresponding unit label,
1687 1687 and total is the highest expected pos.
1688 1688
1689 1689 Multiple nested topics may be active at a time.
1690 1690
1691 1691 All topics should be marked closed by setting pos to None at
1692 1692 termination.
1693 1693 '''
1694 if getattr(self._fmsgerr, 'structured', False):
1695 # channel for machine-readable output with metadata, just send
1696 # raw information
1697 # TODO: consider porting some useful information (e.g. estimated
1698 # time) from progbar. we might want to support update delay to
1699 # reduce the cost of transferring progress messages.
1700 self._fmsgerr.write(None, type=b'progress', topic=topic, pos=pos,
1701 item=item, unit=unit, total=total)
1702 elif self._progbar is not None:
1703 self._progbar.progress(topic, pos, item=item, unit=unit,
1704 total=total)
1705
1706 # Looking up progress.debug in tight loops is expensive. The value
1707 # is cached on the progbar object and we can avoid the lookup in
1708 # the common case where a progbar is active.
1709 if pos is None or not self._progbar.debug:
1710 return
1711
1712 # Keep this logic in sync with above.
1713 if pos is None or not self.configbool('progress', 'debug'):
1714 return
1715
1716 if unit:
1717 unit = ' ' + unit
1718 if item:
1719 item = ' ' + item
1720
1721 if total:
1722 pct = 100.0 * pos / total
1723 self.debug('%s:%s %d/%d%s (%4.2f%%)\n'
1724 % (topic, item, pos, total, unit, pct))
1694 progress = self.makeprogress(topic, unit, total)
1695 if pos is not None:
1696 progress.update(pos, item=item)
1725 1697 else:
1726 self.debug('%s:%s %d%s\n' % (topic, item, pos, unit))
1698 progress.complete()
1727 1699
1728 1700 def makeprogress(self, topic, unit="", total=None):
1729 1701 '''exists only so low-level modules won't need to import scmutil'''
1730 1702 return scmutil.progress(self, topic, unit, total)
1731 1703
1732 1704 def getlogger(self, name):
1733 1705 """Returns a logger of the given name; or None if not registered"""
1734 1706 return self._loggers.get(name)
1735 1707
1736 1708 def setlogger(self, name, logger):
1737 1709 """Install logger which can be identified later by the given name
1738 1710
1739 1711 More than one loggers can be registered. Use extension or module
1740 1712 name to uniquely identify the logger instance.
1741 1713 """
1742 1714 self._loggers[name] = logger
1743 1715
1744 1716 def log(self, event, msgfmt, *msgargs, **opts):
1745 1717 '''hook for logging facility extensions
1746 1718
1747 1719 event should be a readily-identifiable subsystem, which will
1748 1720 allow filtering.
1749 1721
1750 1722 msgfmt should be a newline-terminated format string to log, and
1751 1723 *msgargs are %-formatted into it.
1752 1724
1753 1725 **opts currently has no defined meanings.
1754 1726 '''
1755 1727 if not self._loggers:
1756 1728 return
1757 1729 activeloggers = [l for l in self._loggers.itervalues()
1758 1730 if l.tracked(event)]
1759 1731 if not activeloggers:
1760 1732 return
1761 1733 msg = msgfmt % msgargs
1762 1734 opts = pycompat.byteskwargs(opts)
1763 1735 # guard against recursion from e.g. ui.debug()
1764 1736 registeredloggers = self._loggers
1765 1737 self._loggers = {}
1766 1738 try:
1767 1739 for logger in activeloggers:
1768 1740 logger.log(self, event, msg, opts)
1769 1741 finally:
1770 1742 self._loggers = registeredloggers
1771 1743
1772 1744 def label(self, msg, label):
1773 1745 '''style msg based on supplied label
1774 1746
1775 1747 If some color mode is enabled, this will add the necessary control
1776 1748 characters to apply such color. In addition, 'debug' color mode adds
1777 1749 markup showing which label affects a piece of text.
1778 1750
1779 1751 ui.write(s, 'label') is equivalent to
1780 1752 ui.write(ui.label(s, 'label')).
1781 1753 '''
1782 1754 if self._colormode is not None:
1783 1755 return color.colorlabel(self, msg, label)
1784 1756 return msg
1785 1757
1786 1758 def develwarn(self, msg, stacklevel=1, config=None):
1787 1759 """issue a developer warning message
1788 1760
1789 1761 Use 'stacklevel' to report the offender some layers further up in the
1790 1762 stack.
1791 1763 """
1792 1764 if not self.configbool('devel', 'all-warnings'):
1793 1765 if config is None or not self.configbool('devel', config):
1794 1766 return
1795 1767 msg = 'devel-warn: ' + msg
1796 1768 stacklevel += 1 # get in develwarn
1797 1769 if self.tracebackflag:
1798 1770 util.debugstacktrace(msg, stacklevel, self._ferr, self._fout)
1799 1771 self.log('develwarn', '%s at:\n%s' %
1800 1772 (msg, ''.join(util.getstackframes(stacklevel))))
1801 1773 else:
1802 1774 curframe = inspect.currentframe()
1803 1775 calframe = inspect.getouterframes(curframe, 2)
1804 1776 fname, lineno, fmsg = calframe[stacklevel][1:4]
1805 1777 fname, fmsg = pycompat.sysbytes(fname), pycompat.sysbytes(fmsg)
1806 1778 self.write_err('%s at: %s:%d (%s)\n'
1807 1779 % (msg, fname, lineno, fmsg))
1808 1780 self.log('develwarn', '%s at: %s:%d (%s)\n',
1809 1781 msg, fname, lineno, fmsg)
1810 1782 curframe = calframe = None # avoid cycles
1811 1783
1812 1784 def deprecwarn(self, msg, version, stacklevel=2):
1813 1785 """issue a deprecation warning
1814 1786
1815 1787 - msg: message explaining what is deprecated and how to upgrade,
1816 1788 - version: last version where the API will be supported,
1817 1789 """
1818 1790 if not (self.configbool('devel', 'all-warnings')
1819 1791 or self.configbool('devel', 'deprec-warn')):
1820 1792 return
1821 1793 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1822 1794 " update your code.)") % version
1823 1795 self.develwarn(msg, stacklevel=stacklevel, config='deprec-warn')
1824 1796
1825 1797 def exportableenviron(self):
1826 1798 """The environment variables that are safe to export, e.g. through
1827 1799 hgweb.
1828 1800 """
1829 1801 return self._exportableenviron
1830 1802
1831 1803 @contextlib.contextmanager
1832 1804 def configoverride(self, overrides, source=""):
1833 1805 """Context manager for temporary config overrides
1834 1806 `overrides` must be a dict of the following structure:
1835 1807 {(section, name) : value}"""
1836 1808 backups = {}
1837 1809 try:
1838 1810 for (section, name), value in overrides.items():
1839 1811 backups[(section, name)] = self.backupconfig(section, name)
1840 1812 self.setconfig(section, name, value, source)
1841 1813 yield
1842 1814 finally:
1843 1815 for __, backup in backups.items():
1844 1816 self.restoreconfig(backup)
1845 1817 # just restoring ui.quiet config to the previous value is not enough
1846 1818 # as it does not update ui.quiet class member
1847 1819 if ('ui', 'quiet') in overrides:
1848 1820 self.fixconfig(section='ui')
1849 1821
1850 1822 class paths(dict):
1851 1823 """Represents a collection of paths and their configs.
1852 1824
1853 1825 Data is initially derived from ui instances and the config files they have
1854 1826 loaded.
1855 1827 """
1856 1828 def __init__(self, ui):
1857 1829 dict.__init__(self)
1858 1830
1859 1831 for name, loc in ui.configitems('paths', ignoresub=True):
1860 1832 # No location is the same as not existing.
1861 1833 if not loc:
1862 1834 continue
1863 1835 loc, sub = ui.configsuboptions('paths', name)
1864 1836 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1865 1837
1866 1838 def getpath(self, name, default=None):
1867 1839 """Return a ``path`` from a string, falling back to default.
1868 1840
1869 1841 ``name`` can be a named path or locations. Locations are filesystem
1870 1842 paths or URIs.
1871 1843
1872 1844 Returns None if ``name`` is not a registered path, a URI, or a local
1873 1845 path to a repo.
1874 1846 """
1875 1847 # Only fall back to default if no path was requested.
1876 1848 if name is None:
1877 1849 if not default:
1878 1850 default = ()
1879 1851 elif not isinstance(default, (tuple, list)):
1880 1852 default = (default,)
1881 1853 for k in default:
1882 1854 try:
1883 1855 return self[k]
1884 1856 except KeyError:
1885 1857 continue
1886 1858 return None
1887 1859
1888 1860 # Most likely empty string.
1889 1861 # This may need to raise in the future.
1890 1862 if not name:
1891 1863 return None
1892 1864
1893 1865 try:
1894 1866 return self[name]
1895 1867 except KeyError:
1896 1868 # Try to resolve as a local path or URI.
1897 1869 try:
1898 1870 # We don't pass sub-options in, so no need to pass ui instance.
1899 1871 return path(None, None, rawloc=name)
1900 1872 except ValueError:
1901 1873 raise error.RepoError(_('repository %s does not exist') %
1902 1874 name)
1903 1875
1904 1876 _pathsuboptions = {}
1905 1877
1906 1878 def pathsuboption(option, attr):
1907 1879 """Decorator used to declare a path sub-option.
1908 1880
1909 1881 Arguments are the sub-option name and the attribute it should set on
1910 1882 ``path`` instances.
1911 1883
1912 1884 The decorated function will receive as arguments a ``ui`` instance,
1913 1885 ``path`` instance, and the string value of this option from the config.
1914 1886 The function should return the value that will be set on the ``path``
1915 1887 instance.
1916 1888
1917 1889 This decorator can be used to perform additional verification of
1918 1890 sub-options and to change the type of sub-options.
1919 1891 """
1920 1892 def register(func):
1921 1893 _pathsuboptions[option] = (attr, func)
1922 1894 return func
1923 1895 return register
1924 1896
1925 1897 @pathsuboption('pushurl', 'pushloc')
1926 1898 def pushurlpathoption(ui, path, value):
1927 1899 u = util.url(value)
1928 1900 # Actually require a URL.
1929 1901 if not u.scheme:
1930 1902 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1931 1903 return None
1932 1904
1933 1905 # Don't support the #foo syntax in the push URL to declare branch to
1934 1906 # push.
1935 1907 if u.fragment:
1936 1908 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1937 1909 'ignoring)\n') % path.name)
1938 1910 u.fragment = None
1939 1911
1940 1912 return bytes(u)
1941 1913
1942 1914 @pathsuboption('pushrev', 'pushrev')
1943 1915 def pushrevpathoption(ui, path, value):
1944 1916 return value
1945 1917
1946 1918 class path(object):
1947 1919 """Represents an individual path and its configuration."""
1948 1920
1949 1921 def __init__(self, ui, name, rawloc=None, suboptions=None):
1950 1922 """Construct a path from its config options.
1951 1923
1952 1924 ``ui`` is the ``ui`` instance the path is coming from.
1953 1925 ``name`` is the symbolic name of the path.
1954 1926 ``rawloc`` is the raw location, as defined in the config.
1955 1927 ``pushloc`` is the raw locations pushes should be made to.
1956 1928
1957 1929 If ``name`` is not defined, we require that the location be a) a local
1958 1930 filesystem path with a .hg directory or b) a URL. If not,
1959 1931 ``ValueError`` is raised.
1960 1932 """
1961 1933 if not rawloc:
1962 1934 raise ValueError('rawloc must be defined')
1963 1935
1964 1936 # Locations may define branches via syntax <base>#<branch>.
1965 1937 u = util.url(rawloc)
1966 1938 branch = None
1967 1939 if u.fragment:
1968 1940 branch = u.fragment
1969 1941 u.fragment = None
1970 1942
1971 1943 self.url = u
1972 1944 self.branch = branch
1973 1945
1974 1946 self.name = name
1975 1947 self.rawloc = rawloc
1976 1948 self.loc = '%s' % u
1977 1949
1978 1950 # When given a raw location but not a symbolic name, validate the
1979 1951 # location is valid.
1980 1952 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1981 1953 raise ValueError('location is not a URL or path to a local '
1982 1954 'repo: %s' % rawloc)
1983 1955
1984 1956 suboptions = suboptions or {}
1985 1957
1986 1958 # Now process the sub-options. If a sub-option is registered, its
1987 1959 # attribute will always be present. The value will be None if there
1988 1960 # was no valid sub-option.
1989 1961 for suboption, (attr, func) in _pathsuboptions.iteritems():
1990 1962 if suboption not in suboptions:
1991 1963 setattr(self, attr, None)
1992 1964 continue
1993 1965
1994 1966 value = func(ui, self, suboptions[suboption])
1995 1967 setattr(self, attr, value)
1996 1968
1997 1969 def _isvalidlocalpath(self, path):
1998 1970 """Returns True if the given path is a potentially valid repository.
1999 1971 This is its own function so that extensions can change the definition of
2000 1972 'valid' in this case (like when pulling from a git repo into a hg
2001 1973 one)."""
2002 1974 return os.path.isdir(os.path.join(path, '.hg'))
2003 1975
2004 1976 @property
2005 1977 def suboptions(self):
2006 1978 """Return sub-options and their values for this path.
2007 1979
2008 1980 This is intended to be used for presentation purposes.
2009 1981 """
2010 1982 d = {}
2011 1983 for subopt, (attr, _func) in _pathsuboptions.iteritems():
2012 1984 value = getattr(self, attr)
2013 1985 if value is not None:
2014 1986 d[subopt] = value
2015 1987 return d
2016 1988
2017 1989 # we instantiate one globally shared progress bar to avoid
2018 1990 # competing progress bars when multiple UI objects get created
2019 1991 _progresssingleton = None
2020 1992
2021 1993 def getprogbar(ui):
2022 1994 global _progresssingleton
2023 1995 if _progresssingleton is None:
2024 1996 # passing 'ui' object to the singleton is fishy,
2025 1997 # this is how the extension used to work but feel free to rework it.
2026 1998 _progresssingleton = progress.progbar(ui)
2027 1999 return _progresssingleton
2028 2000
2029 2001 def haveprogbar():
2030 2002 return _progresssingleton is not None
2031 2003
2032 2004 def _selectmsgdests(ui):
2033 2005 name = ui.config(b'ui', b'message-output')
2034 2006 if name == b'channel':
2035 2007 if ui.fmsg:
2036 2008 return ui.fmsg, ui.fmsg
2037 2009 else:
2038 2010 # fall back to ferr if channel isn't ready so that status/error
2039 2011 # messages can be printed
2040 2012 return ui.ferr, ui.ferr
2041 2013 if name == b'stdio':
2042 2014 return ui.fout, ui.ferr
2043 2015 if name == b'stderr':
2044 2016 return ui.ferr, ui.ferr
2045 2017 raise error.Abort(b'invalid ui.message-output destination: %s' % name)
2046 2018
2047 2019 def _writemsgwith(write, dest, *args, **opts):
2048 2020 """Write ui message with the given ui._write*() function
2049 2021
2050 2022 The specified message type is translated to 'ui.<type>' label if the dest
2051 2023 isn't a structured channel, so that the message will be colorized.
2052 2024 """
2053 2025 # TODO: maybe change 'type' to a mandatory option
2054 2026 if r'type' in opts and not getattr(dest, 'structured', False):
2055 2027 opts[r'label'] = opts.get(r'label', '') + ' ui.%s' % opts.pop(r'type')
2056 2028 write(dest, *args, **opts)
General Comments 0
You need to be logged in to leave comments. Login now