##// END OF EJS Templates
dispatch: add HGPLAIN=+strictflags to restrict early parsing of global options...
Yuya Nishihara -
r35180:c9740b69 stable
parent child Browse files
Show More
@@ -1,593 +1,601
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 socket
48 48 import struct
49 49 import time
50 50
51 51 from .i18n import _
52 52
53 53 from . import (
54 54 commandserver,
55 55 encoding,
56 56 error,
57 57 extensions,
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 'eol', # uses setconfig('eol', ...)
72 72 'extdiff', # uisetup will register new commands
73 73 'extensions',
74 74 ]
75 75
76 76 _configsectionitems = [
77 77 ('commands', 'show.aliasprefix'), # show.py reads it in extsetup
78 78 ]
79 79
80 80 # sensitive environment variables affecting confighash
81 81 _envre = re.compile(r'''\A(?:
82 82 CHGHG
83 83 |HG(?:DEMANDIMPORT|EMITWARNINGS|MODULEPOLICY|PROF|RCPATH)?
84 84 |HG(?:ENCODING|PLAIN).*
85 85 |LANG(?:UAGE)?
86 86 |LC_.*
87 87 |LD_.*
88 88 |PATH
89 89 |PYTHON.*
90 90 |TERM(?:INFO)?
91 91 |TZ
92 92 )\Z''', re.X)
93 93
94 94 def _confighash(ui):
95 95 """return a quick hash for detecting config/env changes
96 96
97 97 confighash is the hash of sensitive config items and environment variables.
98 98
99 99 for chgserver, it is designed that once confighash changes, the server is
100 100 not qualified to serve its client and should redirect the client to a new
101 101 server. different from mtimehash, confighash change will not mark the
102 102 server outdated and exit since the user can have different configs at the
103 103 same time.
104 104 """
105 105 sectionitems = []
106 106 for section in _configsections:
107 107 sectionitems.append(ui.configitems(section))
108 108 for section, item in _configsectionitems:
109 109 sectionitems.append(ui.config(section, item))
110 110 sectionhash = _hashlist(sectionitems)
111 111 # If $CHGHG is set, the change to $HG should not trigger a new chg server
112 112 if 'CHGHG' in encoding.environ:
113 113 ignored = {'HG'}
114 114 else:
115 115 ignored = set()
116 116 envitems = [(k, v) for k, v in encoding.environ.iteritems()
117 117 if _envre.match(k) and k not in ignored]
118 118 envhash = _hashlist(sorted(envitems))
119 119 return sectionhash[:6] + envhash[:6]
120 120
121 121 def _getmtimepaths(ui):
122 122 """get a list of paths that should be checked to detect change
123 123
124 124 The list will include:
125 125 - extensions (will not cover all files for complex extensions)
126 126 - mercurial/__version__.py
127 127 - python binary
128 128 """
129 129 modules = [m for n, m in extensions.extensions(ui)]
130 130 try:
131 131 from . import __version__
132 132 modules.append(__version__)
133 133 except ImportError:
134 134 pass
135 135 files = [pycompat.sysexecutable]
136 136 for m in modules:
137 137 try:
138 138 files.append(inspect.getabsfile(m))
139 139 except TypeError:
140 140 pass
141 141 return sorted(set(files))
142 142
143 143 def _mtimehash(paths):
144 144 """return a quick hash for detecting file changes
145 145
146 146 mtimehash calls stat on given paths and calculate a hash based on size and
147 147 mtime of each file. mtimehash does not read file content because reading is
148 148 expensive. therefore it's not 100% reliable for detecting content changes.
149 149 it's possible to return different hashes for same file contents.
150 150 it's also possible to return a same hash for different file contents for
151 151 some carefully crafted situation.
152 152
153 153 for chgserver, it is designed that once mtimehash changes, the server is
154 154 considered outdated immediately and should no longer provide service.
155 155
156 156 mtimehash is not included in confighash because we only know the paths of
157 157 extensions after importing them (there is imp.find_module but that faces
158 158 race conditions). We need to calculate confighash without importing.
159 159 """
160 160 def trystat(path):
161 161 try:
162 162 st = os.stat(path)
163 163 return (st.st_mtime, st.st_size)
164 164 except OSError:
165 165 # could be ENOENT, EPERM etc. not fatal in any case
166 166 pass
167 167 return _hashlist(map(trystat, paths))[:12]
168 168
169 169 class hashstate(object):
170 170 """a structure storing confighash, mtimehash, paths used for mtimehash"""
171 171 def __init__(self, confighash, mtimehash, mtimepaths):
172 172 self.confighash = confighash
173 173 self.mtimehash = mtimehash
174 174 self.mtimepaths = mtimepaths
175 175
176 176 @staticmethod
177 177 def fromui(ui, mtimepaths=None):
178 178 if mtimepaths is None:
179 179 mtimepaths = _getmtimepaths(ui)
180 180 confighash = _confighash(ui)
181 181 mtimehash = _mtimehash(mtimepaths)
182 182 _log('confighash = %s mtimehash = %s\n' % (confighash, mtimehash))
183 183 return hashstate(confighash, mtimehash, mtimepaths)
184 184
185 185 def _newchgui(srcui, csystem, attachio):
186 186 class chgui(srcui.__class__):
187 187 def __init__(self, src=None):
188 188 super(chgui, self).__init__(src)
189 189 if src:
190 190 self._csystem = getattr(src, '_csystem', csystem)
191 191 else:
192 192 self._csystem = csystem
193 193
194 194 def _runsystem(self, cmd, environ, cwd, out):
195 195 # fallback to the original system method if the output needs to be
196 196 # captured (to self._buffers), or the output stream is not stdout
197 197 # (e.g. stderr, cStringIO), because the chg client is not aware of
198 198 # these situations and will behave differently (write to stdout).
199 199 if (out is not self.fout
200 200 or not util.safehasattr(self.fout, 'fileno')
201 201 or self.fout.fileno() != util.stdout.fileno()):
202 202 return util.system(cmd, environ=environ, cwd=cwd, out=out)
203 203 self.flush()
204 204 return self._csystem(cmd, util.shellenviron(environ), cwd)
205 205
206 206 def _runpager(self, cmd, env=None):
207 207 self._csystem(cmd, util.shellenviron(env), type='pager',
208 208 cmdtable={'attachio': attachio})
209 209 return True
210 210
211 211 return chgui(srcui)
212 212
213 213 def _loadnewui(srcui, args):
214 214 from . import dispatch # avoid cycle
215 215
216 216 newui = srcui.__class__.load()
217 217 for a in ['fin', 'fout', 'ferr', 'environ']:
218 218 setattr(newui, a, getattr(srcui, a))
219 219 if util.safehasattr(srcui, '_csystem'):
220 220 newui._csystem = srcui._csystem
221 221
222 222 # command line args
223 options = {}
224 if srcui.plain('strictflags'):
225 options.update(dispatch._earlyparseopts(args))
226 else:
223 227 args = args[:]
224 dispatch._parseconfig(newui, dispatch._earlygetopt(['--config'], args))
228 options['config'] = dispatch._earlygetopt(['--config'], args)
229 cwds = dispatch._earlygetopt(['--cwd'], args)
230 options['cwd'] = cwds and cwds[-1] or ''
231 rpath = dispatch._earlygetopt(["-R", "--repository", "--repo"], args)
232 options['repository'] = rpath and rpath[-1] or ''
233 dispatch._parseconfig(newui, options['config'])
225 234
226 235 # stolen from tortoisehg.util.copydynamicconfig()
227 236 for section, name, value in srcui.walkconfig():
228 237 source = srcui.configsource(section, name)
229 238 if ':' in source or source == '--config' or source.startswith('$'):
230 239 # path:line or command line, or environ
231 240 continue
232 241 newui.setconfig(section, name, value, source)
233 242
234 243 # load wd and repo config, copied from dispatch.py
235 cwds = dispatch._earlygetopt(['--cwd'], args)
236 cwd = cwds and os.path.realpath(cwds[-1]) or None
237 rpath = dispatch._earlygetopt(["-R", "--repository", "--repo"], args)
238 rpath = rpath and rpath[-1] or ''
244 cwd = options['cwd']
245 cwd = cwd and os.path.realpath(cwd) or None
246 rpath = options['repository']
239 247 path, newlui = dispatch._getlocal(newui, rpath, wd=cwd)
240 248
241 249 return (newui, newlui)
242 250
243 251 class channeledsystem(object):
244 252 """Propagate ui.system() request in the following format:
245 253
246 254 payload length (unsigned int),
247 255 type, '\0',
248 256 cmd, '\0',
249 257 cwd, '\0',
250 258 envkey, '=', val, '\0',
251 259 ...
252 260 envkey, '=', val
253 261
254 262 if type == 'system', waits for:
255 263
256 264 exitcode length (unsigned int),
257 265 exitcode (int)
258 266
259 267 if type == 'pager', repetitively waits for a command name ending with '\n'
260 268 and executes it defined by cmdtable, or exits the loop if the command name
261 269 is empty.
262 270 """
263 271 def __init__(self, in_, out, channel):
264 272 self.in_ = in_
265 273 self.out = out
266 274 self.channel = channel
267 275
268 276 def __call__(self, cmd, environ, cwd=None, type='system', cmdtable=None):
269 277 args = [type, util.quotecommand(cmd), os.path.abspath(cwd or '.')]
270 278 args.extend('%s=%s' % (k, v) for k, v in environ.iteritems())
271 279 data = '\0'.join(args)
272 280 self.out.write(struct.pack('>cI', self.channel, len(data)))
273 281 self.out.write(data)
274 282 self.out.flush()
275 283
276 284 if type == 'system':
277 285 length = self.in_.read(4)
278 286 length, = struct.unpack('>I', length)
279 287 if length != 4:
280 288 raise error.Abort(_('invalid response'))
281 289 rc, = struct.unpack('>i', self.in_.read(4))
282 290 return rc
283 291 elif type == 'pager':
284 292 while True:
285 293 cmd = self.in_.readline()[:-1]
286 294 if not cmd:
287 295 break
288 296 if cmdtable and cmd in cmdtable:
289 297 _log('pager subcommand: %s' % cmd)
290 298 cmdtable[cmd]()
291 299 else:
292 300 raise error.Abort(_('unexpected command: %s') % cmd)
293 301 else:
294 302 raise error.ProgrammingError('invalid S channel type: %s' % type)
295 303
296 304 _iochannels = [
297 305 # server.ch, ui.fp, mode
298 306 ('cin', 'fin', pycompat.sysstr('rb')),
299 307 ('cout', 'fout', pycompat.sysstr('wb')),
300 308 ('cerr', 'ferr', pycompat.sysstr('wb')),
301 309 ]
302 310
303 311 class chgcmdserver(commandserver.server):
304 312 def __init__(self, ui, repo, fin, fout, sock, hashstate, baseaddress):
305 313 super(chgcmdserver, self).__init__(
306 314 _newchgui(ui, channeledsystem(fin, fout, 'S'), self.attachio),
307 315 repo, fin, fout)
308 316 self.clientsock = sock
309 317 self._oldios = [] # original (self.ch, ui.fp, fd) before "attachio"
310 318 self.hashstate = hashstate
311 319 self.baseaddress = baseaddress
312 320 if hashstate is not None:
313 321 self.capabilities = self.capabilities.copy()
314 322 self.capabilities['validate'] = chgcmdserver.validate
315 323
316 324 def cleanup(self):
317 325 super(chgcmdserver, self).cleanup()
318 326 # dispatch._runcatch() does not flush outputs if exception is not
319 327 # handled by dispatch._dispatch()
320 328 self.ui.flush()
321 329 self._restoreio()
322 330
323 331 def attachio(self):
324 332 """Attach to client's stdio passed via unix domain socket; all
325 333 channels except cresult will no longer be used
326 334 """
327 335 # tell client to sendmsg() with 1-byte payload, which makes it
328 336 # distinctive from "attachio\n" command consumed by client.read()
329 337 self.clientsock.sendall(struct.pack('>cI', 'I', 1))
330 338 clientfds = util.recvfds(self.clientsock.fileno())
331 339 _log('received fds: %r\n' % clientfds)
332 340
333 341 ui = self.ui
334 342 ui.flush()
335 343 first = self._saveio()
336 344 for fd, (cn, fn, mode) in zip(clientfds, _iochannels):
337 345 assert fd > 0
338 346 fp = getattr(ui, fn)
339 347 os.dup2(fd, fp.fileno())
340 348 os.close(fd)
341 349 if not first:
342 350 continue
343 351 # reset buffering mode when client is first attached. as we want
344 352 # to see output immediately on pager, the mode stays unchanged
345 353 # when client re-attached. ferr is unchanged because it should
346 354 # be unbuffered no matter if it is a tty or not.
347 355 if fn == 'ferr':
348 356 newfp = fp
349 357 else:
350 358 # make it line buffered explicitly because the default is
351 359 # decided on first write(), where fout could be a pager.
352 360 if fp.isatty():
353 361 bufsize = 1 # line buffered
354 362 else:
355 363 bufsize = -1 # system default
356 364 newfp = os.fdopen(fp.fileno(), mode, bufsize)
357 365 setattr(ui, fn, newfp)
358 366 setattr(self, cn, newfp)
359 367
360 368 self.cresult.write(struct.pack('>i', len(clientfds)))
361 369
362 370 def _saveio(self):
363 371 if self._oldios:
364 372 return False
365 373 ui = self.ui
366 374 for cn, fn, _mode in _iochannels:
367 375 ch = getattr(self, cn)
368 376 fp = getattr(ui, fn)
369 377 fd = os.dup(fp.fileno())
370 378 self._oldios.append((ch, fp, fd))
371 379 return True
372 380
373 381 def _restoreio(self):
374 382 ui = self.ui
375 383 for (ch, fp, fd), (cn, fn, _mode) in zip(self._oldios, _iochannels):
376 384 newfp = getattr(ui, fn)
377 385 # close newfp while it's associated with client; otherwise it
378 386 # would be closed when newfp is deleted
379 387 if newfp is not fp:
380 388 newfp.close()
381 389 # restore original fd: fp is open again
382 390 os.dup2(fd, fp.fileno())
383 391 os.close(fd)
384 392 setattr(self, cn, ch)
385 393 setattr(ui, fn, fp)
386 394 del self._oldios[:]
387 395
388 396 def validate(self):
389 397 """Reload the config and check if the server is up to date
390 398
391 399 Read a list of '\0' separated arguments.
392 400 Write a non-empty list of '\0' separated instruction strings or '\0'
393 401 if the list is empty.
394 402 An instruction string could be either:
395 403 - "unlink $path", the client should unlink the path to stop the
396 404 outdated server.
397 405 - "redirect $path", the client should attempt to connect to $path
398 406 first. If it does not work, start a new server. It implies
399 407 "reconnect".
400 408 - "exit $n", the client should exit directly with code n.
401 409 This may happen if we cannot parse the config.
402 410 - "reconnect", the client should close the connection and
403 411 reconnect.
404 412 If neither "reconnect" nor "redirect" is included in the instruction
405 413 list, the client can continue with this server after completing all
406 414 the instructions.
407 415 """
408 416 from . import dispatch # avoid cycle
409 417
410 418 args = self._readlist()
411 419 try:
412 420 self.ui, lui = _loadnewui(self.ui, args)
413 421 except error.ParseError as inst:
414 422 dispatch._formatparse(self.ui.warn, inst)
415 423 self.ui.flush()
416 424 self.cresult.write('exit 255')
417 425 return
418 426 newhash = hashstate.fromui(lui, self.hashstate.mtimepaths)
419 427 insts = []
420 428 if newhash.mtimehash != self.hashstate.mtimehash:
421 429 addr = _hashaddress(self.baseaddress, self.hashstate.confighash)
422 430 insts.append('unlink %s' % addr)
423 431 # mtimehash is empty if one or more extensions fail to load.
424 432 # to be compatible with hg, still serve the client this time.
425 433 if self.hashstate.mtimehash:
426 434 insts.append('reconnect')
427 435 if newhash.confighash != self.hashstate.confighash:
428 436 addr = _hashaddress(self.baseaddress, newhash.confighash)
429 437 insts.append('redirect %s' % addr)
430 438 _log('validate: %s\n' % insts)
431 439 self.cresult.write('\0'.join(insts) or '\0')
432 440
433 441 def chdir(self):
434 442 """Change current directory
435 443
436 444 Note that the behavior of --cwd option is bit different from this.
437 445 It does not affect --config parameter.
438 446 """
439 447 path = self._readstr()
440 448 if not path:
441 449 return
442 450 _log('chdir to %r\n' % path)
443 451 os.chdir(path)
444 452
445 453 def setumask(self):
446 454 """Change umask"""
447 455 mask = struct.unpack('>I', self._read(4))[0]
448 456 _log('setumask %r\n' % mask)
449 457 os.umask(mask)
450 458
451 459 def runcommand(self):
452 460 return super(chgcmdserver, self).runcommand()
453 461
454 462 def setenv(self):
455 463 """Clear and update os.environ
456 464
457 465 Note that not all variables can make an effect on the running process.
458 466 """
459 467 l = self._readlist()
460 468 try:
461 469 newenv = dict(s.split('=', 1) for s in l)
462 470 except ValueError:
463 471 raise ValueError('unexpected value in setenv request')
464 472 _log('setenv: %r\n' % sorted(newenv.keys()))
465 473 encoding.environ.clear()
466 474 encoding.environ.update(newenv)
467 475
468 476 capabilities = commandserver.server.capabilities.copy()
469 477 capabilities.update({'attachio': attachio,
470 478 'chdir': chdir,
471 479 'runcommand': runcommand,
472 480 'setenv': setenv,
473 481 'setumask': setumask})
474 482
475 483 if util.safehasattr(util, 'setprocname'):
476 484 def setprocname(self):
477 485 """Change process title"""
478 486 name = self._readstr()
479 487 _log('setprocname: %r\n' % name)
480 488 util.setprocname(name)
481 489 capabilities['setprocname'] = setprocname
482 490
483 491 def _tempaddress(address):
484 492 return '%s.%d.tmp' % (address, os.getpid())
485 493
486 494 def _hashaddress(address, hashstr):
487 495 # if the basename of address contains '.', use only the left part. this
488 496 # makes it possible for the client to pass 'server.tmp$PID' and follow by
489 497 # an atomic rename to avoid locking when spawning new servers.
490 498 dirname, basename = os.path.split(address)
491 499 basename = basename.split('.', 1)[0]
492 500 return '%s-%s' % (os.path.join(dirname, basename), hashstr)
493 501
494 502 class chgunixservicehandler(object):
495 503 """Set of operations for chg services"""
496 504
497 505 pollinterval = 1 # [sec]
498 506
499 507 def __init__(self, ui):
500 508 self.ui = ui
501 509 self._idletimeout = ui.configint('chgserver', 'idletimeout')
502 510 self._lastactive = time.time()
503 511
504 512 def bindsocket(self, sock, address):
505 513 self._inithashstate(address)
506 514 self._checkextensions()
507 515 self._bind(sock)
508 516 self._createsymlink()
509 517 # no "listening at" message should be printed to simulate hg behavior
510 518
511 519 def _inithashstate(self, address):
512 520 self._baseaddress = address
513 521 if self.ui.configbool('chgserver', 'skiphash'):
514 522 self._hashstate = None
515 523 self._realaddress = address
516 524 return
517 525 self._hashstate = hashstate.fromui(self.ui)
518 526 self._realaddress = _hashaddress(address, self._hashstate.confighash)
519 527
520 528 def _checkextensions(self):
521 529 if not self._hashstate:
522 530 return
523 531 if extensions.notloaded():
524 532 # one or more extensions failed to load. mtimehash becomes
525 533 # meaningless because we do not know the paths of those extensions.
526 534 # set mtimehash to an illegal hash value to invalidate the server.
527 535 self._hashstate.mtimehash = ''
528 536
529 537 def _bind(self, sock):
530 538 # use a unique temp address so we can stat the file and do ownership
531 539 # check later
532 540 tempaddress = _tempaddress(self._realaddress)
533 541 util.bindunixsocket(sock, tempaddress)
534 542 self._socketstat = os.stat(tempaddress)
535 543 sock.listen(socket.SOMAXCONN)
536 544 # rename will replace the old socket file if exists atomically. the
537 545 # old server will detect ownership change and exit.
538 546 util.rename(tempaddress, self._realaddress)
539 547
540 548 def _createsymlink(self):
541 549 if self._baseaddress == self._realaddress:
542 550 return
543 551 tempaddress = _tempaddress(self._baseaddress)
544 552 os.symlink(os.path.basename(self._realaddress), tempaddress)
545 553 util.rename(tempaddress, self._baseaddress)
546 554
547 555 def _issocketowner(self):
548 556 try:
549 557 stat = os.stat(self._realaddress)
550 558 return (stat.st_ino == self._socketstat.st_ino and
551 559 stat.st_mtime == self._socketstat.st_mtime)
552 560 except OSError:
553 561 return False
554 562
555 563 def unlinksocket(self, address):
556 564 if not self._issocketowner():
557 565 return
558 566 # it is possible to have a race condition here that we may
559 567 # remove another server's socket file. but that's okay
560 568 # since that server will detect and exit automatically and
561 569 # the client will start a new server on demand.
562 570 util.tryunlink(self._realaddress)
563 571
564 572 def shouldexit(self):
565 573 if not self._issocketowner():
566 574 self.ui.debug('%s is not owned, exiting.\n' % self._realaddress)
567 575 return True
568 576 if time.time() - self._lastactive > self._idletimeout:
569 577 self.ui.debug('being idle too long. exiting.\n')
570 578 return True
571 579 return False
572 580
573 581 def newconnection(self):
574 582 self._lastactive = time.time()
575 583
576 584 def createcmdserver(self, repo, conn, fin, fout):
577 585 return chgcmdserver(self.ui, repo, fin, fout, conn,
578 586 self._hashstate, self._baseaddress)
579 587
580 588 def chgunixservice(ui, repo, opts):
581 589 # CHGINTERNALMARK is set by chg client. It is an indication of things are
582 590 # started by chg so other code can do things accordingly, like disabling
583 591 # demandimport or detecting chg client started by chg client. When executed
584 592 # here, CHGINTERNALMARK is no longer useful and hence dropped to make
585 593 # environ cleaner.
586 594 if 'CHGINTERNALMARK' in encoding.environ:
587 595 del encoding.environ['CHGINTERNALMARK']
588 596
589 597 if repo:
590 598 # one chgserver can serve multiple repos. drop repo information
591 599 ui.setconfig('bundle', 'mainreporoot', '', 'repo')
592 600 h = chgunixservicehandler(ui)
593 601 return commandserver.unixforkingservice(ui, repo=None, opts=opts, handler=h)
@@ -1,1103 +1,1117
1 1 # dispatch.py - command dispatching 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, print_function
9 9
10 10 import difflib
11 11 import errno
12 12 import getopt
13 13 import os
14 14 import pdb
15 15 import re
16 16 import signal
17 17 import sys
18 18 import time
19 19 import traceback
20 20
21 21
22 22 from .i18n import _
23 23
24 24 from . import (
25 25 cmdutil,
26 26 color,
27 27 commands,
28 28 demandimport,
29 29 encoding,
30 30 error,
31 31 extensions,
32 32 fancyopts,
33 33 help,
34 34 hg,
35 35 hook,
36 36 profiling,
37 37 pycompat,
38 38 registrar,
39 39 scmutil,
40 40 ui as uimod,
41 41 util,
42 42 )
43 43
44 44 unrecoverablewrite = registrar.command.unrecoverablewrite
45 45
46 46 class request(object):
47 47 def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
48 48 ferr=None, prereposetups=None):
49 49 self.args = args
50 50 self.ui = ui
51 51 self.repo = repo
52 52
53 53 # input/output/error streams
54 54 self.fin = fin
55 55 self.fout = fout
56 56 self.ferr = ferr
57 57
58 58 # remember options pre-parsed by _earlyreqopt*()
59 59 self.earlyoptions = {}
60 60
61 61 # reposetups which run before extensions, useful for chg to pre-fill
62 62 # low-level repo state (for example, changelog) before extensions.
63 63 self.prereposetups = prereposetups or []
64 64
65 65 def _runexithandlers(self):
66 66 exc = None
67 67 handlers = self.ui._exithandlers
68 68 try:
69 69 while handlers:
70 70 func, args, kwargs = handlers.pop()
71 71 try:
72 72 func(*args, **kwargs)
73 73 except: # re-raises below
74 74 if exc is None:
75 75 exc = sys.exc_info()[1]
76 76 self.ui.warn(('error in exit handlers:\n'))
77 77 self.ui.traceback(force=True)
78 78 finally:
79 79 if exc is not None:
80 80 raise exc
81 81
82 82 def run():
83 83 "run the command in sys.argv"
84 84 _initstdio()
85 85 req = request(pycompat.sysargv[1:])
86 86 err = None
87 87 try:
88 88 status = (dispatch(req) or 0) & 255
89 89 except error.StdioError as e:
90 90 err = e
91 91 status = -1
92 92 if util.safehasattr(req.ui, 'fout'):
93 93 try:
94 94 req.ui.fout.flush()
95 95 except IOError as e:
96 96 err = e
97 97 status = -1
98 98 if util.safehasattr(req.ui, 'ferr'):
99 99 if err is not None and err.errno != errno.EPIPE:
100 100 req.ui.ferr.write('abort: %s\n' %
101 101 encoding.strtolocal(err.strerror))
102 102 req.ui.ferr.flush()
103 103 sys.exit(status & 255)
104 104
105 105 def _initstdio():
106 106 for fp in (sys.stdin, sys.stdout, sys.stderr):
107 107 util.setbinary(fp)
108 108
109 109 def _getsimilar(symbols, value):
110 110 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
111 111 # The cutoff for similarity here is pretty arbitrary. It should
112 112 # probably be investigated and tweaked.
113 113 return [s for s in symbols if sim(s) > 0.6]
114 114
115 115 def _reportsimilar(write, similar):
116 116 if len(similar) == 1:
117 117 write(_("(did you mean %s?)\n") % similar[0])
118 118 elif similar:
119 119 ss = ", ".join(sorted(similar))
120 120 write(_("(did you mean one of %s?)\n") % ss)
121 121
122 122 def _formatparse(write, inst):
123 123 similar = []
124 124 if isinstance(inst, error.UnknownIdentifier):
125 125 # make sure to check fileset first, as revset can invoke fileset
126 126 similar = _getsimilar(inst.symbols, inst.function)
127 127 if len(inst.args) > 1:
128 128 write(_("hg: parse error at %s: %s\n") %
129 129 (inst.args[1], inst.args[0]))
130 130 if (inst.args[0][0] == ' '):
131 131 write(_("unexpected leading whitespace\n"))
132 132 else:
133 133 write(_("hg: parse error: %s\n") % inst.args[0])
134 134 _reportsimilar(write, similar)
135 135 if inst.hint:
136 136 write(_("(%s)\n") % inst.hint)
137 137
138 138 def _formatargs(args):
139 139 return ' '.join(util.shellquote(a) for a in args)
140 140
141 141 def dispatch(req):
142 142 "run the command specified in req.args"
143 143 if req.ferr:
144 144 ferr = req.ferr
145 145 elif req.ui:
146 146 ferr = req.ui.ferr
147 147 else:
148 148 ferr = util.stderr
149 149
150 150 try:
151 151 if not req.ui:
152 152 req.ui = uimod.ui.load()
153 if req.ui.plain('strictflags'):
154 req.earlyoptions.update(_earlyparseopts(req.args))
153 155 if _earlyreqoptbool(req, 'traceback', ['--traceback']):
154 156 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
155 157
156 158 # set ui streams from the request
157 159 if req.fin:
158 160 req.ui.fin = req.fin
159 161 if req.fout:
160 162 req.ui.fout = req.fout
161 163 if req.ferr:
162 164 req.ui.ferr = req.ferr
163 165 except error.Abort as inst:
164 166 ferr.write(_("abort: %s\n") % inst)
165 167 if inst.hint:
166 168 ferr.write(_("(%s)\n") % inst.hint)
167 169 return -1
168 170 except error.ParseError as inst:
169 171 _formatparse(ferr.write, inst)
170 172 return -1
171 173
172 174 msg = _formatargs(req.args)
173 175 starttime = util.timer()
174 176 ret = None
175 177 try:
176 178 ret = _runcatch(req)
177 179 except error.ProgrammingError as inst:
178 180 req.ui.warn(_('** ProgrammingError: %s\n') % inst)
179 181 if inst.hint:
180 182 req.ui.warn(_('** (%s)\n') % inst.hint)
181 183 raise
182 184 except KeyboardInterrupt as inst:
183 185 try:
184 186 if isinstance(inst, error.SignalInterrupt):
185 187 msg = _("killed!\n")
186 188 else:
187 189 msg = _("interrupted!\n")
188 190 req.ui.warn(msg)
189 191 except error.SignalInterrupt:
190 192 # maybe pager would quit without consuming all the output, and
191 193 # SIGPIPE was raised. we cannot print anything in this case.
192 194 pass
193 195 except IOError as inst:
194 196 if inst.errno != errno.EPIPE:
195 197 raise
196 198 ret = -1
197 199 finally:
198 200 duration = util.timer() - starttime
199 201 req.ui.flush()
200 202 if req.ui.logblockedtimes:
201 203 req.ui._blockedtimes['command_duration'] = duration * 1000
202 204 req.ui.log('uiblocked', 'ui blocked ms', **req.ui._blockedtimes)
203 205 req.ui.log("commandfinish", "%s exited %d after %0.2f seconds\n",
204 206 msg, ret or 0, duration)
205 207 try:
206 208 req._runexithandlers()
207 209 except: # exiting, so no re-raises
208 210 ret = ret or -1
209 211 return ret
210 212
211 213 def _runcatch(req):
212 214 def catchterm(*args):
213 215 raise error.SignalInterrupt
214 216
215 217 ui = req.ui
216 218 try:
217 219 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
218 220 num = getattr(signal, name, None)
219 221 if num:
220 222 signal.signal(num, catchterm)
221 223 except ValueError:
222 224 pass # happens if called in a thread
223 225
224 226 def _runcatchfunc():
225 227 realcmd = None
226 228 try:
227 229 cmdargs = fancyopts.fancyopts(req.args[:], commands.globalopts, {})
228 230 cmd = cmdargs[0]
229 231 aliases, entry = cmdutil.findcmd(cmd, commands.table, False)
230 232 realcmd = aliases[0]
231 233 except (error.UnknownCommand, error.AmbiguousCommand,
232 234 IndexError, getopt.GetoptError):
233 235 # Don't handle this here. We know the command is
234 236 # invalid, but all we're worried about for now is that
235 237 # it's not a command that server operators expect to
236 238 # be safe to offer to users in a sandbox.
237 239 pass
238 240 if realcmd == 'serve' and '--stdio' in cmdargs:
239 241 # We want to constrain 'hg serve --stdio' instances pretty
240 242 # closely, as many shared-ssh access tools want to grant
241 243 # access to run *only* 'hg -R $repo serve --stdio'. We
242 244 # restrict to exactly that set of arguments, and prohibit
243 245 # any repo name that starts with '--' to prevent
244 246 # shenanigans wherein a user does something like pass
245 247 # --debugger or --config=ui.debugger=1 as a repo
246 248 # name. This used to actually run the debugger.
247 249 if (len(req.args) != 4 or
248 250 req.args[0] != '-R' or
249 251 req.args[1].startswith('--') or
250 252 req.args[2] != 'serve' or
251 253 req.args[3] != '--stdio'):
252 254 raise error.Abort(
253 255 _('potentially unsafe serve --stdio invocation: %r') %
254 256 (req.args,))
255 257
256 258 try:
257 259 debugger = 'pdb'
258 260 debugtrace = {
259 261 'pdb': pdb.set_trace
260 262 }
261 263 debugmortem = {
262 264 'pdb': pdb.post_mortem
263 265 }
264 266
265 267 # read --config before doing anything else
266 268 # (e.g. to change trust settings for reading .hg/hgrc)
267 269 cfgs = _parseconfig(req.ui,
268 270 _earlyreqopt(req, 'config', ['--config']))
269 271
270 272 if req.repo:
271 273 # copy configs that were passed on the cmdline (--config) to
272 274 # the repo ui
273 275 for sec, name, val in cfgs:
274 276 req.repo.ui.setconfig(sec, name, val, source='--config')
275 277
276 278 # developer config: ui.debugger
277 279 debugger = ui.config("ui", "debugger")
278 280 debugmod = pdb
279 281 if not debugger or ui.plain():
280 282 # if we are in HGPLAIN mode, then disable custom debugging
281 283 debugger = 'pdb'
282 284 elif _earlyreqoptbool(req, 'debugger', ['--debugger']):
283 285 # This import can be slow for fancy debuggers, so only
284 286 # do it when absolutely necessary, i.e. when actual
285 287 # debugging has been requested
286 288 with demandimport.deactivated():
287 289 try:
288 290 debugmod = __import__(debugger)
289 291 except ImportError:
290 292 pass # Leave debugmod = pdb
291 293
292 294 debugtrace[debugger] = debugmod.set_trace
293 295 debugmortem[debugger] = debugmod.post_mortem
294 296
295 297 # enter the debugger before command execution
296 298 if _earlyreqoptbool(req, 'debugger', ['--debugger']):
297 299 ui.warn(_("entering debugger - "
298 300 "type c to continue starting hg or h for help\n"))
299 301
300 302 if (debugger != 'pdb' and
301 303 debugtrace[debugger] == debugtrace['pdb']):
302 304 ui.warn(_("%s debugger specified "
303 305 "but its module was not found\n") % debugger)
304 306 with demandimport.deactivated():
305 307 debugtrace[debugger]()
306 308 try:
307 309 return _dispatch(req)
308 310 finally:
309 311 ui.flush()
310 312 except: # re-raises
311 313 # enter the debugger when we hit an exception
312 314 if _earlyreqoptbool(req, 'debugger', ['--debugger']):
313 315 traceback.print_exc()
314 316 debugmortem[debugger](sys.exc_info()[2])
315 317 raise
316 318
317 319 return _callcatch(ui, _runcatchfunc)
318 320
319 321 def _callcatch(ui, func):
320 322 """like scmutil.callcatch but handles more high-level exceptions about
321 323 config parsing and commands. besides, use handlecommandexception to handle
322 324 uncaught exceptions.
323 325 """
324 326 try:
325 327 return scmutil.callcatch(ui, func)
326 328 except error.AmbiguousCommand as inst:
327 329 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
328 330 (inst.args[0], " ".join(inst.args[1])))
329 331 except error.CommandError as inst:
330 332 if inst.args[0]:
331 333 ui.pager('help')
332 334 msgbytes = pycompat.bytestr(inst.args[1])
333 335 ui.warn(_("hg %s: %s\n") % (inst.args[0], msgbytes))
334 336 commands.help_(ui, inst.args[0], full=False, command=True)
335 337 else:
336 338 ui.pager('help')
337 339 ui.warn(_("hg: %s\n") % inst.args[1])
338 340 commands.help_(ui, 'shortlist')
339 341 except error.ParseError as inst:
340 342 _formatparse(ui.warn, inst)
341 343 return -1
342 344 except error.UnknownCommand as inst:
343 345 nocmdmsg = _("hg: unknown command '%s'\n") % inst.args[0]
344 346 try:
345 347 # check if the command is in a disabled extension
346 348 # (but don't check for extensions themselves)
347 349 formatted = help.formattedhelp(ui, commands, inst.args[0],
348 350 unknowncmd=True)
349 351 ui.warn(nocmdmsg)
350 352 ui.write(formatted)
351 353 except (error.UnknownCommand, error.Abort):
352 354 suggested = False
353 355 if len(inst.args) == 2:
354 356 sim = _getsimilar(inst.args[1], inst.args[0])
355 357 if sim:
356 358 ui.warn(nocmdmsg)
357 359 _reportsimilar(ui.warn, sim)
358 360 suggested = True
359 361 if not suggested:
360 362 ui.pager('help')
361 363 ui.warn(nocmdmsg)
362 364 commands.help_(ui, 'shortlist')
363 365 except IOError:
364 366 raise
365 367 except KeyboardInterrupt:
366 368 raise
367 369 except: # probably re-raises
368 370 if not handlecommandexception(ui):
369 371 raise
370 372
371 373 return -1
372 374
373 375 def aliasargs(fn, givenargs):
374 376 args = []
375 377 # only care about alias 'args', ignore 'args' set by extensions.wrapfunction
376 378 if not util.safehasattr(fn, '_origfunc'):
377 379 args = getattr(fn, 'args', args)
378 380 if args:
379 381 cmd = ' '.join(map(util.shellquote, args))
380 382
381 383 nums = []
382 384 def replacer(m):
383 385 num = int(m.group(1)) - 1
384 386 nums.append(num)
385 387 if num < len(givenargs):
386 388 return givenargs[num]
387 389 raise error.Abort(_('too few arguments for command alias'))
388 390 cmd = re.sub(br'\$(\d+|\$)', replacer, cmd)
389 391 givenargs = [x for i, x in enumerate(givenargs)
390 392 if i not in nums]
391 393 args = pycompat.shlexsplit(cmd)
392 394 return args + givenargs
393 395
394 396 def aliasinterpolate(name, args, cmd):
395 397 '''interpolate args into cmd for shell aliases
396 398
397 399 This also handles $0, $@ and "$@".
398 400 '''
399 401 # util.interpolate can't deal with "$@" (with quotes) because it's only
400 402 # built to match prefix + patterns.
401 403 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
402 404 replacemap['$0'] = name
403 405 replacemap['$$'] = '$'
404 406 replacemap['$@'] = ' '.join(args)
405 407 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
406 408 # parameters, separated out into words. Emulate the same behavior here by
407 409 # quoting the arguments individually. POSIX shells will then typically
408 410 # tokenize each argument into exactly one word.
409 411 replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
410 412 # escape '\$' for regex
411 413 regex = '|'.join(replacemap.keys()).replace('$', r'\$')
412 414 r = re.compile(regex)
413 415 return r.sub(lambda x: replacemap[x.group()], cmd)
414 416
415 417 class cmdalias(object):
416 418 def __init__(self, name, definition, cmdtable, source):
417 419 self.name = self.cmd = name
418 420 self.cmdname = ''
419 421 self.definition = definition
420 422 self.fn = None
421 423 self.givenargs = []
422 424 self.opts = []
423 425 self.help = ''
424 426 self.badalias = None
425 427 self.unknowncmd = False
426 428 self.source = source
427 429
428 430 try:
429 431 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
430 432 for alias, e in cmdtable.iteritems():
431 433 if e is entry:
432 434 self.cmd = alias
433 435 break
434 436 self.shadows = True
435 437 except error.UnknownCommand:
436 438 self.shadows = False
437 439
438 440 if not self.definition:
439 441 self.badalias = _("no definition for alias '%s'") % self.name
440 442 return
441 443
442 444 if self.definition.startswith('!'):
443 445 self.shell = True
444 446 def fn(ui, *args):
445 447 env = {'HG_ARGS': ' '.join((self.name,) + args)}
446 448 def _checkvar(m):
447 449 if m.groups()[0] == '$':
448 450 return m.group()
449 451 elif int(m.groups()[0]) <= len(args):
450 452 return m.group()
451 453 else:
452 454 ui.debug("No argument found for substitution "
453 455 "of %i variable in alias '%s' definition."
454 456 % (int(m.groups()[0]), self.name))
455 457 return ''
456 458 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
457 459 cmd = aliasinterpolate(self.name, args, cmd)
458 460 return ui.system(cmd, environ=env,
459 461 blockedtag='alias_%s' % self.name)
460 462 self.fn = fn
461 463 return
462 464
463 465 try:
464 466 args = pycompat.shlexsplit(self.definition)
465 467 except ValueError as inst:
466 468 self.badalias = (_("error in definition for alias '%s': %s")
467 469 % (self.name, inst))
468 470 return
469 471 self.cmdname = cmd = args.pop(0)
470 472 self.givenargs = args
471 473
472 474 for invalidarg in commands.earlyoptflags:
473 475 if _earlygetopt([invalidarg], args):
474 476 self.badalias = (_("error in definition for alias '%s': %s may "
475 477 "only be given on the command line")
476 478 % (self.name, invalidarg))
477 479 return
478 480
479 481 try:
480 482 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
481 483 if len(tableentry) > 2:
482 484 self.fn, self.opts, self.help = tableentry
483 485 else:
484 486 self.fn, self.opts = tableentry
485 487
486 488 if self.help.startswith("hg " + cmd):
487 489 # drop prefix in old-style help lines so hg shows the alias
488 490 self.help = self.help[4 + len(cmd):]
489 491 self.__doc__ = self.fn.__doc__
490 492
491 493 except error.UnknownCommand:
492 494 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
493 495 % (self.name, cmd))
494 496 self.unknowncmd = True
495 497 except error.AmbiguousCommand:
496 498 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
497 499 % (self.name, cmd))
498 500
499 501 @property
500 502 def args(self):
501 503 args = pycompat.maplist(util.expandpath, self.givenargs)
502 504 return aliasargs(self.fn, args)
503 505
504 506 def __getattr__(self, name):
505 507 adefaults = {r'norepo': True, r'cmdtype': unrecoverablewrite,
506 508 r'optionalrepo': False, r'inferrepo': False}
507 509 if name not in adefaults:
508 510 raise AttributeError(name)
509 511 if self.badalias or util.safehasattr(self, 'shell'):
510 512 return adefaults[name]
511 513 return getattr(self.fn, name)
512 514
513 515 def __call__(self, ui, *args, **opts):
514 516 if self.badalias:
515 517 hint = None
516 518 if self.unknowncmd:
517 519 try:
518 520 # check if the command is in a disabled extension
519 521 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
520 522 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
521 523 except error.UnknownCommand:
522 524 pass
523 525 raise error.Abort(self.badalias, hint=hint)
524 526 if self.shadows:
525 527 ui.debug("alias '%s' shadows command '%s'\n" %
526 528 (self.name, self.cmdname))
527 529
528 530 ui.log('commandalias', "alias '%s' expands to '%s'\n",
529 531 self.name, self.definition)
530 532 if util.safehasattr(self, 'shell'):
531 533 return self.fn(ui, *args, **opts)
532 534 else:
533 535 try:
534 536 return util.checksignature(self.fn)(ui, *args, **opts)
535 537 except error.SignatureError:
536 538 args = ' '.join([self.cmdname] + self.args)
537 539 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
538 540 raise
539 541
540 542 class lazyaliasentry(object):
541 543 """like a typical command entry (func, opts, help), but is lazy"""
542 544
543 545 def __init__(self, name, definition, cmdtable, source):
544 546 self.name = name
545 547 self.definition = definition
546 548 self.cmdtable = cmdtable.copy()
547 549 self.source = source
548 550
549 551 @util.propertycache
550 552 def _aliasdef(self):
551 553 return cmdalias(self.name, self.definition, self.cmdtable, self.source)
552 554
553 555 def __getitem__(self, n):
554 556 aliasdef = self._aliasdef
555 557 if n == 0:
556 558 return aliasdef
557 559 elif n == 1:
558 560 return aliasdef.opts
559 561 elif n == 2:
560 562 return aliasdef.help
561 563 else:
562 564 raise IndexError
563 565
564 566 def __iter__(self):
565 567 for i in range(3):
566 568 yield self[i]
567 569
568 570 def __len__(self):
569 571 return 3
570 572
571 573 def addaliases(ui, cmdtable):
572 574 # aliases are processed after extensions have been loaded, so they
573 575 # may use extension commands. Aliases can also use other alias definitions,
574 576 # but only if they have been defined prior to the current definition.
575 577 for alias, definition in ui.configitems('alias'):
576 578 try:
577 579 if cmdtable[alias].definition == definition:
578 580 continue
579 581 except (KeyError, AttributeError):
580 582 # definition might not exist or it might not be a cmdalias
581 583 pass
582 584
583 585 source = ui.configsource('alias', alias)
584 586 entry = lazyaliasentry(alias, definition, cmdtable, source)
585 587 cmdtable[alias] = entry
586 588
587 589 def _parse(ui, args):
588 590 options = {}
589 591 cmdoptions = {}
590 592
591 593 try:
592 594 args = fancyopts.fancyopts(args, commands.globalopts, options)
593 595 except getopt.GetoptError as inst:
594 596 raise error.CommandError(None, inst)
595 597
596 598 if args:
597 599 cmd, args = args[0], args[1:]
598 600 aliases, entry = cmdutil.findcmd(cmd, commands.table,
599 601 ui.configbool("ui", "strict"))
600 602 cmd = aliases[0]
601 603 args = aliasargs(entry[0], args)
602 604 defaults = ui.config("defaults", cmd)
603 605 if defaults:
604 606 args = pycompat.maplist(
605 607 util.expandpath, pycompat.shlexsplit(defaults)) + args
606 608 c = list(entry[1])
607 609 else:
608 610 cmd = None
609 611 c = []
610 612
611 613 # combine global options into local
612 614 for o in commands.globalopts:
613 615 c.append((o[0], o[1], options[o[1]], o[3]))
614 616
615 617 try:
616 618 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
617 619 except getopt.GetoptError as inst:
618 620 raise error.CommandError(cmd, inst)
619 621
620 622 # separate global options back out
621 623 for o in commands.globalopts:
622 624 n = o[1]
623 625 options[n] = cmdoptions[n]
624 626 del cmdoptions[n]
625 627
626 628 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
627 629
628 630 def _parseconfig(ui, config):
629 631 """parse the --config options from the command line"""
630 632 configs = []
631 633
632 634 for cfg in config:
633 635 try:
634 636 name, value = [cfgelem.strip()
635 637 for cfgelem in cfg.split('=', 1)]
636 638 section, name = name.split('.', 1)
637 639 if not section or not name:
638 640 raise IndexError
639 641 ui.setconfig(section, name, value, '--config')
640 642 configs.append((section, name, value))
641 643 except (IndexError, ValueError):
642 644 raise error.Abort(_('malformed --config option: %r '
643 645 '(use --config section.name=value)') % cfg)
644 646
645 647 return configs
646 648
649 def _earlyparseopts(args):
650 options = {}
651 fancyopts.fancyopts(args, commands.globalopts, options,
652 gnu=False, early=True)
653 return options
654
647 655 def _earlygetopt(aliases, args, strip=True):
648 656 """Return list of values for an option (or aliases).
649 657
650 658 The values are listed in the order they appear in args.
651 659 The options and values are removed from args if strip=True.
652 660
653 661 >>> args = [b'x', b'--cwd', b'foo', b'y']
654 662 >>> _earlygetopt([b'--cwd'], args), args
655 663 (['foo'], ['x', 'y'])
656 664
657 665 >>> args = [b'x', b'--cwd=bar', b'y']
658 666 >>> _earlygetopt([b'--cwd'], args), args
659 667 (['bar'], ['x', 'y'])
660 668
661 669 >>> args = [b'x', b'--cwd=bar', b'y']
662 670 >>> _earlygetopt([b'--cwd'], args, strip=False), args
663 671 (['bar'], ['x', '--cwd=bar', 'y'])
664 672
665 673 >>> args = [b'x', b'-R', b'foo', b'y']
666 674 >>> _earlygetopt([b'-R'], args), args
667 675 (['foo'], ['x', 'y'])
668 676
669 677 >>> args = [b'x', b'-R', b'foo', b'y']
670 678 >>> _earlygetopt([b'-R'], args, strip=False), args
671 679 (['foo'], ['x', '-R', 'foo', 'y'])
672 680
673 681 >>> args = [b'x', b'-Rbar', b'y']
674 682 >>> _earlygetopt([b'-R'], args), args
675 683 (['bar'], ['x', 'y'])
676 684
677 685 >>> args = [b'x', b'-Rbar', b'y']
678 686 >>> _earlygetopt([b'-R'], args, strip=False), args
679 687 (['bar'], ['x', '-Rbar', 'y'])
680 688
681 689 >>> args = [b'x', b'-R=bar', b'y']
682 690 >>> _earlygetopt([b'-R'], args), args
683 691 (['=bar'], ['x', 'y'])
684 692
685 693 >>> args = [b'x', b'-R', b'--', b'y']
686 694 >>> _earlygetopt([b'-R'], args), args
687 695 ([], ['x', '-R', '--', 'y'])
688 696 """
689 697 try:
690 698 argcount = args.index("--")
691 699 except ValueError:
692 700 argcount = len(args)
693 701 shortopts = [opt for opt in aliases if len(opt) == 2]
694 702 values = []
695 703 pos = 0
696 704 while pos < argcount:
697 705 fullarg = arg = args[pos]
698 706 equals = -1
699 707 if arg.startswith('--'):
700 708 equals = arg.find('=')
701 709 if equals > -1:
702 710 arg = arg[:equals]
703 711 if arg in aliases:
704 712 if equals > -1:
705 713 values.append(fullarg[equals + 1:])
706 714 if strip:
707 715 del args[pos]
708 716 argcount -= 1
709 717 else:
710 718 pos += 1
711 719 else:
712 720 if pos + 1 >= argcount:
713 721 # ignore and let getopt report an error if there is no value
714 722 break
715 723 values.append(args[pos + 1])
716 724 if strip:
717 725 del args[pos:pos + 2]
718 726 argcount -= 2
719 727 else:
720 728 pos += 2
721 729 elif arg[:2] in shortopts:
722 730 # short option can have no following space, e.g. hg log -Rfoo
723 731 values.append(args[pos][2:])
724 732 if strip:
725 733 del args[pos]
726 734 argcount -= 1
727 735 else:
728 736 pos += 1
729 737 else:
730 738 pos += 1
731 739 return values
732 740
733 741 def _earlyreqopt(req, name, aliases):
734 742 """Peek a list option without using a full options table"""
743 if req.ui.plain('strictflags'):
744 return req.earlyoptions[name]
735 745 values = _earlygetopt(aliases, req.args, strip=False)
736 746 req.earlyoptions[name] = values
737 747 return values
738 748
739 749 def _earlyreqoptstr(req, name, aliases):
740 750 """Peek a string option without using a full options table"""
751 if req.ui.plain('strictflags'):
752 return req.earlyoptions[name]
741 753 value = (_earlygetopt(aliases, req.args, strip=False) or [''])[-1]
742 754 req.earlyoptions[name] = value
743 755 return value
744 756
745 757 def _earlyreqoptbool(req, name, aliases):
746 758 """Peek a boolean option without using a full options table
747 759
748 >>> req = request([b'x', b'--debugger'])
760 >>> req = request([b'x', b'--debugger'], uimod.ui())
749 761 >>> _earlyreqoptbool(req, b'debugger', [b'--debugger'])
750 762 True
751 763
752 >>> req = request([b'x', b'--', b'--debugger'])
764 >>> req = request([b'x', b'--', b'--debugger'], uimod.ui())
753 765 >>> _earlyreqoptbool(req, b'debugger', [b'--debugger'])
754 766 """
767 if req.ui.plain('strictflags'):
768 return req.earlyoptions[name]
755 769 try:
756 770 argcount = req.args.index("--")
757 771 except ValueError:
758 772 argcount = len(req.args)
759 773 value = None
760 774 pos = 0
761 775 while pos < argcount:
762 776 arg = req.args[pos]
763 777 if arg in aliases:
764 778 value = True
765 779 pos += 1
766 780 req.earlyoptions[name] = value
767 781 return value
768 782
769 783 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
770 784 # run pre-hook, and abort if it fails
771 785 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
772 786 pats=cmdpats, opts=cmdoptions)
773 787 try:
774 788 ret = _runcommand(ui, options, cmd, d)
775 789 # run post-hook, passing command result
776 790 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
777 791 result=ret, pats=cmdpats, opts=cmdoptions)
778 792 except Exception:
779 793 # run failure hook and re-raise
780 794 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
781 795 pats=cmdpats, opts=cmdoptions)
782 796 raise
783 797 return ret
784 798
785 799 def _getlocal(ui, rpath, wd=None):
786 800 """Return (path, local ui object) for the given target path.
787 801
788 802 Takes paths in [cwd]/.hg/hgrc into account."
789 803 """
790 804 if wd is None:
791 805 try:
792 806 wd = pycompat.getcwd()
793 807 except OSError as e:
794 808 raise error.Abort(_("error getting current working directory: %s") %
795 809 encoding.strtolocal(e.strerror))
796 810 path = cmdutil.findrepo(wd) or ""
797 811 if not path:
798 812 lui = ui
799 813 else:
800 814 lui = ui.copy()
801 815 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
802 816
803 817 if rpath:
804 818 path = lui.expandpath(rpath)
805 819 lui = ui.copy()
806 820 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
807 821
808 822 return path, lui
809 823
810 824 def _checkshellalias(lui, ui, args):
811 825 """Return the function to run the shell alias, if it is required"""
812 826 options = {}
813 827
814 828 try:
815 829 args = fancyopts.fancyopts(args, commands.globalopts, options)
816 830 except getopt.GetoptError:
817 831 return
818 832
819 833 if not args:
820 834 return
821 835
822 836 cmdtable = commands.table
823 837
824 838 cmd = args[0]
825 839 try:
826 840 strict = ui.configbool("ui", "strict")
827 841 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
828 842 except (error.AmbiguousCommand, error.UnknownCommand):
829 843 return
830 844
831 845 cmd = aliases[0]
832 846 fn = entry[0]
833 847
834 848 if cmd and util.safehasattr(fn, 'shell'):
835 849 # shell alias shouldn't receive early options which are consumed by hg
836 850 args = args[:]
837 851 _earlygetopt(commands.earlyoptflags, args, strip=True)
838 852 d = lambda: fn(ui, *args[1:])
839 853 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
840 854 [], {})
841 855
842 856 def _dispatch(req):
843 857 args = req.args
844 858 ui = req.ui
845 859
846 860 # check for cwd
847 861 cwd = _earlyreqoptstr(req, 'cwd', ['--cwd'])
848 862 if cwd:
849 863 os.chdir(cwd)
850 864
851 865 rpath = _earlyreqoptstr(req, 'repository', ["-R", "--repository", "--repo"])
852 866 path, lui = _getlocal(ui, rpath)
853 867
854 868 uis = {ui, lui}
855 869
856 870 if req.repo:
857 871 uis.add(req.repo.ui)
858 872
859 873 if _earlyreqoptbool(req, 'profile', ['--profile']):
860 874 for ui_ in uis:
861 875 ui_.setconfig('profiling', 'enabled', 'true', '--profile')
862 876
863 877 profile = lui.configbool('profiling', 'enabled')
864 878 with profiling.profile(lui, enabled=profile) as profiler:
865 879 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
866 880 # reposetup
867 881 extensions.loadall(lui)
868 882 # Propagate any changes to lui.__class__ by extensions
869 883 ui.__class__ = lui.__class__
870 884
871 885 # (uisetup and extsetup are handled in extensions.loadall)
872 886
873 887 # (reposetup is handled in hg.repository)
874 888
875 889 addaliases(lui, commands.table)
876 890
877 891 # All aliases and commands are completely defined, now.
878 892 # Check abbreviation/ambiguity of shell alias.
879 893 shellaliasfn = _checkshellalias(lui, ui, args)
880 894 if shellaliasfn:
881 895 return shellaliasfn()
882 896
883 897 # check for fallback encoding
884 898 fallback = lui.config('ui', 'fallbackencoding')
885 899 if fallback:
886 900 encoding.fallbackencoding = fallback
887 901
888 902 fullargs = args
889 903 cmd, func, args, options, cmdoptions = _parse(lui, args)
890 904
891 905 if options["config"] != req.earlyoptions["config"]:
892 906 raise error.Abort(_("option --config may not be abbreviated!"))
893 907 if options["cwd"] != req.earlyoptions["cwd"]:
894 908 raise error.Abort(_("option --cwd may not be abbreviated!"))
895 909 if options["repository"] != req.earlyoptions["repository"]:
896 910 raise error.Abort(_(
897 911 "option -R has to be separated from other options (e.g. not "
898 912 "-qR) and --repository may only be abbreviated as --repo!"))
899 913 if options["debugger"] != req.earlyoptions["debugger"]:
900 914 raise error.Abort(_("option --debugger may not be abbreviated!"))
901 915 # don't validate --profile/--traceback, which can be enabled from now
902 916
903 917 if options["encoding"]:
904 918 encoding.encoding = options["encoding"]
905 919 if options["encodingmode"]:
906 920 encoding.encodingmode = options["encodingmode"]
907 921 if options["time"]:
908 922 def get_times():
909 923 t = os.times()
910 924 if t[4] == 0.0:
911 925 # Windows leaves this as zero, so use time.clock()
912 926 t = (t[0], t[1], t[2], t[3], time.clock())
913 927 return t
914 928 s = get_times()
915 929 def print_time():
916 930 t = get_times()
917 931 ui.warn(
918 932 _("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
919 933 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
920 934 ui.atexit(print_time)
921 935 if options["profile"]:
922 936 profiler.start()
923 937
924 938 if options['verbose'] or options['debug'] or options['quiet']:
925 939 for opt in ('verbose', 'debug', 'quiet'):
926 940 val = str(bool(options[opt]))
927 941 if pycompat.ispy3:
928 942 val = val.encode('ascii')
929 943 for ui_ in uis:
930 944 ui_.setconfig('ui', opt, val, '--' + opt)
931 945
932 946 if options['traceback']:
933 947 for ui_ in uis:
934 948 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
935 949
936 950 if options['noninteractive']:
937 951 for ui_ in uis:
938 952 ui_.setconfig('ui', 'interactive', 'off', '-y')
939 953
940 954 if cmdoptions.get('insecure', False):
941 955 for ui_ in uis:
942 956 ui_.insecureconnections = True
943 957
944 958 # setup color handling before pager, because setting up pager
945 959 # might cause incorrect console information
946 960 coloropt = options['color']
947 961 for ui_ in uis:
948 962 if coloropt:
949 963 ui_.setconfig('ui', 'color', coloropt, '--color')
950 964 color.setup(ui_)
951 965
952 966 if util.parsebool(options['pager']):
953 967 # ui.pager() expects 'internal-always-' prefix in this case
954 968 ui.pager('internal-always-' + cmd)
955 969 elif options['pager'] != 'auto':
956 970 for ui_ in uis:
957 971 ui_.disablepager()
958 972
959 973 if options['version']:
960 974 return commands.version_(ui)
961 975 if options['help']:
962 976 return commands.help_(ui, cmd, command=cmd is not None)
963 977 elif not cmd:
964 978 return commands.help_(ui, 'shortlist')
965 979
966 980 repo = None
967 981 cmdpats = args[:]
968 982 if not func.norepo:
969 983 # use the repo from the request only if we don't have -R
970 984 if not rpath and not cwd:
971 985 repo = req.repo
972 986
973 987 if repo:
974 988 # set the descriptors of the repo ui to those of ui
975 989 repo.ui.fin = ui.fin
976 990 repo.ui.fout = ui.fout
977 991 repo.ui.ferr = ui.ferr
978 992 else:
979 993 try:
980 994 repo = hg.repository(ui, path=path,
981 995 presetupfuncs=req.prereposetups)
982 996 if not repo.local():
983 997 raise error.Abort(_("repository '%s' is not local")
984 998 % path)
985 999 repo.ui.setconfig("bundle", "mainreporoot", repo.root,
986 1000 'repo')
987 1001 except error.RequirementError:
988 1002 raise
989 1003 except error.RepoError:
990 1004 if rpath: # invalid -R path
991 1005 raise
992 1006 if not func.optionalrepo:
993 1007 if func.inferrepo and args and not path:
994 1008 # try to infer -R from command args
995 1009 repos = map(cmdutil.findrepo, args)
996 1010 guess = repos[0]
997 1011 if guess and repos.count(guess) == len(repos):
998 1012 req.args = ['--repository', guess] + fullargs
999 1013 return _dispatch(req)
1000 1014 if not path:
1001 1015 raise error.RepoError(_("no repository found in"
1002 1016 " '%s' (.hg not found)")
1003 1017 % pycompat.getcwd())
1004 1018 raise
1005 1019 if repo:
1006 1020 ui = repo.ui
1007 1021 if options['hidden']:
1008 1022 repo = repo.unfiltered()
1009 1023 args.insert(0, repo)
1010 1024 elif rpath:
1011 1025 ui.warn(_("warning: --repository ignored\n"))
1012 1026
1013 1027 msg = _formatargs(fullargs)
1014 1028 ui.log("command", '%s\n', msg)
1015 1029 strcmdopt = pycompat.strkwargs(cmdoptions)
1016 1030 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
1017 1031 try:
1018 1032 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
1019 1033 cmdpats, cmdoptions)
1020 1034 finally:
1021 1035 if repo and repo != req.repo:
1022 1036 repo.close()
1023 1037
1024 1038 def _runcommand(ui, options, cmd, cmdfunc):
1025 1039 """Run a command function, possibly with profiling enabled."""
1026 1040 try:
1027 1041 return cmdfunc()
1028 1042 except error.SignatureError:
1029 1043 raise error.CommandError(cmd, _('invalid arguments'))
1030 1044
1031 1045 def _exceptionwarning(ui):
1032 1046 """Produce a warning message for the current active exception"""
1033 1047
1034 1048 # For compatibility checking, we discard the portion of the hg
1035 1049 # version after the + on the assumption that if a "normal
1036 1050 # user" is running a build with a + in it the packager
1037 1051 # probably built from fairly close to a tag and anyone with a
1038 1052 # 'make local' copy of hg (where the version number can be out
1039 1053 # of date) will be clueful enough to notice the implausible
1040 1054 # version number and try updating.
1041 1055 ct = util.versiontuple(n=2)
1042 1056 worst = None, ct, ''
1043 1057 if ui.config('ui', 'supportcontact') is None:
1044 1058 for name, mod in extensions.extensions():
1045 1059 testedwith = getattr(mod, 'testedwith', '')
1046 1060 if pycompat.ispy3 and isinstance(testedwith, str):
1047 1061 testedwith = testedwith.encode(u'utf-8')
1048 1062 report = getattr(mod, 'buglink', _('the extension author.'))
1049 1063 if not testedwith.strip():
1050 1064 # We found an untested extension. It's likely the culprit.
1051 1065 worst = name, 'unknown', report
1052 1066 break
1053 1067
1054 1068 # Never blame on extensions bundled with Mercurial.
1055 1069 if extensions.ismoduleinternal(mod):
1056 1070 continue
1057 1071
1058 1072 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
1059 1073 if ct in tested:
1060 1074 continue
1061 1075
1062 1076 lower = [t for t in tested if t < ct]
1063 1077 nearest = max(lower or tested)
1064 1078 if worst[0] is None or nearest < worst[1]:
1065 1079 worst = name, nearest, report
1066 1080 if worst[0] is not None:
1067 1081 name, testedwith, report = worst
1068 1082 if not isinstance(testedwith, (bytes, str)):
1069 1083 testedwith = '.'.join([str(c) for c in testedwith])
1070 1084 warning = (_('** Unknown exception encountered with '
1071 1085 'possibly-broken third-party extension %s\n'
1072 1086 '** which supports versions %s of Mercurial.\n'
1073 1087 '** Please disable %s and try your action again.\n'
1074 1088 '** If that fixes the bug please report it to %s\n')
1075 1089 % (name, testedwith, name, report))
1076 1090 else:
1077 1091 bugtracker = ui.config('ui', 'supportcontact')
1078 1092 if bugtracker is None:
1079 1093 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
1080 1094 warning = (_("** unknown exception encountered, "
1081 1095 "please report by visiting\n** ") + bugtracker + '\n')
1082 1096 if pycompat.ispy3:
1083 1097 sysversion = sys.version.encode(u'utf-8')
1084 1098 else:
1085 1099 sysversion = sys.version
1086 1100 sysversion = sysversion.replace('\n', '')
1087 1101 warning += ((_("** Python %s\n") % sysversion) +
1088 1102 (_("** Mercurial Distributed SCM (version %s)\n") %
1089 1103 util.version()) +
1090 1104 (_("** Extensions loaded: %s\n") %
1091 1105 ", ".join([x[0] for x in extensions.extensions()])))
1092 1106 return warning
1093 1107
1094 1108 def handlecommandexception(ui):
1095 1109 """Produce a warning message for broken commands
1096 1110
1097 1111 Called when handling an exception; the exception is reraised if
1098 1112 this function returns False, ignored otherwise.
1099 1113 """
1100 1114 warning = _exceptionwarning(ui)
1101 1115 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
1102 1116 ui.warn(warning)
1103 1117 return False # re-raise the exception
@@ -1,111 +1,119
1 1 HG
2 2 Path to the 'hg' executable, automatically passed when running
3 3 hooks, extensions or external tools. If unset or empty, this is
4 4 the hg executable's name if it's frozen, or an executable named
5 5 'hg' (with %PATHEXT% [defaulting to COM/EXE/BAT/CMD] extensions on
6 6 Windows) is searched.
7 7
8 8 HGEDITOR
9 9 This is the name of the editor to run when committing. See EDITOR.
10 10
11 11 (deprecated, see :hg:`help config.ui.editor`)
12 12
13 13 HGENCODING
14 14 This overrides the default locale setting detected by Mercurial.
15 15 This setting is used to convert data including usernames,
16 16 changeset descriptions, tag names, and branches. This setting can
17 17 be overridden with the --encoding command-line option.
18 18
19 19 HGENCODINGMODE
20 20 This sets Mercurial's behavior for handling unknown characters
21 21 while transcoding user input. The default is "strict", which
22 22 causes Mercurial to abort if it can't map a character. Other
23 23 settings include "replace", which replaces unknown characters, and
24 24 "ignore", which drops them. This setting can be overridden with
25 25 the --encodingmode command-line option.
26 26
27 27 HGENCODINGAMBIGUOUS
28 28 This sets Mercurial's behavior for handling characters with
29 29 "ambiguous" widths like accented Latin characters with East Asian
30 30 fonts. By default, Mercurial assumes ambiguous characters are
31 31 narrow, set this variable to "wide" if such characters cause
32 32 formatting problems.
33 33
34 34 HGMERGE
35 35 An executable to use for resolving merge conflicts. The program
36 36 will be executed with three arguments: local file, remote file,
37 37 ancestor file.
38 38
39 39 (deprecated, see :hg:`help config.ui.merge`)
40 40
41 41 HGRCPATH
42 42 A list of files or directories to search for configuration
43 43 files. Item separator is ":" on Unix, ";" on Windows. If HGRCPATH
44 44 is not set, platform default search path is used. If empty, only
45 45 the .hg/hgrc from the current repository is read.
46 46
47 47 For each element in HGRCPATH:
48 48
49 49 - if it's a directory, all files ending with .rc are added
50 50 - otherwise, the file itself will be added
51 51
52 52 HGPLAIN
53 53 When set, this disables any configuration settings that might
54 54 change Mercurial's default output. This includes encoding,
55 55 defaults, verbose mode, debug mode, quiet mode, tracebacks, and
56 56 localization. This can be useful when scripting against Mercurial
57 57 in the face of existing user configuration.
58 58
59 In addition to the features disabled by ``HGPLAIN=``, the following
60 values can be specified to adjust behavior:
61
62 ``+strictflags``
63 Restrict parsing of command line flags.
64
59 65 Equivalent options set via command line flags or environment
60 66 variables are not overridden.
61 67
68 See :hg:`help scripting` for details.
69
62 70 HGPLAINEXCEPT
63 71 This is a comma-separated list of features to preserve when
64 72 HGPLAIN is enabled. Currently the following values are supported:
65 73
66 74 ``alias``
67 75 Don't remove aliases.
68 76 ``i18n``
69 77 Preserve internationalization.
70 78 ``revsetalias``
71 79 Don't remove revset aliases.
72 80 ``templatealias``
73 81 Don't remove template aliases.
74 82 ``progress``
75 83 Don't hide progress output.
76 84
77 85 Setting HGPLAINEXCEPT to anything (even an empty string) will
78 86 enable plain mode.
79 87
80 88 HGUSER
81 89 This is the string used as the author of a commit. If not set,
82 90 available values will be considered in this order:
83 91
84 92 - HGUSER (deprecated)
85 93 - configuration files from the HGRCPATH
86 94 - EMAIL
87 95 - interactive prompt
88 96 - LOGNAME (with ``@hostname`` appended)
89 97
90 98 (deprecated, see :hg:`help config.ui.username`)
91 99
92 100 EMAIL
93 101 May be used as the author of a commit; see HGUSER.
94 102
95 103 LOGNAME
96 104 May be used as the author of a commit; see HGUSER.
97 105
98 106 VISUAL
99 107 This is the name of the editor to use when committing. See EDITOR.
100 108
101 109 EDITOR
102 110 Sometimes Mercurial needs to open a text file in an editor for a
103 111 user to modify, for example when writing commit messages. The
104 112 editor it uses is determined by looking at the environment
105 113 variables HGEDITOR, VISUAL and EDITOR, in that order. The first
106 114 non-empty one is chosen. If all of them are empty, the editor
107 115 defaults to 'vi'.
108 116
109 117 PYTHONPATH
110 118 This is used by Python to find imported modules and may need to be
111 119 set appropriately if this Mercurial is not installed system-wide.
@@ -1,174 +1,200
1 1 It is common for machines (as opposed to humans) to consume Mercurial.
2 2 This help topic describes some of the considerations for interfacing
3 3 machines with Mercurial.
4 4
5 5 Choosing an Interface
6 6 =====================
7 7
8 8 Machines have a choice of several methods to interface with Mercurial.
9 9 These include:
10 10
11 11 - Executing the ``hg`` process
12 12 - Querying a HTTP server
13 13 - Calling out to a command server
14 14
15 15 Executing ``hg`` processes is very similar to how humans interact with
16 16 Mercurial in the shell. It should already be familiar to you.
17 17
18 18 :hg:`serve` can be used to start a server. By default, this will start
19 19 a "hgweb" HTTP server. This HTTP server has support for machine-readable
20 20 output, such as JSON. For more, see :hg:`help hgweb`.
21 21
22 22 :hg:`serve` can also start a "command server." Clients can connect
23 23 to this server and issue Mercurial commands over a special protocol.
24 24 For more details on the command server, including links to client
25 25 libraries, see https://www.mercurial-scm.org/wiki/CommandServer.
26 26
27 27 :hg:`serve` based interfaces (the hgweb and command servers) have the
28 28 advantage over simple ``hg`` process invocations in that they are
29 29 likely more efficient. This is because there is significant overhead
30 30 to spawn new Python processes.
31 31
32 32 .. tip::
33 33
34 34 If you need to invoke several ``hg`` processes in short order and/or
35 35 performance is important to you, use of a server-based interface
36 36 is highly recommended.
37 37
38 38 Environment Variables
39 39 =====================
40 40
41 41 As documented in :hg:`help environment`, various environment variables
42 42 influence the operation of Mercurial. The following are particularly
43 43 relevant for machines consuming Mercurial:
44 44
45 45 HGPLAIN
46 46 If not set, Mercurial's output could be influenced by configuration
47 47 settings that impact its encoding, verbose mode, localization, etc.
48 48
49 49 It is highly recommended for machines to set this variable when
50 50 invoking ``hg`` processes.
51 51
52 52 HGENCODING
53 53 If not set, the locale used by Mercurial will be detected from the
54 54 environment. If the determined locale does not support display of
55 55 certain characters, Mercurial may render these character sequences
56 56 incorrectly (often by using "?" as a placeholder for invalid
57 57 characters in the current locale).
58 58
59 59 Explicitly setting this environment variable is a good practice to
60 60 guarantee consistent results. "utf-8" is a good choice on UNIX-like
61 61 environments.
62 62
63 63 HGRCPATH
64 64 If not set, Mercurial will inherit config options from config files
65 65 using the process described in :hg:`help config`. This includes
66 66 inheriting user or system-wide config files.
67 67
68 68 When utmost control over the Mercurial configuration is desired, the
69 69 value of ``HGRCPATH`` can be set to an explicit file with known good
70 70 configs. In rare cases, the value can be set to an empty file or the
71 71 null device (often ``/dev/null``) to bypass loading of any user or
72 72 system config files. Note that these approaches can have unintended
73 73 consequences, as the user and system config files often define things
74 74 like the username and extensions that may be required to interface
75 75 with a repository.
76 76
77 Command-line Flags
78 ==================
79
80 Mercurial's default command-line parser is designed for humans, and is not
81 robust against malicious input. For instance, you can start a debugger by
82 passing ``--debugger`` as an option value::
83
84 $ REV=--debugger sh -c 'hg log -r "$REV"'
85
86 This happens because several command-line flags need to be scanned without
87 using a concrete command table, which may be modified while loading repository
88 settings and extensions.
89
90 Since Mercurial 4.4.2, the parsing of such flags may be restricted by setting
91 ``HGPLAIN=+strictflags``. When this feature is enabled, all early options
92 (e.g. ``-R/--repository``, ``--cwd``, ``--config``) must be specified first
93 amongst the other global options, and cannot be injected to an arbitrary
94 location::
95
96 $ HGPLAIN=+strictflags hg -R "$REPO" log -r "$REV"
97
98 In earlier Mercurial versions where ``+strictflags`` isn't available, you
99 can mitigate the issue by concatenating an option value with its flag::
100
101 $ hg log -r"$REV" --keyword="$KEYWORD"
102
77 103 Consuming Command Output
78 104 ========================
79 105
80 106 It is common for machines to need to parse the output of Mercurial
81 107 commands for relevant data. This section describes the various
82 108 techniques for doing so.
83 109
84 110 Parsing Raw Command Output
85 111 --------------------------
86 112
87 113 Likely the simplest and most effective solution for consuming command
88 114 output is to simply invoke ``hg`` commands as you would as a user and
89 115 parse their output.
90 116
91 117 The output of many commands can easily be parsed with tools like
92 118 ``grep``, ``sed``, and ``awk``.
93 119
94 120 A potential downside with parsing command output is that the output
95 121 of commands can change when Mercurial is upgraded. While Mercurial
96 122 does generally strive for strong backwards compatibility, command
97 123 output does occasionally change. Having tests for your automated
98 124 interactions with ``hg`` commands is generally recommended, but is
99 125 even more important when raw command output parsing is involved.
100 126
101 127 Using Templates to Control Output
102 128 ---------------------------------
103 129
104 130 Many ``hg`` commands support templatized output via the
105 131 ``-T/--template`` argument. For more, see :hg:`help templates`.
106 132
107 133 Templates are useful for explicitly controlling output so that
108 134 you get exactly the data you want formatted how you want it. For
109 135 example, ``log -T {node}\n`` can be used to print a newline
110 136 delimited list of changeset nodes instead of a human-tailored
111 137 output containing authors, dates, descriptions, etc.
112 138
113 139 .. tip::
114 140
115 141 If parsing raw command output is too complicated, consider
116 142 using templates to make your life easier.
117 143
118 144 The ``-T/--template`` argument allows specifying pre-defined styles.
119 145 Mercurial ships with the machine-readable styles ``json`` and ``xml``,
120 146 which provide JSON and XML output, respectively. These are useful for
121 147 producing output that is machine readable as-is.
122 148
123 149 .. important::
124 150
125 151 The ``json`` and ``xml`` styles are considered experimental. While
126 152 they may be attractive to use for easily obtaining machine-readable
127 153 output, their behavior may change in subsequent versions.
128 154
129 155 These styles may also exhibit unexpected results when dealing with
130 156 certain encodings. Mercurial treats things like filenames as a
131 157 series of bytes and normalizing certain byte sequences to JSON
132 158 or XML with certain encoding settings can lead to surprises.
133 159
134 160 Command Server Output
135 161 ---------------------
136 162
137 163 If using the command server to interact with Mercurial, you are likely
138 164 using an existing library/API that abstracts implementation details of
139 165 the command server. If so, this interface layer may perform parsing for
140 166 you, saving you the work of implementing it yourself.
141 167
142 168 Output Verbosity
143 169 ----------------
144 170
145 171 Commands often have varying output verbosity, even when machine
146 172 readable styles are being used (e.g. ``-T json``). Adding
147 173 ``-v/--verbose`` and ``--debug`` to the command's arguments can
148 174 increase the amount of data exposed by Mercurial.
149 175
150 176 An alternate way to get the data you need is by explicitly specifying
151 177 a template.
152 178
153 179 Other Topics
154 180 ============
155 181
156 182 revsets
157 183 Revisions sets is a functional query language for selecting a set
158 184 of revisions. Think of it as SQL for Mercurial repositories. Revsets
159 185 are useful for querying repositories for specific data.
160 186
161 187 See :hg:`help revsets` for more.
162 188
163 189 share extension
164 190 The ``share`` extension provides functionality for sharing
165 191 repository data across several working copies. It can even
166 192 automatically "pool" storage for logically related repositories when
167 193 cloning.
168 194
169 195 Configuring the ``share`` extension can lead to significant resource
170 196 utilization reduction, particularly around disk space and the
171 197 network. This is especially true for continuous integration (CI)
172 198 environments.
173 199
174 200 See :hg:`help -e share` for more.
@@ -1,1836 +1,1840
1 1 # ui.py - user interface bits for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import collections
11 11 import contextlib
12 12 import errno
13 13 import getpass
14 14 import inspect
15 15 import os
16 16 import re
17 17 import signal
18 18 import socket
19 19 import subprocess
20 20 import sys
21 21 import tempfile
22 22 import traceback
23 23
24 24 from .i18n import _
25 25 from .node import hex
26 26
27 27 from . import (
28 28 color,
29 29 config,
30 30 configitems,
31 31 encoding,
32 32 error,
33 33 formatter,
34 34 progress,
35 35 pycompat,
36 36 rcutil,
37 37 scmutil,
38 38 util,
39 39 )
40 40
41 41 urlreq = util.urlreq
42 42
43 43 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
44 44 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
45 45 if not c.isalnum())
46 46
47 47 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
48 48 tweakrc = """
49 49 [ui]
50 50 # The rollback command is dangerous. As a rule, don't use it.
51 51 rollback = False
52 52
53 53 [commands]
54 54 # Make `hg status` emit cwd-relative paths by default.
55 55 status.relative = yes
56 56 # Refuse to perform an `hg update` that would cause a file content merge
57 57 update.check = noconflict
58 58
59 59 [diff]
60 60 git = 1
61 61 """
62 62
63 63 samplehgrcs = {
64 64 'user':
65 65 b"""# example user config (see 'hg help config' for more info)
66 66 [ui]
67 67 # name and email, e.g.
68 68 # username = Jane Doe <jdoe@example.com>
69 69 username =
70 70
71 71 # We recommend enabling tweakdefaults to get slight improvements to
72 72 # the UI over time. Make sure to set HGPLAIN in the environment when
73 73 # writing scripts!
74 74 # tweakdefaults = True
75 75
76 76 # uncomment to disable color in command output
77 77 # (see 'hg help color' for details)
78 78 # color = never
79 79
80 80 # uncomment to disable command output pagination
81 81 # (see 'hg help pager' for details)
82 82 # paginate = never
83 83
84 84 [extensions]
85 85 # uncomment these lines to enable some popular extensions
86 86 # (see 'hg help extensions' for more info)
87 87 #
88 88 # churn =
89 89 """,
90 90
91 91 'cloned':
92 92 b"""# example repository config (see 'hg help config' for more info)
93 93 [paths]
94 94 default = %s
95 95
96 96 # path aliases to other clones of this repo in URLs or filesystem paths
97 97 # (see 'hg help config.paths' for more info)
98 98 #
99 99 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
100 100 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
101 101 # my-clone = /home/jdoe/jdoes-clone
102 102
103 103 [ui]
104 104 # name and email (local to this repository, optional), e.g.
105 105 # username = Jane Doe <jdoe@example.com>
106 106 """,
107 107
108 108 'local':
109 109 b"""# example repository config (see 'hg help config' for more info)
110 110 [paths]
111 111 # path aliases to other clones of this repo in URLs or filesystem paths
112 112 # (see 'hg help config.paths' for more info)
113 113 #
114 114 # default = http://example.com/hg/example-repo
115 115 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
116 116 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
117 117 # my-clone = /home/jdoe/jdoes-clone
118 118
119 119 [ui]
120 120 # name and email (local to this repository, optional), e.g.
121 121 # username = Jane Doe <jdoe@example.com>
122 122 """,
123 123
124 124 'global':
125 125 b"""# example system-wide hg config (see 'hg help config' for more info)
126 126
127 127 [ui]
128 128 # uncomment to disable color in command output
129 129 # (see 'hg help color' for details)
130 130 # color = never
131 131
132 132 # uncomment to disable command output pagination
133 133 # (see 'hg help pager' for details)
134 134 # paginate = never
135 135
136 136 [extensions]
137 137 # uncomment these lines to enable some popular extensions
138 138 # (see 'hg help extensions' for more info)
139 139 #
140 140 # blackbox =
141 141 # churn =
142 142 """,
143 143 }
144 144
145 145 def _maybestrurl(maybebytes):
146 146 if maybebytes is None:
147 147 return None
148 148 return pycompat.strurl(maybebytes)
149 149
150 150 def _maybebytesurl(maybestr):
151 151 if maybestr is None:
152 152 return None
153 153 return pycompat.bytesurl(maybestr)
154 154
155 155 class httppasswordmgrdbproxy(object):
156 156 """Delays loading urllib2 until it's needed."""
157 157 def __init__(self):
158 158 self._mgr = None
159 159
160 160 def _get_mgr(self):
161 161 if self._mgr is None:
162 162 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
163 163 return self._mgr
164 164
165 165 def add_password(self, realm, uris, user, passwd):
166 166 if isinstance(uris, tuple):
167 167 uris = tuple(_maybestrurl(u) for u in uris)
168 168 else:
169 169 uris = _maybestrurl(uris)
170 170 return self._get_mgr().add_password(
171 171 _maybestrurl(realm), uris,
172 172 _maybestrurl(user), _maybestrurl(passwd))
173 173
174 174 def find_user_password(self, realm, uri):
175 175 return tuple(_maybebytesurl(v) for v in
176 176 self._get_mgr().find_user_password(_maybestrurl(realm),
177 177 _maybestrurl(uri)))
178 178
179 179 def _catchterm(*args):
180 180 raise error.SignalInterrupt
181 181
182 182 # unique object used to detect no default value has been provided when
183 183 # retrieving configuration value.
184 184 _unset = object()
185 185
186 186 # _reqexithandlers: callbacks run at the end of a request
187 187 _reqexithandlers = []
188 188
189 189 class ui(object):
190 190 def __init__(self, src=None):
191 191 """Create a fresh new ui object if no src given
192 192
193 193 Use uimod.ui.load() to create a ui which knows global and user configs.
194 194 In most cases, you should use ui.copy() to create a copy of an existing
195 195 ui object.
196 196 """
197 197 # _buffers: used for temporary capture of output
198 198 self._buffers = []
199 199 # 3-tuple describing how each buffer in the stack behaves.
200 200 # Values are (capture stderr, capture subprocesses, apply labels).
201 201 self._bufferstates = []
202 202 # When a buffer is active, defines whether we are expanding labels.
203 203 # This exists to prevent an extra list lookup.
204 204 self._bufferapplylabels = None
205 205 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
206 206 self._reportuntrusted = True
207 207 self._knownconfig = configitems.coreitems
208 208 self._ocfg = config.config() # overlay
209 209 self._tcfg = config.config() # trusted
210 210 self._ucfg = config.config() # untrusted
211 211 self._trustusers = set()
212 212 self._trustgroups = set()
213 213 self.callhooks = True
214 214 # Insecure server connections requested.
215 215 self.insecureconnections = False
216 216 # Blocked time
217 217 self.logblockedtimes = False
218 218 # color mode: see mercurial/color.py for possible value
219 219 self._colormode = None
220 220 self._terminfoparams = {}
221 221 self._styles = {}
222 222
223 223 if src:
224 224 self.fout = src.fout
225 225 self.ferr = src.ferr
226 226 self.fin = src.fin
227 227 self.pageractive = src.pageractive
228 228 self._disablepager = src._disablepager
229 229 self._tweaked = src._tweaked
230 230
231 231 self._tcfg = src._tcfg.copy()
232 232 self._ucfg = src._ucfg.copy()
233 233 self._ocfg = src._ocfg.copy()
234 234 self._trustusers = src._trustusers.copy()
235 235 self._trustgroups = src._trustgroups.copy()
236 236 self.environ = src.environ
237 237 self.callhooks = src.callhooks
238 238 self.insecureconnections = src.insecureconnections
239 239 self._colormode = src._colormode
240 240 self._terminfoparams = src._terminfoparams.copy()
241 241 self._styles = src._styles.copy()
242 242
243 243 self.fixconfig()
244 244
245 245 self.httppasswordmgrdb = src.httppasswordmgrdb
246 246 self._blockedtimes = src._blockedtimes
247 247 else:
248 248 self.fout = util.stdout
249 249 self.ferr = util.stderr
250 250 self.fin = util.stdin
251 251 self.pageractive = False
252 252 self._disablepager = False
253 253 self._tweaked = False
254 254
255 255 # shared read-only environment
256 256 self.environ = encoding.environ
257 257
258 258 self.httppasswordmgrdb = httppasswordmgrdbproxy()
259 259 self._blockedtimes = collections.defaultdict(int)
260 260
261 261 allowed = self.configlist('experimental', 'exportableenviron')
262 262 if '*' in allowed:
263 263 self._exportableenviron = self.environ
264 264 else:
265 265 self._exportableenviron = {}
266 266 for k in allowed:
267 267 if k in self.environ:
268 268 self._exportableenviron[k] = self.environ[k]
269 269
270 270 @classmethod
271 271 def load(cls):
272 272 """Create a ui and load global and user configs"""
273 273 u = cls()
274 274 # we always trust global config files and environment variables
275 275 for t, f in rcutil.rccomponents():
276 276 if t == 'path':
277 277 u.readconfig(f, trust=True)
278 278 elif t == 'items':
279 279 sections = set()
280 280 for section, name, value, source in f:
281 281 # do not set u._ocfg
282 282 # XXX clean this up once immutable config object is a thing
283 283 u._tcfg.set(section, name, value, source)
284 284 u._ucfg.set(section, name, value, source)
285 285 sections.add(section)
286 286 for section in sections:
287 287 u.fixconfig(section=section)
288 288 else:
289 289 raise error.ProgrammingError('unknown rctype: %s' % t)
290 290 u._maybetweakdefaults()
291 291 return u
292 292
293 293 def _maybetweakdefaults(self):
294 294 if not self.configbool('ui', 'tweakdefaults'):
295 295 return
296 296 if self._tweaked or self.plain('tweakdefaults'):
297 297 return
298 298
299 299 # Note: it is SUPER IMPORTANT that you set self._tweaked to
300 300 # True *before* any calls to setconfig(), otherwise you'll get
301 301 # infinite recursion between setconfig and this method.
302 302 #
303 303 # TODO: We should extract an inner method in setconfig() to
304 304 # avoid this weirdness.
305 305 self._tweaked = True
306 306 tmpcfg = config.config()
307 307 tmpcfg.parse('<tweakdefaults>', tweakrc)
308 308 for section in tmpcfg:
309 309 for name, value in tmpcfg.items(section):
310 310 if not self.hasconfig(section, name):
311 311 self.setconfig(section, name, value, "<tweakdefaults>")
312 312
313 313 def copy(self):
314 314 return self.__class__(self)
315 315
316 316 def resetstate(self):
317 317 """Clear internal state that shouldn't persist across commands"""
318 318 if self._progbar:
319 319 self._progbar.resetstate() # reset last-print time of progress bar
320 320 self.httppasswordmgrdb = httppasswordmgrdbproxy()
321 321
322 322 @contextlib.contextmanager
323 323 def timeblockedsection(self, key):
324 324 # this is open-coded below - search for timeblockedsection to find them
325 325 starttime = util.timer()
326 326 try:
327 327 yield
328 328 finally:
329 329 self._blockedtimes[key + '_blocked'] += \
330 330 (util.timer() - starttime) * 1000
331 331
332 332 def formatter(self, topic, opts):
333 333 return formatter.formatter(self, self, topic, opts)
334 334
335 335 def _trusted(self, fp, f):
336 336 st = util.fstat(fp)
337 337 if util.isowner(st):
338 338 return True
339 339
340 340 tusers, tgroups = self._trustusers, self._trustgroups
341 341 if '*' in tusers or '*' in tgroups:
342 342 return True
343 343
344 344 user = util.username(st.st_uid)
345 345 group = util.groupname(st.st_gid)
346 346 if user in tusers or group in tgroups or user == util.username():
347 347 return True
348 348
349 349 if self._reportuntrusted:
350 350 self.warn(_('not trusting file %s from untrusted '
351 351 'user %s, group %s\n') % (f, user, group))
352 352 return False
353 353
354 354 def readconfig(self, filename, root=None, trust=False,
355 355 sections=None, remap=None):
356 356 try:
357 357 fp = open(filename, u'rb')
358 358 except IOError:
359 359 if not sections: # ignore unless we were looking for something
360 360 return
361 361 raise
362 362
363 363 cfg = config.config()
364 364 trusted = sections or trust or self._trusted(fp, filename)
365 365
366 366 try:
367 367 cfg.read(filename, fp, sections=sections, remap=remap)
368 368 fp.close()
369 369 except error.ConfigError as inst:
370 370 if trusted:
371 371 raise
372 372 self.warn(_("ignored: %s\n") % str(inst))
373 373
374 374 if self.plain():
375 375 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
376 376 'logtemplate', 'statuscopies', 'style',
377 377 'traceback', 'verbose'):
378 378 if k in cfg['ui']:
379 379 del cfg['ui'][k]
380 380 for k, v in cfg.items('defaults'):
381 381 del cfg['defaults'][k]
382 382 for k, v in cfg.items('commands'):
383 383 del cfg['commands'][k]
384 384 # Don't remove aliases from the configuration if in the exceptionlist
385 385 if self.plain('alias'):
386 386 for k, v in cfg.items('alias'):
387 387 del cfg['alias'][k]
388 388 if self.plain('revsetalias'):
389 389 for k, v in cfg.items('revsetalias'):
390 390 del cfg['revsetalias'][k]
391 391 if self.plain('templatealias'):
392 392 for k, v in cfg.items('templatealias'):
393 393 del cfg['templatealias'][k]
394 394
395 395 if trusted:
396 396 self._tcfg.update(cfg)
397 397 self._tcfg.update(self._ocfg)
398 398 self._ucfg.update(cfg)
399 399 self._ucfg.update(self._ocfg)
400 400
401 401 if root is None:
402 402 root = os.path.expanduser('~')
403 403 self.fixconfig(root=root)
404 404
405 405 def fixconfig(self, root=None, section=None):
406 406 if section in (None, 'paths'):
407 407 # expand vars and ~
408 408 # translate paths relative to root (or home) into absolute paths
409 409 root = root or pycompat.getcwd()
410 410 for c in self._tcfg, self._ucfg, self._ocfg:
411 411 for n, p in c.items('paths'):
412 412 # Ignore sub-options.
413 413 if ':' in n:
414 414 continue
415 415 if not p:
416 416 continue
417 417 if '%%' in p:
418 418 s = self.configsource('paths', n) or 'none'
419 419 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
420 420 % (n, p, s))
421 421 p = p.replace('%%', '%')
422 422 p = util.expandpath(p)
423 423 if not util.hasscheme(p) and not os.path.isabs(p):
424 424 p = os.path.normpath(os.path.join(root, p))
425 425 c.set("paths", n, p)
426 426
427 427 if section in (None, 'ui'):
428 428 # update ui options
429 429 self.debugflag = self.configbool('ui', 'debug')
430 430 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
431 431 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
432 432 if self.verbose and self.quiet:
433 433 self.quiet = self.verbose = False
434 434 self._reportuntrusted = self.debugflag or self.configbool("ui",
435 435 "report_untrusted")
436 436 self.tracebackflag = self.configbool('ui', 'traceback')
437 437 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
438 438
439 439 if section in (None, 'trusted'):
440 440 # update trust information
441 441 self._trustusers.update(self.configlist('trusted', 'users'))
442 442 self._trustgroups.update(self.configlist('trusted', 'groups'))
443 443
444 444 def backupconfig(self, section, item):
445 445 return (self._ocfg.backup(section, item),
446 446 self._tcfg.backup(section, item),
447 447 self._ucfg.backup(section, item),)
448 448 def restoreconfig(self, data):
449 449 self._ocfg.restore(data[0])
450 450 self._tcfg.restore(data[1])
451 451 self._ucfg.restore(data[2])
452 452
453 453 def setconfig(self, section, name, value, source=''):
454 454 for cfg in (self._ocfg, self._tcfg, self._ucfg):
455 455 cfg.set(section, name, value, source)
456 456 self.fixconfig(section=section)
457 457 self._maybetweakdefaults()
458 458
459 459 def _data(self, untrusted):
460 460 return untrusted and self._ucfg or self._tcfg
461 461
462 462 def configsource(self, section, name, untrusted=False):
463 463 return self._data(untrusted).source(section, name)
464 464
465 465 def config(self, section, name, default=_unset, untrusted=False):
466 466 """return the plain string version of a config"""
467 467 value = self._config(section, name, default=default,
468 468 untrusted=untrusted)
469 469 if value is _unset:
470 470 return None
471 471 return value
472 472
473 473 def _config(self, section, name, default=_unset, untrusted=False):
474 474 value = itemdefault = default
475 475 item = self._knownconfig.get(section, {}).get(name)
476 476 alternates = [(section, name)]
477 477
478 478 if item is not None:
479 479 alternates.extend(item.alias)
480 480 if callable(item.default):
481 481 itemdefault = item.default()
482 482 else:
483 483 itemdefault = item.default
484 484 else:
485 485 msg = ("accessing unregistered config item: '%s.%s'")
486 486 msg %= (section, name)
487 487 self.develwarn(msg, 2, 'warn-config-unknown')
488 488
489 489 if default is _unset:
490 490 if item is None:
491 491 value = default
492 492 elif item.default is configitems.dynamicdefault:
493 493 value = None
494 494 msg = "config item requires an explicit default value: '%s.%s'"
495 495 msg %= (section, name)
496 496 self.develwarn(msg, 2, 'warn-config-default')
497 497 else:
498 498 value = itemdefault
499 499 elif (item is not None
500 500 and item.default is not configitems.dynamicdefault
501 501 and default != itemdefault):
502 502 msg = ("specifying a mismatched default value for a registered "
503 503 "config item: '%s.%s' '%s'")
504 504 msg %= (section, name, default)
505 505 self.develwarn(msg, 2, 'warn-config-default')
506 506
507 507 for s, n in alternates:
508 508 candidate = self._data(untrusted).get(s, n, None)
509 509 if candidate is not None:
510 510 value = candidate
511 511 section = s
512 512 name = n
513 513 break
514 514
515 515 if self.debugflag and not untrusted and self._reportuntrusted:
516 516 for s, n in alternates:
517 517 uvalue = self._ucfg.get(s, n)
518 518 if uvalue is not None and uvalue != value:
519 519 self.debug("ignoring untrusted configuration option "
520 520 "%s.%s = %s\n" % (s, n, uvalue))
521 521 return value
522 522
523 523 def configsuboptions(self, section, name, default=_unset, untrusted=False):
524 524 """Get a config option and all sub-options.
525 525
526 526 Some config options have sub-options that are declared with the
527 527 format "key:opt = value". This method is used to return the main
528 528 option and all its declared sub-options.
529 529
530 530 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
531 531 is a dict of defined sub-options where keys and values are strings.
532 532 """
533 533 main = self.config(section, name, default, untrusted=untrusted)
534 534 data = self._data(untrusted)
535 535 sub = {}
536 536 prefix = '%s:' % name
537 537 for k, v in data.items(section):
538 538 if k.startswith(prefix):
539 539 sub[k[len(prefix):]] = v
540 540
541 541 if self.debugflag and not untrusted and self._reportuntrusted:
542 542 for k, v in sub.items():
543 543 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
544 544 if uvalue is not None and uvalue != v:
545 545 self.debug('ignoring untrusted configuration option '
546 546 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
547 547
548 548 return main, sub
549 549
550 550 def configpath(self, section, name, default=_unset, untrusted=False):
551 551 'get a path config item, expanded relative to repo root or config file'
552 552 v = self.config(section, name, default, untrusted)
553 553 if v is None:
554 554 return None
555 555 if not os.path.isabs(v) or "://" not in v:
556 556 src = self.configsource(section, name, untrusted)
557 557 if ':' in src:
558 558 base = os.path.dirname(src.rsplit(':')[0])
559 559 v = os.path.join(base, os.path.expanduser(v))
560 560 return v
561 561
562 562 def configbool(self, section, name, default=_unset, untrusted=False):
563 563 """parse a configuration element as a boolean
564 564
565 565 >>> u = ui(); s = b'foo'
566 566 >>> u.setconfig(s, b'true', b'yes')
567 567 >>> u.configbool(s, b'true')
568 568 True
569 569 >>> u.setconfig(s, b'false', b'no')
570 570 >>> u.configbool(s, b'false')
571 571 False
572 572 >>> u.configbool(s, b'unknown')
573 573 False
574 574 >>> u.configbool(s, b'unknown', True)
575 575 True
576 576 >>> u.setconfig(s, b'invalid', b'somevalue')
577 577 >>> u.configbool(s, b'invalid')
578 578 Traceback (most recent call last):
579 579 ...
580 580 ConfigError: foo.invalid is not a boolean ('somevalue')
581 581 """
582 582
583 583 v = self._config(section, name, default, untrusted=untrusted)
584 584 if v is None:
585 585 return v
586 586 if v is _unset:
587 587 if default is _unset:
588 588 return False
589 589 return default
590 590 if isinstance(v, bool):
591 591 return v
592 592 b = util.parsebool(v)
593 593 if b is None:
594 594 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
595 595 % (section, name, v))
596 596 return b
597 597
598 598 def configwith(self, convert, section, name, default=_unset,
599 599 desc=None, untrusted=False):
600 600 """parse a configuration element with a conversion function
601 601
602 602 >>> u = ui(); s = b'foo'
603 603 >>> u.setconfig(s, b'float1', b'42')
604 604 >>> u.configwith(float, s, b'float1')
605 605 42.0
606 606 >>> u.setconfig(s, b'float2', b'-4.25')
607 607 >>> u.configwith(float, s, b'float2')
608 608 -4.25
609 609 >>> u.configwith(float, s, b'unknown', 7)
610 610 7.0
611 611 >>> u.setconfig(s, b'invalid', b'somevalue')
612 612 >>> u.configwith(float, s, b'invalid')
613 613 Traceback (most recent call last):
614 614 ...
615 615 ConfigError: foo.invalid is not a valid float ('somevalue')
616 616 >>> u.configwith(float, s, b'invalid', desc=b'womble')
617 617 Traceback (most recent call last):
618 618 ...
619 619 ConfigError: foo.invalid is not a valid womble ('somevalue')
620 620 """
621 621
622 622 v = self.config(section, name, default, untrusted)
623 623 if v is None:
624 624 return v # do not attempt to convert None
625 625 try:
626 626 return convert(v)
627 627 except (ValueError, error.ParseError):
628 628 if desc is None:
629 629 desc = pycompat.sysbytes(convert.__name__)
630 630 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
631 631 % (section, name, desc, v))
632 632
633 633 def configint(self, section, name, default=_unset, untrusted=False):
634 634 """parse a configuration element as an integer
635 635
636 636 >>> u = ui(); s = b'foo'
637 637 >>> u.setconfig(s, b'int1', b'42')
638 638 >>> u.configint(s, b'int1')
639 639 42
640 640 >>> u.setconfig(s, b'int2', b'-42')
641 641 >>> u.configint(s, b'int2')
642 642 -42
643 643 >>> u.configint(s, b'unknown', 7)
644 644 7
645 645 >>> u.setconfig(s, b'invalid', b'somevalue')
646 646 >>> u.configint(s, b'invalid')
647 647 Traceback (most recent call last):
648 648 ...
649 649 ConfigError: foo.invalid is not a valid integer ('somevalue')
650 650 """
651 651
652 652 return self.configwith(int, section, name, default, 'integer',
653 653 untrusted)
654 654
655 655 def configbytes(self, section, name, default=_unset, untrusted=False):
656 656 """parse a configuration element as a quantity in bytes
657 657
658 658 Units can be specified as b (bytes), k or kb (kilobytes), m or
659 659 mb (megabytes), g or gb (gigabytes).
660 660
661 661 >>> u = ui(); s = b'foo'
662 662 >>> u.setconfig(s, b'val1', b'42')
663 663 >>> u.configbytes(s, b'val1')
664 664 42
665 665 >>> u.setconfig(s, b'val2', b'42.5 kb')
666 666 >>> u.configbytes(s, b'val2')
667 667 43520
668 668 >>> u.configbytes(s, b'unknown', b'7 MB')
669 669 7340032
670 670 >>> u.setconfig(s, b'invalid', b'somevalue')
671 671 >>> u.configbytes(s, b'invalid')
672 672 Traceback (most recent call last):
673 673 ...
674 674 ConfigError: foo.invalid is not a byte quantity ('somevalue')
675 675 """
676 676
677 677 value = self._config(section, name, default, untrusted)
678 678 if value is _unset:
679 679 if default is _unset:
680 680 default = 0
681 681 value = default
682 682 if not isinstance(value, bytes):
683 683 return value
684 684 try:
685 685 return util.sizetoint(value)
686 686 except error.ParseError:
687 687 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
688 688 % (section, name, value))
689 689
690 690 def configlist(self, section, name, default=_unset, untrusted=False):
691 691 """parse a configuration element as a list of comma/space separated
692 692 strings
693 693
694 694 >>> u = ui(); s = b'foo'
695 695 >>> u.setconfig(s, b'list1', b'this,is "a small" ,test')
696 696 >>> u.configlist(s, b'list1')
697 697 ['this', 'is', 'a small', 'test']
698 698 """
699 699 # default is not always a list
700 700 v = self.configwith(config.parselist, section, name, default,
701 701 'list', untrusted)
702 702 if isinstance(v, bytes):
703 703 return config.parselist(v)
704 704 elif v is None:
705 705 return []
706 706 return v
707 707
708 708 def configdate(self, section, name, default=_unset, untrusted=False):
709 709 """parse a configuration element as a tuple of ints
710 710
711 711 >>> u = ui(); s = b'foo'
712 712 >>> u.setconfig(s, b'date', b'0 0')
713 713 >>> u.configdate(s, b'date')
714 714 (0, 0)
715 715 """
716 716 if self.config(section, name, default, untrusted):
717 717 return self.configwith(util.parsedate, section, name, default,
718 718 'date', untrusted)
719 719 if default is _unset:
720 720 return None
721 721 return default
722 722
723 723 def hasconfig(self, section, name, untrusted=False):
724 724 return self._data(untrusted).hasitem(section, name)
725 725
726 726 def has_section(self, section, untrusted=False):
727 727 '''tell whether section exists in config.'''
728 728 return section in self._data(untrusted)
729 729
730 730 def configitems(self, section, untrusted=False, ignoresub=False):
731 731 items = self._data(untrusted).items(section)
732 732 if ignoresub:
733 733 newitems = {}
734 734 for k, v in items:
735 735 if ':' not in k:
736 736 newitems[k] = v
737 737 items = newitems.items()
738 738 if self.debugflag and not untrusted and self._reportuntrusted:
739 739 for k, v in self._ucfg.items(section):
740 740 if self._tcfg.get(section, k) != v:
741 741 self.debug("ignoring untrusted configuration option "
742 742 "%s.%s = %s\n" % (section, k, v))
743 743 return items
744 744
745 745 def walkconfig(self, untrusted=False):
746 746 cfg = self._data(untrusted)
747 747 for section in cfg.sections():
748 748 for name, value in self.configitems(section, untrusted):
749 749 yield section, name, value
750 750
751 751 def plain(self, feature=None):
752 752 '''is plain mode active?
753 753
754 754 Plain mode means that all configuration variables which affect
755 755 the behavior and output of Mercurial should be
756 756 ignored. Additionally, the output should be stable,
757 757 reproducible and suitable for use in scripts or applications.
758 758
759 759 The only way to trigger plain mode is by setting either the
760 760 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
761 761
762 762 The return value can either be
763 763 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
764 - False if feature is disabled by default and not included in HGPLAIN
764 765 - True otherwise
765 766 '''
766 767 if ('HGPLAIN' not in encoding.environ and
767 768 'HGPLAINEXCEPT' not in encoding.environ):
768 769 return False
769 770 exceptions = encoding.environ.get('HGPLAINEXCEPT',
770 771 '').strip().split(',')
772 # TODO: add support for HGPLAIN=+feature,-feature syntax
773 if '+strictflags' not in encoding.environ.get('HGPLAIN', '').split(','):
774 exceptions.append('strictflags')
771 775 if feature and exceptions:
772 776 return feature not in exceptions
773 777 return True
774 778
775 779 def username(self, acceptempty=False):
776 780 """Return default username to be used in commits.
777 781
778 782 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
779 783 and stop searching if one of these is set.
780 784 If not found and acceptempty is True, returns None.
781 785 If not found and ui.askusername is True, ask the user, else use
782 786 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
783 787 If no username could be found, raise an Abort error.
784 788 """
785 789 user = encoding.environ.get("HGUSER")
786 790 if user is None:
787 791 user = self.config("ui", "username")
788 792 if user is not None:
789 793 user = os.path.expandvars(user)
790 794 if user is None:
791 795 user = encoding.environ.get("EMAIL")
792 796 if user is None and acceptempty:
793 797 return user
794 798 if user is None and self.configbool("ui", "askusername"):
795 799 user = self.prompt(_("enter a commit username:"), default=None)
796 800 if user is None and not self.interactive():
797 801 try:
798 802 user = '%s@%s' % (util.getuser(), socket.getfqdn())
799 803 self.warn(_("no username found, using '%s' instead\n") % user)
800 804 except KeyError:
801 805 pass
802 806 if not user:
803 807 raise error.Abort(_('no username supplied'),
804 808 hint=_("use 'hg config --edit' "
805 809 'to set your username'))
806 810 if "\n" in user:
807 811 raise error.Abort(_("username %s contains a newline\n")
808 812 % repr(user))
809 813 return user
810 814
811 815 def shortuser(self, user):
812 816 """Return a short representation of a user name or email address."""
813 817 if not self.verbose:
814 818 user = util.shortuser(user)
815 819 return user
816 820
817 821 def expandpath(self, loc, default=None):
818 822 """Return repository location relative to cwd or from [paths]"""
819 823 try:
820 824 p = self.paths.getpath(loc)
821 825 if p:
822 826 return p.rawloc
823 827 except error.RepoError:
824 828 pass
825 829
826 830 if default:
827 831 try:
828 832 p = self.paths.getpath(default)
829 833 if p:
830 834 return p.rawloc
831 835 except error.RepoError:
832 836 pass
833 837
834 838 return loc
835 839
836 840 @util.propertycache
837 841 def paths(self):
838 842 return paths(self)
839 843
840 844 def pushbuffer(self, error=False, subproc=False, labeled=False):
841 845 """install a buffer to capture standard output of the ui object
842 846
843 847 If error is True, the error output will be captured too.
844 848
845 849 If subproc is True, output from subprocesses (typically hooks) will be
846 850 captured too.
847 851
848 852 If labeled is True, any labels associated with buffered
849 853 output will be handled. By default, this has no effect
850 854 on the output returned, but extensions and GUI tools may
851 855 handle this argument and returned styled output. If output
852 856 is being buffered so it can be captured and parsed or
853 857 processed, labeled should not be set to True.
854 858 """
855 859 self._buffers.append([])
856 860 self._bufferstates.append((error, subproc, labeled))
857 861 self._bufferapplylabels = labeled
858 862
859 863 def popbuffer(self):
860 864 '''pop the last buffer and return the buffered output'''
861 865 self._bufferstates.pop()
862 866 if self._bufferstates:
863 867 self._bufferapplylabels = self._bufferstates[-1][2]
864 868 else:
865 869 self._bufferapplylabels = None
866 870
867 871 return "".join(self._buffers.pop())
868 872
869 873 def write(self, *args, **opts):
870 874 '''write args to output
871 875
872 876 By default, this method simply writes to the buffer or stdout.
873 877 Color mode can be set on the UI class to have the output decorated
874 878 with color modifier before being written to stdout.
875 879
876 880 The color used is controlled by an optional keyword argument, "label".
877 881 This should be a string containing label names separated by space.
878 882 Label names take the form of "topic.type". For example, ui.debug()
879 883 issues a label of "ui.debug".
880 884
881 885 When labeling output for a specific command, a label of
882 886 "cmdname.type" is recommended. For example, status issues
883 887 a label of "status.modified" for modified files.
884 888 '''
885 889 if self._buffers and not opts.get('prompt', False):
886 890 if self._bufferapplylabels:
887 891 label = opts.get('label', '')
888 892 self._buffers[-1].extend(self.label(a, label) for a in args)
889 893 else:
890 894 self._buffers[-1].extend(args)
891 895 elif self._colormode == 'win32':
892 896 # windows color printing is its own can of crab, defer to
893 897 # the color module and that is it.
894 898 color.win32print(self, self._write, *args, **opts)
895 899 else:
896 900 msgs = args
897 901 if self._colormode is not None:
898 902 label = opts.get('label', '')
899 903 msgs = [self.label(a, label) for a in args]
900 904 self._write(*msgs, **opts)
901 905
902 906 def _write(self, *msgs, **opts):
903 907 self._progclear()
904 908 # opencode timeblockedsection because this is a critical path
905 909 starttime = util.timer()
906 910 try:
907 911 for a in msgs:
908 912 self.fout.write(a)
909 913 except IOError as err:
910 914 raise error.StdioError(err)
911 915 finally:
912 916 self._blockedtimes['stdio_blocked'] += \
913 917 (util.timer() - starttime) * 1000
914 918
915 919 def write_err(self, *args, **opts):
916 920 self._progclear()
917 921 if self._bufferstates and self._bufferstates[-1][0]:
918 922 self.write(*args, **opts)
919 923 elif self._colormode == 'win32':
920 924 # windows color printing is its own can of crab, defer to
921 925 # the color module and that is it.
922 926 color.win32print(self, self._write_err, *args, **opts)
923 927 else:
924 928 msgs = args
925 929 if self._colormode is not None:
926 930 label = opts.get('label', '')
927 931 msgs = [self.label(a, label) for a in args]
928 932 self._write_err(*msgs, **opts)
929 933
930 934 def _write_err(self, *msgs, **opts):
931 935 try:
932 936 with self.timeblockedsection('stdio'):
933 937 if not getattr(self.fout, 'closed', False):
934 938 self.fout.flush()
935 939 for a in msgs:
936 940 self.ferr.write(a)
937 941 # stderr may be buffered under win32 when redirected to files,
938 942 # including stdout.
939 943 if not getattr(self.ferr, 'closed', False):
940 944 self.ferr.flush()
941 945 except IOError as inst:
942 946 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
943 947 raise error.StdioError(inst)
944 948
945 949 def flush(self):
946 950 # opencode timeblockedsection because this is a critical path
947 951 starttime = util.timer()
948 952 try:
949 953 try:
950 954 self.fout.flush()
951 955 except IOError as err:
952 956 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
953 957 raise error.StdioError(err)
954 958 finally:
955 959 try:
956 960 self.ferr.flush()
957 961 except IOError as err:
958 962 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
959 963 raise error.StdioError(err)
960 964 finally:
961 965 self._blockedtimes['stdio_blocked'] += \
962 966 (util.timer() - starttime) * 1000
963 967
964 968 def _isatty(self, fh):
965 969 if self.configbool('ui', 'nontty'):
966 970 return False
967 971 return util.isatty(fh)
968 972
969 973 def disablepager(self):
970 974 self._disablepager = True
971 975
972 976 def pager(self, command):
973 977 """Start a pager for subsequent command output.
974 978
975 979 Commands which produce a long stream of output should call
976 980 this function to activate the user's preferred pagination
977 981 mechanism (which may be no pager). Calling this function
978 982 precludes any future use of interactive functionality, such as
979 983 prompting the user or activating curses.
980 984
981 985 Args:
982 986 command: The full, non-aliased name of the command. That is, "log"
983 987 not "history, "summary" not "summ", etc.
984 988 """
985 989 if (self._disablepager
986 990 or self.pageractive):
987 991 # how pager should do is already determined
988 992 return
989 993
990 994 if not command.startswith('internal-always-') and (
991 995 # explicit --pager=on (= 'internal-always-' prefix) should
992 996 # take precedence over disabling factors below
993 997 command in self.configlist('pager', 'ignore')
994 998 or not self.configbool('ui', 'paginate')
995 999 or not self.configbool('pager', 'attend-' + command, True)
996 1000 # TODO: if we want to allow HGPLAINEXCEPT=pager,
997 1001 # formatted() will need some adjustment.
998 1002 or not self.formatted()
999 1003 or self.plain()
1000 1004 or self._buffers
1001 1005 # TODO: expose debugger-enabled on the UI object
1002 1006 or '--debugger' in pycompat.sysargv):
1003 1007 # We only want to paginate if the ui appears to be
1004 1008 # interactive, the user didn't say HGPLAIN or
1005 1009 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
1006 1010 return
1007 1011
1008 1012 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
1009 1013 if not pagercmd:
1010 1014 return
1011 1015
1012 1016 pagerenv = {}
1013 1017 for name, value in rcutil.defaultpagerenv().items():
1014 1018 if name not in encoding.environ:
1015 1019 pagerenv[name] = value
1016 1020
1017 1021 self.debug('starting pager for command %r\n' % command)
1018 1022 self.flush()
1019 1023
1020 1024 wasformatted = self.formatted()
1021 1025 if util.safehasattr(signal, "SIGPIPE"):
1022 1026 signal.signal(signal.SIGPIPE, _catchterm)
1023 1027 if self._runpager(pagercmd, pagerenv):
1024 1028 self.pageractive = True
1025 1029 # Preserve the formatted-ness of the UI. This is important
1026 1030 # because we mess with stdout, which might confuse
1027 1031 # auto-detection of things being formatted.
1028 1032 self.setconfig('ui', 'formatted', wasformatted, 'pager')
1029 1033 self.setconfig('ui', 'interactive', False, 'pager')
1030 1034
1031 1035 # If pagermode differs from color.mode, reconfigure color now that
1032 1036 # pageractive is set.
1033 1037 cm = self._colormode
1034 1038 if cm != self.config('color', 'pagermode', cm):
1035 1039 color.setup(self)
1036 1040 else:
1037 1041 # If the pager can't be spawned in dispatch when --pager=on is
1038 1042 # given, don't try again when the command runs, to avoid a duplicate
1039 1043 # warning about a missing pager command.
1040 1044 self.disablepager()
1041 1045
1042 1046 def _runpager(self, command, env=None):
1043 1047 """Actually start the pager and set up file descriptors.
1044 1048
1045 1049 This is separate in part so that extensions (like chg) can
1046 1050 override how a pager is invoked.
1047 1051 """
1048 1052 if command == 'cat':
1049 1053 # Save ourselves some work.
1050 1054 return False
1051 1055 # If the command doesn't contain any of these characters, we
1052 1056 # assume it's a binary and exec it directly. This means for
1053 1057 # simple pager command configurations, we can degrade
1054 1058 # gracefully and tell the user about their broken pager.
1055 1059 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
1056 1060
1057 1061 if pycompat.iswindows and not shell:
1058 1062 # Window's built-in `more` cannot be invoked with shell=False, but
1059 1063 # its `more.com` can. Hide this implementation detail from the
1060 1064 # user so we can also get sane bad PAGER behavior. MSYS has
1061 1065 # `more.exe`, so do a cmd.exe style resolution of the executable to
1062 1066 # determine which one to use.
1063 1067 fullcmd = util.findexe(command)
1064 1068 if not fullcmd:
1065 1069 self.warn(_("missing pager command '%s', skipping pager\n")
1066 1070 % command)
1067 1071 return False
1068 1072
1069 1073 command = fullcmd
1070 1074
1071 1075 try:
1072 1076 pager = subprocess.Popen(
1073 1077 command, shell=shell, bufsize=-1,
1074 1078 close_fds=util.closefds, stdin=subprocess.PIPE,
1075 1079 stdout=util.stdout, stderr=util.stderr,
1076 1080 env=util.shellenviron(env))
1077 1081 except OSError as e:
1078 1082 if e.errno == errno.ENOENT and not shell:
1079 1083 self.warn(_("missing pager command '%s', skipping pager\n")
1080 1084 % command)
1081 1085 return False
1082 1086 raise
1083 1087
1084 1088 # back up original file descriptors
1085 1089 stdoutfd = os.dup(util.stdout.fileno())
1086 1090 stderrfd = os.dup(util.stderr.fileno())
1087 1091
1088 1092 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
1089 1093 if self._isatty(util.stderr):
1090 1094 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
1091 1095
1092 1096 @self.atexit
1093 1097 def killpager():
1094 1098 if util.safehasattr(signal, "SIGINT"):
1095 1099 signal.signal(signal.SIGINT, signal.SIG_IGN)
1096 1100 # restore original fds, closing pager.stdin copies in the process
1097 1101 os.dup2(stdoutfd, util.stdout.fileno())
1098 1102 os.dup2(stderrfd, util.stderr.fileno())
1099 1103 pager.stdin.close()
1100 1104 pager.wait()
1101 1105
1102 1106 return True
1103 1107
1104 1108 @property
1105 1109 def _exithandlers(self):
1106 1110 return _reqexithandlers
1107 1111
1108 1112 def atexit(self, func, *args, **kwargs):
1109 1113 '''register a function to run after dispatching a request
1110 1114
1111 1115 Handlers do not stay registered across request boundaries.'''
1112 1116 self._exithandlers.append((func, args, kwargs))
1113 1117 return func
1114 1118
1115 1119 def interface(self, feature):
1116 1120 """what interface to use for interactive console features?
1117 1121
1118 1122 The interface is controlled by the value of `ui.interface` but also by
1119 1123 the value of feature-specific configuration. For example:
1120 1124
1121 1125 ui.interface.histedit = text
1122 1126 ui.interface.chunkselector = curses
1123 1127
1124 1128 Here the features are "histedit" and "chunkselector".
1125 1129
1126 1130 The configuration above means that the default interfaces for commands
1127 1131 is curses, the interface for histedit is text and the interface for
1128 1132 selecting chunk is crecord (the best curses interface available).
1129 1133
1130 1134 Consider the following example:
1131 1135 ui.interface = curses
1132 1136 ui.interface.histedit = text
1133 1137
1134 1138 Then histedit will use the text interface and chunkselector will use
1135 1139 the default curses interface (crecord at the moment).
1136 1140 """
1137 1141 alldefaults = frozenset(["text", "curses"])
1138 1142
1139 1143 featureinterfaces = {
1140 1144 "chunkselector": [
1141 1145 "text",
1142 1146 "curses",
1143 1147 ]
1144 1148 }
1145 1149
1146 1150 # Feature-specific interface
1147 1151 if feature not in featureinterfaces.keys():
1148 1152 # Programming error, not user error
1149 1153 raise ValueError("Unknown feature requested %s" % feature)
1150 1154
1151 1155 availableinterfaces = frozenset(featureinterfaces[feature])
1152 1156 if alldefaults > availableinterfaces:
1153 1157 # Programming error, not user error. We need a use case to
1154 1158 # define the right thing to do here.
1155 1159 raise ValueError(
1156 1160 "Feature %s does not handle all default interfaces" %
1157 1161 feature)
1158 1162
1159 1163 if self.plain():
1160 1164 return "text"
1161 1165
1162 1166 # Default interface for all the features
1163 1167 defaultinterface = "text"
1164 1168 i = self.config("ui", "interface")
1165 1169 if i in alldefaults:
1166 1170 defaultinterface = i
1167 1171
1168 1172 choseninterface = defaultinterface
1169 1173 f = self.config("ui", "interface.%s" % feature)
1170 1174 if f in availableinterfaces:
1171 1175 choseninterface = f
1172 1176
1173 1177 if i is not None and defaultinterface != i:
1174 1178 if f is not None:
1175 1179 self.warn(_("invalid value for ui.interface: %s\n") %
1176 1180 (i,))
1177 1181 else:
1178 1182 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1179 1183 (i, choseninterface))
1180 1184 if f is not None and choseninterface != f:
1181 1185 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1182 1186 (feature, f, choseninterface))
1183 1187
1184 1188 return choseninterface
1185 1189
1186 1190 def interactive(self):
1187 1191 '''is interactive input allowed?
1188 1192
1189 1193 An interactive session is a session where input can be reasonably read
1190 1194 from `sys.stdin'. If this function returns false, any attempt to read
1191 1195 from stdin should fail with an error, unless a sensible default has been
1192 1196 specified.
1193 1197
1194 1198 Interactiveness is triggered by the value of the `ui.interactive'
1195 1199 configuration variable or - if it is unset - when `sys.stdin' points
1196 1200 to a terminal device.
1197 1201
1198 1202 This function refers to input only; for output, see `ui.formatted()'.
1199 1203 '''
1200 1204 i = self.configbool("ui", "interactive")
1201 1205 if i is None:
1202 1206 # some environments replace stdin without implementing isatty
1203 1207 # usually those are non-interactive
1204 1208 return self._isatty(self.fin)
1205 1209
1206 1210 return i
1207 1211
1208 1212 def termwidth(self):
1209 1213 '''how wide is the terminal in columns?
1210 1214 '''
1211 1215 if 'COLUMNS' in encoding.environ:
1212 1216 try:
1213 1217 return int(encoding.environ['COLUMNS'])
1214 1218 except ValueError:
1215 1219 pass
1216 1220 return scmutil.termsize(self)[0]
1217 1221
1218 1222 def formatted(self):
1219 1223 '''should formatted output be used?
1220 1224
1221 1225 It is often desirable to format the output to suite the output medium.
1222 1226 Examples of this are truncating long lines or colorizing messages.
1223 1227 However, this is not often not desirable when piping output into other
1224 1228 utilities, e.g. `grep'.
1225 1229
1226 1230 Formatted output is triggered by the value of the `ui.formatted'
1227 1231 configuration variable or - if it is unset - when `sys.stdout' points
1228 1232 to a terminal device. Please note that `ui.formatted' should be
1229 1233 considered an implementation detail; it is not intended for use outside
1230 1234 Mercurial or its extensions.
1231 1235
1232 1236 This function refers to output only; for input, see `ui.interactive()'.
1233 1237 This function always returns false when in plain mode, see `ui.plain()'.
1234 1238 '''
1235 1239 if self.plain():
1236 1240 return False
1237 1241
1238 1242 i = self.configbool("ui", "formatted")
1239 1243 if i is None:
1240 1244 # some environments replace stdout without implementing isatty
1241 1245 # usually those are non-interactive
1242 1246 return self._isatty(self.fout)
1243 1247
1244 1248 return i
1245 1249
1246 1250 def _readline(self, prompt=''):
1247 1251 if self._isatty(self.fin):
1248 1252 try:
1249 1253 # magically add command line editing support, where
1250 1254 # available
1251 1255 import readline
1252 1256 # force demandimport to really load the module
1253 1257 readline.read_history_file
1254 1258 # windows sometimes raises something other than ImportError
1255 1259 except Exception:
1256 1260 pass
1257 1261
1258 1262 # call write() so output goes through subclassed implementation
1259 1263 # e.g. color extension on Windows
1260 1264 self.write(prompt, prompt=True)
1261 1265 self.flush()
1262 1266
1263 1267 # prompt ' ' must exist; otherwise readline may delete entire line
1264 1268 # - http://bugs.python.org/issue12833
1265 1269 with self.timeblockedsection('stdio'):
1266 1270 line = util.bytesinput(self.fin, self.fout, r' ')
1267 1271
1268 1272 # When stdin is in binary mode on Windows, it can cause
1269 1273 # raw_input() to emit an extra trailing carriage return
1270 1274 if pycompat.oslinesep == '\r\n' and line and line[-1] == '\r':
1271 1275 line = line[:-1]
1272 1276 return line
1273 1277
1274 1278 def prompt(self, msg, default="y"):
1275 1279 """Prompt user with msg, read response.
1276 1280 If ui is not interactive, the default is returned.
1277 1281 """
1278 1282 if not self.interactive():
1279 1283 self.write(msg, ' ', default or '', "\n")
1280 1284 return default
1281 1285 try:
1282 1286 r = self._readline(self.label(msg, 'ui.prompt'))
1283 1287 if not r:
1284 1288 r = default
1285 1289 if self.configbool('ui', 'promptecho'):
1286 1290 self.write(r, "\n")
1287 1291 return r
1288 1292 except EOFError:
1289 1293 raise error.ResponseExpected()
1290 1294
1291 1295 @staticmethod
1292 1296 def extractchoices(prompt):
1293 1297 """Extract prompt message and list of choices from specified prompt.
1294 1298
1295 1299 This returns tuple "(message, choices)", and "choices" is the
1296 1300 list of tuple "(response character, text without &)".
1297 1301
1298 1302 >>> ui.extractchoices(b"awake? $$ &Yes $$ &No")
1299 1303 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1300 1304 >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No")
1301 1305 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1302 1306 >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o")
1303 1307 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1304 1308 """
1305 1309
1306 1310 # Sadly, the prompt string may have been built with a filename
1307 1311 # containing "$$" so let's try to find the first valid-looking
1308 1312 # prompt to start parsing. Sadly, we also can't rely on
1309 1313 # choices containing spaces, ASCII, or basically anything
1310 1314 # except an ampersand followed by a character.
1311 1315 m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1312 1316 msg = m.group(1)
1313 1317 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1314 1318 def choicetuple(s):
1315 1319 ampidx = s.index('&')
1316 1320 return s[ampidx + 1:ampidx + 2].lower(), s.replace('&', '', 1)
1317 1321 return (msg, [choicetuple(s) for s in choices])
1318 1322
1319 1323 def promptchoice(self, prompt, default=0):
1320 1324 """Prompt user with a message, read response, and ensure it matches
1321 1325 one of the provided choices. The prompt is formatted as follows:
1322 1326
1323 1327 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1324 1328
1325 1329 The index of the choice is returned. Responses are case
1326 1330 insensitive. If ui is not interactive, the default is
1327 1331 returned.
1328 1332 """
1329 1333
1330 1334 msg, choices = self.extractchoices(prompt)
1331 1335 resps = [r for r, t in choices]
1332 1336 while True:
1333 1337 r = self.prompt(msg, resps[default])
1334 1338 if r.lower() in resps:
1335 1339 return resps.index(r.lower())
1336 1340 self.write(_("unrecognized response\n"))
1337 1341
1338 1342 def getpass(self, prompt=None, default=None):
1339 1343 if not self.interactive():
1340 1344 return default
1341 1345 try:
1342 1346 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1343 1347 # disable getpass() only if explicitly specified. it's still valid
1344 1348 # to interact with tty even if fin is not a tty.
1345 1349 with self.timeblockedsection('stdio'):
1346 1350 if self.configbool('ui', 'nontty'):
1347 1351 l = self.fin.readline()
1348 1352 if not l:
1349 1353 raise EOFError
1350 1354 return l.rstrip('\n')
1351 1355 else:
1352 1356 return getpass.getpass('')
1353 1357 except EOFError:
1354 1358 raise error.ResponseExpected()
1355 1359 def status(self, *msg, **opts):
1356 1360 '''write status message to output (if ui.quiet is False)
1357 1361
1358 1362 This adds an output label of "ui.status".
1359 1363 '''
1360 1364 if not self.quiet:
1361 1365 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1362 1366 self.write(*msg, **opts)
1363 1367 def warn(self, *msg, **opts):
1364 1368 '''write warning message to output (stderr)
1365 1369
1366 1370 This adds an output label of "ui.warning".
1367 1371 '''
1368 1372 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1369 1373 self.write_err(*msg, **opts)
1370 1374 def note(self, *msg, **opts):
1371 1375 '''write note to output (if ui.verbose is True)
1372 1376
1373 1377 This adds an output label of "ui.note".
1374 1378 '''
1375 1379 if self.verbose:
1376 1380 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1377 1381 self.write(*msg, **opts)
1378 1382 def debug(self, *msg, **opts):
1379 1383 '''write debug message to output (if ui.debugflag is True)
1380 1384
1381 1385 This adds an output label of "ui.debug".
1382 1386 '''
1383 1387 if self.debugflag:
1384 1388 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1385 1389 self.write(*msg, **opts)
1386 1390
1387 1391 def edit(self, text, user, extra=None, editform=None, pending=None,
1388 1392 repopath=None, action=None):
1389 1393 if action is None:
1390 1394 self.develwarn('action is None but will soon be a required '
1391 1395 'parameter to ui.edit()')
1392 1396 extra_defaults = {
1393 1397 'prefix': 'editor',
1394 1398 'suffix': '.txt',
1395 1399 }
1396 1400 if extra is not None:
1397 1401 if extra.get('suffix') is not None:
1398 1402 self.develwarn('extra.suffix is not None but will soon be '
1399 1403 'ignored by ui.edit()')
1400 1404 extra_defaults.update(extra)
1401 1405 extra = extra_defaults
1402 1406
1403 1407 if action == 'diff':
1404 1408 suffix = '.diff'
1405 1409 elif action:
1406 1410 suffix = '.%s.hg.txt' % action
1407 1411 else:
1408 1412 suffix = extra['suffix']
1409 1413
1410 1414 rdir = None
1411 1415 if self.configbool('experimental', 'editortmpinhg'):
1412 1416 rdir = repopath
1413 1417 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1414 1418 suffix=suffix,
1415 1419 dir=rdir)
1416 1420 try:
1417 1421 f = os.fdopen(fd, r'wb')
1418 1422 f.write(util.tonativeeol(text))
1419 1423 f.close()
1420 1424
1421 1425 environ = {'HGUSER': user}
1422 1426 if 'transplant_source' in extra:
1423 1427 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1424 1428 for label in ('intermediate-source', 'source', 'rebase_source'):
1425 1429 if label in extra:
1426 1430 environ.update({'HGREVISION': extra[label]})
1427 1431 break
1428 1432 if editform:
1429 1433 environ.update({'HGEDITFORM': editform})
1430 1434 if pending:
1431 1435 environ.update({'HG_PENDING': pending})
1432 1436
1433 1437 editor = self.geteditor()
1434 1438
1435 1439 self.system("%s \"%s\"" % (editor, name),
1436 1440 environ=environ,
1437 1441 onerr=error.Abort, errprefix=_("edit failed"),
1438 1442 blockedtag='editor')
1439 1443
1440 1444 f = open(name, r'rb')
1441 1445 t = util.fromnativeeol(f.read())
1442 1446 f.close()
1443 1447 finally:
1444 1448 os.unlink(name)
1445 1449
1446 1450 return t
1447 1451
1448 1452 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1449 1453 blockedtag=None):
1450 1454 '''execute shell command with appropriate output stream. command
1451 1455 output will be redirected if fout is not stdout.
1452 1456
1453 1457 if command fails and onerr is None, return status, else raise onerr
1454 1458 object as exception.
1455 1459 '''
1456 1460 if blockedtag is None:
1457 1461 # Long cmds tend to be because of an absolute path on cmd. Keep
1458 1462 # the tail end instead
1459 1463 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1460 1464 blockedtag = 'unknown_system_' + cmdsuffix
1461 1465 out = self.fout
1462 1466 if any(s[1] for s in self._bufferstates):
1463 1467 out = self
1464 1468 with self.timeblockedsection(blockedtag):
1465 1469 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1466 1470 if rc and onerr:
1467 1471 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1468 1472 util.explainexit(rc)[0])
1469 1473 if errprefix:
1470 1474 errmsg = '%s: %s' % (errprefix, errmsg)
1471 1475 raise onerr(errmsg)
1472 1476 return rc
1473 1477
1474 1478 def _runsystem(self, cmd, environ, cwd, out):
1475 1479 """actually execute the given shell command (can be overridden by
1476 1480 extensions like chg)"""
1477 1481 return util.system(cmd, environ=environ, cwd=cwd, out=out)
1478 1482
1479 1483 def traceback(self, exc=None, force=False):
1480 1484 '''print exception traceback if traceback printing enabled or forced.
1481 1485 only to call in exception handler. returns true if traceback
1482 1486 printed.'''
1483 1487 if self.tracebackflag or force:
1484 1488 if exc is None:
1485 1489 exc = sys.exc_info()
1486 1490 cause = getattr(exc[1], 'cause', None)
1487 1491
1488 1492 if cause is not None:
1489 1493 causetb = traceback.format_tb(cause[2])
1490 1494 exctb = traceback.format_tb(exc[2])
1491 1495 exconly = traceback.format_exception_only(cause[0], cause[1])
1492 1496
1493 1497 # exclude frame where 'exc' was chained and rethrown from exctb
1494 1498 self.write_err('Traceback (most recent call last):\n',
1495 1499 ''.join(exctb[:-1]),
1496 1500 ''.join(causetb),
1497 1501 ''.join(exconly))
1498 1502 else:
1499 1503 output = traceback.format_exception(exc[0], exc[1], exc[2])
1500 1504 data = r''.join(output)
1501 1505 if pycompat.ispy3:
1502 1506 enc = pycompat.sysstr(encoding.encoding)
1503 1507 data = data.encode(enc, errors=r'replace')
1504 1508 self.write_err(data)
1505 1509 return self.tracebackflag or force
1506 1510
1507 1511 def geteditor(self):
1508 1512 '''return editor to use'''
1509 1513 if pycompat.sysplatform == 'plan9':
1510 1514 # vi is the MIPS instruction simulator on Plan 9. We
1511 1515 # instead default to E to plumb commit messages to
1512 1516 # avoid confusion.
1513 1517 editor = 'E'
1514 1518 else:
1515 1519 editor = 'vi'
1516 1520 return (encoding.environ.get("HGEDITOR") or
1517 1521 self.config("ui", "editor", editor))
1518 1522
1519 1523 @util.propertycache
1520 1524 def _progbar(self):
1521 1525 """setup the progbar singleton to the ui object"""
1522 1526 if (self.quiet or self.debugflag
1523 1527 or self.configbool('progress', 'disable')
1524 1528 or not progress.shouldprint(self)):
1525 1529 return None
1526 1530 return getprogbar(self)
1527 1531
1528 1532 def _progclear(self):
1529 1533 """clear progress bar output if any. use it before any output"""
1530 1534 if not haveprogbar(): # nothing loaded yet
1531 1535 return
1532 1536 if self._progbar is not None and self._progbar.printed:
1533 1537 self._progbar.clear()
1534 1538
1535 1539 def progress(self, topic, pos, item="", unit="", total=None):
1536 1540 '''show a progress message
1537 1541
1538 1542 By default a textual progress bar will be displayed if an operation
1539 1543 takes too long. 'topic' is the current operation, 'item' is a
1540 1544 non-numeric marker of the current position (i.e. the currently
1541 1545 in-process file), 'pos' is the current numeric position (i.e.
1542 1546 revision, bytes, etc.), unit is a corresponding unit label,
1543 1547 and total is the highest expected pos.
1544 1548
1545 1549 Multiple nested topics may be active at a time.
1546 1550
1547 1551 All topics should be marked closed by setting pos to None at
1548 1552 termination.
1549 1553 '''
1550 1554 if self._progbar is not None:
1551 1555 self._progbar.progress(topic, pos, item=item, unit=unit,
1552 1556 total=total)
1553 1557 if pos is None or not self.configbool('progress', 'debug'):
1554 1558 return
1555 1559
1556 1560 if unit:
1557 1561 unit = ' ' + unit
1558 1562 if item:
1559 1563 item = ' ' + item
1560 1564
1561 1565 if total:
1562 1566 pct = 100.0 * pos / total
1563 1567 self.debug('%s:%s %d/%d%s (%4.2f%%)\n'
1564 1568 % (topic, item, pos, total, unit, pct))
1565 1569 else:
1566 1570 self.debug('%s:%s %d%s\n' % (topic, item, pos, unit))
1567 1571
1568 1572 def log(self, service, *msg, **opts):
1569 1573 '''hook for logging facility extensions
1570 1574
1571 1575 service should be a readily-identifiable subsystem, which will
1572 1576 allow filtering.
1573 1577
1574 1578 *msg should be a newline-terminated format string to log, and
1575 1579 then any values to %-format into that format string.
1576 1580
1577 1581 **opts currently has no defined meanings.
1578 1582 '''
1579 1583
1580 1584 def label(self, msg, label):
1581 1585 '''style msg based on supplied label
1582 1586
1583 1587 If some color mode is enabled, this will add the necessary control
1584 1588 characters to apply such color. In addition, 'debug' color mode adds
1585 1589 markup showing which label affects a piece of text.
1586 1590
1587 1591 ui.write(s, 'label') is equivalent to
1588 1592 ui.write(ui.label(s, 'label')).
1589 1593 '''
1590 1594 if self._colormode is not None:
1591 1595 return color.colorlabel(self, msg, label)
1592 1596 return msg
1593 1597
1594 1598 def develwarn(self, msg, stacklevel=1, config=None):
1595 1599 """issue a developer warning message
1596 1600
1597 1601 Use 'stacklevel' to report the offender some layers further up in the
1598 1602 stack.
1599 1603 """
1600 1604 if not self.configbool('devel', 'all-warnings'):
1601 1605 if config is not None and not self.configbool('devel', config):
1602 1606 return
1603 1607 msg = 'devel-warn: ' + msg
1604 1608 stacklevel += 1 # get in develwarn
1605 1609 if self.tracebackflag:
1606 1610 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1607 1611 self.log('develwarn', '%s at:\n%s' %
1608 1612 (msg, ''.join(util.getstackframes(stacklevel))))
1609 1613 else:
1610 1614 curframe = inspect.currentframe()
1611 1615 calframe = inspect.getouterframes(curframe, 2)
1612 1616 self.write_err('%s at: %s:%s (%s)\n'
1613 1617 % ((msg,) + calframe[stacklevel][1:4]))
1614 1618 self.log('develwarn', '%s at: %s:%s (%s)\n',
1615 1619 msg, *calframe[stacklevel][1:4])
1616 1620 curframe = calframe = None # avoid cycles
1617 1621
1618 1622 def deprecwarn(self, msg, version):
1619 1623 """issue a deprecation warning
1620 1624
1621 1625 - msg: message explaining what is deprecated and how to upgrade,
1622 1626 - version: last version where the API will be supported,
1623 1627 """
1624 1628 if not (self.configbool('devel', 'all-warnings')
1625 1629 or self.configbool('devel', 'deprec-warn')):
1626 1630 return
1627 1631 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1628 1632 " update your code.)") % version
1629 1633 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1630 1634
1631 1635 def exportableenviron(self):
1632 1636 """The environment variables that are safe to export, e.g. through
1633 1637 hgweb.
1634 1638 """
1635 1639 return self._exportableenviron
1636 1640
1637 1641 @contextlib.contextmanager
1638 1642 def configoverride(self, overrides, source=""):
1639 1643 """Context manager for temporary config overrides
1640 1644 `overrides` must be a dict of the following structure:
1641 1645 {(section, name) : value}"""
1642 1646 backups = {}
1643 1647 try:
1644 1648 for (section, name), value in overrides.items():
1645 1649 backups[(section, name)] = self.backupconfig(section, name)
1646 1650 self.setconfig(section, name, value, source)
1647 1651 yield
1648 1652 finally:
1649 1653 for __, backup in backups.items():
1650 1654 self.restoreconfig(backup)
1651 1655 # just restoring ui.quiet config to the previous value is not enough
1652 1656 # as it does not update ui.quiet class member
1653 1657 if ('ui', 'quiet') in overrides:
1654 1658 self.fixconfig(section='ui')
1655 1659
1656 1660 class paths(dict):
1657 1661 """Represents a collection of paths and their configs.
1658 1662
1659 1663 Data is initially derived from ui instances and the config files they have
1660 1664 loaded.
1661 1665 """
1662 1666 def __init__(self, ui):
1663 1667 dict.__init__(self)
1664 1668
1665 1669 for name, loc in ui.configitems('paths', ignoresub=True):
1666 1670 # No location is the same as not existing.
1667 1671 if not loc:
1668 1672 continue
1669 1673 loc, sub = ui.configsuboptions('paths', name)
1670 1674 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1671 1675
1672 1676 def getpath(self, name, default=None):
1673 1677 """Return a ``path`` from a string, falling back to default.
1674 1678
1675 1679 ``name`` can be a named path or locations. Locations are filesystem
1676 1680 paths or URIs.
1677 1681
1678 1682 Returns None if ``name`` is not a registered path, a URI, or a local
1679 1683 path to a repo.
1680 1684 """
1681 1685 # Only fall back to default if no path was requested.
1682 1686 if name is None:
1683 1687 if not default:
1684 1688 default = ()
1685 1689 elif not isinstance(default, (tuple, list)):
1686 1690 default = (default,)
1687 1691 for k in default:
1688 1692 try:
1689 1693 return self[k]
1690 1694 except KeyError:
1691 1695 continue
1692 1696 return None
1693 1697
1694 1698 # Most likely empty string.
1695 1699 # This may need to raise in the future.
1696 1700 if not name:
1697 1701 return None
1698 1702
1699 1703 try:
1700 1704 return self[name]
1701 1705 except KeyError:
1702 1706 # Try to resolve as a local path or URI.
1703 1707 try:
1704 1708 # We don't pass sub-options in, so no need to pass ui instance.
1705 1709 return path(None, None, rawloc=name)
1706 1710 except ValueError:
1707 1711 raise error.RepoError(_('repository %s does not exist') %
1708 1712 name)
1709 1713
1710 1714 _pathsuboptions = {}
1711 1715
1712 1716 def pathsuboption(option, attr):
1713 1717 """Decorator used to declare a path sub-option.
1714 1718
1715 1719 Arguments are the sub-option name and the attribute it should set on
1716 1720 ``path`` instances.
1717 1721
1718 1722 The decorated function will receive as arguments a ``ui`` instance,
1719 1723 ``path`` instance, and the string value of this option from the config.
1720 1724 The function should return the value that will be set on the ``path``
1721 1725 instance.
1722 1726
1723 1727 This decorator can be used to perform additional verification of
1724 1728 sub-options and to change the type of sub-options.
1725 1729 """
1726 1730 def register(func):
1727 1731 _pathsuboptions[option] = (attr, func)
1728 1732 return func
1729 1733 return register
1730 1734
1731 1735 @pathsuboption('pushurl', 'pushloc')
1732 1736 def pushurlpathoption(ui, path, value):
1733 1737 u = util.url(value)
1734 1738 # Actually require a URL.
1735 1739 if not u.scheme:
1736 1740 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1737 1741 return None
1738 1742
1739 1743 # Don't support the #foo syntax in the push URL to declare branch to
1740 1744 # push.
1741 1745 if u.fragment:
1742 1746 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1743 1747 'ignoring)\n') % path.name)
1744 1748 u.fragment = None
1745 1749
1746 1750 return str(u)
1747 1751
1748 1752 @pathsuboption('pushrev', 'pushrev')
1749 1753 def pushrevpathoption(ui, path, value):
1750 1754 return value
1751 1755
1752 1756 class path(object):
1753 1757 """Represents an individual path and its configuration."""
1754 1758
1755 1759 def __init__(self, ui, name, rawloc=None, suboptions=None):
1756 1760 """Construct a path from its config options.
1757 1761
1758 1762 ``ui`` is the ``ui`` instance the path is coming from.
1759 1763 ``name`` is the symbolic name of the path.
1760 1764 ``rawloc`` is the raw location, as defined in the config.
1761 1765 ``pushloc`` is the raw locations pushes should be made to.
1762 1766
1763 1767 If ``name`` is not defined, we require that the location be a) a local
1764 1768 filesystem path with a .hg directory or b) a URL. If not,
1765 1769 ``ValueError`` is raised.
1766 1770 """
1767 1771 if not rawloc:
1768 1772 raise ValueError('rawloc must be defined')
1769 1773
1770 1774 # Locations may define branches via syntax <base>#<branch>.
1771 1775 u = util.url(rawloc)
1772 1776 branch = None
1773 1777 if u.fragment:
1774 1778 branch = u.fragment
1775 1779 u.fragment = None
1776 1780
1777 1781 self.url = u
1778 1782 self.branch = branch
1779 1783
1780 1784 self.name = name
1781 1785 self.rawloc = rawloc
1782 1786 self.loc = '%s' % u
1783 1787
1784 1788 # When given a raw location but not a symbolic name, validate the
1785 1789 # location is valid.
1786 1790 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1787 1791 raise ValueError('location is not a URL or path to a local '
1788 1792 'repo: %s' % rawloc)
1789 1793
1790 1794 suboptions = suboptions or {}
1791 1795
1792 1796 # Now process the sub-options. If a sub-option is registered, its
1793 1797 # attribute will always be present. The value will be None if there
1794 1798 # was no valid sub-option.
1795 1799 for suboption, (attr, func) in _pathsuboptions.iteritems():
1796 1800 if suboption not in suboptions:
1797 1801 setattr(self, attr, None)
1798 1802 continue
1799 1803
1800 1804 value = func(ui, self, suboptions[suboption])
1801 1805 setattr(self, attr, value)
1802 1806
1803 1807 def _isvalidlocalpath(self, path):
1804 1808 """Returns True if the given path is a potentially valid repository.
1805 1809 This is its own function so that extensions can change the definition of
1806 1810 'valid' in this case (like when pulling from a git repo into a hg
1807 1811 one)."""
1808 1812 return os.path.isdir(os.path.join(path, '.hg'))
1809 1813
1810 1814 @property
1811 1815 def suboptions(self):
1812 1816 """Return sub-options and their values for this path.
1813 1817
1814 1818 This is intended to be used for presentation purposes.
1815 1819 """
1816 1820 d = {}
1817 1821 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1818 1822 value = getattr(self, attr)
1819 1823 if value is not None:
1820 1824 d[subopt] = value
1821 1825 return d
1822 1826
1823 1827 # we instantiate one globally shared progress bar to avoid
1824 1828 # competing progress bars when multiple UI objects get created
1825 1829 _progresssingleton = None
1826 1830
1827 1831 def getprogbar(ui):
1828 1832 global _progresssingleton
1829 1833 if _progresssingleton is None:
1830 1834 # passing 'ui' object to the singleton is fishy,
1831 1835 # this is how the extension used to work but feel free to rework it.
1832 1836 _progresssingleton = progress.progbar(ui)
1833 1837 return _progresssingleton
1834 1838
1835 1839 def haveprogbar():
1836 1840 return _progresssingleton is not None
@@ -1,1009 +1,1023
1 1 #if windows
2 2 $ PYTHONPATH="$TESTDIR/../contrib;$PYTHONPATH"
3 3 #else
4 4 $ PYTHONPATH="$TESTDIR/../contrib:$PYTHONPATH"
5 5 #endif
6 6 $ export PYTHONPATH
7 7
8 8 typical client does not want echo-back messages, so test without it:
9 9
10 10 $ grep -v '^promptecho ' < $HGRCPATH >> $HGRCPATH.new
11 11 $ mv $HGRCPATH.new $HGRCPATH
12 12
13 13 $ hg init repo
14 14 $ cd repo
15 15
16 16 >>> from __future__ import absolute_import, print_function
17 17 >>> import os
18 18 >>> import sys
19 19 >>> from hgclient import check, readchannel, runcommand
20 20 >>> @check
21 21 ... def hellomessage(server):
22 22 ... ch, data = readchannel(server)
23 23 ... print('%c, %r' % (ch, data))
24 24 ... # run an arbitrary command to make sure the next thing the server
25 25 ... # sends isn't part of the hello message
26 26 ... runcommand(server, ['id'])
27 27 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
28 28 *** runcommand id
29 29 000000000000 tip
30 30
31 31 >>> from hgclient import check
32 32 >>> @check
33 33 ... def unknowncommand(server):
34 34 ... server.stdin.write('unknowncommand\n')
35 35 abort: unknown command unknowncommand
36 36
37 37 >>> from hgclient import check, readchannel, runcommand
38 38 >>> @check
39 39 ... def checkruncommand(server):
40 40 ... # hello block
41 41 ... readchannel(server)
42 42 ...
43 43 ... # no args
44 44 ... runcommand(server, [])
45 45 ...
46 46 ... # global options
47 47 ... runcommand(server, ['id', '--quiet'])
48 48 ...
49 49 ... # make sure global options don't stick through requests
50 50 ... runcommand(server, ['id'])
51 51 ...
52 52 ... # --config
53 53 ... runcommand(server, ['id', '--config', 'ui.quiet=True'])
54 54 ...
55 55 ... # make sure --config doesn't stick
56 56 ... runcommand(server, ['id'])
57 57 ...
58 58 ... # negative return code should be masked
59 59 ... runcommand(server, ['id', '-runknown'])
60 60 *** runcommand
61 61 Mercurial Distributed SCM
62 62
63 63 basic commands:
64 64
65 65 add add the specified files on the next commit
66 66 annotate show changeset information by line for each file
67 67 clone make a copy of an existing repository
68 68 commit commit the specified files or all outstanding changes
69 69 diff diff repository (or selected files)
70 70 export dump the header and diffs for one or more changesets
71 71 forget forget the specified files on the next commit
72 72 init create a new repository in the given directory
73 73 log show revision history of entire repository or files
74 74 merge merge another revision into working directory
75 75 pull pull changes from the specified source
76 76 push push changes to the specified destination
77 77 remove remove the specified files on the next commit
78 78 serve start stand-alone webserver
79 79 status show changed files in the working directory
80 80 summary summarize working directory state
81 81 update update working directory (or switch revisions)
82 82
83 83 (use 'hg help' for the full list of commands or 'hg -v' for details)
84 84 *** runcommand id --quiet
85 85 000000000000
86 86 *** runcommand id
87 87 000000000000 tip
88 88 *** runcommand id --config ui.quiet=True
89 89 000000000000
90 90 *** runcommand id
91 91 000000000000 tip
92 92 *** runcommand id -runknown
93 93 abort: unknown revision 'unknown'!
94 94 [255]
95 95
96 96 >>> from hgclient import check, readchannel
97 97 >>> @check
98 98 ... def inputeof(server):
99 99 ... readchannel(server)
100 100 ... server.stdin.write('runcommand\n')
101 101 ... # close stdin while server is waiting for input
102 102 ... server.stdin.close()
103 103 ...
104 104 ... # server exits with 1 if the pipe closed while reading the command
105 105 ... print('server exit code =', server.wait())
106 106 server exit code = 1
107 107
108 108 >>> from hgclient import check, readchannel, runcommand, stringio
109 109 >>> @check
110 110 ... def serverinput(server):
111 111 ... readchannel(server)
112 112 ...
113 113 ... patch = """
114 114 ... # HG changeset patch
115 115 ... # User test
116 116 ... # Date 0 0
117 117 ... # Node ID c103a3dec114d882c98382d684d8af798d09d857
118 118 ... # Parent 0000000000000000000000000000000000000000
119 119 ... 1
120 120 ...
121 121 ... diff -r 000000000000 -r c103a3dec114 a
122 122 ... --- /dev/null Thu Jan 01 00:00:00 1970 +0000
123 123 ... +++ b/a Thu Jan 01 00:00:00 1970 +0000
124 124 ... @@ -0,0 +1,1 @@
125 125 ... +1
126 126 ... """
127 127 ...
128 128 ... runcommand(server, ['import', '-'], input=stringio(patch))
129 129 ... runcommand(server, ['log'])
130 130 *** runcommand import -
131 131 applying patch from stdin
132 132 *** runcommand log
133 133 changeset: 0:eff892de26ec
134 134 tag: tip
135 135 user: test
136 136 date: Thu Jan 01 00:00:00 1970 +0000
137 137 summary: 1
138 138
139 139
140 check strict parsing of early options:
141
142 >>> import os
143 >>> from hgclient import check, readchannel, runcommand
144 >>> os.environ['HGPLAIN'] = '+strictflags'
145 >>> @check
146 ... def cwd(server):
147 ... readchannel(server)
148 ... runcommand(server, ['log', '-b', '--config=alias.log=!echo pwned',
149 ... 'default'])
150 *** runcommand log -b --config=alias.log=!echo pwned default
151 abort: unknown revision '--config=alias.log=!echo pwned'!
152 [255]
153
140 154 check that "histedit --commands=-" can read rules from the input channel:
141 155
142 156 >>> import cStringIO
143 157 >>> from hgclient import check, readchannel, runcommand
144 158 >>> @check
145 159 ... def serverinput(server):
146 160 ... readchannel(server)
147 161 ... rules = 'pick eff892de26ec\n'
148 162 ... runcommand(server, ['histedit', '0', '--commands=-',
149 163 ... '--config', 'extensions.histedit='],
150 164 ... input=cStringIO.StringIO(rules))
151 165 *** runcommand histedit 0 --commands=- --config extensions.histedit=
152 166
153 167 check that --cwd doesn't persist between requests:
154 168
155 169 $ mkdir foo
156 170 $ touch foo/bar
157 171 >>> from hgclient import check, readchannel, runcommand
158 172 >>> @check
159 173 ... def cwd(server):
160 174 ... readchannel(server)
161 175 ... runcommand(server, ['--cwd', 'foo', 'st', 'bar'])
162 176 ... runcommand(server, ['st', 'foo/bar'])
163 177 *** runcommand --cwd foo st bar
164 178 ? bar
165 179 *** runcommand st foo/bar
166 180 ? foo/bar
167 181
168 182 $ rm foo/bar
169 183
170 184
171 185 check that local configs for the cached repo aren't inherited when -R is used:
172 186
173 187 $ cat <<EOF >> .hg/hgrc
174 188 > [ui]
175 189 > foo = bar
176 190 > EOF
177 191
178 192 >>> from hgclient import check, readchannel, runcommand, sep
179 193 >>> @check
180 194 ... def localhgrc(server):
181 195 ... readchannel(server)
182 196 ...
183 197 ... # the cached repo local hgrc contains ui.foo=bar, so showconfig should
184 198 ... # show it
185 199 ... runcommand(server, ['showconfig'], outfilter=sep)
186 200 ...
187 201 ... # but not for this repo
188 202 ... runcommand(server, ['init', 'foo'])
189 203 ... runcommand(server, ['-R', 'foo', 'showconfig', 'ui', 'defaults'])
190 204 *** runcommand showconfig
191 205 bundle.mainreporoot=$TESTTMP/repo
192 206 devel.all-warnings=true
193 207 devel.default-date=0 0
194 208 extensions.fsmonitor= (fsmonitor !)
195 209 largefiles.usercache=$TESTTMP/.cache/largefiles
196 210 ui.slash=True
197 211 ui.interactive=False
198 212 ui.mergemarkers=detailed
199 213 ui.usehttp2=true (?)
200 214 ui.foo=bar
201 215 ui.nontty=true
202 216 web.address=localhost
203 217 web\.ipv6=(?:True|False) (re)
204 218 *** runcommand init foo
205 219 *** runcommand -R foo showconfig ui defaults
206 220 ui.slash=True
207 221 ui.interactive=False
208 222 ui.mergemarkers=detailed
209 223 ui.usehttp2=true (?)
210 224 ui.nontty=true
211 225
212 226 $ rm -R foo
213 227
214 228 #if windows
215 229 $ PYTHONPATH="$TESTTMP/repo;$PYTHONPATH"
216 230 #else
217 231 $ PYTHONPATH="$TESTTMP/repo:$PYTHONPATH"
218 232 #endif
219 233
220 234 $ cat <<EOF > hook.py
221 235 > from __future__ import print_function
222 236 > import sys
223 237 > def hook(**args):
224 238 > print('hook talking')
225 239 > print('now try to read something: %r' % sys.stdin.read())
226 240 > EOF
227 241
228 242 >>> from hgclient import check, readchannel, runcommand, stringio
229 243 >>> @check
230 244 ... def hookoutput(server):
231 245 ... readchannel(server)
232 246 ... runcommand(server, ['--config',
233 247 ... 'hooks.pre-identify=python:hook.hook',
234 248 ... 'id'],
235 249 ... input=stringio('some input'))
236 250 *** runcommand --config hooks.pre-identify=python:hook.hook id
237 251 eff892de26ec tip
238 252
239 253 Clean hook cached version
240 254 $ rm hook.py*
241 255 $ rm -Rf __pycache__
242 256
243 257 $ echo a >> a
244 258 >>> import os
245 259 >>> from hgclient import check, readchannel, runcommand
246 260 >>> @check
247 261 ... def outsidechanges(server):
248 262 ... readchannel(server)
249 263 ... runcommand(server, ['status'])
250 264 ... os.system('hg ci -Am2')
251 265 ... runcommand(server, ['tip'])
252 266 ... runcommand(server, ['status'])
253 267 *** runcommand status
254 268 M a
255 269 *** runcommand tip
256 270 changeset: 1:d3a0a68be6de
257 271 tag: tip
258 272 user: test
259 273 date: Thu Jan 01 00:00:00 1970 +0000
260 274 summary: 2
261 275
262 276 *** runcommand status
263 277
264 278 >>> import os
265 279 >>> from hgclient import check, readchannel, runcommand
266 280 >>> @check
267 281 ... def bookmarks(server):
268 282 ... readchannel(server)
269 283 ... runcommand(server, ['bookmarks'])
270 284 ...
271 285 ... # changes .hg/bookmarks
272 286 ... os.system('hg bookmark -i bm1')
273 287 ... os.system('hg bookmark -i bm2')
274 288 ... runcommand(server, ['bookmarks'])
275 289 ...
276 290 ... # changes .hg/bookmarks.current
277 291 ... os.system('hg upd bm1 -q')
278 292 ... runcommand(server, ['bookmarks'])
279 293 ...
280 294 ... runcommand(server, ['bookmarks', 'bm3'])
281 295 ... f = open('a', 'ab')
282 296 ... f.write('a\n')
283 297 ... f.close()
284 298 ... runcommand(server, ['commit', '-Amm'])
285 299 ... runcommand(server, ['bookmarks'])
286 300 ... print('')
287 301 *** runcommand bookmarks
288 302 no bookmarks set
289 303 *** runcommand bookmarks
290 304 bm1 1:d3a0a68be6de
291 305 bm2 1:d3a0a68be6de
292 306 *** runcommand bookmarks
293 307 * bm1 1:d3a0a68be6de
294 308 bm2 1:d3a0a68be6de
295 309 *** runcommand bookmarks bm3
296 310 *** runcommand commit -Amm
297 311 *** runcommand bookmarks
298 312 bm1 1:d3a0a68be6de
299 313 bm2 1:d3a0a68be6de
300 314 * bm3 2:aef17e88f5f0
301 315
302 316
303 317 >>> import os
304 318 >>> from hgclient import check, readchannel, runcommand
305 319 >>> @check
306 320 ... def tagscache(server):
307 321 ... readchannel(server)
308 322 ... runcommand(server, ['id', '-t', '-r', '0'])
309 323 ... os.system('hg tag -r 0 foo')
310 324 ... runcommand(server, ['id', '-t', '-r', '0'])
311 325 *** runcommand id -t -r 0
312 326
313 327 *** runcommand id -t -r 0
314 328 foo
315 329
316 330 >>> import os
317 331 >>> from hgclient import check, readchannel, runcommand
318 332 >>> @check
319 333 ... def setphase(server):
320 334 ... readchannel(server)
321 335 ... runcommand(server, ['phase', '-r', '.'])
322 336 ... os.system('hg phase -r . -p')
323 337 ... runcommand(server, ['phase', '-r', '.'])
324 338 *** runcommand phase -r .
325 339 3: draft
326 340 *** runcommand phase -r .
327 341 3: public
328 342
329 343 $ echo a >> a
330 344 >>> from hgclient import check, readchannel, runcommand
331 345 >>> @check
332 346 ... def rollback(server):
333 347 ... readchannel(server)
334 348 ... runcommand(server, ['phase', '-r', '.', '-p'])
335 349 ... runcommand(server, ['commit', '-Am.'])
336 350 ... runcommand(server, ['rollback'])
337 351 ... runcommand(server, ['phase', '-r', '.'])
338 352 ... print('')
339 353 *** runcommand phase -r . -p
340 354 no phases changed
341 355 *** runcommand commit -Am.
342 356 *** runcommand rollback
343 357 repository tip rolled back to revision 3 (undo commit)
344 358 working directory now based on revision 3
345 359 *** runcommand phase -r .
346 360 3: public
347 361
348 362
349 363 >>> import os
350 364 >>> from hgclient import check, readchannel, runcommand
351 365 >>> @check
352 366 ... def branch(server):
353 367 ... readchannel(server)
354 368 ... runcommand(server, ['branch'])
355 369 ... os.system('hg branch foo')
356 370 ... runcommand(server, ['branch'])
357 371 ... os.system('hg branch default')
358 372 *** runcommand branch
359 373 default
360 374 marked working directory as branch foo
361 375 (branches are permanent and global, did you want a bookmark?)
362 376 *** runcommand branch
363 377 foo
364 378 marked working directory as branch default
365 379 (branches are permanent and global, did you want a bookmark?)
366 380
367 381 $ touch .hgignore
368 382 >>> import os
369 383 >>> from hgclient import check, readchannel, runcommand
370 384 >>> @check
371 385 ... def hgignore(server):
372 386 ... readchannel(server)
373 387 ... runcommand(server, ['commit', '-Am.'])
374 388 ... f = open('ignored-file', 'ab')
375 389 ... f.write('')
376 390 ... f.close()
377 391 ... f = open('.hgignore', 'ab')
378 392 ... f.write('ignored-file')
379 393 ... f.close()
380 394 ... runcommand(server, ['status', '-i', '-u'])
381 395 ... print('')
382 396 *** runcommand commit -Am.
383 397 adding .hgignore
384 398 *** runcommand status -i -u
385 399 I ignored-file
386 400
387 401
388 402 cache of non-public revisions should be invalidated on repository change
389 403 (issue4855):
390 404
391 405 >>> import os
392 406 >>> from hgclient import check, readchannel, runcommand
393 407 >>> @check
394 408 ... def phasesetscacheaftercommit(server):
395 409 ... readchannel(server)
396 410 ... # load _phasecache._phaserevs and _phasesets
397 411 ... runcommand(server, ['log', '-qr', 'draft()'])
398 412 ... # create draft commits by another process
399 413 ... for i in xrange(5, 7):
400 414 ... f = open('a', 'ab')
401 415 ... f.seek(0, os.SEEK_END)
402 416 ... f.write('a\n')
403 417 ... f.close()
404 418 ... os.system('hg commit -Aqm%d' % i)
405 419 ... # new commits should be listed as draft revisions
406 420 ... runcommand(server, ['log', '-qr', 'draft()'])
407 421 ... print('')
408 422 *** runcommand log -qr draft()
409 423 4:7966c8e3734d
410 424 *** runcommand log -qr draft()
411 425 4:7966c8e3734d
412 426 5:41f6602d1c4f
413 427 6:10501e202c35
414 428
415 429
416 430 >>> import os
417 431 >>> from hgclient import check, readchannel, runcommand
418 432 >>> @check
419 433 ... def phasesetscacheafterstrip(server):
420 434 ... readchannel(server)
421 435 ... # load _phasecache._phaserevs and _phasesets
422 436 ... runcommand(server, ['log', '-qr', 'draft()'])
423 437 ... # strip cached revisions by another process
424 438 ... os.system('hg --config extensions.strip= strip -q 5')
425 439 ... # shouldn't abort by "unknown revision '6'"
426 440 ... runcommand(server, ['log', '-qr', 'draft()'])
427 441 ... print('')
428 442 *** runcommand log -qr draft()
429 443 4:7966c8e3734d
430 444 5:41f6602d1c4f
431 445 6:10501e202c35
432 446 *** runcommand log -qr draft()
433 447 4:7966c8e3734d
434 448
435 449
436 450 cache of phase roots should be invalidated on strip (issue3827):
437 451
438 452 >>> import os
439 453 >>> from hgclient import check, readchannel, runcommand, sep
440 454 >>> @check
441 455 ... def phasecacheafterstrip(server):
442 456 ... readchannel(server)
443 457 ...
444 458 ... # create new head, 5:731265503d86
445 459 ... runcommand(server, ['update', '-C', '0'])
446 460 ... f = open('a', 'ab')
447 461 ... f.write('a\n')
448 462 ... f.close()
449 463 ... runcommand(server, ['commit', '-Am.', 'a'])
450 464 ... runcommand(server, ['log', '-Gq'])
451 465 ...
452 466 ... # make it public; draft marker moves to 4:7966c8e3734d
453 467 ... runcommand(server, ['phase', '-p', '.'])
454 468 ... # load _phasecache.phaseroots
455 469 ... runcommand(server, ['phase', '.'], outfilter=sep)
456 470 ...
457 471 ... # strip 1::4 outside server
458 472 ... os.system('hg -q --config extensions.mq= strip 1')
459 473 ...
460 474 ... # shouldn't raise "7966c8e3734d: no node!"
461 475 ... runcommand(server, ['branches'])
462 476 *** runcommand update -C 0
463 477 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
464 478 (leaving bookmark bm3)
465 479 *** runcommand commit -Am. a
466 480 created new head
467 481 *** runcommand log -Gq
468 482 @ 5:731265503d86
469 483 |
470 484 | o 4:7966c8e3734d
471 485 | |
472 486 | o 3:b9b85890c400
473 487 | |
474 488 | o 2:aef17e88f5f0
475 489 | |
476 490 | o 1:d3a0a68be6de
477 491 |/
478 492 o 0:eff892de26ec
479 493
480 494 *** runcommand phase -p .
481 495 *** runcommand phase .
482 496 5: public
483 497 *** runcommand branches
484 498 default 1:731265503d86
485 499
486 500 in-memory cache must be reloaded if transaction is aborted. otherwise
487 501 changelog and manifest would have invalid node:
488 502
489 503 $ echo a >> a
490 504 >>> from hgclient import check, readchannel, runcommand
491 505 >>> @check
492 506 ... def txabort(server):
493 507 ... readchannel(server)
494 508 ... runcommand(server, ['commit', '--config', 'hooks.pretxncommit=false',
495 509 ... '-mfoo'])
496 510 ... runcommand(server, ['verify'])
497 511 *** runcommand commit --config hooks.pretxncommit=false -mfoo
498 512 transaction abort!
499 513 rollback completed
500 514 abort: pretxncommit hook exited with status 1
501 515 [255]
502 516 *** runcommand verify
503 517 checking changesets
504 518 checking manifests
505 519 crosschecking files in changesets and manifests
506 520 checking files
507 521 1 files, 2 changesets, 2 total revisions
508 522 $ hg revert --no-backup -aq
509 523
510 524 $ cat >> .hg/hgrc << EOF
511 525 > [experimental]
512 526 > evolution.createmarkers=True
513 527 > EOF
514 528
515 529 >>> import os
516 530 >>> from hgclient import check, readchannel, runcommand
517 531 >>> @check
518 532 ... def obsolete(server):
519 533 ... readchannel(server)
520 534 ...
521 535 ... runcommand(server, ['up', 'null'])
522 536 ... runcommand(server, ['phase', '-df', 'tip'])
523 537 ... cmd = 'hg debugobsolete `hg log -r tip --template {node}`'
524 538 ... if os.name == 'nt':
525 539 ... cmd = 'sh -c "%s"' % cmd # run in sh, not cmd.exe
526 540 ... os.system(cmd)
527 541 ... runcommand(server, ['log', '--hidden'])
528 542 ... runcommand(server, ['log'])
529 543 *** runcommand up null
530 544 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
531 545 *** runcommand phase -df tip
532 546 obsoleted 1 changesets
533 547 *** runcommand log --hidden
534 548 changeset: 1:731265503d86
535 549 tag: tip
536 550 user: test
537 551 date: Thu Jan 01 00:00:00 1970 +0000
538 552 obsolete: pruned
539 553 summary: .
540 554
541 555 changeset: 0:eff892de26ec
542 556 bookmark: bm1
543 557 bookmark: bm2
544 558 bookmark: bm3
545 559 user: test
546 560 date: Thu Jan 01 00:00:00 1970 +0000
547 561 summary: 1
548 562
549 563 *** runcommand log
550 564 changeset: 0:eff892de26ec
551 565 bookmark: bm1
552 566 bookmark: bm2
553 567 bookmark: bm3
554 568 tag: tip
555 569 user: test
556 570 date: Thu Jan 01 00:00:00 1970 +0000
557 571 summary: 1
558 572
559 573
560 574 $ cat <<EOF >> .hg/hgrc
561 575 > [extensions]
562 576 > mq =
563 577 > EOF
564 578
565 579 >>> import os
566 580 >>> from hgclient import check, readchannel, runcommand
567 581 >>> @check
568 582 ... def mqoutsidechanges(server):
569 583 ... readchannel(server)
570 584 ...
571 585 ... # load repo.mq
572 586 ... runcommand(server, ['qapplied'])
573 587 ... os.system('hg qnew 0.diff')
574 588 ... # repo.mq should be invalidated
575 589 ... runcommand(server, ['qapplied'])
576 590 ...
577 591 ... runcommand(server, ['qpop', '--all'])
578 592 ... os.system('hg qqueue --create foo')
579 593 ... # repo.mq should be recreated to point to new queue
580 594 ... runcommand(server, ['qqueue', '--active'])
581 595 *** runcommand qapplied
582 596 *** runcommand qapplied
583 597 0.diff
584 598 *** runcommand qpop --all
585 599 popping 0.diff
586 600 patch queue now empty
587 601 *** runcommand qqueue --active
588 602 foo
589 603
590 604 $ cat <<EOF > dbgui.py
591 605 > import os
592 606 > import sys
593 607 > from mercurial import commands, registrar
594 608 > cmdtable = {}
595 609 > command = registrar.command(cmdtable)
596 610 > @command(b"debuggetpass", norepo=True)
597 611 > def debuggetpass(ui):
598 612 > ui.write("%s\\n" % ui.getpass())
599 613 > @command(b"debugprompt", norepo=True)
600 614 > def debugprompt(ui):
601 615 > ui.write("%s\\n" % ui.prompt("prompt:"))
602 616 > @command(b"debugreadstdin", norepo=True)
603 617 > def debugreadstdin(ui):
604 618 > ui.write("read: %r\n" % sys.stdin.read(1))
605 619 > @command(b"debugwritestdout", norepo=True)
606 620 > def debugwritestdout(ui):
607 621 > os.write(1, "low-level stdout fd and\n")
608 622 > sys.stdout.write("stdout should be redirected to /dev/null\n")
609 623 > sys.stdout.flush()
610 624 > EOF
611 625 $ cat <<EOF >> .hg/hgrc
612 626 > [extensions]
613 627 > dbgui = dbgui.py
614 628 > EOF
615 629
616 630 >>> from hgclient import check, readchannel, runcommand, stringio
617 631 >>> @check
618 632 ... def getpass(server):
619 633 ... readchannel(server)
620 634 ... runcommand(server, ['debuggetpass', '--config',
621 635 ... 'ui.interactive=True'],
622 636 ... input=stringio('1234\n'))
623 637 ... runcommand(server, ['debuggetpass', '--config',
624 638 ... 'ui.interactive=True'],
625 639 ... input=stringio('\n'))
626 640 ... runcommand(server, ['debuggetpass', '--config',
627 641 ... 'ui.interactive=True'],
628 642 ... input=stringio(''))
629 643 ... runcommand(server, ['debugprompt', '--config',
630 644 ... 'ui.interactive=True'],
631 645 ... input=stringio('5678\n'))
632 646 ... runcommand(server, ['debugreadstdin'])
633 647 ... runcommand(server, ['debugwritestdout'])
634 648 *** runcommand debuggetpass --config ui.interactive=True
635 649 password: 1234
636 650 *** runcommand debuggetpass --config ui.interactive=True
637 651 password:
638 652 *** runcommand debuggetpass --config ui.interactive=True
639 653 password: abort: response expected
640 654 [255]
641 655 *** runcommand debugprompt --config ui.interactive=True
642 656 prompt: 5678
643 657 *** runcommand debugreadstdin
644 658 read: ''
645 659 *** runcommand debugwritestdout
646 660
647 661
648 662 run commandserver in commandserver, which is silly but should work:
649 663
650 664 >>> from __future__ import print_function
651 665 >>> from hgclient import check, readchannel, runcommand, stringio
652 666 >>> @check
653 667 ... def nested(server):
654 668 ... print('%c, %r' % readchannel(server))
655 669 ... class nestedserver(object):
656 670 ... stdin = stringio('getencoding\n')
657 671 ... stdout = stringio()
658 672 ... runcommand(server, ['serve', '--cmdserver', 'pipe'],
659 673 ... output=nestedserver.stdout, input=nestedserver.stdin)
660 674 ... nestedserver.stdout.seek(0)
661 675 ... print('%c, %r' % readchannel(nestedserver)) # hello
662 676 ... print('%c, %r' % readchannel(nestedserver)) # getencoding
663 677 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
664 678 *** runcommand serve --cmdserver pipe
665 679 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
666 680 r, '*' (glob)
667 681
668 682
669 683 start without repository:
670 684
671 685 $ cd ..
672 686
673 687 >>> from __future__ import print_function
674 688 >>> from hgclient import check, readchannel, runcommand
675 689 >>> @check
676 690 ... def hellomessage(server):
677 691 ... ch, data = readchannel(server)
678 692 ... print('%c, %r' % (ch, data))
679 693 ... # run an arbitrary command to make sure the next thing the server
680 694 ... # sends isn't part of the hello message
681 695 ... runcommand(server, ['id'])
682 696 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
683 697 *** runcommand id
684 698 abort: there is no Mercurial repository here (.hg not found)
685 699 [255]
686 700
687 701 >>> from hgclient import check, readchannel, runcommand
688 702 >>> @check
689 703 ... def startwithoutrepo(server):
690 704 ... readchannel(server)
691 705 ... runcommand(server, ['init', 'repo2'])
692 706 ... runcommand(server, ['id', '-R', 'repo2'])
693 707 *** runcommand init repo2
694 708 *** runcommand id -R repo2
695 709 000000000000 tip
696 710
697 711
698 712 don't fall back to cwd if invalid -R path is specified (issue4805):
699 713
700 714 $ cd repo
701 715 $ hg serve --cmdserver pipe -R ../nonexistent
702 716 abort: repository ../nonexistent not found!
703 717 [255]
704 718 $ cd ..
705 719
706 720
707 721 unix domain socket:
708 722
709 723 $ cd repo
710 724 $ hg update -q
711 725
712 726 #if unix-socket unix-permissions
713 727
714 728 >>> from __future__ import print_function
715 729 >>> from hgclient import check, readchannel, runcommand, stringio, unixserver
716 730 >>> server = unixserver('.hg/server.sock', '.hg/server.log')
717 731 >>> def hellomessage(conn):
718 732 ... ch, data = readchannel(conn)
719 733 ... print('%c, %r' % (ch, data))
720 734 ... runcommand(conn, ['id'])
721 735 >>> check(hellomessage, server.connect)
722 736 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
723 737 *** runcommand id
724 738 eff892de26ec tip bm1/bm2/bm3
725 739 >>> def unknowncommand(conn):
726 740 ... readchannel(conn)
727 741 ... conn.stdin.write('unknowncommand\n')
728 742 >>> check(unknowncommand, server.connect) # error sent to server.log
729 743 >>> def serverinput(conn):
730 744 ... readchannel(conn)
731 745 ... patch = """
732 746 ... # HG changeset patch
733 747 ... # User test
734 748 ... # Date 0 0
735 749 ... 2
736 750 ...
737 751 ... diff -r eff892de26ec -r 1ed24be7e7a0 a
738 752 ... --- a/a
739 753 ... +++ b/a
740 754 ... @@ -1,1 +1,2 @@
741 755 ... 1
742 756 ... +2
743 757 ... """
744 758 ... runcommand(conn, ['import', '-'], input=stringio(patch))
745 759 ... runcommand(conn, ['log', '-rtip', '-q'])
746 760 >>> check(serverinput, server.connect)
747 761 *** runcommand import -
748 762 applying patch from stdin
749 763 *** runcommand log -rtip -q
750 764 2:1ed24be7e7a0
751 765 >>> server.shutdown()
752 766
753 767 $ cat .hg/server.log
754 768 listening at .hg/server.sock
755 769 abort: unknown command unknowncommand
756 770 killed!
757 771 $ rm .hg/server.log
758 772
759 773 if server crashed before hello, traceback will be sent to 'e' channel as
760 774 last ditch:
761 775
762 776 $ cat <<EOF >> .hg/hgrc
763 777 > [cmdserver]
764 778 > log = inexistent/path.log
765 779 > EOF
766 780 >>> from __future__ import print_function
767 781 >>> from hgclient import check, readchannel, unixserver
768 782 >>> server = unixserver('.hg/server.sock', '.hg/server.log')
769 783 >>> def earlycrash(conn):
770 784 ... while True:
771 785 ... try:
772 786 ... ch, data = readchannel(conn)
773 787 ... if not data.startswith(' '):
774 788 ... print('%c, %r' % (ch, data))
775 789 ... except EOFError:
776 790 ... break
777 791 >>> check(earlycrash, server.connect)
778 792 e, 'Traceback (most recent call last):\n'
779 793 e, "IOError: *" (glob)
780 794 >>> server.shutdown()
781 795
782 796 $ cat .hg/server.log | grep -v '^ '
783 797 listening at .hg/server.sock
784 798 Traceback (most recent call last):
785 799 IOError: * (glob)
786 800 killed!
787 801 #endif
788 802 #if no-unix-socket
789 803
790 804 $ hg serve --cmdserver unix -a .hg/server.sock
791 805 abort: unsupported platform
792 806 [255]
793 807
794 808 #endif
795 809
796 810 $ cd ..
797 811
798 812 Test that accessing to invalid changelog cache is avoided at
799 813 subsequent operations even if repo object is reused even after failure
800 814 of transaction (see 0a7610758c42 also)
801 815
802 816 "hg log" after failure of transaction is needed to detect invalid
803 817 cache in repoview: this can't detect by "hg verify" only.
804 818
805 819 Combination of "finalization" and "empty-ness of changelog" (2 x 2 =
806 820 4) are tested, because '00changelog.i' are differently changed in each
807 821 cases.
808 822
809 823 $ cat > $TESTTMP/failafterfinalize.py <<EOF
810 824 > # extension to abort transaction after finalization forcibly
811 825 > from mercurial import commands, error, extensions, lock as lockmod
812 826 > from mercurial import registrar
813 827 > cmdtable = {}
814 828 > command = registrar.command(cmdtable)
815 829 > configtable = {}
816 830 > configitem = registrar.configitem(configtable)
817 831 > configitem('failafterfinalize', 'fail',
818 832 > default=None,
819 833 > )
820 834 > def fail(tr):
821 835 > raise error.Abort('fail after finalization')
822 836 > def reposetup(ui, repo):
823 837 > class failrepo(repo.__class__):
824 838 > def commitctx(self, ctx, error=False):
825 839 > if self.ui.configbool('failafterfinalize', 'fail'):
826 840 > # 'sorted()' by ASCII code on category names causes
827 841 > # invoking 'fail' after finalization of changelog
828 842 > # using "'cl-%i' % id(self)" as category name
829 843 > self.currenttransaction().addfinalize('zzzzzzzz', fail)
830 844 > return super(failrepo, self).commitctx(ctx, error)
831 845 > repo.__class__ = failrepo
832 846 > EOF
833 847
834 848 $ hg init repo3
835 849 $ cd repo3
836 850
837 851 $ cat <<EOF >> $HGRCPATH
838 852 > [ui]
839 853 > logtemplate = {rev} {desc|firstline} ({files})\n
840 854 >
841 855 > [extensions]
842 856 > failafterfinalize = $TESTTMP/failafterfinalize.py
843 857 > EOF
844 858
845 859 - test failure with "empty changelog"
846 860
847 861 $ echo foo > foo
848 862 $ hg add foo
849 863
850 864 (failure before finalization)
851 865
852 866 >>> from hgclient import check, readchannel, runcommand
853 867 >>> @check
854 868 ... def abort(server):
855 869 ... readchannel(server)
856 870 ... runcommand(server, ['commit',
857 871 ... '--config', 'hooks.pretxncommit=false',
858 872 ... '-mfoo'])
859 873 ... runcommand(server, ['log'])
860 874 ... runcommand(server, ['verify', '-q'])
861 875 *** runcommand commit --config hooks.pretxncommit=false -mfoo
862 876 transaction abort!
863 877 rollback completed
864 878 abort: pretxncommit hook exited with status 1
865 879 [255]
866 880 *** runcommand log
867 881 *** runcommand verify -q
868 882
869 883 (failure after finalization)
870 884
871 885 >>> from hgclient import check, readchannel, runcommand
872 886 >>> @check
873 887 ... def abort(server):
874 888 ... readchannel(server)
875 889 ... runcommand(server, ['commit',
876 890 ... '--config', 'failafterfinalize.fail=true',
877 891 ... '-mfoo'])
878 892 ... runcommand(server, ['log'])
879 893 ... runcommand(server, ['verify', '-q'])
880 894 *** runcommand commit --config failafterfinalize.fail=true -mfoo
881 895 transaction abort!
882 896 rollback completed
883 897 abort: fail after finalization
884 898 [255]
885 899 *** runcommand log
886 900 *** runcommand verify -q
887 901
888 902 - test failure with "not-empty changelog"
889 903
890 904 $ echo bar > bar
891 905 $ hg add bar
892 906 $ hg commit -mbar bar
893 907
894 908 (failure before finalization)
895 909
896 910 >>> from hgclient import check, readchannel, runcommand
897 911 >>> @check
898 912 ... def abort(server):
899 913 ... readchannel(server)
900 914 ... runcommand(server, ['commit',
901 915 ... '--config', 'hooks.pretxncommit=false',
902 916 ... '-mfoo', 'foo'])
903 917 ... runcommand(server, ['log'])
904 918 ... runcommand(server, ['verify', '-q'])
905 919 *** runcommand commit --config hooks.pretxncommit=false -mfoo foo
906 920 transaction abort!
907 921 rollback completed
908 922 abort: pretxncommit hook exited with status 1
909 923 [255]
910 924 *** runcommand log
911 925 0 bar (bar)
912 926 *** runcommand verify -q
913 927
914 928 (failure after finalization)
915 929
916 930 >>> from hgclient import check, readchannel, runcommand
917 931 >>> @check
918 932 ... def abort(server):
919 933 ... readchannel(server)
920 934 ... runcommand(server, ['commit',
921 935 ... '--config', 'failafterfinalize.fail=true',
922 936 ... '-mfoo', 'foo'])
923 937 ... runcommand(server, ['log'])
924 938 ... runcommand(server, ['verify', '-q'])
925 939 *** runcommand commit --config failafterfinalize.fail=true -mfoo foo
926 940 transaction abort!
927 941 rollback completed
928 942 abort: fail after finalization
929 943 [255]
930 944 *** runcommand log
931 945 0 bar (bar)
932 946 *** runcommand verify -q
933 947
934 948 $ cd ..
935 949
936 950 Test symlink traversal over cached audited paths:
937 951 -------------------------------------------------
938 952
939 953 #if symlink
940 954
941 955 set up symlink hell
942 956
943 957 $ mkdir merge-symlink-out
944 958 $ hg init merge-symlink
945 959 $ cd merge-symlink
946 960 $ touch base
947 961 $ hg commit -qAm base
948 962 $ ln -s ../merge-symlink-out a
949 963 $ hg commit -qAm 'symlink a -> ../merge-symlink-out'
950 964 $ hg up -q 0
951 965 $ mkdir a
952 966 $ touch a/poisoned
953 967 $ hg commit -qAm 'file a/poisoned'
954 968 $ hg log -G -T '{rev}: {desc}\n'
955 969 @ 2: file a/poisoned
956 970 |
957 971 | o 1: symlink a -> ../merge-symlink-out
958 972 |/
959 973 o 0: base
960 974
961 975
962 976 try trivial merge after update: cache of audited paths should be discarded,
963 977 and the merge should fail (issue5628)
964 978
965 979 $ hg up -q null
966 980 >>> from hgclient import check, readchannel, runcommand
967 981 >>> @check
968 982 ... def merge(server):
969 983 ... readchannel(server)
970 984 ... # audit a/poisoned as a good path
971 985 ... runcommand(server, ['up', '-qC', '2'])
972 986 ... runcommand(server, ['up', '-qC', '1'])
973 987 ... # here a is a symlink, so a/poisoned is bad
974 988 ... runcommand(server, ['merge', '2'])
975 989 *** runcommand up -qC 2
976 990 *** runcommand up -qC 1
977 991 *** runcommand merge 2
978 992 abort: path 'a/poisoned' traverses symbolic link 'a'
979 993 [255]
980 994 $ ls ../merge-symlink-out
981 995
982 996 cache of repo.auditor should be discarded, so matcher would never traverse
983 997 symlinks:
984 998
985 999 $ hg up -qC 0
986 1000 $ touch ../merge-symlink-out/poisoned
987 1001 >>> from hgclient import check, readchannel, runcommand
988 1002 >>> @check
989 1003 ... def files(server):
990 1004 ... readchannel(server)
991 1005 ... runcommand(server, ['up', '-qC', '2'])
992 1006 ... # audit a/poisoned as a good path
993 1007 ... runcommand(server, ['files', 'a/poisoned'])
994 1008 ... runcommand(server, ['up', '-qC', '0'])
995 1009 ... runcommand(server, ['up', '-qC', '1'])
996 1010 ... # here 'a' is a symlink, so a/poisoned should be warned
997 1011 ... runcommand(server, ['files', 'a/poisoned'])
998 1012 *** runcommand up -qC 2
999 1013 *** runcommand files a/poisoned
1000 1014 a/poisoned
1001 1015 *** runcommand up -qC 0
1002 1016 *** runcommand up -qC 1
1003 1017 *** runcommand files a/poisoned
1004 1018 abort: path 'a/poisoned' traverses symbolic link 'a'
1005 1019 [255]
1006 1020
1007 1021 $ cd ..
1008 1022
1009 1023 #endif
@@ -1,163 +1,208
1 1 test command parsing and dispatch
2 2
3 3 $ hg init a
4 4 $ cd a
5 5
6 6 Redundant options used to crash (issue436):
7 7 $ hg -v log -v
8 8 $ hg -v log -v x
9 9
10 10 $ echo a > a
11 11 $ hg ci -Ama
12 12 adding a
13 13
14 14 Missing arg:
15 15
16 16 $ hg cat
17 17 hg cat: invalid arguments
18 18 hg cat [OPTION]... FILE...
19 19
20 20 output the current or given revision of files
21 21
22 22 options ([+] can be repeated):
23 23
24 24 -o --output FORMAT print output to file with formatted name
25 25 -r --rev REV print the given revision
26 26 --decode apply any matching decode filter
27 27 -I --include PATTERN [+] include names matching the given patterns
28 28 -X --exclude PATTERN [+] exclude names matching the given patterns
29 29
30 30 (use 'hg cat -h' to show more help)
31 31 [255]
32 32
33 33 Missing parameter for early option:
34 34
35 35 $ hg log -R 2>&1 | grep 'hg log'
36 36 hg log: option -R requires argument
37 37 hg log [OPTION]... [FILE]
38 38 (use 'hg log -h' to show more help)
39 39
40 40 $ hg log -R -- 2>&1 | grep 'hg log'
41 41 hg log: option -R requires argument
42 42 hg log [OPTION]... [FILE]
43 43 (use 'hg log -h' to show more help)
44 44
45 45 Parsing of early options should stop at "--":
46 46
47 47 $ hg cat -- --config=hooks.pre-cat=false
48 48 --config=hooks.pre-cat=false: no such file in rev cb9a9f314b8b
49 49 [1]
50 50 $ hg cat -- --debugger
51 51 --debugger: no such file in rev cb9a9f314b8b
52 52 [1]
53 53
54 54 Unparsable form of early options:
55 55
56 56 $ hg cat --debugg
57 57 abort: option --debugger may not be abbreviated!
58 58 [255]
59 59
60 60 Parsing failure of early options should be detected before executing the
61 61 command:
62 62
63 63 $ hg log -b '--config=hooks.pre-log=false' default
64 64 abort: option --config may not be abbreviated!
65 65 [255]
66 66 $ hg log -b -R. default
67 67 abort: option -R has to be separated from other options (e.g. not -qR) and --repository may only be abbreviated as --repo!
68 68 [255]
69 69 $ hg log --cwd .. -b --cwd=. default
70 70 abort: option --cwd may not be abbreviated!
71 71 [255]
72 72
73 73 However, we can't prevent it from loading extensions and configs:
74 74
75 75 $ cat <<EOF > bad.py
76 76 > raise Exception('bad')
77 77 > EOF
78 78 $ hg log -b '--config=extensions.bad=bad.py' default
79 79 *** failed to import extension bad from bad.py: bad
80 80 abort: option --config may not be abbreviated!
81 81 [255]
82 82
83 83 $ mkdir -p badrepo/.hg
84 84 $ echo 'invalid-syntax' > badrepo/.hg/hgrc
85 85 $ hg log -b -Rbadrepo default
86 86 hg: parse error at badrepo/.hg/hgrc:1: invalid-syntax
87 87 [255]
88 88
89 89 $ hg log -b --cwd=inexistent default
90 90 abort: No such file or directory: 'inexistent'
91 91 [255]
92 92
93 93 $ hg log -b '--config=ui.traceback=yes' 2>&1 | grep '^Traceback'
94 94 Traceback (most recent call last):
95 95 $ hg log -b '--config=profiling.enabled=yes' 2>&1 | grep -i sample
96 96 Sample count: .*|No samples recorded\. (re)
97 97
98 98 Early options can't be specified in [aliases] and [defaults] because they are
99 99 applied before the command name is resolved:
100 100
101 101 $ hg log -b '--config=alias.log=log --config=hooks.pre-log=false'
102 102 hg log: option -b not recognized
103 103 error in definition for alias 'log': --config may only be given on the command
104 104 line
105 105 [255]
106 106
107 107 $ hg log -b '--config=defaults.log=--config=hooks.pre-log=false'
108 108 abort: option --config may not be abbreviated!
109 109 [255]
110 110
111 111 Shell aliases bypass any command parsing rules but for the early one:
112 112
113 113 $ hg log -b '--config=alias.log=!echo howdy'
114 114 howdy
115 115
116 Early options must come first if HGPLAIN=+strictflags is specified:
117 (BUG: chg cherry-picks early options to pass them as a server command)
118
119 #if no-chg
120 $ HGPLAIN=+strictflags hg log -b --config='hooks.pre-log=false' default
121 abort: unknown revision '--config=hooks.pre-log=false'!
122 [255]
123 $ HGPLAIN=+strictflags hg log -b -R. default
124 abort: unknown revision '-R.'!
125 [255]
126 $ HGPLAIN=+strictflags hg log -b --cwd=. default
127 abort: unknown revision '--cwd=.'!
128 [255]
129 #endif
130 $ HGPLAIN=+strictflags hg log -b --debugger default
131 abort: unknown revision '--debugger'!
132 [255]
133 $ HGPLAIN=+strictflags hg log -b --config='alias.log=!echo pwned' default
134 abort: unknown revision '--config=alias.log=!echo pwned'!
135 [255]
136
137 $ HGPLAIN=+strictflags hg log --config='hooks.pre-log=false' -b default
138 abort: option --config may not be abbreviated!
139 [255]
140 $ HGPLAIN=+strictflags hg log -q --cwd=.. -b default
141 abort: option --cwd may not be abbreviated!
142 [255]
143 $ HGPLAIN=+strictflags hg log -q -R . -b default
144 abort: option -R has to be separated from other options (e.g. not -qR) and --repository may only be abbreviated as --repo!
145 [255]
146
147 $ HGPLAIN=+strictflags hg --config='hooks.pre-log=false' log -b default
148 abort: pre-log hook exited with status 1
149 [255]
150 $ HGPLAIN=+strictflags hg --cwd .. -q -Ra log -b default
151 0:cb9a9f314b8b
152
153 For compatibility reasons, HGPLAIN=+strictflags is not enabled by plain HGPLAIN:
154
155 $ HGPLAIN= hg log --config='hooks.pre-log=false' -b default
156 abort: pre-log hook exited with status 1
157 [255]
158 $ HGPLAINEXCEPT= hg log --cwd .. -q -Ra -b default
159 0:cb9a9f314b8b
160
116 161 [defaults]
117 162
118 163 $ hg cat a
119 164 a
120 165 $ cat >> $HGRCPATH <<EOF
121 166 > [defaults]
122 167 > cat = -r null
123 168 > EOF
124 169 $ hg cat a
125 170 a: no such file in rev 000000000000
126 171 [1]
127 172
128 173 $ cd "$TESTTMP"
129 174
130 175 OSError "No such file or directory" / "The system cannot find the path
131 176 specified" should include filename even when it is empty
132 177
133 178 $ hg -R a archive ''
134 179 abort: *: '' (glob)
135 180 [255]
136 181
137 182 #if no-outer-repo
138 183
139 184 No repo:
140 185
141 186 $ hg cat
142 187 abort: no repository found in '$TESTTMP' (.hg not found)!
143 188 [255]
144 189
145 190 #endif
146 191
147 192 #if rmcwd
148 193
149 194 Current directory removed:
150 195
151 196 $ mkdir $TESTTMP/repo1
152 197 $ cd $TESTTMP/repo1
153 198 $ rm -rf $TESTTMP/repo1
154 199
155 200 The output could be one of the following and something else:
156 201 chg: abort: failed to getcwd (errno = *) (glob)
157 202 abort: error getting current working directory: * (glob)
158 203 sh: 0: getcwd() failed: No such file or directory
159 204 Since the exact behavior depends on the shell, only check it returns non-zero.
160 205 $ HGDEMANDIMPORT=disable hg version -q 2>/dev/null || false
161 206 [1]
162 207
163 208 #endif
General Comments 0
You need to be logged in to leave comments. Login now