##// END OF EJS Templates
pager: set some environment variables if they're not set...
Jun Wu -
r31954:e518192d default
parent child Browse files
Show More
@@ -1,578 +1,578
1 1 # chgserver.py - command server extension for cHg
2 2 #
3 3 # Copyright 2011 Yuya Nishihara <yuya@tcha.org>
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 """command server extension for cHg
9 9
10 10 'S' channel (read/write)
11 11 propagate ui.system() request to client
12 12
13 13 'attachio' command
14 14 attach client's stdio passed by sendmsg()
15 15
16 16 'chdir' command
17 17 change current directory
18 18
19 19 'setenv' command
20 20 replace os.environ completely
21 21
22 22 'setumask' command
23 23 set umask
24 24
25 25 'validate' command
26 26 reload the config and check if the server is up to date
27 27
28 28 Config
29 29 ------
30 30
31 31 ::
32 32
33 33 [chgserver]
34 34 # how long (in seconds) should an idle chg server exit
35 35 idletimeout = 3600
36 36
37 37 # whether to skip config or env change checks
38 38 skiphash = False
39 39 """
40 40
41 41 from __future__ import absolute_import
42 42
43 43 import hashlib
44 44 import inspect
45 45 import os
46 46 import re
47 47 import struct
48 48 import time
49 49
50 50 from .i18n import _
51 51
52 52 from . import (
53 53 commandserver,
54 54 encoding,
55 55 error,
56 56 extensions,
57 57 osutil,
58 58 pycompat,
59 59 util,
60 60 )
61 61
62 62 _log = commandserver.log
63 63
64 64 def _hashlist(items):
65 65 """return sha1 hexdigest for a list"""
66 66 return hashlib.sha1(str(items)).hexdigest()
67 67
68 68 # sensitive config sections affecting confighash
69 69 _configsections = [
70 70 'alias', # affects global state commands.table
71 71 'extdiff', # uisetup will register new commands
72 72 'extensions',
73 73 ]
74 74
75 75 # sensitive environment variables affecting confighash
76 76 _envre = re.compile(r'''\A(?:
77 77 CHGHG
78 78 |HG(?:[A-Z].*)?
79 79 |LANG(?:UAGE)?
80 80 |LC_.*
81 81 |LD_.*
82 82 |PATH
83 83 |PYTHON.*
84 84 |TERM(?:INFO)?
85 85 |TZ
86 86 )\Z''', re.X)
87 87
88 88 def _confighash(ui):
89 89 """return a quick hash for detecting config/env changes
90 90
91 91 confighash is the hash of sensitive config items and environment variables.
92 92
93 93 for chgserver, it is designed that once confighash changes, the server is
94 94 not qualified to serve its client and should redirect the client to a new
95 95 server. different from mtimehash, confighash change will not mark the
96 96 server outdated and exit since the user can have different configs at the
97 97 same time.
98 98 """
99 99 sectionitems = []
100 100 for section in _configsections:
101 101 sectionitems.append(ui.configitems(section))
102 102 sectionhash = _hashlist(sectionitems)
103 103 envitems = [(k, v) for k, v in encoding.environ.iteritems()
104 104 if _envre.match(k)]
105 105 envhash = _hashlist(sorted(envitems))
106 106 return sectionhash[:6] + envhash[:6]
107 107
108 108 def _getmtimepaths(ui):
109 109 """get a list of paths that should be checked to detect change
110 110
111 111 The list will include:
112 112 - extensions (will not cover all files for complex extensions)
113 113 - mercurial/__version__.py
114 114 - python binary
115 115 """
116 116 modules = [m for n, m in extensions.extensions(ui)]
117 117 try:
118 118 from . import __version__
119 119 modules.append(__version__)
120 120 except ImportError:
121 121 pass
122 122 files = [pycompat.sysexecutable]
123 123 for m in modules:
124 124 try:
125 125 files.append(inspect.getabsfile(m))
126 126 except TypeError:
127 127 pass
128 128 return sorted(set(files))
129 129
130 130 def _mtimehash(paths):
131 131 """return a quick hash for detecting file changes
132 132
133 133 mtimehash calls stat on given paths and calculate a hash based on size and
134 134 mtime of each file. mtimehash does not read file content because reading is
135 135 expensive. therefore it's not 100% reliable for detecting content changes.
136 136 it's possible to return different hashes for same file contents.
137 137 it's also possible to return a same hash for different file contents for
138 138 some carefully crafted situation.
139 139
140 140 for chgserver, it is designed that once mtimehash changes, the server is
141 141 considered outdated immediately and should no longer provide service.
142 142
143 143 mtimehash is not included in confighash because we only know the paths of
144 144 extensions after importing them (there is imp.find_module but that faces
145 145 race conditions). We need to calculate confighash without importing.
146 146 """
147 147 def trystat(path):
148 148 try:
149 149 st = os.stat(path)
150 150 return (st.st_mtime, st.st_size)
151 151 except OSError:
152 152 # could be ENOENT, EPERM etc. not fatal in any case
153 153 pass
154 154 return _hashlist(map(trystat, paths))[:12]
155 155
156 156 class hashstate(object):
157 157 """a structure storing confighash, mtimehash, paths used for mtimehash"""
158 158 def __init__(self, confighash, mtimehash, mtimepaths):
159 159 self.confighash = confighash
160 160 self.mtimehash = mtimehash
161 161 self.mtimepaths = mtimepaths
162 162
163 163 @staticmethod
164 164 def fromui(ui, mtimepaths=None):
165 165 if mtimepaths is None:
166 166 mtimepaths = _getmtimepaths(ui)
167 167 confighash = _confighash(ui)
168 168 mtimehash = _mtimehash(mtimepaths)
169 169 _log('confighash = %s mtimehash = %s\n' % (confighash, mtimehash))
170 170 return hashstate(confighash, mtimehash, mtimepaths)
171 171
172 172 def _newchgui(srcui, csystem, attachio):
173 173 class chgui(srcui.__class__):
174 174 def __init__(self, src=None):
175 175 super(chgui, self).__init__(src)
176 176 if src:
177 177 self._csystem = getattr(src, '_csystem', csystem)
178 178 else:
179 179 self._csystem = csystem
180 180
181 181 def _runsystem(self, cmd, environ, cwd, out):
182 182 # fallback to the original system method if the output needs to be
183 183 # captured (to self._buffers), or the output stream is not stdout
184 184 # (e.g. stderr, cStringIO), because the chg client is not aware of
185 185 # these situations and will behave differently (write to stdout).
186 186 if (out is not self.fout
187 187 or not util.safehasattr(self.fout, 'fileno')
188 188 or self.fout.fileno() != util.stdout.fileno()):
189 189 return util.system(cmd, environ=environ, cwd=cwd, out=out)
190 190 self.flush()
191 191 return self._csystem(cmd, util.shellenviron(environ), cwd)
192 192
193 def _runpager(self, cmd):
194 self._csystem(cmd, util.shellenviron(), type='pager',
193 def _runpager(self, cmd, env=None):
194 self._csystem(cmd, util.shellenviron(env), type='pager',
195 195 cmdtable={'attachio': attachio})
196 196 return True
197 197
198 198 return chgui(srcui)
199 199
200 200 def _loadnewui(srcui, args):
201 201 from . import dispatch # avoid cycle
202 202
203 203 newui = srcui.__class__.load()
204 204 for a in ['fin', 'fout', 'ferr', 'environ']:
205 205 setattr(newui, a, getattr(srcui, a))
206 206 if util.safehasattr(srcui, '_csystem'):
207 207 newui._csystem = srcui._csystem
208 208
209 209 # command line args
210 210 args = args[:]
211 211 dispatch._parseconfig(newui, dispatch._earlygetopt(['--config'], args))
212 212
213 213 # stolen from tortoisehg.util.copydynamicconfig()
214 214 for section, name, value in srcui.walkconfig():
215 215 source = srcui.configsource(section, name)
216 216 if ':' in source or source == '--config' or source.startswith('$'):
217 217 # path:line or command line, or environ
218 218 continue
219 219 newui.setconfig(section, name, value, source)
220 220
221 221 # load wd and repo config, copied from dispatch.py
222 222 cwds = dispatch._earlygetopt(['--cwd'], args)
223 223 cwd = cwds and os.path.realpath(cwds[-1]) or None
224 224 rpath = dispatch._earlygetopt(["-R", "--repository", "--repo"], args)
225 225 path, newlui = dispatch._getlocal(newui, rpath, wd=cwd)
226 226
227 227 return (newui, newlui)
228 228
229 229 class channeledsystem(object):
230 230 """Propagate ui.system() request in the following format:
231 231
232 232 payload length (unsigned int),
233 233 type, '\0',
234 234 cmd, '\0',
235 235 cwd, '\0',
236 236 envkey, '=', val, '\0',
237 237 ...
238 238 envkey, '=', val
239 239
240 240 if type == 'system', waits for:
241 241
242 242 exitcode length (unsigned int),
243 243 exitcode (int)
244 244
245 245 if type == 'pager', repetitively waits for a command name ending with '\n'
246 246 and executes it defined by cmdtable, or exits the loop if the command name
247 247 is empty.
248 248 """
249 249 def __init__(self, in_, out, channel):
250 250 self.in_ = in_
251 251 self.out = out
252 252 self.channel = channel
253 253
254 254 def __call__(self, cmd, environ, cwd=None, type='system', cmdtable=None):
255 255 args = [type, util.quotecommand(cmd), os.path.abspath(cwd or '.')]
256 256 args.extend('%s=%s' % (k, v) for k, v in environ.iteritems())
257 257 data = '\0'.join(args)
258 258 self.out.write(struct.pack('>cI', self.channel, len(data)))
259 259 self.out.write(data)
260 260 self.out.flush()
261 261
262 262 if type == 'system':
263 263 length = self.in_.read(4)
264 264 length, = struct.unpack('>I', length)
265 265 if length != 4:
266 266 raise error.Abort(_('invalid response'))
267 267 rc, = struct.unpack('>i', self.in_.read(4))
268 268 return rc
269 269 elif type == 'pager':
270 270 while True:
271 271 cmd = self.in_.readline()[:-1]
272 272 if not cmd:
273 273 break
274 274 if cmdtable and cmd in cmdtable:
275 275 _log('pager subcommand: %s' % cmd)
276 276 cmdtable[cmd]()
277 277 else:
278 278 raise error.Abort(_('unexpected command: %s') % cmd)
279 279 else:
280 280 raise error.ProgrammingError('invalid S channel type: %s' % type)
281 281
282 282 _iochannels = [
283 283 # server.ch, ui.fp, mode
284 284 ('cin', 'fin', pycompat.sysstr('rb')),
285 285 ('cout', 'fout', pycompat.sysstr('wb')),
286 286 ('cerr', 'ferr', pycompat.sysstr('wb')),
287 287 ]
288 288
289 289 class chgcmdserver(commandserver.server):
290 290 def __init__(self, ui, repo, fin, fout, sock, hashstate, baseaddress):
291 291 super(chgcmdserver, self).__init__(
292 292 _newchgui(ui, channeledsystem(fin, fout, 'S'), self.attachio),
293 293 repo, fin, fout)
294 294 self.clientsock = sock
295 295 self._oldios = [] # original (self.ch, ui.fp, fd) before "attachio"
296 296 self.hashstate = hashstate
297 297 self.baseaddress = baseaddress
298 298 if hashstate is not None:
299 299 self.capabilities = self.capabilities.copy()
300 300 self.capabilities['validate'] = chgcmdserver.validate
301 301
302 302 def cleanup(self):
303 303 super(chgcmdserver, self).cleanup()
304 304 # dispatch._runcatch() does not flush outputs if exception is not
305 305 # handled by dispatch._dispatch()
306 306 self.ui.flush()
307 307 self._restoreio()
308 308
309 309 def attachio(self):
310 310 """Attach to client's stdio passed via unix domain socket; all
311 311 channels except cresult will no longer be used
312 312 """
313 313 # tell client to sendmsg() with 1-byte payload, which makes it
314 314 # distinctive from "attachio\n" command consumed by client.read()
315 315 self.clientsock.sendall(struct.pack('>cI', 'I', 1))
316 316 clientfds = osutil.recvfds(self.clientsock.fileno())
317 317 _log('received fds: %r\n' % clientfds)
318 318
319 319 ui = self.ui
320 320 ui.flush()
321 321 first = self._saveio()
322 322 for fd, (cn, fn, mode) in zip(clientfds, _iochannels):
323 323 assert fd > 0
324 324 fp = getattr(ui, fn)
325 325 os.dup2(fd, fp.fileno())
326 326 os.close(fd)
327 327 if not first:
328 328 continue
329 329 # reset buffering mode when client is first attached. as we want
330 330 # to see output immediately on pager, the mode stays unchanged
331 331 # when client re-attached. ferr is unchanged because it should
332 332 # be unbuffered no matter if it is a tty or not.
333 333 if fn == 'ferr':
334 334 newfp = fp
335 335 else:
336 336 # make it line buffered explicitly because the default is
337 337 # decided on first write(), where fout could be a pager.
338 338 if fp.isatty():
339 339 bufsize = 1 # line buffered
340 340 else:
341 341 bufsize = -1 # system default
342 342 newfp = os.fdopen(fp.fileno(), mode, bufsize)
343 343 setattr(ui, fn, newfp)
344 344 setattr(self, cn, newfp)
345 345
346 346 self.cresult.write(struct.pack('>i', len(clientfds)))
347 347
348 348 def _saveio(self):
349 349 if self._oldios:
350 350 return False
351 351 ui = self.ui
352 352 for cn, fn, _mode in _iochannels:
353 353 ch = getattr(self, cn)
354 354 fp = getattr(ui, fn)
355 355 fd = os.dup(fp.fileno())
356 356 self._oldios.append((ch, fp, fd))
357 357 return True
358 358
359 359 def _restoreio(self):
360 360 ui = self.ui
361 361 for (ch, fp, fd), (cn, fn, _mode) in zip(self._oldios, _iochannels):
362 362 newfp = getattr(ui, fn)
363 363 # close newfp while it's associated with client; otherwise it
364 364 # would be closed when newfp is deleted
365 365 if newfp is not fp:
366 366 newfp.close()
367 367 # restore original fd: fp is open again
368 368 os.dup2(fd, fp.fileno())
369 369 os.close(fd)
370 370 setattr(self, cn, ch)
371 371 setattr(ui, fn, fp)
372 372 del self._oldios[:]
373 373
374 374 def validate(self):
375 375 """Reload the config and check if the server is up to date
376 376
377 377 Read a list of '\0' separated arguments.
378 378 Write a non-empty list of '\0' separated instruction strings or '\0'
379 379 if the list is empty.
380 380 An instruction string could be either:
381 381 - "unlink $path", the client should unlink the path to stop the
382 382 outdated server.
383 383 - "redirect $path", the client should attempt to connect to $path
384 384 first. If it does not work, start a new server. It implies
385 385 "reconnect".
386 386 - "exit $n", the client should exit directly with code n.
387 387 This may happen if we cannot parse the config.
388 388 - "reconnect", the client should close the connection and
389 389 reconnect.
390 390 If neither "reconnect" nor "redirect" is included in the instruction
391 391 list, the client can continue with this server after completing all
392 392 the instructions.
393 393 """
394 394 from . import dispatch # avoid cycle
395 395
396 396 args = self._readlist()
397 397 try:
398 398 self.ui, lui = _loadnewui(self.ui, args)
399 399 except error.ParseError as inst:
400 400 dispatch._formatparse(self.ui.warn, inst)
401 401 self.ui.flush()
402 402 self.cresult.write('exit 255')
403 403 return
404 404 newhash = hashstate.fromui(lui, self.hashstate.mtimepaths)
405 405 insts = []
406 406 if newhash.mtimehash != self.hashstate.mtimehash:
407 407 addr = _hashaddress(self.baseaddress, self.hashstate.confighash)
408 408 insts.append('unlink %s' % addr)
409 409 # mtimehash is empty if one or more extensions fail to load.
410 410 # to be compatible with hg, still serve the client this time.
411 411 if self.hashstate.mtimehash:
412 412 insts.append('reconnect')
413 413 if newhash.confighash != self.hashstate.confighash:
414 414 addr = _hashaddress(self.baseaddress, newhash.confighash)
415 415 insts.append('redirect %s' % addr)
416 416 _log('validate: %s\n' % insts)
417 417 self.cresult.write('\0'.join(insts) or '\0')
418 418
419 419 def chdir(self):
420 420 """Change current directory
421 421
422 422 Note that the behavior of --cwd option is bit different from this.
423 423 It does not affect --config parameter.
424 424 """
425 425 path = self._readstr()
426 426 if not path:
427 427 return
428 428 _log('chdir to %r\n' % path)
429 429 os.chdir(path)
430 430
431 431 def setumask(self):
432 432 """Change umask"""
433 433 mask = struct.unpack('>I', self._read(4))[0]
434 434 _log('setumask %r\n' % mask)
435 435 os.umask(mask)
436 436
437 437 def runcommand(self):
438 438 return super(chgcmdserver, self).runcommand()
439 439
440 440 def setenv(self):
441 441 """Clear and update os.environ
442 442
443 443 Note that not all variables can make an effect on the running process.
444 444 """
445 445 l = self._readlist()
446 446 try:
447 447 newenv = dict(s.split('=', 1) for s in l)
448 448 except ValueError:
449 449 raise ValueError('unexpected value in setenv request')
450 450 _log('setenv: %r\n' % sorted(newenv.keys()))
451 451 encoding.environ.clear()
452 452 encoding.environ.update(newenv)
453 453
454 454 capabilities = commandserver.server.capabilities.copy()
455 455 capabilities.update({'attachio': attachio,
456 456 'chdir': chdir,
457 457 'runcommand': runcommand,
458 458 'setenv': setenv,
459 459 'setumask': setumask})
460 460
461 461 if util.safehasattr(osutil, 'setprocname'):
462 462 def setprocname(self):
463 463 """Change process title"""
464 464 name = self._readstr()
465 465 _log('setprocname: %r\n' % name)
466 466 osutil.setprocname(name)
467 467 capabilities['setprocname'] = setprocname
468 468
469 469 def _tempaddress(address):
470 470 return '%s.%d.tmp' % (address, os.getpid())
471 471
472 472 def _hashaddress(address, hashstr):
473 473 # if the basename of address contains '.', use only the left part. this
474 474 # makes it possible for the client to pass 'server.tmp$PID' and follow by
475 475 # an atomic rename to avoid locking when spawning new servers.
476 476 dirname, basename = os.path.split(address)
477 477 basename = basename.split('.', 1)[0]
478 478 return '%s-%s' % (os.path.join(dirname, basename), hashstr)
479 479
480 480 class chgunixservicehandler(object):
481 481 """Set of operations for chg services"""
482 482
483 483 pollinterval = 1 # [sec]
484 484
485 485 def __init__(self, ui):
486 486 self.ui = ui
487 487 self._idletimeout = ui.configint('chgserver', 'idletimeout', 3600)
488 488 self._lastactive = time.time()
489 489
490 490 def bindsocket(self, sock, address):
491 491 self._inithashstate(address)
492 492 self._checkextensions()
493 493 self._bind(sock)
494 494 self._createsymlink()
495 495
496 496 def _inithashstate(self, address):
497 497 self._baseaddress = address
498 498 if self.ui.configbool('chgserver', 'skiphash', False):
499 499 self._hashstate = None
500 500 self._realaddress = address
501 501 return
502 502 self._hashstate = hashstate.fromui(self.ui)
503 503 self._realaddress = _hashaddress(address, self._hashstate.confighash)
504 504
505 505 def _checkextensions(self):
506 506 if not self._hashstate:
507 507 return
508 508 if extensions.notloaded():
509 509 # one or more extensions failed to load. mtimehash becomes
510 510 # meaningless because we do not know the paths of those extensions.
511 511 # set mtimehash to an illegal hash value to invalidate the server.
512 512 self._hashstate.mtimehash = ''
513 513
514 514 def _bind(self, sock):
515 515 # use a unique temp address so we can stat the file and do ownership
516 516 # check later
517 517 tempaddress = _tempaddress(self._realaddress)
518 518 util.bindunixsocket(sock, tempaddress)
519 519 self._socketstat = os.stat(tempaddress)
520 520 # rename will replace the old socket file if exists atomically. the
521 521 # old server will detect ownership change and exit.
522 522 util.rename(tempaddress, self._realaddress)
523 523
524 524 def _createsymlink(self):
525 525 if self._baseaddress == self._realaddress:
526 526 return
527 527 tempaddress = _tempaddress(self._baseaddress)
528 528 os.symlink(os.path.basename(self._realaddress), tempaddress)
529 529 util.rename(tempaddress, self._baseaddress)
530 530
531 531 def _issocketowner(self):
532 532 try:
533 533 stat = os.stat(self._realaddress)
534 534 return (stat.st_ino == self._socketstat.st_ino and
535 535 stat.st_mtime == self._socketstat.st_mtime)
536 536 except OSError:
537 537 return False
538 538
539 539 def unlinksocket(self, address):
540 540 if not self._issocketowner():
541 541 return
542 542 # it is possible to have a race condition here that we may
543 543 # remove another server's socket file. but that's okay
544 544 # since that server will detect and exit automatically and
545 545 # the client will start a new server on demand.
546 546 util.tryunlink(self._realaddress)
547 547
548 548 def printbanner(self, address):
549 549 # no "listening at" message should be printed to simulate hg behavior
550 550 pass
551 551
552 552 def shouldexit(self):
553 553 if not self._issocketowner():
554 554 self.ui.debug('%s is not owned, exiting.\n' % self._realaddress)
555 555 return True
556 556 if time.time() - self._lastactive > self._idletimeout:
557 557 self.ui.debug('being idle too long. exiting.\n')
558 558 return True
559 559 return False
560 560
561 561 def newconnection(self):
562 562 self._lastactive = time.time()
563 563
564 564 def createcmdserver(self, repo, conn, fin, fout):
565 565 return chgcmdserver(self.ui, repo, fin, fout, conn,
566 566 self._hashstate, self._baseaddress)
567 567
568 568 def chgunixservice(ui, repo, opts):
569 569 # CHGINTERNALMARK is temporarily set by chg client to detect if chg will
570 570 # start another chg. drop it to avoid possible side effects.
571 571 if 'CHGINTERNALMARK' in encoding.environ:
572 572 del encoding.environ['CHGINTERNALMARK']
573 573
574 574 if repo:
575 575 # one chgserver can serve multiple repos. drop repo information
576 576 ui.setconfig('bundle', 'mainreporoot', '', 'repo')
577 577 h = chgunixservicehandler(ui)
578 578 return commandserver.unixforkingservice(ui, repo=None, opts=opts, handler=h)
@@ -1,92 +1,98
1 1 # rcutil.py - utilities about config paths, special config sections etc.
2 2 #
3 3 # Copyright Mercurial Contributors
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 os
11 11
12 12 from . import (
13 13 encoding,
14 14 osutil,
15 15 pycompat,
16 16 util,
17 17 )
18 18
19 19 if pycompat.osname == 'nt':
20 20 from . import scmwindows as scmplatform
21 21 else:
22 22 from . import scmposix as scmplatform
23 23
24 24 systemrcpath = scmplatform.systemrcpath
25 25 userrcpath = scmplatform.userrcpath
26 26
27 27 def _expandrcpath(path):
28 28 '''path could be a file or a directory. return a list of file paths'''
29 29 p = util.expandpath(path)
30 30 if os.path.isdir(p):
31 31 join = os.path.join
32 32 return [join(p, f) for f, k in osutil.listdir(p) if f.endswith('.rc')]
33 33 return [p]
34 34
35 35 def envrcitems(env=None):
36 36 '''Return [(section, name, value, source)] config items.
37 37
38 38 The config items are extracted from environment variables specified by env,
39 39 used to override systemrc, but not userrc.
40 40
41 41 If env is not provided, encoding.environ will be used.
42 42 '''
43 43 if env is None:
44 44 env = encoding.environ
45 45 checklist = [
46 46 ('EDITOR', 'ui', 'editor'),
47 47 ('VISUAL', 'ui', 'editor'),
48 48 ('PAGER', 'pager', 'pager'),
49 49 ]
50 50 result = []
51 51 for envname, section, configname in checklist:
52 52 if envname not in env:
53 53 continue
54 54 result.append((section, configname, env[envname], '$%s' % envname))
55 55 return result
56 56
57 57 def defaultrcpath():
58 58 '''return rc paths in default.d'''
59 59 path = []
60 60 defaultpath = os.path.join(util.datapath, 'default.d')
61 61 if os.path.isdir(defaultpath):
62 62 path = _expandrcpath(defaultpath)
63 63 return path
64 64
65 65 def rccomponents():
66 66 '''return an ordered [(type, obj)] about where to load configs.
67 67
68 68 respect $HGRCPATH. if $HGRCPATH is empty, only .hg/hgrc of current repo is
69 69 used. if $HGRCPATH is not set, the platform default will be used.
70 70
71 71 if a directory is provided, *.rc files under it will be used.
72 72
73 73 type could be either 'path' or 'items', if type is 'path', obj is a string,
74 74 and is the config file path. if type is 'items', obj is a list of (section,
75 75 name, value, source) that should fill the config directly.
76 76 '''
77 77 envrc = ('items', envrcitems())
78 78
79 79 if 'HGRCPATH' in encoding.environ:
80 80 # assume HGRCPATH is all about user configs so environments can be
81 81 # overridden.
82 82 _rccomponents = [envrc]
83 83 for p in encoding.environ['HGRCPATH'].split(pycompat.ospathsep):
84 84 if not p:
85 85 continue
86 86 _rccomponents.extend(('path', p) for p in _expandrcpath(p))
87 87 else:
88 88 normpaths = lambda paths: [('path', os.path.normpath(p)) for p in paths]
89 89 _rccomponents = normpaths(defaultrcpath() + systemrcpath())
90 90 _rccomponents.append(envrc)
91 91 _rccomponents.extend(normpaths(userrcpath()))
92 92 return _rccomponents
93
94 def defaultpagerenv():
95 '''return a dict of default environment variables and their values,
96 intended to be set before starting a pager.
97 '''
98 return {'LESS': 'FRX', 'LV': '-c'}
@@ -1,1654 +1,1660
1 1 # ui.py - user interface bits for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import atexit
11 11 import collections
12 12 import contextlib
13 13 import errno
14 14 import getpass
15 15 import inspect
16 16 import os
17 17 import re
18 18 import signal
19 19 import socket
20 20 import subprocess
21 21 import sys
22 22 import tempfile
23 23 import traceback
24 24
25 25 from .i18n import _
26 26 from .node import hex
27 27
28 28 from . import (
29 29 color,
30 30 config,
31 31 encoding,
32 32 error,
33 33 formatter,
34 34 progress,
35 35 pycompat,
36 36 rcutil,
37 37 scmutil,
38 38 util,
39 39 )
40 40
41 41 urlreq = util.urlreq
42 42
43 43 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
44 44 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
45 45 if not c.isalnum())
46 46
47 47 samplehgrcs = {
48 48 'user':
49 49 """# example user config (see 'hg help config' for more info)
50 50 [ui]
51 51 # name and email, e.g.
52 52 # username = Jane Doe <jdoe@example.com>
53 53 username =
54 54
55 55 # uncomment to colorize command output
56 56 # color = auto
57 57
58 58 [extensions]
59 59 # uncomment these lines to enable some popular extensions
60 60 # (see 'hg help extensions' for more info)
61 61 #
62 62 # pager =""",
63 63
64 64 'cloned':
65 65 """# example repository config (see 'hg help config' for more info)
66 66 [paths]
67 67 default = %s
68 68
69 69 # path aliases to other clones of this repo in URLs or filesystem paths
70 70 # (see 'hg help config.paths' for more info)
71 71 #
72 72 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
73 73 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
74 74 # my-clone = /home/jdoe/jdoes-clone
75 75
76 76 [ui]
77 77 # name and email (local to this repository, optional), e.g.
78 78 # username = Jane Doe <jdoe@example.com>
79 79 """,
80 80
81 81 'local':
82 82 """# example repository config (see 'hg help config' for more info)
83 83 [paths]
84 84 # path aliases to other clones of this repo in URLs or filesystem paths
85 85 # (see 'hg help config.paths' for more info)
86 86 #
87 87 # default = http://example.com/hg/example-repo
88 88 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
89 89 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
90 90 # my-clone = /home/jdoe/jdoes-clone
91 91
92 92 [ui]
93 93 # name and email (local to this repository, optional), e.g.
94 94 # username = Jane Doe <jdoe@example.com>
95 95 """,
96 96
97 97 'global':
98 98 """# example system-wide hg config (see 'hg help config' for more info)
99 99
100 100 [ui]
101 101 # uncomment to colorize command output
102 102 # color = auto
103 103
104 104 [extensions]
105 105 # uncomment these lines to enable some popular extensions
106 106 # (see 'hg help extensions' for more info)
107 107 #
108 108 # blackbox =
109 109 # pager =""",
110 110 }
111 111
112 112
113 113 class httppasswordmgrdbproxy(object):
114 114 """Delays loading urllib2 until it's needed."""
115 115 def __init__(self):
116 116 self._mgr = None
117 117
118 118 def _get_mgr(self):
119 119 if self._mgr is None:
120 120 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
121 121 return self._mgr
122 122
123 123 def add_password(self, *args, **kwargs):
124 124 return self._get_mgr().add_password(*args, **kwargs)
125 125
126 126 def find_user_password(self, *args, **kwargs):
127 127 return self._get_mgr().find_user_password(*args, **kwargs)
128 128
129 129 def _catchterm(*args):
130 130 raise error.SignalInterrupt
131 131
132 132 class ui(object):
133 133 def __init__(self, src=None):
134 134 """Create a fresh new ui object if no src given
135 135
136 136 Use uimod.ui.load() to create a ui which knows global and user configs.
137 137 In most cases, you should use ui.copy() to create a copy of an existing
138 138 ui object.
139 139 """
140 140 # _buffers: used for temporary capture of output
141 141 self._buffers = []
142 142 # 3-tuple describing how each buffer in the stack behaves.
143 143 # Values are (capture stderr, capture subprocesses, apply labels).
144 144 self._bufferstates = []
145 145 # When a buffer is active, defines whether we are expanding labels.
146 146 # This exists to prevent an extra list lookup.
147 147 self._bufferapplylabels = None
148 148 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
149 149 self._reportuntrusted = True
150 150 self._ocfg = config.config() # overlay
151 151 self._tcfg = config.config() # trusted
152 152 self._ucfg = config.config() # untrusted
153 153 self._trustusers = set()
154 154 self._trustgroups = set()
155 155 self.callhooks = True
156 156 # Insecure server connections requested.
157 157 self.insecureconnections = False
158 158 # Blocked time
159 159 self.logblockedtimes = False
160 160 # color mode: see mercurial/color.py for possible value
161 161 self._colormode = None
162 162 self._terminfoparams = {}
163 163 self._styles = {}
164 164
165 165 if src:
166 166 self.fout = src.fout
167 167 self.ferr = src.ferr
168 168 self.fin = src.fin
169 169 self.pageractive = src.pageractive
170 170 self._disablepager = src._disablepager
171 171
172 172 self._tcfg = src._tcfg.copy()
173 173 self._ucfg = src._ucfg.copy()
174 174 self._ocfg = src._ocfg.copy()
175 175 self._trustusers = src._trustusers.copy()
176 176 self._trustgroups = src._trustgroups.copy()
177 177 self.environ = src.environ
178 178 self.callhooks = src.callhooks
179 179 self.insecureconnections = src.insecureconnections
180 180 self._colormode = src._colormode
181 181 self._terminfoparams = src._terminfoparams.copy()
182 182 self._styles = src._styles.copy()
183 183
184 184 self.fixconfig()
185 185
186 186 self.httppasswordmgrdb = src.httppasswordmgrdb
187 187 self._blockedtimes = src._blockedtimes
188 188 else:
189 189 self.fout = util.stdout
190 190 self.ferr = util.stderr
191 191 self.fin = util.stdin
192 192 self.pageractive = False
193 193 self._disablepager = False
194 194
195 195 # shared read-only environment
196 196 self.environ = encoding.environ
197 197
198 198 self.httppasswordmgrdb = httppasswordmgrdbproxy()
199 199 self._blockedtimes = collections.defaultdict(int)
200 200
201 201 allowed = self.configlist('experimental', 'exportableenviron')
202 202 if '*' in allowed:
203 203 self._exportableenviron = self.environ
204 204 else:
205 205 self._exportableenviron = {}
206 206 for k in allowed:
207 207 if k in self.environ:
208 208 self._exportableenviron[k] = self.environ[k]
209 209
210 210 @classmethod
211 211 def load(cls):
212 212 """Create a ui and load global and user configs"""
213 213 u = cls()
214 214 # we always trust global config files and environment variables
215 215 for t, f in rcutil.rccomponents():
216 216 if t == 'path':
217 217 u.readconfig(f, trust=True)
218 218 elif t == 'items':
219 219 sections = set()
220 220 for section, name, value, source in f:
221 221 # do not set u._ocfg
222 222 # XXX clean this up once immutable config object is a thing
223 223 u._tcfg.set(section, name, value, source)
224 224 u._ucfg.set(section, name, value, source)
225 225 sections.add(section)
226 226 for section in sections:
227 227 u.fixconfig(section=section)
228 228 else:
229 229 raise error.ProgrammingError('unknown rctype: %s' % t)
230 230 return u
231 231
232 232 def copy(self):
233 233 return self.__class__(self)
234 234
235 235 def resetstate(self):
236 236 """Clear internal state that shouldn't persist across commands"""
237 237 if self._progbar:
238 238 self._progbar.resetstate() # reset last-print time of progress bar
239 239 self.httppasswordmgrdb = httppasswordmgrdbproxy()
240 240
241 241 @contextlib.contextmanager
242 242 def timeblockedsection(self, key):
243 243 # this is open-coded below - search for timeblockedsection to find them
244 244 starttime = util.timer()
245 245 try:
246 246 yield
247 247 finally:
248 248 self._blockedtimes[key + '_blocked'] += \
249 249 (util.timer() - starttime) * 1000
250 250
251 251 def formatter(self, topic, opts):
252 252 return formatter.formatter(self, topic, opts)
253 253
254 254 def _trusted(self, fp, f):
255 255 st = util.fstat(fp)
256 256 if util.isowner(st):
257 257 return True
258 258
259 259 tusers, tgroups = self._trustusers, self._trustgroups
260 260 if '*' in tusers or '*' in tgroups:
261 261 return True
262 262
263 263 user = util.username(st.st_uid)
264 264 group = util.groupname(st.st_gid)
265 265 if user in tusers or group in tgroups or user == util.username():
266 266 return True
267 267
268 268 if self._reportuntrusted:
269 269 self.warn(_('not trusting file %s from untrusted '
270 270 'user %s, group %s\n') % (f, user, group))
271 271 return False
272 272
273 273 def readconfig(self, filename, root=None, trust=False,
274 274 sections=None, remap=None):
275 275 try:
276 276 fp = open(filename, u'rb')
277 277 except IOError:
278 278 if not sections: # ignore unless we were looking for something
279 279 return
280 280 raise
281 281
282 282 cfg = config.config()
283 283 trusted = sections or trust or self._trusted(fp, filename)
284 284
285 285 try:
286 286 cfg.read(filename, fp, sections=sections, remap=remap)
287 287 fp.close()
288 288 except error.ConfigError as inst:
289 289 if trusted:
290 290 raise
291 291 self.warn(_("ignored: %s\n") % str(inst))
292 292
293 293 if self.plain():
294 294 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
295 295 'logtemplate', 'statuscopies', 'style',
296 296 'traceback', 'verbose'):
297 297 if k in cfg['ui']:
298 298 del cfg['ui'][k]
299 299 for k, v in cfg.items('defaults'):
300 300 del cfg['defaults'][k]
301 301 for k, v in cfg.items('commands'):
302 302 del cfg['commands'][k]
303 303 # Don't remove aliases from the configuration if in the exceptionlist
304 304 if self.plain('alias'):
305 305 for k, v in cfg.items('alias'):
306 306 del cfg['alias'][k]
307 307 if self.plain('revsetalias'):
308 308 for k, v in cfg.items('revsetalias'):
309 309 del cfg['revsetalias'][k]
310 310 if self.plain('templatealias'):
311 311 for k, v in cfg.items('templatealias'):
312 312 del cfg['templatealias'][k]
313 313
314 314 if trusted:
315 315 self._tcfg.update(cfg)
316 316 self._tcfg.update(self._ocfg)
317 317 self._ucfg.update(cfg)
318 318 self._ucfg.update(self._ocfg)
319 319
320 320 if root is None:
321 321 root = os.path.expanduser('~')
322 322 self.fixconfig(root=root)
323 323
324 324 def fixconfig(self, root=None, section=None):
325 325 if section in (None, 'paths'):
326 326 # expand vars and ~
327 327 # translate paths relative to root (or home) into absolute paths
328 328 root = root or pycompat.getcwd()
329 329 for c in self._tcfg, self._ucfg, self._ocfg:
330 330 for n, p in c.items('paths'):
331 331 # Ignore sub-options.
332 332 if ':' in n:
333 333 continue
334 334 if not p:
335 335 continue
336 336 if '%%' in p:
337 337 s = self.configsource('paths', n) or 'none'
338 338 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
339 339 % (n, p, s))
340 340 p = p.replace('%%', '%')
341 341 p = util.expandpath(p)
342 342 if not util.hasscheme(p) and not os.path.isabs(p):
343 343 p = os.path.normpath(os.path.join(root, p))
344 344 c.set("paths", n, p)
345 345
346 346 if section in (None, 'ui'):
347 347 # update ui options
348 348 self.debugflag = self.configbool('ui', 'debug')
349 349 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
350 350 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
351 351 if self.verbose and self.quiet:
352 352 self.quiet = self.verbose = False
353 353 self._reportuntrusted = self.debugflag or self.configbool("ui",
354 354 "report_untrusted", True)
355 355 self.tracebackflag = self.configbool('ui', 'traceback', False)
356 356 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
357 357
358 358 if section in (None, 'trusted'):
359 359 # update trust information
360 360 self._trustusers.update(self.configlist('trusted', 'users'))
361 361 self._trustgroups.update(self.configlist('trusted', 'groups'))
362 362
363 363 def backupconfig(self, section, item):
364 364 return (self._ocfg.backup(section, item),
365 365 self._tcfg.backup(section, item),
366 366 self._ucfg.backup(section, item),)
367 367 def restoreconfig(self, data):
368 368 self._ocfg.restore(data[0])
369 369 self._tcfg.restore(data[1])
370 370 self._ucfg.restore(data[2])
371 371
372 372 def setconfig(self, section, name, value, source=''):
373 373 for cfg in (self._ocfg, self._tcfg, self._ucfg):
374 374 cfg.set(section, name, value, source)
375 375 self.fixconfig(section=section)
376 376
377 377 def _data(self, untrusted):
378 378 return untrusted and self._ucfg or self._tcfg
379 379
380 380 def configsource(self, section, name, untrusted=False):
381 381 return self._data(untrusted).source(section, name)
382 382
383 383 def config(self, section, name, default=None, untrusted=False):
384 384 if isinstance(name, list):
385 385 alternates = name
386 386 else:
387 387 alternates = [name]
388 388
389 389 for n in alternates:
390 390 value = self._data(untrusted).get(section, n, None)
391 391 if value is not None:
392 392 name = n
393 393 break
394 394 else:
395 395 value = default
396 396
397 397 if self.debugflag and not untrusted and self._reportuntrusted:
398 398 for n in alternates:
399 399 uvalue = self._ucfg.get(section, n)
400 400 if uvalue is not None and uvalue != value:
401 401 self.debug("ignoring untrusted configuration option "
402 402 "%s.%s = %s\n" % (section, n, uvalue))
403 403 return value
404 404
405 405 def configsuboptions(self, section, name, default=None, untrusted=False):
406 406 """Get a config option and all sub-options.
407 407
408 408 Some config options have sub-options that are declared with the
409 409 format "key:opt = value". This method is used to return the main
410 410 option and all its declared sub-options.
411 411
412 412 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
413 413 is a dict of defined sub-options where keys and values are strings.
414 414 """
415 415 data = self._data(untrusted)
416 416 main = data.get(section, name, default)
417 417 if self.debugflag and not untrusted and self._reportuntrusted:
418 418 uvalue = self._ucfg.get(section, name)
419 419 if uvalue is not None and uvalue != main:
420 420 self.debug('ignoring untrusted configuration option '
421 421 '%s.%s = %s\n' % (section, name, uvalue))
422 422
423 423 sub = {}
424 424 prefix = '%s:' % name
425 425 for k, v in data.items(section):
426 426 if k.startswith(prefix):
427 427 sub[k[len(prefix):]] = v
428 428
429 429 if self.debugflag and not untrusted and self._reportuntrusted:
430 430 for k, v in sub.items():
431 431 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
432 432 if uvalue is not None and uvalue != v:
433 433 self.debug('ignoring untrusted configuration option '
434 434 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
435 435
436 436 return main, sub
437 437
438 438 def configpath(self, section, name, default=None, untrusted=False):
439 439 'get a path config item, expanded relative to repo root or config file'
440 440 v = self.config(section, name, default, untrusted)
441 441 if v is None:
442 442 return None
443 443 if not os.path.isabs(v) or "://" not in v:
444 444 src = self.configsource(section, name, untrusted)
445 445 if ':' in src:
446 446 base = os.path.dirname(src.rsplit(':')[0])
447 447 v = os.path.join(base, os.path.expanduser(v))
448 448 return v
449 449
450 450 def configbool(self, section, name, default=False, untrusted=False):
451 451 """parse a configuration element as a boolean
452 452
453 453 >>> u = ui(); s = 'foo'
454 454 >>> u.setconfig(s, 'true', 'yes')
455 455 >>> u.configbool(s, 'true')
456 456 True
457 457 >>> u.setconfig(s, 'false', 'no')
458 458 >>> u.configbool(s, 'false')
459 459 False
460 460 >>> u.configbool(s, 'unknown')
461 461 False
462 462 >>> u.configbool(s, 'unknown', True)
463 463 True
464 464 >>> u.setconfig(s, 'invalid', 'somevalue')
465 465 >>> u.configbool(s, 'invalid')
466 466 Traceback (most recent call last):
467 467 ...
468 468 ConfigError: foo.invalid is not a boolean ('somevalue')
469 469 """
470 470
471 471 v = self.config(section, name, None, untrusted)
472 472 if v is None:
473 473 return default
474 474 if isinstance(v, bool):
475 475 return v
476 476 b = util.parsebool(v)
477 477 if b is None:
478 478 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
479 479 % (section, name, v))
480 480 return b
481 481
482 482 def configwith(self, convert, section, name, default=None,
483 483 desc=None, untrusted=False):
484 484 """parse a configuration element with a conversion function
485 485
486 486 >>> u = ui(); s = 'foo'
487 487 >>> u.setconfig(s, 'float1', '42')
488 488 >>> u.configwith(float, s, 'float1')
489 489 42.0
490 490 >>> u.setconfig(s, 'float2', '-4.25')
491 491 >>> u.configwith(float, s, 'float2')
492 492 -4.25
493 493 >>> u.configwith(float, s, 'unknown', 7)
494 494 7
495 495 >>> u.setconfig(s, 'invalid', 'somevalue')
496 496 >>> u.configwith(float, s, 'invalid')
497 497 Traceback (most recent call last):
498 498 ...
499 499 ConfigError: foo.invalid is not a valid float ('somevalue')
500 500 >>> u.configwith(float, s, 'invalid', desc='womble')
501 501 Traceback (most recent call last):
502 502 ...
503 503 ConfigError: foo.invalid is not a valid womble ('somevalue')
504 504 """
505 505
506 506 v = self.config(section, name, None, untrusted)
507 507 if v is None:
508 508 return default
509 509 try:
510 510 return convert(v)
511 511 except ValueError:
512 512 if desc is None:
513 513 desc = convert.__name__
514 514 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
515 515 % (section, name, desc, v))
516 516
517 517 def configint(self, section, name, default=None, untrusted=False):
518 518 """parse a configuration element as an integer
519 519
520 520 >>> u = ui(); s = 'foo'
521 521 >>> u.setconfig(s, 'int1', '42')
522 522 >>> u.configint(s, 'int1')
523 523 42
524 524 >>> u.setconfig(s, 'int2', '-42')
525 525 >>> u.configint(s, 'int2')
526 526 -42
527 527 >>> u.configint(s, 'unknown', 7)
528 528 7
529 529 >>> u.setconfig(s, 'invalid', 'somevalue')
530 530 >>> u.configint(s, 'invalid')
531 531 Traceback (most recent call last):
532 532 ...
533 533 ConfigError: foo.invalid is not a valid integer ('somevalue')
534 534 """
535 535
536 536 return self.configwith(int, section, name, default, 'integer',
537 537 untrusted)
538 538
539 539 def configbytes(self, section, name, default=0, untrusted=False):
540 540 """parse a configuration element as a quantity in bytes
541 541
542 542 Units can be specified as b (bytes), k or kb (kilobytes), m or
543 543 mb (megabytes), g or gb (gigabytes).
544 544
545 545 >>> u = ui(); s = 'foo'
546 546 >>> u.setconfig(s, 'val1', '42')
547 547 >>> u.configbytes(s, 'val1')
548 548 42
549 549 >>> u.setconfig(s, 'val2', '42.5 kb')
550 550 >>> u.configbytes(s, 'val2')
551 551 43520
552 552 >>> u.configbytes(s, 'unknown', '7 MB')
553 553 7340032
554 554 >>> u.setconfig(s, 'invalid', 'somevalue')
555 555 >>> u.configbytes(s, 'invalid')
556 556 Traceback (most recent call last):
557 557 ...
558 558 ConfigError: foo.invalid is not a byte quantity ('somevalue')
559 559 """
560 560
561 561 value = self.config(section, name, None, untrusted)
562 562 if value is None:
563 563 if not isinstance(default, str):
564 564 return default
565 565 value = default
566 566 try:
567 567 return util.sizetoint(value)
568 568 except error.ParseError:
569 569 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
570 570 % (section, name, value))
571 571
572 572 def configlist(self, section, name, default=None, untrusted=False):
573 573 """parse a configuration element as a list of comma/space separated
574 574 strings
575 575
576 576 >>> u = ui(); s = 'foo'
577 577 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
578 578 >>> u.configlist(s, 'list1')
579 579 ['this', 'is', 'a small', 'test']
580 580 """
581 581 # default is not always a list
582 582 if isinstance(default, bytes):
583 583 default = config.parselist(default)
584 584 return self.configwith(config.parselist, section, name, default or [],
585 585 'list', untrusted)
586 586
587 587 def hasconfig(self, section, name, untrusted=False):
588 588 return self._data(untrusted).hasitem(section, name)
589 589
590 590 def has_section(self, section, untrusted=False):
591 591 '''tell whether section exists in config.'''
592 592 return section in self._data(untrusted)
593 593
594 594 def configitems(self, section, untrusted=False, ignoresub=False):
595 595 items = self._data(untrusted).items(section)
596 596 if ignoresub:
597 597 newitems = {}
598 598 for k, v in items:
599 599 if ':' not in k:
600 600 newitems[k] = v
601 601 items = newitems.items()
602 602 if self.debugflag and not untrusted and self._reportuntrusted:
603 603 for k, v in self._ucfg.items(section):
604 604 if self._tcfg.get(section, k) != v:
605 605 self.debug("ignoring untrusted configuration option "
606 606 "%s.%s = %s\n" % (section, k, v))
607 607 return items
608 608
609 609 def walkconfig(self, untrusted=False):
610 610 cfg = self._data(untrusted)
611 611 for section in cfg.sections():
612 612 for name, value in self.configitems(section, untrusted):
613 613 yield section, name, value
614 614
615 615 def plain(self, feature=None):
616 616 '''is plain mode active?
617 617
618 618 Plain mode means that all configuration variables which affect
619 619 the behavior and output of Mercurial should be
620 620 ignored. Additionally, the output should be stable,
621 621 reproducible and suitable for use in scripts or applications.
622 622
623 623 The only way to trigger plain mode is by setting either the
624 624 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
625 625
626 626 The return value can either be
627 627 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
628 628 - True otherwise
629 629 '''
630 630 if ('HGPLAIN' not in encoding.environ and
631 631 'HGPLAINEXCEPT' not in encoding.environ):
632 632 return False
633 633 exceptions = encoding.environ.get('HGPLAINEXCEPT',
634 634 '').strip().split(',')
635 635 if feature and exceptions:
636 636 return feature not in exceptions
637 637 return True
638 638
639 639 def username(self):
640 640 """Return default username to be used in commits.
641 641
642 642 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
643 643 and stop searching if one of these is set.
644 644 If not found and ui.askusername is True, ask the user, else use
645 645 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
646 646 """
647 647 user = encoding.environ.get("HGUSER")
648 648 if user is None:
649 649 user = self.config("ui", ["username", "user"])
650 650 if user is not None:
651 651 user = os.path.expandvars(user)
652 652 if user is None:
653 653 user = encoding.environ.get("EMAIL")
654 654 if user is None and self.configbool("ui", "askusername"):
655 655 user = self.prompt(_("enter a commit username:"), default=None)
656 656 if user is None and not self.interactive():
657 657 try:
658 658 user = '%s@%s' % (util.getuser(), socket.getfqdn())
659 659 self.warn(_("no username found, using '%s' instead\n") % user)
660 660 except KeyError:
661 661 pass
662 662 if not user:
663 663 raise error.Abort(_('no username supplied'),
664 664 hint=_("use 'hg config --edit' "
665 665 'to set your username'))
666 666 if "\n" in user:
667 667 raise error.Abort(_("username %s contains a newline\n")
668 668 % repr(user))
669 669 return user
670 670
671 671 def shortuser(self, user):
672 672 """Return a short representation of a user name or email address."""
673 673 if not self.verbose:
674 674 user = util.shortuser(user)
675 675 return user
676 676
677 677 def expandpath(self, loc, default=None):
678 678 """Return repository location relative to cwd or from [paths]"""
679 679 try:
680 680 p = self.paths.getpath(loc)
681 681 if p:
682 682 return p.rawloc
683 683 except error.RepoError:
684 684 pass
685 685
686 686 if default:
687 687 try:
688 688 p = self.paths.getpath(default)
689 689 if p:
690 690 return p.rawloc
691 691 except error.RepoError:
692 692 pass
693 693
694 694 return loc
695 695
696 696 @util.propertycache
697 697 def paths(self):
698 698 return paths(self)
699 699
700 700 def pushbuffer(self, error=False, subproc=False, labeled=False):
701 701 """install a buffer to capture standard output of the ui object
702 702
703 703 If error is True, the error output will be captured too.
704 704
705 705 If subproc is True, output from subprocesses (typically hooks) will be
706 706 captured too.
707 707
708 708 If labeled is True, any labels associated with buffered
709 709 output will be handled. By default, this has no effect
710 710 on the output returned, but extensions and GUI tools may
711 711 handle this argument and returned styled output. If output
712 712 is being buffered so it can be captured and parsed or
713 713 processed, labeled should not be set to True.
714 714 """
715 715 self._buffers.append([])
716 716 self._bufferstates.append((error, subproc, labeled))
717 717 self._bufferapplylabels = labeled
718 718
719 719 def popbuffer(self):
720 720 '''pop the last buffer and return the buffered output'''
721 721 self._bufferstates.pop()
722 722 if self._bufferstates:
723 723 self._bufferapplylabels = self._bufferstates[-1][2]
724 724 else:
725 725 self._bufferapplylabels = None
726 726
727 727 return "".join(self._buffers.pop())
728 728
729 729 def write(self, *args, **opts):
730 730 '''write args to output
731 731
732 732 By default, this method simply writes to the buffer or stdout.
733 733 Color mode can be set on the UI class to have the output decorated
734 734 with color modifier before being written to stdout.
735 735
736 736 The color used is controlled by an optional keyword argument, "label".
737 737 This should be a string containing label names separated by space.
738 738 Label names take the form of "topic.type". For example, ui.debug()
739 739 issues a label of "ui.debug".
740 740
741 741 When labeling output for a specific command, a label of
742 742 "cmdname.type" is recommended. For example, status issues
743 743 a label of "status.modified" for modified files.
744 744 '''
745 745 if self._buffers and not opts.get('prompt', False):
746 746 if self._bufferapplylabels:
747 747 label = opts.get('label', '')
748 748 self._buffers[-1].extend(self.label(a, label) for a in args)
749 749 else:
750 750 self._buffers[-1].extend(args)
751 751 elif self._colormode == 'win32':
752 752 # windows color printing is its own can of crab, defer to
753 753 # the color module and that is it.
754 754 color.win32print(self, self._write, *args, **opts)
755 755 else:
756 756 msgs = args
757 757 if self._colormode is not None:
758 758 label = opts.get('label', '')
759 759 msgs = [self.label(a, label) for a in args]
760 760 self._write(*msgs, **opts)
761 761
762 762 def _write(self, *msgs, **opts):
763 763 self._progclear()
764 764 # opencode timeblockedsection because this is a critical path
765 765 starttime = util.timer()
766 766 try:
767 767 for a in msgs:
768 768 self.fout.write(a)
769 769 finally:
770 770 self._blockedtimes['stdio_blocked'] += \
771 771 (util.timer() - starttime) * 1000
772 772
773 773 def write_err(self, *args, **opts):
774 774 self._progclear()
775 775 if self._bufferstates and self._bufferstates[-1][0]:
776 776 self.write(*args, **opts)
777 777 elif self._colormode == 'win32':
778 778 # windows color printing is its own can of crab, defer to
779 779 # the color module and that is it.
780 780 color.win32print(self, self._write_err, *args, **opts)
781 781 else:
782 782 msgs = args
783 783 if self._colormode is not None:
784 784 label = opts.get('label', '')
785 785 msgs = [self.label(a, label) for a in args]
786 786 self._write_err(*msgs, **opts)
787 787
788 788 def _write_err(self, *msgs, **opts):
789 789 try:
790 790 with self.timeblockedsection('stdio'):
791 791 if not getattr(self.fout, 'closed', False):
792 792 self.fout.flush()
793 793 for a in msgs:
794 794 self.ferr.write(a)
795 795 # stderr may be buffered under win32 when redirected to files,
796 796 # including stdout.
797 797 if not getattr(self.ferr, 'closed', False):
798 798 self.ferr.flush()
799 799 except IOError as inst:
800 800 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
801 801 raise
802 802
803 803 def flush(self):
804 804 # opencode timeblockedsection because this is a critical path
805 805 starttime = util.timer()
806 806 try:
807 807 try: self.fout.flush()
808 808 except (IOError, ValueError): pass
809 809 try: self.ferr.flush()
810 810 except (IOError, ValueError): pass
811 811 finally:
812 812 self._blockedtimes['stdio_blocked'] += \
813 813 (util.timer() - starttime) * 1000
814 814
815 815 def _isatty(self, fh):
816 816 if self.configbool('ui', 'nontty', False):
817 817 return False
818 818 return util.isatty(fh)
819 819
820 820 def disablepager(self):
821 821 self._disablepager = True
822 822
823 823 def pager(self, command):
824 824 """Start a pager for subsequent command output.
825 825
826 826 Commands which produce a long stream of output should call
827 827 this function to activate the user's preferred pagination
828 828 mechanism (which may be no pager). Calling this function
829 829 precludes any future use of interactive functionality, such as
830 830 prompting the user or activating curses.
831 831
832 832 Args:
833 833 command: The full, non-aliased name of the command. That is, "log"
834 834 not "history, "summary" not "summ", etc.
835 835 """
836 836 if (self._disablepager
837 837 or self.pageractive
838 838 or command in self.configlist('pager', 'ignore')
839 839 or not self.configbool('pager', 'enable', True)
840 840 or not self.configbool('pager', 'attend-' + command, True)
841 841 # TODO: if we want to allow HGPLAINEXCEPT=pager,
842 842 # formatted() will need some adjustment.
843 843 or not self.formatted()
844 844 or self.plain()
845 845 # TODO: expose debugger-enabled on the UI object
846 846 or '--debugger' in pycompat.sysargv):
847 847 # We only want to paginate if the ui appears to be
848 848 # interactive, the user didn't say HGPLAIN or
849 849 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
850 850 return
851 851
852 852 fallbackpager = 'more'
853 853 pagercmd = self.config('pager', 'pager', fallbackpager)
854 854 if not pagercmd:
855 855 return
856 856
857 pagerenv = {}
858 for name, value in rcutil.defaultpagerenv().items():
859 if name not in encoding.environ:
860 pagerenv[name] = value
861
857 862 self.debug('starting pager for command %r\n' % command)
858 863 self.flush()
859 864
860 865 wasformatted = self.formatted()
861 866 if util.safehasattr(signal, "SIGPIPE"):
862 867 signal.signal(signal.SIGPIPE, _catchterm)
863 if self._runpager(pagercmd):
868 if self._runpager(pagercmd, pagerenv):
864 869 self.pageractive = True
865 870 # Preserve the formatted-ness of the UI. This is important
866 871 # because we mess with stdout, which might confuse
867 872 # auto-detection of things being formatted.
868 873 self.setconfig('ui', 'formatted', wasformatted, 'pager')
869 874 self.setconfig('ui', 'interactive', False, 'pager')
870 875
871 876 # If pagermode differs from color.mode, reconfigure color now that
872 877 # pageractive is set.
873 878 cm = self._colormode
874 879 if cm != self.config('color', 'pagermode', cm):
875 880 color.setup(self)
876 881 else:
877 882 # If the pager can't be spawned in dispatch when --pager=on is
878 883 # given, don't try again when the command runs, to avoid a duplicate
879 884 # warning about a missing pager command.
880 885 self.disablepager()
881 886
882 def _runpager(self, command):
887 def _runpager(self, command, env=None):
883 888 """Actually start the pager and set up file descriptors.
884 889
885 890 This is separate in part so that extensions (like chg) can
886 891 override how a pager is invoked.
887 892 """
888 893 if command == 'cat':
889 894 # Save ourselves some work.
890 895 return False
891 896 # If the command doesn't contain any of these characters, we
892 897 # assume it's a binary and exec it directly. This means for
893 898 # simple pager command configurations, we can degrade
894 899 # gracefully and tell the user about their broken pager.
895 900 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
896 901
897 902 if pycompat.osname == 'nt' and not shell:
898 903 # Window's built-in `more` cannot be invoked with shell=False, but
899 904 # its `more.com` can. Hide this implementation detail from the
900 905 # user so we can also get sane bad PAGER behavior. MSYS has
901 906 # `more.exe`, so do a cmd.exe style resolution of the executable to
902 907 # determine which one to use.
903 908 fullcmd = util.findexe(command)
904 909 if not fullcmd:
905 910 self.warn(_("missing pager command '%s', skipping pager\n")
906 911 % command)
907 912 return False
908 913
909 914 command = fullcmd
910 915
911 916 try:
912 917 pager = subprocess.Popen(
913 918 command, shell=shell, bufsize=-1,
914 919 close_fds=util.closefds, stdin=subprocess.PIPE,
915 stdout=util.stdout, stderr=util.stderr)
920 stdout=util.stdout, stderr=util.stderr,
921 env=util.shellenviron(env))
916 922 except OSError as e:
917 923 if e.errno == errno.ENOENT and not shell:
918 924 self.warn(_("missing pager command '%s', skipping pager\n")
919 925 % command)
920 926 return False
921 927 raise
922 928
923 929 # back up original file descriptors
924 930 stdoutfd = os.dup(util.stdout.fileno())
925 931 stderrfd = os.dup(util.stderr.fileno())
926 932
927 933 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
928 934 if self._isatty(util.stderr):
929 935 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
930 936
931 937 @atexit.register
932 938 def killpager():
933 939 if util.safehasattr(signal, "SIGINT"):
934 940 signal.signal(signal.SIGINT, signal.SIG_IGN)
935 941 # restore original fds, closing pager.stdin copies in the process
936 942 os.dup2(stdoutfd, util.stdout.fileno())
937 943 os.dup2(stderrfd, util.stderr.fileno())
938 944 pager.stdin.close()
939 945 pager.wait()
940 946
941 947 return True
942 948
943 949 def interface(self, feature):
944 950 """what interface to use for interactive console features?
945 951
946 952 The interface is controlled by the value of `ui.interface` but also by
947 953 the value of feature-specific configuration. For example:
948 954
949 955 ui.interface.histedit = text
950 956 ui.interface.chunkselector = curses
951 957
952 958 Here the features are "histedit" and "chunkselector".
953 959
954 960 The configuration above means that the default interfaces for commands
955 961 is curses, the interface for histedit is text and the interface for
956 962 selecting chunk is crecord (the best curses interface available).
957 963
958 964 Consider the following example:
959 965 ui.interface = curses
960 966 ui.interface.histedit = text
961 967
962 968 Then histedit will use the text interface and chunkselector will use
963 969 the default curses interface (crecord at the moment).
964 970 """
965 971 alldefaults = frozenset(["text", "curses"])
966 972
967 973 featureinterfaces = {
968 974 "chunkselector": [
969 975 "text",
970 976 "curses",
971 977 ]
972 978 }
973 979
974 980 # Feature-specific interface
975 981 if feature not in featureinterfaces.keys():
976 982 # Programming error, not user error
977 983 raise ValueError("Unknown feature requested %s" % feature)
978 984
979 985 availableinterfaces = frozenset(featureinterfaces[feature])
980 986 if alldefaults > availableinterfaces:
981 987 # Programming error, not user error. We need a use case to
982 988 # define the right thing to do here.
983 989 raise ValueError(
984 990 "Feature %s does not handle all default interfaces" %
985 991 feature)
986 992
987 993 if self.plain():
988 994 return "text"
989 995
990 996 # Default interface for all the features
991 997 defaultinterface = "text"
992 998 i = self.config("ui", "interface", None)
993 999 if i in alldefaults:
994 1000 defaultinterface = i
995 1001
996 1002 choseninterface = defaultinterface
997 1003 f = self.config("ui", "interface.%s" % feature, None)
998 1004 if f in availableinterfaces:
999 1005 choseninterface = f
1000 1006
1001 1007 if i is not None and defaultinterface != i:
1002 1008 if f is not None:
1003 1009 self.warn(_("invalid value for ui.interface: %s\n") %
1004 1010 (i,))
1005 1011 else:
1006 1012 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1007 1013 (i, choseninterface))
1008 1014 if f is not None and choseninterface != f:
1009 1015 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1010 1016 (feature, f, choseninterface))
1011 1017
1012 1018 return choseninterface
1013 1019
1014 1020 def interactive(self):
1015 1021 '''is interactive input allowed?
1016 1022
1017 1023 An interactive session is a session where input can be reasonably read
1018 1024 from `sys.stdin'. If this function returns false, any attempt to read
1019 1025 from stdin should fail with an error, unless a sensible default has been
1020 1026 specified.
1021 1027
1022 1028 Interactiveness is triggered by the value of the `ui.interactive'
1023 1029 configuration variable or - if it is unset - when `sys.stdin' points
1024 1030 to a terminal device.
1025 1031
1026 1032 This function refers to input only; for output, see `ui.formatted()'.
1027 1033 '''
1028 1034 i = self.configbool("ui", "interactive", None)
1029 1035 if i is None:
1030 1036 # some environments replace stdin without implementing isatty
1031 1037 # usually those are non-interactive
1032 1038 return self._isatty(self.fin)
1033 1039
1034 1040 return i
1035 1041
1036 1042 def termwidth(self):
1037 1043 '''how wide is the terminal in columns?
1038 1044 '''
1039 1045 if 'COLUMNS' in encoding.environ:
1040 1046 try:
1041 1047 return int(encoding.environ['COLUMNS'])
1042 1048 except ValueError:
1043 1049 pass
1044 1050 return scmutil.termsize(self)[0]
1045 1051
1046 1052 def formatted(self):
1047 1053 '''should formatted output be used?
1048 1054
1049 1055 It is often desirable to format the output to suite the output medium.
1050 1056 Examples of this are truncating long lines or colorizing messages.
1051 1057 However, this is not often not desirable when piping output into other
1052 1058 utilities, e.g. `grep'.
1053 1059
1054 1060 Formatted output is triggered by the value of the `ui.formatted'
1055 1061 configuration variable or - if it is unset - when `sys.stdout' points
1056 1062 to a terminal device. Please note that `ui.formatted' should be
1057 1063 considered an implementation detail; it is not intended for use outside
1058 1064 Mercurial or its extensions.
1059 1065
1060 1066 This function refers to output only; for input, see `ui.interactive()'.
1061 1067 This function always returns false when in plain mode, see `ui.plain()'.
1062 1068 '''
1063 1069 if self.plain():
1064 1070 return False
1065 1071
1066 1072 i = self.configbool("ui", "formatted", None)
1067 1073 if i is None:
1068 1074 # some environments replace stdout without implementing isatty
1069 1075 # usually those are non-interactive
1070 1076 return self._isatty(self.fout)
1071 1077
1072 1078 return i
1073 1079
1074 1080 def _readline(self, prompt=''):
1075 1081 if self._isatty(self.fin):
1076 1082 try:
1077 1083 # magically add command line editing support, where
1078 1084 # available
1079 1085 import readline
1080 1086 # force demandimport to really load the module
1081 1087 readline.read_history_file
1082 1088 # windows sometimes raises something other than ImportError
1083 1089 except Exception:
1084 1090 pass
1085 1091
1086 1092 # call write() so output goes through subclassed implementation
1087 1093 # e.g. color extension on Windows
1088 1094 self.write(prompt, prompt=True)
1089 1095
1090 1096 # instead of trying to emulate raw_input, swap (self.fin,
1091 1097 # self.fout) with (sys.stdin, sys.stdout)
1092 1098 oldin = sys.stdin
1093 1099 oldout = sys.stdout
1094 1100 sys.stdin = self.fin
1095 1101 sys.stdout = self.fout
1096 1102 # prompt ' ' must exist; otherwise readline may delete entire line
1097 1103 # - http://bugs.python.org/issue12833
1098 1104 with self.timeblockedsection('stdio'):
1099 1105 line = raw_input(' ')
1100 1106 sys.stdin = oldin
1101 1107 sys.stdout = oldout
1102 1108
1103 1109 # When stdin is in binary mode on Windows, it can cause
1104 1110 # raw_input() to emit an extra trailing carriage return
1105 1111 if pycompat.oslinesep == '\r\n' and line and line[-1] == '\r':
1106 1112 line = line[:-1]
1107 1113 return line
1108 1114
1109 1115 def prompt(self, msg, default="y"):
1110 1116 """Prompt user with msg, read response.
1111 1117 If ui is not interactive, the default is returned.
1112 1118 """
1113 1119 if not self.interactive():
1114 1120 self.write(msg, ' ', default or '', "\n")
1115 1121 return default
1116 1122 try:
1117 1123 r = self._readline(self.label(msg, 'ui.prompt'))
1118 1124 if not r:
1119 1125 r = default
1120 1126 if self.configbool('ui', 'promptecho'):
1121 1127 self.write(r, "\n")
1122 1128 return r
1123 1129 except EOFError:
1124 1130 raise error.ResponseExpected()
1125 1131
1126 1132 @staticmethod
1127 1133 def extractchoices(prompt):
1128 1134 """Extract prompt message and list of choices from specified prompt.
1129 1135
1130 1136 This returns tuple "(message, choices)", and "choices" is the
1131 1137 list of tuple "(response character, text without &)".
1132 1138
1133 1139 >>> ui.extractchoices("awake? $$ &Yes $$ &No")
1134 1140 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1135 1141 >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
1136 1142 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1137 1143 >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
1138 1144 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1139 1145 """
1140 1146
1141 1147 # Sadly, the prompt string may have been built with a filename
1142 1148 # containing "$$" so let's try to find the first valid-looking
1143 1149 # prompt to start parsing. Sadly, we also can't rely on
1144 1150 # choices containing spaces, ASCII, or basically anything
1145 1151 # except an ampersand followed by a character.
1146 1152 m = re.match(r'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1147 1153 msg = m.group(1)
1148 1154 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1149 1155 return (msg,
1150 1156 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
1151 1157 for s in choices])
1152 1158
1153 1159 def promptchoice(self, prompt, default=0):
1154 1160 """Prompt user with a message, read response, and ensure it matches
1155 1161 one of the provided choices. The prompt is formatted as follows:
1156 1162
1157 1163 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1158 1164
1159 1165 The index of the choice is returned. Responses are case
1160 1166 insensitive. If ui is not interactive, the default is
1161 1167 returned.
1162 1168 """
1163 1169
1164 1170 msg, choices = self.extractchoices(prompt)
1165 1171 resps = [r for r, t in choices]
1166 1172 while True:
1167 1173 r = self.prompt(msg, resps[default])
1168 1174 if r.lower() in resps:
1169 1175 return resps.index(r.lower())
1170 1176 self.write(_("unrecognized response\n"))
1171 1177
1172 1178 def getpass(self, prompt=None, default=None):
1173 1179 if not self.interactive():
1174 1180 return default
1175 1181 try:
1176 1182 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1177 1183 # disable getpass() only if explicitly specified. it's still valid
1178 1184 # to interact with tty even if fin is not a tty.
1179 1185 with self.timeblockedsection('stdio'):
1180 1186 if self.configbool('ui', 'nontty'):
1181 1187 l = self.fin.readline()
1182 1188 if not l:
1183 1189 raise EOFError
1184 1190 return l.rstrip('\n')
1185 1191 else:
1186 1192 return getpass.getpass('')
1187 1193 except EOFError:
1188 1194 raise error.ResponseExpected()
1189 1195 def status(self, *msg, **opts):
1190 1196 '''write status message to output (if ui.quiet is False)
1191 1197
1192 1198 This adds an output label of "ui.status".
1193 1199 '''
1194 1200 if not self.quiet:
1195 1201 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1196 1202 self.write(*msg, **opts)
1197 1203 def warn(self, *msg, **opts):
1198 1204 '''write warning message to output (stderr)
1199 1205
1200 1206 This adds an output label of "ui.warning".
1201 1207 '''
1202 1208 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1203 1209 self.write_err(*msg, **opts)
1204 1210 def note(self, *msg, **opts):
1205 1211 '''write note to output (if ui.verbose is True)
1206 1212
1207 1213 This adds an output label of "ui.note".
1208 1214 '''
1209 1215 if self.verbose:
1210 1216 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1211 1217 self.write(*msg, **opts)
1212 1218 def debug(self, *msg, **opts):
1213 1219 '''write debug message to output (if ui.debugflag is True)
1214 1220
1215 1221 This adds an output label of "ui.debug".
1216 1222 '''
1217 1223 if self.debugflag:
1218 1224 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1219 1225 self.write(*msg, **opts)
1220 1226
1221 1227 def edit(self, text, user, extra=None, editform=None, pending=None,
1222 1228 repopath=None):
1223 1229 extra_defaults = {
1224 1230 'prefix': 'editor',
1225 1231 'suffix': '.txt',
1226 1232 }
1227 1233 if extra is not None:
1228 1234 extra_defaults.update(extra)
1229 1235 extra = extra_defaults
1230 1236
1231 1237 rdir = None
1232 1238 if self.configbool('experimental', 'editortmpinhg'):
1233 1239 rdir = repopath
1234 1240 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1235 1241 suffix=extra['suffix'],
1236 1242 dir=rdir)
1237 1243 try:
1238 1244 f = os.fdopen(fd, r'wb')
1239 1245 f.write(util.tonativeeol(text))
1240 1246 f.close()
1241 1247
1242 1248 environ = {'HGUSER': user}
1243 1249 if 'transplant_source' in extra:
1244 1250 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1245 1251 for label in ('intermediate-source', 'source', 'rebase_source'):
1246 1252 if label in extra:
1247 1253 environ.update({'HGREVISION': extra[label]})
1248 1254 break
1249 1255 if editform:
1250 1256 environ.update({'HGEDITFORM': editform})
1251 1257 if pending:
1252 1258 environ.update({'HG_PENDING': pending})
1253 1259
1254 1260 editor = self.geteditor()
1255 1261
1256 1262 self.system("%s \"%s\"" % (editor, name),
1257 1263 environ=environ,
1258 1264 onerr=error.Abort, errprefix=_("edit failed"),
1259 1265 blockedtag='editor')
1260 1266
1261 1267 f = open(name, r'rb')
1262 1268 t = util.fromnativeeol(f.read())
1263 1269 f.close()
1264 1270 finally:
1265 1271 os.unlink(name)
1266 1272
1267 1273 return t
1268 1274
1269 1275 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1270 1276 blockedtag=None):
1271 1277 '''execute shell command with appropriate output stream. command
1272 1278 output will be redirected if fout is not stdout.
1273 1279
1274 1280 if command fails and onerr is None, return status, else raise onerr
1275 1281 object as exception.
1276 1282 '''
1277 1283 if blockedtag is None:
1278 1284 # Long cmds tend to be because of an absolute path on cmd. Keep
1279 1285 # the tail end instead
1280 1286 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1281 1287 blockedtag = 'unknown_system_' + cmdsuffix
1282 1288 out = self.fout
1283 1289 if any(s[1] for s in self._bufferstates):
1284 1290 out = self
1285 1291 with self.timeblockedsection(blockedtag):
1286 1292 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1287 1293 if rc and onerr:
1288 1294 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1289 1295 util.explainexit(rc)[0])
1290 1296 if errprefix:
1291 1297 errmsg = '%s: %s' % (errprefix, errmsg)
1292 1298 raise onerr(errmsg)
1293 1299 return rc
1294 1300
1295 1301 def _runsystem(self, cmd, environ, cwd, out):
1296 1302 """actually execute the given shell command (can be overridden by
1297 1303 extensions like chg)"""
1298 1304 return util.system(cmd, environ=environ, cwd=cwd, out=out)
1299 1305
1300 1306 def traceback(self, exc=None, force=False):
1301 1307 '''print exception traceback if traceback printing enabled or forced.
1302 1308 only to call in exception handler. returns true if traceback
1303 1309 printed.'''
1304 1310 if self.tracebackflag or force:
1305 1311 if exc is None:
1306 1312 exc = sys.exc_info()
1307 1313 cause = getattr(exc[1], 'cause', None)
1308 1314
1309 1315 if cause is not None:
1310 1316 causetb = traceback.format_tb(cause[2])
1311 1317 exctb = traceback.format_tb(exc[2])
1312 1318 exconly = traceback.format_exception_only(cause[0], cause[1])
1313 1319
1314 1320 # exclude frame where 'exc' was chained and rethrown from exctb
1315 1321 self.write_err('Traceback (most recent call last):\n',
1316 1322 ''.join(exctb[:-1]),
1317 1323 ''.join(causetb),
1318 1324 ''.join(exconly))
1319 1325 else:
1320 1326 output = traceback.format_exception(exc[0], exc[1], exc[2])
1321 1327 data = r''.join(output)
1322 1328 if pycompat.ispy3:
1323 1329 enc = pycompat.sysstr(encoding.encoding)
1324 1330 data = data.encode(enc, errors=r'replace')
1325 1331 self.write_err(data)
1326 1332 return self.tracebackflag or force
1327 1333
1328 1334 def geteditor(self):
1329 1335 '''return editor to use'''
1330 1336 if pycompat.sysplatform == 'plan9':
1331 1337 # vi is the MIPS instruction simulator on Plan 9. We
1332 1338 # instead default to E to plumb commit messages to
1333 1339 # avoid confusion.
1334 1340 editor = 'E'
1335 1341 else:
1336 1342 editor = 'vi'
1337 1343 return (encoding.environ.get("HGEDITOR") or
1338 1344 self.config("ui", "editor", editor))
1339 1345
1340 1346 @util.propertycache
1341 1347 def _progbar(self):
1342 1348 """setup the progbar singleton to the ui object"""
1343 1349 if (self.quiet or self.debugflag
1344 1350 or self.configbool('progress', 'disable', False)
1345 1351 or not progress.shouldprint(self)):
1346 1352 return None
1347 1353 return getprogbar(self)
1348 1354
1349 1355 def _progclear(self):
1350 1356 """clear progress bar output if any. use it before any output"""
1351 1357 if '_progbar' not in vars(self): # nothing loaded yet
1352 1358 return
1353 1359 if self._progbar is not None and self._progbar.printed:
1354 1360 self._progbar.clear()
1355 1361
1356 1362 def progress(self, topic, pos, item="", unit="", total=None):
1357 1363 '''show a progress message
1358 1364
1359 1365 By default a textual progress bar will be displayed if an operation
1360 1366 takes too long. 'topic' is the current operation, 'item' is a
1361 1367 non-numeric marker of the current position (i.e. the currently
1362 1368 in-process file), 'pos' is the current numeric position (i.e.
1363 1369 revision, bytes, etc.), unit is a corresponding unit label,
1364 1370 and total is the highest expected pos.
1365 1371
1366 1372 Multiple nested topics may be active at a time.
1367 1373
1368 1374 All topics should be marked closed by setting pos to None at
1369 1375 termination.
1370 1376 '''
1371 1377 if self._progbar is not None:
1372 1378 self._progbar.progress(topic, pos, item=item, unit=unit,
1373 1379 total=total)
1374 1380 if pos is None or not self.configbool('progress', 'debug'):
1375 1381 return
1376 1382
1377 1383 if unit:
1378 1384 unit = ' ' + unit
1379 1385 if item:
1380 1386 item = ' ' + item
1381 1387
1382 1388 if total:
1383 1389 pct = 100.0 * pos / total
1384 1390 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
1385 1391 % (topic, item, pos, total, unit, pct))
1386 1392 else:
1387 1393 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
1388 1394
1389 1395 def log(self, service, *msg, **opts):
1390 1396 '''hook for logging facility extensions
1391 1397
1392 1398 service should be a readily-identifiable subsystem, which will
1393 1399 allow filtering.
1394 1400
1395 1401 *msg should be a newline-terminated format string to log, and
1396 1402 then any values to %-format into that format string.
1397 1403
1398 1404 **opts currently has no defined meanings.
1399 1405 '''
1400 1406
1401 1407 def label(self, msg, label):
1402 1408 '''style msg based on supplied label
1403 1409
1404 1410 If some color mode is enabled, this will add the necessary control
1405 1411 characters to apply such color. In addition, 'debug' color mode adds
1406 1412 markup showing which label affects a piece of text.
1407 1413
1408 1414 ui.write(s, 'label') is equivalent to
1409 1415 ui.write(ui.label(s, 'label')).
1410 1416 '''
1411 1417 if self._colormode is not None:
1412 1418 return color.colorlabel(self, msg, label)
1413 1419 return msg
1414 1420
1415 1421 def develwarn(self, msg, stacklevel=1, config=None):
1416 1422 """issue a developer warning message
1417 1423
1418 1424 Use 'stacklevel' to report the offender some layers further up in the
1419 1425 stack.
1420 1426 """
1421 1427 if not self.configbool('devel', 'all-warnings'):
1422 1428 if config is not None and not self.configbool('devel', config):
1423 1429 return
1424 1430 msg = 'devel-warn: ' + msg
1425 1431 stacklevel += 1 # get in develwarn
1426 1432 if self.tracebackflag:
1427 1433 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1428 1434 self.log('develwarn', '%s at:\n%s' %
1429 1435 (msg, ''.join(util.getstackframes(stacklevel))))
1430 1436 else:
1431 1437 curframe = inspect.currentframe()
1432 1438 calframe = inspect.getouterframes(curframe, 2)
1433 1439 self.write_err('%s at: %s:%s (%s)\n'
1434 1440 % ((msg,) + calframe[stacklevel][1:4]))
1435 1441 self.log('develwarn', '%s at: %s:%s (%s)\n',
1436 1442 msg, *calframe[stacklevel][1:4])
1437 1443 curframe = calframe = None # avoid cycles
1438 1444
1439 1445 def deprecwarn(self, msg, version):
1440 1446 """issue a deprecation warning
1441 1447
1442 1448 - msg: message explaining what is deprecated and how to upgrade,
1443 1449 - version: last version where the API will be supported,
1444 1450 """
1445 1451 if not (self.configbool('devel', 'all-warnings')
1446 1452 or self.configbool('devel', 'deprec-warn')):
1447 1453 return
1448 1454 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1449 1455 " update your code.)") % version
1450 1456 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1451 1457
1452 1458 def exportableenviron(self):
1453 1459 """The environment variables that are safe to export, e.g. through
1454 1460 hgweb.
1455 1461 """
1456 1462 return self._exportableenviron
1457 1463
1458 1464 @contextlib.contextmanager
1459 1465 def configoverride(self, overrides, source=""):
1460 1466 """Context manager for temporary config overrides
1461 1467 `overrides` must be a dict of the following structure:
1462 1468 {(section, name) : value}"""
1463 1469 backups = {}
1464 1470 try:
1465 1471 for (section, name), value in overrides.items():
1466 1472 backups[(section, name)] = self.backupconfig(section, name)
1467 1473 self.setconfig(section, name, value, source)
1468 1474 yield
1469 1475 finally:
1470 1476 for __, backup in backups.items():
1471 1477 self.restoreconfig(backup)
1472 1478 # just restoring ui.quiet config to the previous value is not enough
1473 1479 # as it does not update ui.quiet class member
1474 1480 if ('ui', 'quiet') in overrides:
1475 1481 self.fixconfig(section='ui')
1476 1482
1477 1483 class paths(dict):
1478 1484 """Represents a collection of paths and their configs.
1479 1485
1480 1486 Data is initially derived from ui instances and the config files they have
1481 1487 loaded.
1482 1488 """
1483 1489 def __init__(self, ui):
1484 1490 dict.__init__(self)
1485 1491
1486 1492 for name, loc in ui.configitems('paths', ignoresub=True):
1487 1493 # No location is the same as not existing.
1488 1494 if not loc:
1489 1495 continue
1490 1496 loc, sub = ui.configsuboptions('paths', name)
1491 1497 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1492 1498
1493 1499 def getpath(self, name, default=None):
1494 1500 """Return a ``path`` from a string, falling back to default.
1495 1501
1496 1502 ``name`` can be a named path or locations. Locations are filesystem
1497 1503 paths or URIs.
1498 1504
1499 1505 Returns None if ``name`` is not a registered path, a URI, or a local
1500 1506 path to a repo.
1501 1507 """
1502 1508 # Only fall back to default if no path was requested.
1503 1509 if name is None:
1504 1510 if not default:
1505 1511 default = ()
1506 1512 elif not isinstance(default, (tuple, list)):
1507 1513 default = (default,)
1508 1514 for k in default:
1509 1515 try:
1510 1516 return self[k]
1511 1517 except KeyError:
1512 1518 continue
1513 1519 return None
1514 1520
1515 1521 # Most likely empty string.
1516 1522 # This may need to raise in the future.
1517 1523 if not name:
1518 1524 return None
1519 1525
1520 1526 try:
1521 1527 return self[name]
1522 1528 except KeyError:
1523 1529 # Try to resolve as a local path or URI.
1524 1530 try:
1525 1531 # We don't pass sub-options in, so no need to pass ui instance.
1526 1532 return path(None, None, rawloc=name)
1527 1533 except ValueError:
1528 1534 raise error.RepoError(_('repository %s does not exist') %
1529 1535 name)
1530 1536
1531 1537 _pathsuboptions = {}
1532 1538
1533 1539 def pathsuboption(option, attr):
1534 1540 """Decorator used to declare a path sub-option.
1535 1541
1536 1542 Arguments are the sub-option name and the attribute it should set on
1537 1543 ``path`` instances.
1538 1544
1539 1545 The decorated function will receive as arguments a ``ui`` instance,
1540 1546 ``path`` instance, and the string value of this option from the config.
1541 1547 The function should return the value that will be set on the ``path``
1542 1548 instance.
1543 1549
1544 1550 This decorator can be used to perform additional verification of
1545 1551 sub-options and to change the type of sub-options.
1546 1552 """
1547 1553 def register(func):
1548 1554 _pathsuboptions[option] = (attr, func)
1549 1555 return func
1550 1556 return register
1551 1557
1552 1558 @pathsuboption('pushurl', 'pushloc')
1553 1559 def pushurlpathoption(ui, path, value):
1554 1560 u = util.url(value)
1555 1561 # Actually require a URL.
1556 1562 if not u.scheme:
1557 1563 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1558 1564 return None
1559 1565
1560 1566 # Don't support the #foo syntax in the push URL to declare branch to
1561 1567 # push.
1562 1568 if u.fragment:
1563 1569 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1564 1570 'ignoring)\n') % path.name)
1565 1571 u.fragment = None
1566 1572
1567 1573 return str(u)
1568 1574
1569 1575 @pathsuboption('pushrev', 'pushrev')
1570 1576 def pushrevpathoption(ui, path, value):
1571 1577 return value
1572 1578
1573 1579 class path(object):
1574 1580 """Represents an individual path and its configuration."""
1575 1581
1576 1582 def __init__(self, ui, name, rawloc=None, suboptions=None):
1577 1583 """Construct a path from its config options.
1578 1584
1579 1585 ``ui`` is the ``ui`` instance the path is coming from.
1580 1586 ``name`` is the symbolic name of the path.
1581 1587 ``rawloc`` is the raw location, as defined in the config.
1582 1588 ``pushloc`` is the raw locations pushes should be made to.
1583 1589
1584 1590 If ``name`` is not defined, we require that the location be a) a local
1585 1591 filesystem path with a .hg directory or b) a URL. If not,
1586 1592 ``ValueError`` is raised.
1587 1593 """
1588 1594 if not rawloc:
1589 1595 raise ValueError('rawloc must be defined')
1590 1596
1591 1597 # Locations may define branches via syntax <base>#<branch>.
1592 1598 u = util.url(rawloc)
1593 1599 branch = None
1594 1600 if u.fragment:
1595 1601 branch = u.fragment
1596 1602 u.fragment = None
1597 1603
1598 1604 self.url = u
1599 1605 self.branch = branch
1600 1606
1601 1607 self.name = name
1602 1608 self.rawloc = rawloc
1603 1609 self.loc = '%s' % u
1604 1610
1605 1611 # When given a raw location but not a symbolic name, validate the
1606 1612 # location is valid.
1607 1613 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1608 1614 raise ValueError('location is not a URL or path to a local '
1609 1615 'repo: %s' % rawloc)
1610 1616
1611 1617 suboptions = suboptions or {}
1612 1618
1613 1619 # Now process the sub-options. If a sub-option is registered, its
1614 1620 # attribute will always be present. The value will be None if there
1615 1621 # was no valid sub-option.
1616 1622 for suboption, (attr, func) in _pathsuboptions.iteritems():
1617 1623 if suboption not in suboptions:
1618 1624 setattr(self, attr, None)
1619 1625 continue
1620 1626
1621 1627 value = func(ui, self, suboptions[suboption])
1622 1628 setattr(self, attr, value)
1623 1629
1624 1630 def _isvalidlocalpath(self, path):
1625 1631 """Returns True if the given path is a potentially valid repository.
1626 1632 This is its own function so that extensions can change the definition of
1627 1633 'valid' in this case (like when pulling from a git repo into a hg
1628 1634 one)."""
1629 1635 return os.path.isdir(os.path.join(path, '.hg'))
1630 1636
1631 1637 @property
1632 1638 def suboptions(self):
1633 1639 """Return sub-options and their values for this path.
1634 1640
1635 1641 This is intended to be used for presentation purposes.
1636 1642 """
1637 1643 d = {}
1638 1644 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1639 1645 value = getattr(self, attr)
1640 1646 if value is not None:
1641 1647 d[subopt] = value
1642 1648 return d
1643 1649
1644 1650 # we instantiate one globally shared progress bar to avoid
1645 1651 # competing progress bars when multiple UI objects get created
1646 1652 _progresssingleton = None
1647 1653
1648 1654 def getprogbar(ui):
1649 1655 global _progresssingleton
1650 1656 if _progresssingleton is None:
1651 1657 # passing 'ui' object to the singleton is fishy,
1652 1658 # this is how the extension used to work but feel free to rework it.
1653 1659 _progresssingleton = progress.progbar(ui)
1654 1660 return _progresssingleton
@@ -1,256 +1,282
1 1 $ cat >> fakepager.py <<EOF
2 2 > import sys
3 3 > for line in sys.stdin:
4 4 > sys.stdout.write('paged! %r\n' % line)
5 5 > EOF
6 6
7 7 Enable ui.formatted because pager won't fire without it, and set up
8 8 pager and tell it to use our fake pager that lets us see when the
9 9 pager was running.
10 10 $ cat >> $HGRCPATH <<EOF
11 11 > [ui]
12 12 > formatted = yes
13 13 > [pager]
14 14 > pager = python $TESTTMP/fakepager.py
15 15 > EOF
16 16
17 17 $ hg init repo
18 18 $ cd repo
19 19 $ echo a >> a
20 20 $ hg add a
21 21 $ hg ci -m 'add a'
22 22 $ for x in `python $TESTDIR/seq.py 1 10`; do
23 23 > echo a $x >> a
24 24 > hg ci -m "modify a $x"
25 25 > done
26 26
27 27 By default diff and log are paged, but id is not:
28 28
29 29 $ hg diff -c 2 --pager=yes
30 30 paged! 'diff -r f4be7687d414 -r bce265549556 a\n'
31 31 paged! '--- a/a\tThu Jan 01 00:00:00 1970 +0000\n'
32 32 paged! '+++ b/a\tThu Jan 01 00:00:00 1970 +0000\n'
33 33 paged! '@@ -1,2 +1,3 @@\n'
34 34 paged! ' a\n'
35 35 paged! ' a 1\n'
36 36 paged! '+a 2\n'
37 37
38 38 $ hg log --limit 2
39 39 paged! 'changeset: 10:46106edeeb38\n'
40 40 paged! 'tag: tip\n'
41 41 paged! 'user: test\n'
42 42 paged! 'date: Thu Jan 01 00:00:00 1970 +0000\n'
43 43 paged! 'summary: modify a 10\n'
44 44 paged! '\n'
45 45 paged! 'changeset: 9:6dd8ea7dd621\n'
46 46 paged! 'user: test\n'
47 47 paged! 'date: Thu Jan 01 00:00:00 1970 +0000\n'
48 48 paged! 'summary: modify a 9\n'
49 49 paged! '\n'
50 50
51 51 $ hg id
52 52 46106edeeb38 tip
53 53
54 54 We can enable the pager on id:
55 55
56 56 BROKEN: should be paged
57 57 $ hg --config pager.attend-id=yes id
58 58 46106edeeb38 tip
59 59
60 60 Setting attend-$COMMAND to a false value works, even with pager in
61 61 core:
62 62 $ hg --config pager.attend-diff=no diff -c 2
63 63 diff -r f4be7687d414 -r bce265549556 a
64 64 --- a/a Thu Jan 01 00:00:00 1970 +0000
65 65 +++ b/a Thu Jan 01 00:00:00 1970 +0000
66 66 @@ -1,2 +1,3 @@
67 67 a
68 68 a 1
69 69 +a 2
70 70
71 71 If 'log' is in attend, then 'history' should also be paged:
72 72 $ hg history --limit 2 --config pager.attend=log
73 73 paged! 'changeset: 10:46106edeeb38\n'
74 74 paged! 'tag: tip\n'
75 75 paged! 'user: test\n'
76 76 paged! 'date: Thu Jan 01 00:00:00 1970 +0000\n'
77 77 paged! 'summary: modify a 10\n'
78 78 paged! '\n'
79 79 paged! 'changeset: 9:6dd8ea7dd621\n'
80 80 paged! 'user: test\n'
81 81 paged! 'date: Thu Jan 01 00:00:00 1970 +0000\n'
82 82 paged! 'summary: modify a 9\n'
83 83 paged! '\n'
84 84
85 85 Pager should not start if stdout is not a tty.
86 86
87 87 $ hg log -l1 -q --config ui.formatted=False
88 88 10:46106edeeb38
89 89
90 90 Pager should be disabled if pager.pager is empty (otherwise the output would
91 91 be silently lost.)
92 92
93 93 $ hg log -l1 -q --config pager.pager=
94 94 10:46106edeeb38
95 95
96 96 Pager with color enabled allows colors to come through by default,
97 97 even though stdout is no longer a tty.
98 98 $ cat >> $HGRCPATH <<EOF
99 99 > [extensions]
100 100 > color=
101 101 > [color]
102 102 > mode = ansi
103 103 > EOF
104 104 $ hg log --limit 3
105 105 paged! '\x1b[0;33mchangeset: 10:46106edeeb38\x1b[0m\n'
106 106 paged! 'tag: tip\n'
107 107 paged! 'user: test\n'
108 108 paged! 'date: Thu Jan 01 00:00:00 1970 +0000\n'
109 109 paged! 'summary: modify a 10\n'
110 110 paged! '\n'
111 111 paged! '\x1b[0;33mchangeset: 9:6dd8ea7dd621\x1b[0m\n'
112 112 paged! 'user: test\n'
113 113 paged! 'date: Thu Jan 01 00:00:00 1970 +0000\n'
114 114 paged! 'summary: modify a 9\n'
115 115 paged! '\n'
116 116 paged! '\x1b[0;33mchangeset: 8:cff05a6312fe\x1b[0m\n'
117 117 paged! 'user: test\n'
118 118 paged! 'date: Thu Jan 01 00:00:00 1970 +0000\n'
119 119 paged! 'summary: modify a 8\n'
120 120 paged! '\n'
121 121
122 122 An invalid pager command name is reported sensibly if we don't have to
123 123 use shell=True in the subprocess call:
124 124 $ hg log --limit 3 --config pager.pager=this-command-better-never-exist
125 125 missing pager command 'this-command-better-never-exist', skipping pager
126 126 \x1b[0;33mchangeset: 10:46106edeeb38\x1b[0m (esc)
127 127 tag: tip
128 128 user: test
129 129 date: Thu Jan 01 00:00:00 1970 +0000
130 130 summary: modify a 10
131 131
132 132 \x1b[0;33mchangeset: 9:6dd8ea7dd621\x1b[0m (esc)
133 133 user: test
134 134 date: Thu Jan 01 00:00:00 1970 +0000
135 135 summary: modify a 9
136 136
137 137 \x1b[0;33mchangeset: 8:cff05a6312fe\x1b[0m (esc)
138 138 user: test
139 139 date: Thu Jan 01 00:00:00 1970 +0000
140 140 summary: modify a 8
141 141
142 142
143 143 A complicated pager command gets worse behavior. Bonus points if you can
144 144 improve this.
145 145 $ hg log --limit 3 \
146 146 > --config pager.pager='this-command-better-never-exist --seriously' \
147 147 > 2>/dev/null || true
148 148
149 149 Pager works with shell aliases.
150 150
151 151 $ cat >> $HGRCPATH <<EOF
152 152 > [alias]
153 153 > echoa = !echo a
154 154 > EOF
155 155
156 156 $ hg echoa
157 157 a
158 158 BROKEN: should be paged
159 159 $ hg --config pager.attend-echoa=yes echoa
160 160 a
161 161
162 162 Pager works with hg aliases including environment variables.
163 163
164 164 $ cat >> $HGRCPATH <<'EOF'
165 165 > [alias]
166 166 > printa = log -T "$A\n" -r 0
167 167 > EOF
168 168
169 169 $ A=1 hg --config pager.attend-printa=yes printa
170 170 paged! '1\n'
171 171 $ A=2 hg --config pager.attend-printa=yes printa
172 172 paged! '2\n'
173 173
174 174 Something that's explicitly attended is still not paginated if the
175 175 pager is globally set to off using a flag:
176 176 $ A=2 hg --config pager.attend-printa=yes printa --pager=no
177 177 2
178 178
179 179 Pager should not override the exit code of other commands
180 180
181 181 $ cat >> $TESTTMP/fortytwo.py <<'EOF'
182 182 > from mercurial import cmdutil, commands
183 183 > cmdtable = {}
184 184 > command = cmdutil.command(cmdtable)
185 185 > @command('fortytwo', [], 'fortytwo', norepo=True)
186 186 > def fortytwo(ui, *opts):
187 187 > ui.write('42\n')
188 188 > return 42
189 189 > EOF
190 190
191 191 $ cat >> $HGRCPATH <<'EOF'
192 192 > [extensions]
193 193 > fortytwo = $TESTTMP/fortytwo.py
194 194 > EOF
195 195
196 196 $ hg fortytwo --pager=on
197 197 paged! '42\n'
198 198 [42]
199 199
200 200 A command that asks for paging using ui.pager() directly works:
201 201 $ hg blame a
202 202 paged! ' 0: a\n'
203 203 paged! ' 1: a 1\n'
204 204 paged! ' 2: a 2\n'
205 205 paged! ' 3: a 3\n'
206 206 paged! ' 4: a 4\n'
207 207 paged! ' 5: a 5\n'
208 208 paged! ' 6: a 6\n'
209 209 paged! ' 7: a 7\n'
210 210 paged! ' 8: a 8\n'
211 211 paged! ' 9: a 9\n'
212 212 paged! '10: a 10\n'
213 213 but not with HGPLAIN
214 214 $ HGPLAIN=1 hg blame a
215 215 0: a
216 216 1: a 1
217 217 2: a 2
218 218 3: a 3
219 219 4: a 4
220 220 5: a 5
221 221 6: a 6
222 222 7: a 7
223 223 8: a 8
224 224 9: a 9
225 225 10: a 10
226 226 explicit flags work too:
227 227 $ hg blame --pager=no a
228 228 0: a
229 229 1: a 1
230 230 2: a 2
231 231 3: a 3
232 232 4: a 4
233 233 5: a 5
234 234 6: a 6
235 235 7: a 7
236 236 8: a 8
237 237 9: a 9
238 238 10: a 10
239 239
240 240 Put annotate in the ignore list for pager:
241 241 $ cat >> $HGRCPATH <<EOF
242 242 > [pager]
243 243 > ignore = annotate
244 244 > EOF
245 245 $ hg blame a
246 246 0: a
247 247 1: a 1
248 248 2: a 2
249 249 3: a 3
250 250 4: a 4
251 251 5: a 5
252 252 6: a 6
253 253 7: a 7
254 254 8: a 8
255 255 9: a 9
256 256 10: a 10
257
258 Environment variables like LESS and LV are set automatically:
259 $ cat > $TESTTMP/printlesslv.py <<EOF
260 > import os, sys
261 > sys.stdin.read()
262 > for name in ['LESS', 'LV']:
263 > sys.stdout.write(('%s=%s\n') % (name, os.environ.get(name, '-')))
264 > sys.stdout.flush()
265 > EOF
266
267 $ cat >> $HGRCPATH <<EOF
268 > [alias]
269 > noop = log -r 0 -T ''
270 > [ui]
271 > formatted=1
272 > [pager]
273 > pager = $PYTHON $TESTTMP/printlesslv.py
274 > EOF
275 $ unset LESS
276 $ unset LV
277 $ hg noop --pager=on
278 LESS=FRX
279 LV=-c
280 $ LESS=EFGH hg noop --pager=on
281 LESS=EFGH
282 LV=-c
General Comments 0
You need to be logged in to leave comments. Login now