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