##// END OF EJS Templates
backout: backed out changeset 6edc8800dbc3...
Raphaël Gomès -
r49071:8c34edb1 default
parent child Browse files
Show More
@@ -1,1407 +1,1404 b''
1 1 # dispatch.py - command dispatching for mercurial
2 2 #
3 3 # Copyright 2005-2007 Olivia Mackall <olivia@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 errno
11 11 import getopt
12 12 import io
13 13 import os
14 14 import pdb
15 15 import re
16 16 import signal
17 17 import sys
18 18 import traceback
19 19
20 20
21 21 from .i18n import _
22 22 from .pycompat import getattr
23 23
24 24 from hgdemandimport import tracing
25 25
26 26 from . import (
27 27 cmdutil,
28 28 color,
29 29 commands,
30 30 demandimport,
31 31 encoding,
32 32 error,
33 33 extensions,
34 34 fancyopts,
35 35 help,
36 36 hg,
37 37 hook,
38 38 localrepo,
39 39 profiling,
40 40 pycompat,
41 41 rcutil,
42 42 registrar,
43 43 requirements as requirementsmod,
44 44 scmutil,
45 45 ui as uimod,
46 46 util,
47 47 vfs,
48 48 )
49 49
50 50 from .utils import (
51 51 procutil,
52 52 stringutil,
53 53 urlutil,
54 54 )
55 55
56 56
57 57 class request(object):
58 58 def __init__(
59 59 self,
60 60 args,
61 61 ui=None,
62 62 repo=None,
63 63 fin=None,
64 64 fout=None,
65 65 ferr=None,
66 66 fmsg=None,
67 67 prereposetups=None,
68 68 ):
69 69 self.args = args
70 70 self.ui = ui
71 71 self.repo = repo
72 72
73 73 # input/output/error streams
74 74 self.fin = fin
75 75 self.fout = fout
76 76 self.ferr = ferr
77 77 # separate stream for status/error messages
78 78 self.fmsg = fmsg
79 79
80 80 # remember options pre-parsed by _earlyparseopts()
81 81 self.earlyoptions = {}
82 82
83 83 # reposetups which run before extensions, useful for chg to pre-fill
84 84 # low-level repo state (for example, changelog) before extensions.
85 85 self.prereposetups = prereposetups or []
86 86
87 87 # store the parsed and canonical command
88 88 self.canonical_command = None
89 89
90 90 def _runexithandlers(self):
91 91 exc = None
92 92 handlers = self.ui._exithandlers
93 93 try:
94 94 while handlers:
95 95 func, args, kwargs = handlers.pop()
96 96 try:
97 97 func(*args, **kwargs)
98 98 except: # re-raises below
99 99 if exc is None:
100 100 exc = sys.exc_info()[1]
101 101 self.ui.warnnoi18n(b'error in exit handlers:\n')
102 102 self.ui.traceback(force=True)
103 103 finally:
104 104 if exc is not None:
105 105 raise exc
106 106
107 107
108 108 def _flushstdio(ui, err):
109 109 status = None
110 110 # In all cases we try to flush stdio streams.
111 111 if util.safehasattr(ui, b'fout'):
112 112 assert ui is not None # help pytype
113 113 assert ui.fout is not None # help pytype
114 114 try:
115 115 ui.fout.flush()
116 116 except IOError as e:
117 117 err = e
118 118 status = -1
119 119
120 120 if util.safehasattr(ui, b'ferr'):
121 121 assert ui is not None # help pytype
122 122 assert ui.ferr is not None # help pytype
123 123 try:
124 124 if err is not None and err.errno != errno.EPIPE:
125 125 ui.ferr.write(
126 126 b'abort: %s\n' % encoding.strtolocal(err.strerror)
127 127 )
128 128 ui.ferr.flush()
129 129 # There's not much we can do about an I/O error here. So (possibly)
130 130 # change the status code and move on.
131 131 except IOError:
132 132 status = -1
133 133
134 134 return status
135 135
136 136
137 137 def run():
138 138 """run the command in sys.argv"""
139 139 try:
140 140 initstdio()
141 141 with tracing.log('parse args into request'):
142 142 req = request(pycompat.sysargv[1:])
143 143
144 144 status = dispatch(req)
145 145 _silencestdio()
146 146 except KeyboardInterrupt:
147 147 # Catch early/late KeyboardInterrupt as last ditch. Here nothing will
148 148 # be printed to console to avoid another IOError/KeyboardInterrupt.
149 149 status = -1
150 150 sys.exit(status & 255)
151 151
152 152
153 153 if pycompat.ispy3:
154 154
155 155 def initstdio():
156 156 # stdio streams on Python 3 are io.TextIOWrapper instances proxying another
157 157 # buffer. These streams will normalize \n to \r\n by default. Mercurial's
158 158 # preferred mechanism for writing output (ui.write()) uses io.BufferedWriter
159 159 # instances, which write to the underlying stdio file descriptor in binary
160 160 # mode. ui.write() uses \n for line endings and no line ending normalization
161 161 # is attempted through this interface. This "just works," even if the system
162 162 # preferred line ending is not \n.
163 163 #
164 164 # But some parts of Mercurial (e.g. hooks) can still send data to sys.stdout
165 165 # and sys.stderr. They will inherit the line ending normalization settings,
166 166 # potentially causing e.g. \r\n to be emitted. Since emitting \n should
167 167 # "just work," here we change the sys.* streams to disable line ending
168 168 # normalization, ensuring compatibility with our ui type.
169 169
170 170 if sys.stdout is not None:
171 171 # write_through is new in Python 3.7.
172 172 kwargs = {
173 173 "newline": "\n",
174 174 "line_buffering": sys.stdout.line_buffering,
175 175 }
176 176 if util.safehasattr(sys.stdout, "write_through"):
177 177 # pytype: disable=attribute-error
178 178 kwargs["write_through"] = sys.stdout.write_through
179 179 # pytype: enable=attribute-error
180 180 sys.stdout = io.TextIOWrapper(
181 181 sys.stdout.buffer,
182 182 sys.stdout.encoding,
183 183 sys.stdout.errors,
184 184 **kwargs
185 185 )
186 186
187 187 if sys.stderr is not None:
188 188 kwargs = {
189 189 "newline": "\n",
190 190 "line_buffering": sys.stderr.line_buffering,
191 191 }
192 192 if util.safehasattr(sys.stderr, "write_through"):
193 193 # pytype: disable=attribute-error
194 194 kwargs["write_through"] = sys.stderr.write_through
195 195 # pytype: enable=attribute-error
196 196 sys.stderr = io.TextIOWrapper(
197 197 sys.stderr.buffer,
198 198 sys.stderr.encoding,
199 199 sys.stderr.errors,
200 200 **kwargs
201 201 )
202 202
203 203 if sys.stdin is not None:
204 204 # No write_through on read-only stream.
205 205 sys.stdin = io.TextIOWrapper(
206 206 sys.stdin.buffer,
207 207 sys.stdin.encoding,
208 208 sys.stdin.errors,
209 209 # None is universal newlines mode.
210 210 newline=None,
211 211 line_buffering=sys.stdin.line_buffering,
212 212 )
213 213
214 214 def _silencestdio():
215 215 for fp in (sys.stdout, sys.stderr):
216 216 if fp is None:
217 217 continue
218 218 # Check if the file is okay
219 219 try:
220 220 fp.flush()
221 221 continue
222 222 except IOError:
223 223 pass
224 224 # Otherwise mark it as closed to silence "Exception ignored in"
225 225 # message emitted by the interpreter finalizer.
226 226 try:
227 227 fp.close()
228 228 except IOError:
229 229 pass
230 230
231 231
232 232 else:
233 233
234 234 def initstdio():
235 235 for fp in (sys.stdin, sys.stdout, sys.stderr):
236 236 procutil.setbinary(fp)
237 237
238 238 def _silencestdio():
239 239 pass
240 240
241 241
242 242 def _formatargs(args):
243 243 return b' '.join(procutil.shellquote(a) for a in args)
244 244
245 245
246 246 def dispatch(req):
247 247 """run the command specified in req.args; returns an integer status code"""
248 248 err = None
249 249 try:
250 250 status = _rundispatch(req)
251 251 except error.StdioError as e:
252 252 err = e
253 253 status = -1
254 254
255 255 # Somehow we have to catcht he exception here; catching it inside
256 256 # _flushstdio() doesn't work.
257 257 try:
258 258 ret = _flushstdio(req.ui, err)
259 259 if ret and not status:
260 260 status = ret
261 261 except BaseException:
262 262 pass
263 263 return status
264 264
265 265
266 266 def _rundispatch(req):
267 267 with tracing.log('dispatch._rundispatch'):
268 268 if req.ferr:
269 269 ferr = req.ferr
270 270 elif req.ui:
271 271 ferr = req.ui.ferr
272 272 else:
273 273 ferr = procutil.stderr
274 274
275 275 try:
276 276 if not req.ui:
277 277 req.ui = uimod.ui.load()
278 278 req.earlyoptions.update(_earlyparseopts(req.ui, req.args))
279 279 if req.earlyoptions[b'traceback']:
280 280 req.ui.setconfig(b'ui', b'traceback', b'on', b'--traceback')
281 281
282 282 # set ui streams from the request
283 283 if req.fin:
284 284 req.ui.fin = req.fin
285 285 if req.fout:
286 286 req.ui.fout = req.fout
287 287 if req.ferr:
288 288 req.ui.ferr = req.ferr
289 289 if req.fmsg:
290 290 req.ui.fmsg = req.fmsg
291 291 except error.Abort as inst:
292 292 ferr.write(inst.format())
293 293 return -1
294 294
295 295 msg = _formatargs(req.args)
296 296 starttime = util.timer()
297 297 ret = 1 # default of Python exit code on unhandled exception
298 298 try:
299 299 ret = _runcatch(req) or 0
300 300 except error.ProgrammingError as inst:
301 301 req.ui.error(_(b'** ProgrammingError: %s\n') % inst)
302 302 if inst.hint:
303 303 req.ui.error(_(b'** (%s)\n') % inst.hint)
304 304 raise
305 305 except KeyboardInterrupt as inst:
306 306 try:
307 307 if isinstance(inst, error.SignalInterrupt):
308 308 msg = _(b"killed!\n")
309 309 else:
310 310 msg = _(b"interrupted!\n")
311 311 req.ui.error(msg)
312 312 except error.SignalInterrupt:
313 313 # maybe pager would quit without consuming all the output, and
314 314 # SIGPIPE was raised. we cannot print anything in this case.
315 315 pass
316 316 except IOError as inst:
317 317 if inst.errno != errno.EPIPE:
318 318 raise
319 if req.ui.configbool(b'ui', b'detailed-exit-code'):
320 ret = 250
321 else:
322 319 ret = -1
323 320 finally:
324 321 duration = util.timer() - starttime
325 322 try:
326 323 req.ui.flush() # record blocked times
327 324 except BaseException:
328 325 pass
329 326 if req.ui.logblockedtimes:
330 327 req.ui._blockedtimes[b'command_duration'] = duration * 1000
331 328 req.ui.log(
332 329 b'uiblocked',
333 330 b'ui blocked ms\n',
334 331 **pycompat.strkwargs(req.ui._blockedtimes)
335 332 )
336 333 return_code = ret & 255
337 334 req.ui.log(
338 335 b"commandfinish",
339 336 b"%s exited %d after %0.2f seconds\n",
340 337 msg,
341 338 return_code,
342 339 duration,
343 340 return_code=return_code,
344 341 duration=duration,
345 342 canonical_command=req.canonical_command,
346 343 )
347 344 try:
348 345 req._runexithandlers()
349 346 except: # exiting, so no re-raises
350 347 ret = ret or -1
351 348 # do flush again since ui.log() and exit handlers may write to ui
352 349 try:
353 350 req.ui.flush()
354 351 except BaseException:
355 352 pass
356 353 return ret
357 354
358 355
359 356 def _runcatch(req):
360 357 with tracing.log('dispatch._runcatch'):
361 358
362 359 def catchterm(*args):
363 360 raise error.SignalInterrupt
364 361
365 362 ui = req.ui
366 363 try:
367 364 for name in b'SIGBREAK', b'SIGHUP', b'SIGTERM':
368 365 num = getattr(signal, name, None)
369 366 if num:
370 367 signal.signal(num, catchterm)
371 368 except ValueError:
372 369 pass # happens if called in a thread
373 370
374 371 def _runcatchfunc():
375 372 realcmd = None
376 373 try:
377 374 cmdargs = fancyopts.fancyopts(
378 375 req.args[:], commands.globalopts, {}
379 376 )
380 377 cmd = cmdargs[0]
381 378 aliases, entry = cmdutil.findcmd(cmd, commands.table, False)
382 379 realcmd = aliases[0]
383 380 except (
384 381 error.UnknownCommand,
385 382 error.AmbiguousCommand,
386 383 IndexError,
387 384 getopt.GetoptError,
388 385 ):
389 386 # Don't handle this here. We know the command is
390 387 # invalid, but all we're worried about for now is that
391 388 # it's not a command that server operators expect to
392 389 # be safe to offer to users in a sandbox.
393 390 pass
394 391 if realcmd == b'serve' and b'--stdio' in cmdargs:
395 392 # We want to constrain 'hg serve --stdio' instances pretty
396 393 # closely, as many shared-ssh access tools want to grant
397 394 # access to run *only* 'hg -R $repo serve --stdio'. We
398 395 # restrict to exactly that set of arguments, and prohibit
399 396 # any repo name that starts with '--' to prevent
400 397 # shenanigans wherein a user does something like pass
401 398 # --debugger or --config=ui.debugger=1 as a repo
402 399 # name. This used to actually run the debugger.
403 400 if (
404 401 len(req.args) != 4
405 402 or req.args[0] != b'-R'
406 403 or req.args[1].startswith(b'--')
407 404 or req.args[2] != b'serve'
408 405 or req.args[3] != b'--stdio'
409 406 ):
410 407 raise error.Abort(
411 408 _(b'potentially unsafe serve --stdio invocation: %s')
412 409 % (stringutil.pprint(req.args),)
413 410 )
414 411
415 412 try:
416 413 debugger = b'pdb'
417 414 debugtrace = {b'pdb': pdb.set_trace}
418 415 debugmortem = {b'pdb': pdb.post_mortem}
419 416
420 417 # read --config before doing anything else
421 418 # (e.g. to change trust settings for reading .hg/hgrc)
422 419 cfgs = _parseconfig(req.ui, req.earlyoptions[b'config'])
423 420
424 421 if req.repo:
425 422 # copy configs that were passed on the cmdline (--config) to
426 423 # the repo ui
427 424 for sec, name, val in cfgs:
428 425 req.repo.ui.setconfig(
429 426 sec, name, val, source=b'--config'
430 427 )
431 428
432 429 # developer config: ui.debugger
433 430 debugger = ui.config(b"ui", b"debugger")
434 431 debugmod = pdb
435 432 if not debugger or ui.plain():
436 433 # if we are in HGPLAIN mode, then disable custom debugging
437 434 debugger = b'pdb'
438 435 elif req.earlyoptions[b'debugger']:
439 436 # This import can be slow for fancy debuggers, so only
440 437 # do it when absolutely necessary, i.e. when actual
441 438 # debugging has been requested
442 439 with demandimport.deactivated():
443 440 try:
444 441 debugmod = __import__(debugger)
445 442 except ImportError:
446 443 pass # Leave debugmod = pdb
447 444
448 445 debugtrace[debugger] = debugmod.set_trace
449 446 debugmortem[debugger] = debugmod.post_mortem
450 447
451 448 # enter the debugger before command execution
452 449 if req.earlyoptions[b'debugger']:
453 450 ui.warn(
454 451 _(
455 452 b"entering debugger - "
456 453 b"type c to continue starting hg or h for help\n"
457 454 )
458 455 )
459 456
460 457 if (
461 458 debugger != b'pdb'
462 459 and debugtrace[debugger] == debugtrace[b'pdb']
463 460 ):
464 461 ui.warn(
465 462 _(
466 463 b"%s debugger specified "
467 464 b"but its module was not found\n"
468 465 )
469 466 % debugger
470 467 )
471 468 with demandimport.deactivated():
472 469 debugtrace[debugger]()
473 470 try:
474 471 return _dispatch(req)
475 472 finally:
476 473 try:
477 474 ui.flush() # record blocked times
478 475 except BaseException:
479 476 pass
480 477 except: # re-raises
481 478 # enter the debugger when we hit an exception
482 479 if req.earlyoptions[b'debugger']:
483 480 traceback.print_exc()
484 481 debugmortem[debugger](sys.exc_info()[2])
485 482 raise
486 483
487 484 return _callcatch(ui, _runcatchfunc)
488 485
489 486
490 487 def _callcatch(ui, func):
491 488 """like scmutil.callcatch but handles more high-level exceptions about
492 489 config parsing and commands. besides, use handlecommandexception to handle
493 490 uncaught exceptions.
494 491 """
495 492 detailed_exit_code = -1
496 493 try:
497 494 return scmutil.callcatch(ui, func)
498 495 except error.AmbiguousCommand as inst:
499 496 detailed_exit_code = 10
500 497 ui.warn(
501 498 _(b"hg: command '%s' is ambiguous:\n %s\n")
502 499 % (inst.prefix, b" ".join(inst.matches))
503 500 )
504 501 except error.CommandError as inst:
505 502 detailed_exit_code = 10
506 503 if inst.command:
507 504 ui.pager(b'help')
508 505 msgbytes = pycompat.bytestr(inst.message)
509 506 ui.warn(_(b"hg %s: %s\n") % (inst.command, msgbytes))
510 507 commands.help_(ui, inst.command, full=False, command=True)
511 508 else:
512 509 ui.warn(_(b"hg: %s\n") % inst.message)
513 510 ui.warn(_(b"(use 'hg help -v' for a list of global options)\n"))
514 511 except error.UnknownCommand as inst:
515 512 detailed_exit_code = 10
516 513 nocmdmsg = _(b"hg: unknown command '%s'\n") % inst.command
517 514 try:
518 515 # check if the command is in a disabled extension
519 516 # (but don't check for extensions themselves)
520 517 formatted = help.formattedhelp(
521 518 ui, commands, inst.command, unknowncmd=True
522 519 )
523 520 ui.warn(nocmdmsg)
524 521 ui.write(formatted)
525 522 except (error.UnknownCommand, error.Abort):
526 523 suggested = False
527 524 if inst.all_commands:
528 525 sim = error.getsimilar(inst.all_commands, inst.command)
529 526 if sim:
530 527 ui.warn(nocmdmsg)
531 528 ui.warn(b"(%s)\n" % error.similarity_hint(sim))
532 529 suggested = True
533 530 if not suggested:
534 531 ui.warn(nocmdmsg)
535 532 ui.warn(_(b"(use 'hg help' for a list of commands)\n"))
536 533 except IOError:
537 534 raise
538 535 except KeyboardInterrupt:
539 536 raise
540 537 except: # probably re-raises
541 538 if not handlecommandexception(ui):
542 539 raise
543 540
544 541 if ui.configbool(b'ui', b'detailed-exit-code'):
545 542 return detailed_exit_code
546 543 else:
547 544 return -1
548 545
549 546
550 547 def aliasargs(fn, givenargs):
551 548 args = []
552 549 # only care about alias 'args', ignore 'args' set by extensions.wrapfunction
553 550 if not util.safehasattr(fn, b'_origfunc'):
554 551 args = getattr(fn, 'args', args)
555 552 if args:
556 553 cmd = b' '.join(map(procutil.shellquote, args))
557 554
558 555 nums = []
559 556
560 557 def replacer(m):
561 558 num = int(m.group(1)) - 1
562 559 nums.append(num)
563 560 if num < len(givenargs):
564 561 return givenargs[num]
565 562 raise error.InputError(_(b'too few arguments for command alias'))
566 563
567 564 cmd = re.sub(br'\$(\d+|\$)', replacer, cmd)
568 565 givenargs = [x for i, x in enumerate(givenargs) if i not in nums]
569 566 args = pycompat.shlexsplit(cmd)
570 567 return args + givenargs
571 568
572 569
573 570 def aliasinterpolate(name, args, cmd):
574 571 """interpolate args into cmd for shell aliases
575 572
576 573 This also handles $0, $@ and "$@".
577 574 """
578 575 # util.interpolate can't deal with "$@" (with quotes) because it's only
579 576 # built to match prefix + patterns.
580 577 replacemap = {b'$%d' % (i + 1): arg for i, arg in enumerate(args)}
581 578 replacemap[b'$0'] = name
582 579 replacemap[b'$$'] = b'$'
583 580 replacemap[b'$@'] = b' '.join(args)
584 581 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
585 582 # parameters, separated out into words. Emulate the same behavior here by
586 583 # quoting the arguments individually. POSIX shells will then typically
587 584 # tokenize each argument into exactly one word.
588 585 replacemap[b'"$@"'] = b' '.join(procutil.shellquote(arg) for arg in args)
589 586 # escape '\$' for regex
590 587 regex = b'|'.join(replacemap.keys()).replace(b'$', br'\$')
591 588 r = re.compile(regex)
592 589 return r.sub(lambda x: replacemap[x.group()], cmd)
593 590
594 591
595 592 class cmdalias(object):
596 593 def __init__(self, ui, name, definition, cmdtable, source):
597 594 self.name = self.cmd = name
598 595 self.cmdname = b''
599 596 self.definition = definition
600 597 self.fn = None
601 598 self.givenargs = []
602 599 self.opts = []
603 600 self.help = b''
604 601 self.badalias = None
605 602 self.unknowncmd = False
606 603 self.source = source
607 604
608 605 try:
609 606 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
610 607 for alias, e in pycompat.iteritems(cmdtable):
611 608 if e is entry:
612 609 self.cmd = alias
613 610 break
614 611 self.shadows = True
615 612 except error.UnknownCommand:
616 613 self.shadows = False
617 614
618 615 if not self.definition:
619 616 self.badalias = _(b"no definition for alias '%s'") % self.name
620 617 return
621 618
622 619 if self.definition.startswith(b'!'):
623 620 shdef = self.definition[1:]
624 621 self.shell = True
625 622
626 623 def fn(ui, *args):
627 624 env = {b'HG_ARGS': b' '.join((self.name,) + args)}
628 625
629 626 def _checkvar(m):
630 627 if m.groups()[0] == b'$':
631 628 return m.group()
632 629 elif int(m.groups()[0]) <= len(args):
633 630 return m.group()
634 631 else:
635 632 ui.debug(
636 633 b"No argument found for substitution "
637 634 b"of %i variable in alias '%s' definition.\n"
638 635 % (int(m.groups()[0]), self.name)
639 636 )
640 637 return b''
641 638
642 639 cmd = re.sub(br'\$(\d+|\$)', _checkvar, shdef)
643 640 cmd = aliasinterpolate(self.name, args, cmd)
644 641 return ui.system(
645 642 cmd, environ=env, blockedtag=b'alias_%s' % self.name
646 643 )
647 644
648 645 self.fn = fn
649 646 self.alias = True
650 647 self._populatehelp(ui, name, shdef, self.fn)
651 648 return
652 649
653 650 try:
654 651 args = pycompat.shlexsplit(self.definition)
655 652 except ValueError as inst:
656 653 self.badalias = _(b"error in definition for alias '%s': %s") % (
657 654 self.name,
658 655 stringutil.forcebytestr(inst),
659 656 )
660 657 return
661 658 earlyopts, args = _earlysplitopts(args)
662 659 if earlyopts:
663 660 self.badalias = _(
664 661 b"error in definition for alias '%s': %s may "
665 662 b"only be given on the command line"
666 663 ) % (self.name, b'/'.join(pycompat.ziplist(*earlyopts)[0]))
667 664 return
668 665 self.cmdname = cmd = args.pop(0)
669 666 self.givenargs = args
670 667
671 668 try:
672 669 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
673 670 if len(tableentry) > 2:
674 671 self.fn, self.opts, cmdhelp = tableentry
675 672 else:
676 673 self.fn, self.opts = tableentry
677 674 cmdhelp = None
678 675
679 676 self.alias = True
680 677 self._populatehelp(ui, name, cmd, self.fn, cmdhelp)
681 678
682 679 except error.UnknownCommand:
683 680 self.badalias = _(
684 681 b"alias '%s' resolves to unknown command '%s'"
685 682 ) % (
686 683 self.name,
687 684 cmd,
688 685 )
689 686 self.unknowncmd = True
690 687 except error.AmbiguousCommand:
691 688 self.badalias = _(
692 689 b"alias '%s' resolves to ambiguous command '%s'"
693 690 ) % (
694 691 self.name,
695 692 cmd,
696 693 )
697 694
698 695 def _populatehelp(self, ui, name, cmd, fn, defaulthelp=None):
699 696 # confine strings to be passed to i18n.gettext()
700 697 cfg = {}
701 698 for k in (b'doc', b'help', b'category'):
702 699 v = ui.config(b'alias', b'%s:%s' % (name, k), None)
703 700 if v is None:
704 701 continue
705 702 if not encoding.isasciistr(v):
706 703 self.badalias = _(
707 704 b"non-ASCII character in alias definition '%s:%s'"
708 705 ) % (name, k)
709 706 return
710 707 cfg[k] = v
711 708
712 709 self.help = cfg.get(b'help', defaulthelp or b'')
713 710 if self.help and self.help.startswith(b"hg " + cmd):
714 711 # drop prefix in old-style help lines so hg shows the alias
715 712 self.help = self.help[4 + len(cmd) :]
716 713
717 714 self.owndoc = b'doc' in cfg
718 715 doc = cfg.get(b'doc', pycompat.getdoc(fn))
719 716 if doc is not None:
720 717 doc = pycompat.sysstr(doc)
721 718 self.__doc__ = doc
722 719
723 720 self.helpcategory = cfg.get(
724 721 b'category', registrar.command.CATEGORY_NONE
725 722 )
726 723
727 724 @property
728 725 def args(self):
729 726 args = pycompat.maplist(util.expandpath, self.givenargs)
730 727 return aliasargs(self.fn, args)
731 728
732 729 def __getattr__(self, name):
733 730 adefaults = {
734 731 'norepo': True,
735 732 'intents': set(),
736 733 'optionalrepo': False,
737 734 'inferrepo': False,
738 735 }
739 736 if name not in adefaults:
740 737 raise AttributeError(name)
741 738 if self.badalias or util.safehasattr(self, b'shell'):
742 739 return adefaults[name]
743 740 return getattr(self.fn, name)
744 741
745 742 def __call__(self, ui, *args, **opts):
746 743 if self.badalias:
747 744 hint = None
748 745 if self.unknowncmd:
749 746 try:
750 747 # check if the command is in a disabled extension
751 748 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
752 749 hint = _(b"'%s' is provided by '%s' extension") % (cmd, ext)
753 750 except error.UnknownCommand:
754 751 pass
755 752 raise error.ConfigError(self.badalias, hint=hint)
756 753 if self.shadows:
757 754 ui.debug(
758 755 b"alias '%s' shadows command '%s'\n" % (self.name, self.cmdname)
759 756 )
760 757
761 758 ui.log(
762 759 b'commandalias',
763 760 b"alias '%s' expands to '%s'\n",
764 761 self.name,
765 762 self.definition,
766 763 )
767 764 if util.safehasattr(self, b'shell'):
768 765 return self.fn(ui, *args, **opts)
769 766 else:
770 767 try:
771 768 return util.checksignature(self.fn)(ui, *args, **opts)
772 769 except error.SignatureError:
773 770 args = b' '.join([self.cmdname] + self.args)
774 771 ui.debug(b"alias '%s' expands to '%s'\n" % (self.name, args))
775 772 raise
776 773
777 774
778 775 class lazyaliasentry(object):
779 776 """like a typical command entry (func, opts, help), but is lazy"""
780 777
781 778 def __init__(self, ui, name, definition, cmdtable, source):
782 779 self.ui = ui
783 780 self.name = name
784 781 self.definition = definition
785 782 self.cmdtable = cmdtable.copy()
786 783 self.source = source
787 784 self.alias = True
788 785
789 786 @util.propertycache
790 787 def _aliasdef(self):
791 788 return cmdalias(
792 789 self.ui, self.name, self.definition, self.cmdtable, self.source
793 790 )
794 791
795 792 def __getitem__(self, n):
796 793 aliasdef = self._aliasdef
797 794 if n == 0:
798 795 return aliasdef
799 796 elif n == 1:
800 797 return aliasdef.opts
801 798 elif n == 2:
802 799 return aliasdef.help
803 800 else:
804 801 raise IndexError
805 802
806 803 def __iter__(self):
807 804 for i in range(3):
808 805 yield self[i]
809 806
810 807 def __len__(self):
811 808 return 3
812 809
813 810
814 811 def addaliases(ui, cmdtable):
815 812 # aliases are processed after extensions have been loaded, so they
816 813 # may use extension commands. Aliases can also use other alias definitions,
817 814 # but only if they have been defined prior to the current definition.
818 815 for alias, definition in ui.configitems(b'alias', ignoresub=True):
819 816 try:
820 817 if cmdtable[alias].definition == definition:
821 818 continue
822 819 except (KeyError, AttributeError):
823 820 # definition might not exist or it might not be a cmdalias
824 821 pass
825 822
826 823 source = ui.configsource(b'alias', alias)
827 824 entry = lazyaliasentry(ui, alias, definition, cmdtable, source)
828 825 cmdtable[alias] = entry
829 826
830 827
831 828 def _parse(ui, args):
832 829 options = {}
833 830 cmdoptions = {}
834 831
835 832 try:
836 833 args = fancyopts.fancyopts(args, commands.globalopts, options)
837 834 except getopt.GetoptError as inst:
838 835 raise error.CommandError(None, stringutil.forcebytestr(inst))
839 836
840 837 if args:
841 838 cmd, args = args[0], args[1:]
842 839 aliases, entry = cmdutil.findcmd(
843 840 cmd, commands.table, ui.configbool(b"ui", b"strict")
844 841 )
845 842 cmd = aliases[0]
846 843 args = aliasargs(entry[0], args)
847 844 defaults = ui.config(b"defaults", cmd)
848 845 if defaults:
849 846 args = (
850 847 pycompat.maplist(util.expandpath, pycompat.shlexsplit(defaults))
851 848 + args
852 849 )
853 850 c = list(entry[1])
854 851 else:
855 852 cmd = None
856 853 c = []
857 854
858 855 # combine global options into local
859 856 for o in commands.globalopts:
860 857 c.append((o[0], o[1], options[o[1]], o[3]))
861 858
862 859 try:
863 860 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
864 861 except getopt.GetoptError as inst:
865 862 raise error.CommandError(cmd, stringutil.forcebytestr(inst))
866 863
867 864 # separate global options back out
868 865 for o in commands.globalopts:
869 866 n = o[1]
870 867 options[n] = cmdoptions[n]
871 868 del cmdoptions[n]
872 869
873 870 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
874 871
875 872
876 873 def _parseconfig(ui, config):
877 874 """parse the --config options from the command line"""
878 875 configs = []
879 876
880 877 for cfg in config:
881 878 try:
882 879 name, value = [cfgelem.strip() for cfgelem in cfg.split(b'=', 1)]
883 880 section, name = name.split(b'.', 1)
884 881 if not section or not name:
885 882 raise IndexError
886 883 ui.setconfig(section, name, value, b'--config')
887 884 configs.append((section, name, value))
888 885 except (IndexError, ValueError):
889 886 raise error.InputError(
890 887 _(
891 888 b'malformed --config option: %r '
892 889 b'(use --config section.name=value)'
893 890 )
894 891 % pycompat.bytestr(cfg)
895 892 )
896 893
897 894 return configs
898 895
899 896
900 897 def _earlyparseopts(ui, args):
901 898 options = {}
902 899 fancyopts.fancyopts(
903 900 args,
904 901 commands.globalopts,
905 902 options,
906 903 gnu=not ui.plain(b'strictflags'),
907 904 early=True,
908 905 optaliases={b'repository': [b'repo']},
909 906 )
910 907 return options
911 908
912 909
913 910 def _earlysplitopts(args):
914 911 """Split args into a list of possible early options and remainder args"""
915 912 shortoptions = b'R:'
916 913 # TODO: perhaps 'debugger' should be included
917 914 longoptions = [b'cwd=', b'repository=', b'repo=', b'config=']
918 915 return fancyopts.earlygetopt(
919 916 args, shortoptions, longoptions, gnu=True, keepsep=True
920 917 )
921 918
922 919
923 920 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
924 921 # run pre-hook, and abort if it fails
925 922 hook.hook(
926 923 lui,
927 924 repo,
928 925 b"pre-%s" % cmd,
929 926 True,
930 927 args=b" ".join(fullargs),
931 928 pats=cmdpats,
932 929 opts=cmdoptions,
933 930 )
934 931 try:
935 932 ret = _runcommand(ui, options, cmd, d)
936 933 # run post-hook, passing command result
937 934 hook.hook(
938 935 lui,
939 936 repo,
940 937 b"post-%s" % cmd,
941 938 False,
942 939 args=b" ".join(fullargs),
943 940 result=ret,
944 941 pats=cmdpats,
945 942 opts=cmdoptions,
946 943 )
947 944 except Exception:
948 945 # run failure hook and re-raise
949 946 hook.hook(
950 947 lui,
951 948 repo,
952 949 b"fail-%s" % cmd,
953 950 False,
954 951 args=b" ".join(fullargs),
955 952 pats=cmdpats,
956 953 opts=cmdoptions,
957 954 )
958 955 raise
959 956 return ret
960 957
961 958
962 959 def _readsharedsourceconfig(ui, path):
963 960 """if the current repository is shared one, this tries to read
964 961 .hg/hgrc of shared source if we are in share-safe mode
965 962
966 963 Config read is loaded into the ui object passed
967 964
968 965 This should be called before reading .hg/hgrc or the main repo
969 966 as that overrides config set in shared source"""
970 967 try:
971 968 with open(os.path.join(path, b".hg", b"requires"), "rb") as fp:
972 969 requirements = set(fp.read().splitlines())
973 970 if not (
974 971 requirementsmod.SHARESAFE_REQUIREMENT in requirements
975 972 and requirementsmod.SHARED_REQUIREMENT in requirements
976 973 ):
977 974 return
978 975 hgvfs = vfs.vfs(os.path.join(path, b".hg"))
979 976 sharedvfs = localrepo._getsharedvfs(hgvfs, requirements)
980 977 root = sharedvfs.base
981 978 ui.readconfig(sharedvfs.join(b"hgrc"), root)
982 979 except IOError:
983 980 pass
984 981
985 982
986 983 def _getlocal(ui, rpath, wd=None):
987 984 """Return (path, local ui object) for the given target path.
988 985
989 986 Takes paths in [cwd]/.hg/hgrc into account."
990 987 """
991 988 if wd is None:
992 989 try:
993 990 wd = encoding.getcwd()
994 991 except OSError as e:
995 992 raise error.Abort(
996 993 _(b"error getting current working directory: %s")
997 994 % encoding.strtolocal(e.strerror)
998 995 )
999 996
1000 997 path = cmdutil.findrepo(wd) or b""
1001 998 if not path:
1002 999 lui = ui
1003 1000 else:
1004 1001 lui = ui.copy()
1005 1002 if rcutil.use_repo_hgrc():
1006 1003 _readsharedsourceconfig(lui, path)
1007 1004 lui.readconfig(os.path.join(path, b".hg", b"hgrc"), path)
1008 1005 lui.readconfig(os.path.join(path, b".hg", b"hgrc-not-shared"), path)
1009 1006
1010 1007 if rpath:
1011 1008 path = urlutil.get_clone_path(lui, rpath)[0]
1012 1009 lui = ui.copy()
1013 1010 if rcutil.use_repo_hgrc():
1014 1011 _readsharedsourceconfig(lui, path)
1015 1012 lui.readconfig(os.path.join(path, b".hg", b"hgrc"), path)
1016 1013 lui.readconfig(os.path.join(path, b".hg", b"hgrc-not-shared"), path)
1017 1014
1018 1015 return path, lui
1019 1016
1020 1017
1021 1018 def _checkshellalias(lui, ui, args):
1022 1019 """Return the function to run the shell alias, if it is required"""
1023 1020 options = {}
1024 1021
1025 1022 try:
1026 1023 args = fancyopts.fancyopts(args, commands.globalopts, options)
1027 1024 except getopt.GetoptError:
1028 1025 return
1029 1026
1030 1027 if not args:
1031 1028 return
1032 1029
1033 1030 cmdtable = commands.table
1034 1031
1035 1032 cmd = args[0]
1036 1033 try:
1037 1034 strict = ui.configbool(b"ui", b"strict")
1038 1035 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
1039 1036 except (error.AmbiguousCommand, error.UnknownCommand):
1040 1037 return
1041 1038
1042 1039 cmd = aliases[0]
1043 1040 fn = entry[0]
1044 1041
1045 1042 if cmd and util.safehasattr(fn, b'shell'):
1046 1043 # shell alias shouldn't receive early options which are consumed by hg
1047 1044 _earlyopts, args = _earlysplitopts(args)
1048 1045 d = lambda: fn(ui, *args[1:])
1049 1046 return lambda: runcommand(
1050 1047 lui, None, cmd, args[:1], ui, options, d, [], {}
1051 1048 )
1052 1049
1053 1050
1054 1051 def _dispatch(req):
1055 1052 args = req.args
1056 1053 ui = req.ui
1057 1054
1058 1055 # check for cwd
1059 1056 cwd = req.earlyoptions[b'cwd']
1060 1057 if cwd:
1061 1058 os.chdir(cwd)
1062 1059
1063 1060 rpath = req.earlyoptions[b'repository']
1064 1061 path, lui = _getlocal(ui, rpath)
1065 1062
1066 1063 uis = {ui, lui}
1067 1064
1068 1065 if req.repo:
1069 1066 uis.add(req.repo.ui)
1070 1067
1071 1068 if (
1072 1069 req.earlyoptions[b'verbose']
1073 1070 or req.earlyoptions[b'debug']
1074 1071 or req.earlyoptions[b'quiet']
1075 1072 ):
1076 1073 for opt in (b'verbose', b'debug', b'quiet'):
1077 1074 val = pycompat.bytestr(bool(req.earlyoptions[opt]))
1078 1075 for ui_ in uis:
1079 1076 ui_.setconfig(b'ui', opt, val, b'--' + opt)
1080 1077
1081 1078 if req.earlyoptions[b'profile']:
1082 1079 for ui_ in uis:
1083 1080 ui_.setconfig(b'profiling', b'enabled', b'true', b'--profile')
1084 1081 elif req.earlyoptions[b'profile'] is False:
1085 1082 # Check for it being set already, so that we don't pollute the config
1086 1083 # with this when using chg in the very common case that it's not
1087 1084 # enabled.
1088 1085 if lui.configbool(b'profiling', b'enabled'):
1089 1086 # Only do this on lui so that `chg foo` with a user config setting
1090 1087 # profiling.enabled=1 still shows profiling information (chg will
1091 1088 # specify `--no-profile` when `hg serve` is starting up, we don't
1092 1089 # want that to propagate to every later invocation).
1093 1090 lui.setconfig(b'profiling', b'enabled', b'false', b'--no-profile')
1094 1091
1095 1092 profile = lui.configbool(b'profiling', b'enabled')
1096 1093 with profiling.profile(lui, enabled=profile) as profiler:
1097 1094 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
1098 1095 # reposetup
1099 1096 extensions.loadall(lui)
1100 1097 # Propagate any changes to lui.__class__ by extensions
1101 1098 ui.__class__ = lui.__class__
1102 1099
1103 1100 # (uisetup and extsetup are handled in extensions.loadall)
1104 1101
1105 1102 # (reposetup is handled in hg.repository)
1106 1103
1107 1104 addaliases(lui, commands.table)
1108 1105
1109 1106 # All aliases and commands are completely defined, now.
1110 1107 # Check abbreviation/ambiguity of shell alias.
1111 1108 shellaliasfn = _checkshellalias(lui, ui, args)
1112 1109 if shellaliasfn:
1113 1110 # no additional configs will be set, set up the ui instances
1114 1111 for ui_ in uis:
1115 1112 extensions.populateui(ui_)
1116 1113 return shellaliasfn()
1117 1114
1118 1115 # check for fallback encoding
1119 1116 fallback = lui.config(b'ui', b'fallbackencoding')
1120 1117 if fallback:
1121 1118 encoding.fallbackencoding = fallback
1122 1119
1123 1120 fullargs = args
1124 1121 cmd, func, args, options, cmdoptions = _parse(lui, args)
1125 1122
1126 1123 # store the canonical command name in request object for later access
1127 1124 req.canonical_command = cmd
1128 1125
1129 1126 if options[b"config"] != req.earlyoptions[b"config"]:
1130 1127 raise error.InputError(_(b"option --config may not be abbreviated"))
1131 1128 if options[b"cwd"] != req.earlyoptions[b"cwd"]:
1132 1129 raise error.InputError(_(b"option --cwd may not be abbreviated"))
1133 1130 if options[b"repository"] != req.earlyoptions[b"repository"]:
1134 1131 raise error.InputError(
1135 1132 _(
1136 1133 b"option -R has to be separated from other options (e.g. not "
1137 1134 b"-qR) and --repository may only be abbreviated as --repo"
1138 1135 )
1139 1136 )
1140 1137 if options[b"debugger"] != req.earlyoptions[b"debugger"]:
1141 1138 raise error.InputError(
1142 1139 _(b"option --debugger may not be abbreviated")
1143 1140 )
1144 1141 # don't validate --profile/--traceback, which can be enabled from now
1145 1142
1146 1143 if options[b"encoding"]:
1147 1144 encoding.encoding = options[b"encoding"]
1148 1145 if options[b"encodingmode"]:
1149 1146 encoding.encodingmode = options[b"encodingmode"]
1150 1147 if options[b"time"]:
1151 1148
1152 1149 def get_times():
1153 1150 t = os.times()
1154 1151 if t[4] == 0.0:
1155 1152 # Windows leaves this as zero, so use time.perf_counter()
1156 1153 t = (t[0], t[1], t[2], t[3], util.timer())
1157 1154 return t
1158 1155
1159 1156 s = get_times()
1160 1157
1161 1158 def print_time():
1162 1159 t = get_times()
1163 1160 ui.warn(
1164 1161 _(b"time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n")
1165 1162 % (
1166 1163 t[4] - s[4],
1167 1164 t[0] - s[0],
1168 1165 t[2] - s[2],
1169 1166 t[1] - s[1],
1170 1167 t[3] - s[3],
1171 1168 )
1172 1169 )
1173 1170
1174 1171 ui.atexit(print_time)
1175 1172 if options[b"profile"]:
1176 1173 profiler.start()
1177 1174
1178 1175 # if abbreviated version of this were used, take them in account, now
1179 1176 if options[b'verbose'] or options[b'debug'] or options[b'quiet']:
1180 1177 for opt in (b'verbose', b'debug', b'quiet'):
1181 1178 if options[opt] == req.earlyoptions[opt]:
1182 1179 continue
1183 1180 val = pycompat.bytestr(bool(options[opt]))
1184 1181 for ui_ in uis:
1185 1182 ui_.setconfig(b'ui', opt, val, b'--' + opt)
1186 1183
1187 1184 if options[b'traceback']:
1188 1185 for ui_ in uis:
1189 1186 ui_.setconfig(b'ui', b'traceback', b'on', b'--traceback')
1190 1187
1191 1188 if options[b'noninteractive']:
1192 1189 for ui_ in uis:
1193 1190 ui_.setconfig(b'ui', b'interactive', b'off', b'-y')
1194 1191
1195 1192 if cmdoptions.get(b'insecure', False):
1196 1193 for ui_ in uis:
1197 1194 ui_.insecureconnections = True
1198 1195
1199 1196 # setup color handling before pager, because setting up pager
1200 1197 # might cause incorrect console information
1201 1198 coloropt = options[b'color']
1202 1199 for ui_ in uis:
1203 1200 if coloropt:
1204 1201 ui_.setconfig(b'ui', b'color', coloropt, b'--color')
1205 1202 color.setup(ui_)
1206 1203
1207 1204 if stringutil.parsebool(options[b'pager']):
1208 1205 # ui.pager() expects 'internal-always-' prefix in this case
1209 1206 ui.pager(b'internal-always-' + cmd)
1210 1207 elif options[b'pager'] != b'auto':
1211 1208 for ui_ in uis:
1212 1209 ui_.disablepager()
1213 1210
1214 1211 # configs are fully loaded, set up the ui instances
1215 1212 for ui_ in uis:
1216 1213 extensions.populateui(ui_)
1217 1214
1218 1215 if options[b'version']:
1219 1216 return commands.version_(ui)
1220 1217 if options[b'help']:
1221 1218 return commands.help_(ui, cmd, command=cmd is not None)
1222 1219 elif not cmd:
1223 1220 return commands.help_(ui, b'shortlist')
1224 1221
1225 1222 repo = None
1226 1223 cmdpats = args[:]
1227 1224 assert func is not None # help out pytype
1228 1225 if not func.norepo:
1229 1226 # use the repo from the request only if we don't have -R
1230 1227 if not rpath and not cwd:
1231 1228 repo = req.repo
1232 1229
1233 1230 if repo:
1234 1231 # set the descriptors of the repo ui to those of ui
1235 1232 repo.ui.fin = ui.fin
1236 1233 repo.ui.fout = ui.fout
1237 1234 repo.ui.ferr = ui.ferr
1238 1235 repo.ui.fmsg = ui.fmsg
1239 1236 else:
1240 1237 try:
1241 1238 repo = hg.repository(
1242 1239 ui,
1243 1240 path=path,
1244 1241 presetupfuncs=req.prereposetups,
1245 1242 intents=func.intents,
1246 1243 )
1247 1244 if not repo.local():
1248 1245 raise error.InputError(
1249 1246 _(b"repository '%s' is not local") % path
1250 1247 )
1251 1248 repo.ui.setconfig(
1252 1249 b"bundle", b"mainreporoot", repo.root, b'repo'
1253 1250 )
1254 1251 except error.RequirementError:
1255 1252 raise
1256 1253 except error.RepoError:
1257 1254 if rpath: # invalid -R path
1258 1255 raise
1259 1256 if not func.optionalrepo:
1260 1257 if func.inferrepo and args and not path:
1261 1258 # try to infer -R from command args
1262 1259 repos = pycompat.maplist(cmdutil.findrepo, args)
1263 1260 guess = repos[0]
1264 1261 if guess and repos.count(guess) == len(repos):
1265 1262 req.args = [b'--repository', guess] + fullargs
1266 1263 req.earlyoptions[b'repository'] = guess
1267 1264 return _dispatch(req)
1268 1265 if not path:
1269 1266 raise error.InputError(
1270 1267 _(
1271 1268 b"no repository found in"
1272 1269 b" '%s' (.hg not found)"
1273 1270 )
1274 1271 % encoding.getcwd()
1275 1272 )
1276 1273 raise
1277 1274 if repo:
1278 1275 ui = repo.ui
1279 1276 if options[b'hidden']:
1280 1277 repo = repo.unfiltered()
1281 1278 args.insert(0, repo)
1282 1279 elif rpath:
1283 1280 ui.warn(_(b"warning: --repository ignored\n"))
1284 1281
1285 1282 msg = _formatargs(fullargs)
1286 1283 ui.log(b"command", b'%s\n', msg)
1287 1284 strcmdopt = pycompat.strkwargs(cmdoptions)
1288 1285 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
1289 1286 try:
1290 1287 return runcommand(
1291 1288 lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions
1292 1289 )
1293 1290 finally:
1294 1291 if repo and repo != req.repo:
1295 1292 repo.close()
1296 1293
1297 1294
1298 1295 def _runcommand(ui, options, cmd, cmdfunc):
1299 1296 """Run a command function, possibly with profiling enabled."""
1300 1297 try:
1301 1298 with tracing.log("Running %s command" % cmd):
1302 1299 return cmdfunc()
1303 1300 except error.SignatureError:
1304 1301 raise error.CommandError(cmd, _(b'invalid arguments'))
1305 1302
1306 1303
1307 1304 def _exceptionwarning(ui):
1308 1305 """Produce a warning message for the current active exception"""
1309 1306
1310 1307 # For compatibility checking, we discard the portion of the hg
1311 1308 # version after the + on the assumption that if a "normal
1312 1309 # user" is running a build with a + in it the packager
1313 1310 # probably built from fairly close to a tag and anyone with a
1314 1311 # 'make local' copy of hg (where the version number can be out
1315 1312 # of date) will be clueful enough to notice the implausible
1316 1313 # version number and try updating.
1317 1314 ct = util.versiontuple(n=2)
1318 1315 worst = None, ct, b'', b''
1319 1316 if ui.config(b'ui', b'supportcontact') is None:
1320 1317 for name, mod in extensions.extensions():
1321 1318 # 'testedwith' should be bytes, but not all extensions are ported
1322 1319 # to py3 and we don't want UnicodeException because of that.
1323 1320 testedwith = stringutil.forcebytestr(
1324 1321 getattr(mod, 'testedwith', b'')
1325 1322 )
1326 1323 version = extensions.moduleversion(mod)
1327 1324 report = getattr(mod, 'buglink', _(b'the extension author.'))
1328 1325 if not testedwith.strip():
1329 1326 # We found an untested extension. It's likely the culprit.
1330 1327 worst = name, b'unknown', report, version
1331 1328 break
1332 1329
1333 1330 # Never blame on extensions bundled with Mercurial.
1334 1331 if extensions.ismoduleinternal(mod):
1335 1332 continue
1336 1333
1337 1334 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
1338 1335 if ct in tested:
1339 1336 continue
1340 1337
1341 1338 lower = [t for t in tested if t < ct]
1342 1339 nearest = max(lower or tested)
1343 1340 if worst[0] is None or nearest < worst[1]:
1344 1341 worst = name, nearest, report, version
1345 1342 if worst[0] is not None:
1346 1343 name, testedwith, report, version = worst
1347 1344 if not isinstance(testedwith, (bytes, str)):
1348 1345 testedwith = b'.'.join(
1349 1346 [stringutil.forcebytestr(c) for c in testedwith]
1350 1347 )
1351 1348 extver = version or _(b"(version N/A)")
1352 1349 warning = _(
1353 1350 b'** Unknown exception encountered with '
1354 1351 b'possibly-broken third-party extension "%s" %s\n'
1355 1352 b'** which supports versions %s of Mercurial.\n'
1356 1353 b'** Please disable "%s" and try your action again.\n'
1357 1354 b'** If that fixes the bug please report it to %s\n'
1358 1355 ) % (name, extver, testedwith, name, stringutil.forcebytestr(report))
1359 1356 else:
1360 1357 bugtracker = ui.config(b'ui', b'supportcontact')
1361 1358 if bugtracker is None:
1362 1359 bugtracker = _(b"https://mercurial-scm.org/wiki/BugTracker")
1363 1360 warning = (
1364 1361 _(
1365 1362 b"** unknown exception encountered, "
1366 1363 b"please report by visiting\n** "
1367 1364 )
1368 1365 + bugtracker
1369 1366 + b'\n'
1370 1367 )
1371 1368 sysversion = pycompat.sysbytes(sys.version).replace(b'\n', b'')
1372 1369
1373 1370 def ext_with_ver(x):
1374 1371 ext = x[0]
1375 1372 ver = extensions.moduleversion(x[1])
1376 1373 if ver:
1377 1374 ext += b' ' + ver
1378 1375 return ext
1379 1376
1380 1377 warning += (
1381 1378 (_(b"** Python %s\n") % sysversion)
1382 1379 + (_(b"** Mercurial Distributed SCM (version %s)\n") % util.version())
1383 1380 + (
1384 1381 _(b"** Extensions loaded: %s\n")
1385 1382 % b", ".join(
1386 1383 [ext_with_ver(x) for x in sorted(extensions.extensions())]
1387 1384 )
1388 1385 )
1389 1386 )
1390 1387 return warning
1391 1388
1392 1389
1393 1390 def handlecommandexception(ui):
1394 1391 """Produce a warning message for broken commands
1395 1392
1396 1393 Called when handling an exception; the exception is reraised if
1397 1394 this function returns False, ignored otherwise.
1398 1395 """
1399 1396 warning = _exceptionwarning(ui)
1400 1397 ui.log(
1401 1398 b"commandexception",
1402 1399 b"%s\n%s\n",
1403 1400 warning,
1404 1401 pycompat.sysbytes(traceback.format_exc()),
1405 1402 )
1406 1403 ui.warn(warning)
1407 1404 return False # re-raise the exception
@@ -1,539 +1,539 b''
1 1 #require chg
2 2
3 3 $ mkdir log
4 4 $ cp $HGRCPATH $HGRCPATH.unconfigured
5 5 $ cat <<'EOF' >> $HGRCPATH
6 6 > [cmdserver]
7 7 > log = $TESTTMP/log/server.log
8 8 > max-log-files = 1
9 9 > max-log-size = 10 kB
10 10 > EOF
11 11 $ cp $HGRCPATH $HGRCPATH.orig
12 12
13 13 $ filterlog () {
14 14 > sed -e 's!^[0-9/]* [0-9:]* ([0-9]*)>!YYYY/MM/DD HH:MM:SS (PID)>!' \
15 15 > -e 's!\(setprocname\|received fds\|setenv\): .*!\1: ...!' \
16 16 > -e 's!\(confighash\|mtimehash\) = [0-9a-f]*!\1 = ...!g' \
17 17 > -e 's!\(in \)[0-9.]*s\b!\1 ...s!g' \
18 18 > -e 's!\(pid\)=[0-9]*!\1=...!g' \
19 19 > -e 's!\(/server-\)[0-9a-f]*!\1...!g'
20 20 > }
21 21
22 22 init repo
23 23
24 24 $ chg init foo
25 25 $ cd foo
26 26
27 27 ill-formed config
28 28
29 29 $ chg status
30 30 $ echo '=brokenconfig' >> $HGRCPATH
31 31 $ chg status
32 32 config error at * =brokenconfig (glob)
33 33 [30]
34 34
35 35 $ cp $HGRCPATH.orig $HGRCPATH
36 36
37 37 long socket path
38 38
39 39 $ sockpath=$TESTTMP/this/path/should/be/longer/than/one-hundred-and-seven/characters/where/107/is/the/typical/size/limit/of/unix-domain-socket
40 40 $ mkdir -p $sockpath
41 41 $ bakchgsockname=$CHGSOCKNAME
42 42 $ CHGSOCKNAME=$sockpath/server
43 43 $ export CHGSOCKNAME
44 44 $ chg root
45 45 $TESTTMP/foo
46 46 $ rm -rf $sockpath
47 47 $ CHGSOCKNAME=$bakchgsockname
48 48 $ export CHGSOCKNAME
49 49
50 50 $ cd ..
51 51
52 52 editor
53 53 ------
54 54
55 55 $ cat >> pushbuffer.py <<EOF
56 56 > def reposetup(ui, repo):
57 57 > repo.ui.pushbuffer(subproc=True)
58 58 > EOF
59 59
60 60 $ chg init editor
61 61 $ cd editor
62 62
63 63 by default, system() should be redirected to the client:
64 64
65 65 $ touch foo
66 66 $ CHGDEBUG= HGEDITOR=cat chg ci -Am channeled --edit 2>&1 \
67 67 > | egrep "HG:|run 'cat"
68 68 chg: debug: * run 'cat "*"' at '$TESTTMP/editor' (glob)
69 69 HG: Enter commit message. Lines beginning with 'HG:' are removed.
70 70 HG: Leave message empty to abort commit.
71 71 HG: --
72 72 HG: user: test
73 73 HG: branch 'default'
74 74 HG: added foo
75 75
76 76 but no redirection should be made if output is captured:
77 77
78 78 $ touch bar
79 79 $ CHGDEBUG= HGEDITOR=cat chg ci -Am bufferred --edit \
80 80 > --config extensions.pushbuffer="$TESTTMP/pushbuffer.py" 2>&1 \
81 81 > | egrep "HG:|run 'cat"
82 82 [1]
83 83
84 84 check that commit commands succeeded:
85 85
86 86 $ hg log -T '{rev}:{desc}\n'
87 87 1:bufferred
88 88 0:channeled
89 89
90 90 $ cd ..
91 91
92 92 pager
93 93 -----
94 94
95 95 $ cat >> fakepager.py <<EOF
96 96 > import sys
97 97 > for line in sys.stdin:
98 98 > sys.stdout.write('paged! %r\n' % line)
99 99 > EOF
100 100
101 101 enable pager extension globally, but spawns the master server with no tty:
102 102
103 103 $ chg init pager
104 104 $ cd pager
105 105 $ cat >> $HGRCPATH <<EOF
106 106 > [extensions]
107 107 > pager =
108 108 > [pager]
109 109 > pager = "$PYTHON" $TESTTMP/fakepager.py
110 110 > EOF
111 111 $ chg version > /dev/null
112 112 $ touch foo
113 113 $ chg ci -qAm foo
114 114
115 115 pager should be enabled if the attached client has a tty:
116 116
117 117 $ chg log -l1 -q --config ui.formatted=True
118 118 paged! '0:1f7b0de80e11\n'
119 119 $ chg log -l1 -q --config ui.formatted=False
120 120 0:1f7b0de80e11
121 121
122 122 chg waits for pager if runcommand raises
123 123
124 124 $ cat > $TESTTMP/crash.py <<EOF
125 125 > from mercurial import registrar
126 126 > cmdtable = {}
127 127 > command = registrar.command(cmdtable)
128 128 > @command(b'crash')
129 129 > def pagercrash(ui, repo, *pats, **opts):
130 130 > ui.write(b'going to crash\n')
131 131 > raise Exception('.')
132 132 > EOF
133 133
134 134 $ cat > $TESTTMP/fakepager.py <<EOF
135 135 > from __future__ import absolute_import
136 136 > import sys
137 137 > import time
138 138 > for line in iter(sys.stdin.readline, ''):
139 139 > if 'crash' in line: # only interested in lines containing 'crash'
140 140 > # if chg exits when pager is sleeping (incorrectly), the output
141 141 > # will be captured by the next test case
142 142 > time.sleep(1)
143 143 > sys.stdout.write('crash-pager: %s' % line)
144 144 > EOF
145 145
146 146 $ cat >> .hg/hgrc <<EOF
147 147 > [extensions]
148 148 > crash = $TESTTMP/crash.py
149 149 > EOF
150 150
151 151 $ chg crash --pager=on --config ui.formatted=True 2>/dev/null
152 152 crash-pager: going to crash
153 153 [255]
154 154
155 155 no stdout data should be printed after pager quits, and the buffered data
156 156 should never persist (issue6207)
157 157
158 158 "killed!" may be printed if terminated by SIGPIPE, which isn't important
159 159 in this test.
160 160
161 161 $ cat > $TESTTMP/bulkwrite.py <<'EOF'
162 162 > import time
163 163 > from mercurial import error, registrar
164 164 > cmdtable = {}
165 165 > command = registrar.command(cmdtable)
166 166 > @command(b'bulkwrite')
167 167 > def bulkwrite(ui, repo, *pats, **opts):
168 168 > ui.write(b'going to write massive data\n')
169 169 > ui.flush()
170 170 > t = time.time()
171 171 > while time.time() - t < 2:
172 172 > ui.write(b'x' * 1023 + b'\n') # will be interrupted by SIGPIPE
173 173 > raise error.Abort(b"write() doesn't block")
174 174 > EOF
175 175
176 176 $ cat > $TESTTMP/fakepager.py <<'EOF'
177 177 > import sys
178 178 > import time
179 179 > sys.stdout.write('paged! %r\n' % sys.stdin.readline())
180 180 > time.sleep(1) # new data will be written
181 181 > EOF
182 182
183 183 $ cat >> .hg/hgrc <<EOF
184 184 > [extensions]
185 185 > bulkwrite = $TESTTMP/bulkwrite.py
186 186 > EOF
187 187
188 188 $ chg bulkwrite --pager=on --color no --config ui.formatted=True
189 189 paged! 'going to write massive data\n'
190 190 killed! (?)
191 [250]
191 [255]
192 192
193 193 $ chg bulkwrite --pager=on --color no --config ui.formatted=True
194 194 paged! 'going to write massive data\n'
195 195 killed! (?)
196 [250]
196 [255]
197 197
198 198 $ cd ..
199 199
200 200 missing stdio
201 201 -------------
202 202
203 203 $ CHGDEBUG=1 chg version -q 0<&-
204 204 chg: debug: * stdio fds are missing (glob)
205 205 chg: debug: * execute original hg (glob)
206 206 Mercurial Distributed SCM * (glob)
207 207
208 208 server lifecycle
209 209 ----------------
210 210
211 211 chg server should be restarted on code change, and old server will shut down
212 212 automatically. In this test, we use the following time parameters:
213 213
214 214 - "sleep 1" to make mtime different
215 215 - "sleep 2" to notice mtime change (polling interval is 1 sec)
216 216
217 217 set up repository with an extension:
218 218
219 219 $ chg init extreload
220 220 $ cd extreload
221 221 $ touch dummyext.py
222 222 $ cat <<EOF >> .hg/hgrc
223 223 > [extensions]
224 224 > dummyext = dummyext.py
225 225 > EOF
226 226
227 227 isolate socket directory for stable result:
228 228
229 229 $ OLDCHGSOCKNAME=$CHGSOCKNAME
230 230 $ mkdir chgsock
231 231 $ CHGSOCKNAME=`pwd`/chgsock/server
232 232
233 233 warm up server:
234 234
235 235 $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
236 236 chg: debug: * start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
237 237
238 238 new server should be started if extension modified:
239 239
240 240 $ sleep 1
241 241 $ touch dummyext.py
242 242 $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
243 243 chg: debug: * instruction: unlink $TESTTMP/extreload/chgsock/server-* (glob)
244 244 chg: debug: * instruction: reconnect (glob)
245 245 chg: debug: * start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
246 246
247 247 old server will shut down, while new server should still be reachable:
248 248
249 249 $ sleep 2
250 250 $ CHGDEBUG= chg log 2>&1 | (egrep 'instruction|start' || true)
251 251
252 252 socket file should never be unlinked by old server:
253 253 (simulates unowned socket by updating mtime, which makes sure server exits
254 254 at polling cycle)
255 255
256 256 $ ls chgsock/server-*
257 257 chgsock/server-* (glob)
258 258 $ touch chgsock/server-*
259 259 $ sleep 2
260 260 $ ls chgsock/server-*
261 261 chgsock/server-* (glob)
262 262
263 263 since no server is reachable from socket file, new server should be started:
264 264 (this test makes sure that old server shut down automatically)
265 265
266 266 $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
267 267 chg: debug: * start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
268 268
269 269 shut down servers and restore environment:
270 270
271 271 $ rm -R chgsock
272 272 $ sleep 2
273 273 $ CHGSOCKNAME=$OLDCHGSOCKNAME
274 274 $ cd ..
275 275
276 276 check that server events are recorded:
277 277
278 278 $ ls log
279 279 server.log
280 280 server.log.1
281 281
282 282 print only the last 10 lines, since we aren't sure how many records are
283 283 preserved (since setprocname isn't available on py3 and pure version,
284 284 the 10th-most-recent line is different when using py3):
285 285
286 286 $ cat log/server.log.1 log/server.log | tail -10 | filterlog
287 287 YYYY/MM/DD HH:MM:SS (PID)> confighash = ... mtimehash = ... (no-setprocname !)
288 288 YYYY/MM/DD HH:MM:SS (PID)> forked worker process (pid=...)
289 289 YYYY/MM/DD HH:MM:SS (PID)> setprocname: ... (setprocname !)
290 290 YYYY/MM/DD HH:MM:SS (PID)> received fds: ...
291 291 YYYY/MM/DD HH:MM:SS (PID)> chdir to '$TESTTMP/extreload'
292 292 YYYY/MM/DD HH:MM:SS (PID)> setumask 18
293 293 YYYY/MM/DD HH:MM:SS (PID)> setenv: ...
294 294 YYYY/MM/DD HH:MM:SS (PID)> confighash = ... mtimehash = ...
295 295 YYYY/MM/DD HH:MM:SS (PID)> validate: []
296 296 YYYY/MM/DD HH:MM:SS (PID)> worker process exited (pid=...)
297 297 YYYY/MM/DD HH:MM:SS (PID)> $TESTTMP/extreload/chgsock/server-... is not owned, exiting.
298 298
299 299 global data mutated by schems
300 300 -----------------------------
301 301
302 302 $ hg init schemes
303 303 $ cd schemes
304 304
305 305 initial state
306 306
307 307 $ cat > .hg/hgrc <<'EOF'
308 308 > [extensions]
309 309 > schemes =
310 310 > [schemes]
311 311 > foo = https://foo.example.org/
312 312 > EOF
313 313 $ hg debugexpandscheme foo://expanded
314 314 https://foo.example.org/expanded
315 315 $ hg debugexpandscheme bar://unexpanded
316 316 bar://unexpanded
317 317
318 318 add bar
319 319
320 320 $ cat > .hg/hgrc <<'EOF'
321 321 > [extensions]
322 322 > schemes =
323 323 > [schemes]
324 324 > foo = https://foo.example.org/
325 325 > bar = https://bar.example.org/
326 326 > EOF
327 327 $ hg debugexpandscheme foo://expanded
328 328 https://foo.example.org/expanded
329 329 $ hg debugexpandscheme bar://expanded
330 330 https://bar.example.org/expanded
331 331
332 332 remove foo
333 333
334 334 $ cat > .hg/hgrc <<'EOF'
335 335 > [extensions]
336 336 > schemes =
337 337 > [schemes]
338 338 > bar = https://bar.example.org/
339 339 > EOF
340 340 $ hg debugexpandscheme foo://unexpanded
341 341 foo://unexpanded
342 342 $ hg debugexpandscheme bar://expanded
343 343 https://bar.example.org/expanded
344 344
345 345 $ cd ..
346 346
347 347 repository cache
348 348 ----------------
349 349
350 350 $ rm log/server.log*
351 351 $ cp $HGRCPATH.unconfigured $HGRCPATH
352 352 $ cat <<'EOF' >> $HGRCPATH
353 353 > [cmdserver]
354 354 > log = $TESTTMP/log/server.log
355 355 > max-repo-cache = 1
356 356 > track-log = command, repocache
357 357 > EOF
358 358
359 359 isolate socket directory for stable result:
360 360
361 361 $ OLDCHGSOCKNAME=$CHGSOCKNAME
362 362 $ mkdir chgsock
363 363 $ CHGSOCKNAME=`pwd`/chgsock/server
364 364
365 365 create empty repo and cache it:
366 366
367 367 $ hg init cached
368 368 $ hg id -R cached
369 369 000000000000 tip
370 370 $ sleep 1
371 371
372 372 modify repo (and cache will be invalidated):
373 373
374 374 $ touch cached/a
375 375 $ hg ci -R cached -Am 'add a'
376 376 adding a
377 377 $ sleep 1
378 378
379 379 read cached repo:
380 380
381 381 $ hg log -R cached
382 382 changeset: 0:ac82d8b1f7c4
383 383 tag: tip
384 384 user: test
385 385 date: Thu Jan 01 00:00:00 1970 +0000
386 386 summary: add a
387 387
388 388 $ sleep 1
389 389
390 390 discard cached from LRU cache:
391 391
392 392 $ hg clone cached cached2
393 393 updating to branch default
394 394 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
395 395 $ hg id -R cached2
396 396 ac82d8b1f7c4 tip
397 397 $ sleep 1
398 398
399 399 read uncached repo:
400 400
401 401 $ hg log -R cached
402 402 changeset: 0:ac82d8b1f7c4
403 403 tag: tip
404 404 user: test
405 405 date: Thu Jan 01 00:00:00 1970 +0000
406 406 summary: add a
407 407
408 408 $ sleep 1
409 409
410 410 shut down servers and restore environment:
411 411
412 412 $ rm -R chgsock
413 413 $ sleep 2
414 414 $ CHGSOCKNAME=$OLDCHGSOCKNAME
415 415
416 416 check server log:
417 417
418 418 $ cat log/server.log | filterlog
419 419 YYYY/MM/DD HH:MM:SS (PID)> worker process exited (pid=...)
420 420 YYYY/MM/DD HH:MM:SS (PID)> worker process exited (pid=...) (?)
421 421 YYYY/MM/DD HH:MM:SS (PID)> init cached
422 422 YYYY/MM/DD HH:MM:SS (PID)> id -R cached
423 423 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached (in ...s)
424 424 YYYY/MM/DD HH:MM:SS (PID)> repo from cache: $TESTTMP/cached
425 425 YYYY/MM/DD HH:MM:SS (PID)> ci -R cached -Am 'add a'
426 426 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached (in ...s)
427 427 YYYY/MM/DD HH:MM:SS (PID)> repo from cache: $TESTTMP/cached
428 428 YYYY/MM/DD HH:MM:SS (PID)> log -R cached
429 429 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached (in ...s)
430 430 YYYY/MM/DD HH:MM:SS (PID)> clone cached cached2
431 431 YYYY/MM/DD HH:MM:SS (PID)> id -R cached2
432 432 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached2 (in ...s)
433 433 YYYY/MM/DD HH:MM:SS (PID)> log -R cached
434 434 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached (in ...s)
435 435
436 436 Test that chg works (sets to the user's actual LC_CTYPE) even when python
437 437 "coerces" the locale (py3.7+)
438 438
439 439 $ cat > $TESTTMP/debugenv.py <<EOF
440 440 > from mercurial import encoding
441 441 > from mercurial import registrar
442 442 > cmdtable = {}
443 443 > command = registrar.command(cmdtable)
444 444 > @command(b'debugenv', [], b'', norepo=True)
445 445 > def debugenv(ui):
446 446 > for k in [b'LC_ALL', b'LC_CTYPE', b'LANG']:
447 447 > v = encoding.environ.get(k)
448 448 > if v is not None:
449 449 > ui.write(b'%s=%s\n' % (k, encoding.environ[k]))
450 450 > EOF
451 451 (hg keeps python's modified LC_CTYPE, chg doesn't)
452 452 $ (unset LC_ALL; unset LANG; LC_CTYPE= "$CHGHG" \
453 453 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
454 454 LC_CTYPE=C.UTF-8 (py37 !)
455 455 LC_CTYPE= (no-py37 !)
456 456 $ (unset LC_ALL; unset LANG; LC_CTYPE= chg \
457 457 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
458 458 LC_CTYPE=
459 459 $ (unset LC_ALL; unset LANG; LC_CTYPE=unsupported_value chg \
460 460 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
461 461 *cannot change locale* (glob) (?)
462 462 LC_CTYPE=unsupported_value
463 463 $ (unset LC_ALL; unset LANG; LC_CTYPE= chg \
464 464 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
465 465 LC_CTYPE=
466 466 $ LANG= LC_ALL= LC_CTYPE= chg \
467 467 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv
468 468 LC_ALL=
469 469 LC_CTYPE=
470 470 LANG=
471 471
472 472 Profiling isn't permanently enabled or carried over between chg invocations that
473 473 share the same server
474 474 $ cp $HGRCPATH.orig $HGRCPATH
475 475 $ hg init $TESTTMP/profiling
476 476 $ cd $TESTTMP/profiling
477 477 $ filteredchg() {
478 478 > CHGDEBUG=1 chg "$@" 2>&1 | egrep 'Sample count|start cmdserver' || true
479 479 > }
480 480 $ newchg() {
481 481 > chg --kill-chg-daemon
482 482 > filteredchg "$@" | egrep -v 'start cmdserver' || true
483 483 > }
484 484 (--profile isn't permanently on just because it was specified when chg was
485 485 started)
486 486 $ newchg log -r . --profile
487 487 Sample count: * (glob)
488 488 $ filteredchg log -r .
489 489 (enabling profiling via config works, even on the first chg command that starts
490 490 a cmdserver)
491 491 $ cat >> $HGRCPATH <<EOF
492 492 > [profiling]
493 493 > type=stat
494 494 > enabled=1
495 495 > EOF
496 496 $ newchg log -r .
497 497 Sample count: * (glob)
498 498 $ filteredchg log -r .
499 499 Sample count: * (glob)
500 500 (test that we aren't accumulating more and more samples each run)
501 501 $ cat > $TESTTMP/debugsleep.py <<EOF
502 502 > import time
503 503 > from mercurial import registrar
504 504 > cmdtable = {}
505 505 > command = registrar.command(cmdtable)
506 506 > @command(b'debugsleep', [], b'', norepo=True)
507 507 > def debugsleep(ui):
508 508 > start = time.time()
509 509 > x = 0
510 510 > while time.time() < start + 0.5:
511 511 > time.sleep(.1)
512 512 > x += 1
513 513 > ui.status(b'%d debugsleep iterations in %.03fs\n' % (x, time.time() - start))
514 514 > EOF
515 515 $ cat >> $HGRCPATH <<EOF
516 516 > [extensions]
517 517 > debugsleep = $TESTTMP/debugsleep.py
518 518 > EOF
519 519 $ newchg debugsleep > run_1
520 520 $ filteredchg debugsleep > run_2
521 521 $ filteredchg debugsleep > run_3
522 522 $ filteredchg debugsleep > run_4
523 523 FIXME: Run 4 should not be >3x Run 1's number of samples.
524 524 $ "$PYTHON" <<EOF
525 525 > r1 = int(open("run_1", "r").read().split()[-1])
526 526 > r4 = int(open("run_4", "r").read().split()[-1])
527 527 > print("Run 1: %d samples\nRun 4: %d samples\nRun 4 > 3 * Run 1: %s" %
528 528 > (r1, r4, r4 > (r1 * 3)))
529 529 > EOF
530 530 Run 1: * samples (glob)
531 531 Run 4: * samples (glob)
532 532 Run 4 > 3 * Run 1: False
533 533 (Disabling with --no-profile on the commandline still works, but isn't permanent)
534 534 $ newchg log -r . --no-profile
535 535 $ filteredchg log -r .
536 536 Sample count: * (glob)
537 537 $ filteredchg log -r . --no-profile
538 538 $ filteredchg log -r .
539 539 Sample count: * (glob)
@@ -1,1174 +1,1174 b''
1 1 #require no-rhg no-chg
2 2
3 3 XXX-RHG this test hangs if `hg` is really `rhg`. This was hidden by the use of
4 4 `alias hg=rhg` by run-tests.py. With such alias removed, this test is revealed
5 5 buggy. This need to be resolved sooner than later.
6 6
7 7 XXX-CHG this test hangs if `hg` is really `chg`. This was hidden by the use of
8 8 `alias hg=chg` by run-tests.py. With such alias removed, this test is revealed
9 9 buggy. This need to be resolved sooner than later.
10 10
11 11 #if windows
12 12 $ PYTHONPATH="$TESTDIR/../contrib;$PYTHONPATH"
13 13 #else
14 14 $ PYTHONPATH="$TESTDIR/../contrib:$PYTHONPATH"
15 15 #endif
16 16 $ export PYTHONPATH
17 17
18 18 typical client does not want echo-back messages, so test without it:
19 19
20 20 $ grep -v '^promptecho ' < $HGRCPATH >> $HGRCPATH.new
21 21 $ mv $HGRCPATH.new $HGRCPATH
22 22
23 23 $ hg init repo
24 24 $ cd repo
25 25
26 26 >>> from __future__ import absolute_import
27 27 >>> import os
28 28 >>> import sys
29 29 >>> from hgclient import bprint, check, readchannel, runcommand
30 30 >>> @check
31 31 ... def hellomessage(server):
32 32 ... ch, data = readchannel(server)
33 33 ... bprint(b'%c, %r' % (ch, data))
34 34 ... # run an arbitrary command to make sure the next thing the server
35 35 ... # sends isn't part of the hello message
36 36 ... runcommand(server, [b'id'])
37 37 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
38 38 *** runcommand id
39 39 000000000000 tip
40 40
41 41 >>> from hgclient import check
42 42 >>> @check
43 43 ... def unknowncommand(server):
44 44 ... server.stdin.write(b'unknowncommand\n')
45 45 abort: unknown command unknowncommand
46 46
47 47 >>> from hgclient import check, readchannel, runcommand
48 48 >>> @check
49 49 ... def checkruncommand(server):
50 50 ... # hello block
51 51 ... readchannel(server)
52 52 ...
53 53 ... # no args
54 54 ... runcommand(server, [])
55 55 ...
56 56 ... # global options
57 57 ... runcommand(server, [b'id', b'--quiet'])
58 58 ...
59 59 ... # make sure global options don't stick through requests
60 60 ... runcommand(server, [b'id'])
61 61 ...
62 62 ... # --config
63 63 ... runcommand(server, [b'id', b'--config', b'ui.quiet=True'])
64 64 ...
65 65 ... # make sure --config doesn't stick
66 66 ... runcommand(server, [b'id'])
67 67 ...
68 68 ... # negative return code should be masked
69 69 ... runcommand(server, [b'id', b'-runknown'])
70 70 *** runcommand
71 71 Mercurial Distributed SCM
72 72
73 73 basic commands:
74 74
75 75 add add the specified files on the next commit
76 76 annotate show changeset information by line for each file
77 77 clone make a copy of an existing repository
78 78 commit commit the specified files or all outstanding changes
79 79 diff diff repository (or selected files)
80 80 export dump the header and diffs for one or more changesets
81 81 forget forget the specified files on the next commit
82 82 init create a new repository in the given directory
83 83 log show revision history of entire repository or files
84 84 merge merge another revision into working directory
85 85 pull pull changes from the specified source
86 86 push push changes to the specified destination
87 87 remove remove the specified files on the next commit
88 88 serve start stand-alone webserver
89 89 status show changed files in the working directory
90 90 summary summarize working directory state
91 91 update update working directory (or switch revisions)
92 92
93 93 (use 'hg help' for the full list of commands or 'hg -v' for details)
94 94 *** runcommand id --quiet
95 95 000000000000
96 96 *** runcommand id
97 97 000000000000 tip
98 98 *** runcommand id --config ui.quiet=True
99 99 000000000000
100 100 *** runcommand id
101 101 000000000000 tip
102 102 *** runcommand id -runknown
103 103 abort: unknown revision 'unknown'
104 104 [10]
105 105
106 106 >>> from hgclient import bprint, check, readchannel
107 107 >>> @check
108 108 ... def inputeof(server):
109 109 ... readchannel(server)
110 110 ... server.stdin.write(b'runcommand\n')
111 111 ... # close stdin while server is waiting for input
112 112 ... server.stdin.close()
113 113 ...
114 114 ... # server exits with 1 if the pipe closed while reading the command
115 115 ... bprint(b'server exit code =', b'%d' % server.wait())
116 116 server exit code = 1
117 117
118 118 >>> from hgclient import check, readchannel, runcommand, stringio
119 119 >>> @check
120 120 ... def serverinput(server):
121 121 ... readchannel(server)
122 122 ...
123 123 ... patch = b"""
124 124 ... # HG changeset patch
125 125 ... # User test
126 126 ... # Date 0 0
127 127 ... # Node ID c103a3dec114d882c98382d684d8af798d09d857
128 128 ... # Parent 0000000000000000000000000000000000000000
129 129 ... 1
130 130 ...
131 131 ... diff -r 000000000000 -r c103a3dec114 a
132 132 ... --- /dev/null Thu Jan 01 00:00:00 1970 +0000
133 133 ... +++ b/a Thu Jan 01 00:00:00 1970 +0000
134 134 ... @@ -0,0 +1,1 @@
135 135 ... +1
136 136 ... """
137 137 ...
138 138 ... runcommand(server, [b'import', b'-'], input=stringio(patch))
139 139 ... runcommand(server, [b'log'])
140 140 *** runcommand import -
141 141 applying patch from stdin
142 142 *** runcommand log
143 143 changeset: 0:eff892de26ec
144 144 tag: tip
145 145 user: test
146 146 date: Thu Jan 01 00:00:00 1970 +0000
147 147 summary: 1
148 148
149 149
150 150 check strict parsing of early options:
151 151
152 152 >>> import os
153 153 >>> from hgclient import check, readchannel, runcommand
154 154 >>> os.environ['HGPLAIN'] = '+strictflags'
155 155 >>> @check
156 156 ... def cwd(server):
157 157 ... readchannel(server)
158 158 ... runcommand(server, [b'log', b'-b', b'--config=alias.log=!echo pwned',
159 159 ... b'default'])
160 160 *** runcommand log -b --config=alias.log=!echo pwned default
161 161 abort: unknown revision '--config=alias.log=!echo pwned'
162 162 [255]
163 163
164 164 check that "histedit --commands=-" can read rules from the input channel:
165 165
166 166 >>> from hgclient import check, readchannel, runcommand, stringio
167 167 >>> @check
168 168 ... def serverinput(server):
169 169 ... readchannel(server)
170 170 ... rules = b'pick eff892de26ec\n'
171 171 ... runcommand(server, [b'histedit', b'0', b'--commands=-',
172 172 ... b'--config', b'extensions.histedit='],
173 173 ... input=stringio(rules))
174 174 *** runcommand histedit 0 --commands=- --config extensions.histedit=
175 175
176 176 check that --cwd doesn't persist between requests:
177 177
178 178 $ mkdir foo
179 179 $ touch foo/bar
180 180 >>> from hgclient import check, readchannel, runcommand
181 181 >>> @check
182 182 ... def cwd(server):
183 183 ... readchannel(server)
184 184 ... runcommand(server, [b'--cwd', b'foo', b'st', b'bar'])
185 185 ... runcommand(server, [b'st', b'foo/bar'])
186 186 *** runcommand --cwd foo st bar
187 187 ? bar
188 188 *** runcommand st foo/bar
189 189 ? foo/bar
190 190
191 191 $ rm foo/bar
192 192
193 193
194 194 check that local configs for the cached repo aren't inherited when -R is used:
195 195
196 196 $ cat <<EOF >> .hg/hgrc
197 197 > [ui]
198 198 > foo = bar
199 199 > EOF
200 200
201 201 #if no-extraextensions
202 202
203 203 >>> from hgclient import check, readchannel, runcommand, sep
204 204 >>> @check
205 205 ... def localhgrc(server):
206 206 ... readchannel(server)
207 207 ...
208 208 ... # the cached repo local hgrc contains ui.foo=bar, so showconfig should
209 209 ... # show it
210 210 ... runcommand(server, [b'showconfig'], outfilter=sep)
211 211 ...
212 212 ... # but not for this repo
213 213 ... runcommand(server, [b'init', b'foo'])
214 214 ... runcommand(server, [b'-R', b'foo', b'showconfig', b'ui', b'defaults'])
215 215 *** runcommand showconfig
216 216 bundle.mainreporoot=$TESTTMP/repo
217 217 chgserver.idletimeout=60
218 218 devel.all-warnings=true
219 219 devel.default-date=0 0
220 220 extensions.fsmonitor= (fsmonitor !)
221 221 format.exp-dirstate-v2=1 (dirstate-v2 !)
222 222 largefiles.usercache=$TESTTMP/.cache/largefiles
223 223 lfs.usercache=$TESTTMP/.cache/lfs
224 224 ui.slash=True
225 225 ui.interactive=False
226 226 ui.detailed-exit-code=True
227 227 ui.merge=internal:merge
228 228 ui.mergemarkers=detailed
229 229 ui.ssh=* (glob)
230 230 ui.timeout.warn=15
231 231 ui.foo=bar
232 232 ui.nontty=true
233 233 web.address=localhost
234 234 web\.ipv6=(?:True|False) (re)
235 235 web.server-header=testing stub value
236 236 *** runcommand init foo
237 237 *** runcommand -R foo showconfig ui defaults
238 238 ui.slash=True
239 239 ui.interactive=False
240 240 ui.detailed-exit-code=True
241 241 ui.merge=internal:merge
242 242 ui.mergemarkers=detailed
243 243 ui.ssh=* (glob)
244 244 ui.timeout.warn=15
245 245 ui.nontty=true
246 246 #endif
247 247
248 248 $ rm -R foo
249 249
250 250 #if windows
251 251 $ PYTHONPATH="$TESTTMP/repo;$PYTHONPATH"
252 252 #else
253 253 $ PYTHONPATH="$TESTTMP/repo:$PYTHONPATH"
254 254 #endif
255 255
256 256 $ cat <<EOF > hook.py
257 257 > import sys
258 258 > from hgclient import bprint
259 259 > def hook(**args):
260 260 > bprint(b'hook talking')
261 261 > bprint(b'now try to read something: %r' % sys.stdin.read())
262 262 > EOF
263 263
264 264 >>> from hgclient import check, readchannel, runcommand, stringio
265 265 >>> @check
266 266 ... def hookoutput(server):
267 267 ... readchannel(server)
268 268 ... runcommand(server, [b'--config',
269 269 ... b'hooks.pre-identify=python:hook.hook',
270 270 ... b'id'],
271 271 ... input=stringio(b'some input'))
272 272 *** runcommand --config hooks.pre-identify=python:hook.hook id
273 273 eff892de26ec tip
274 274 hook talking
275 275 now try to read something: ''
276 276
277 277 Clean hook cached version
278 278 $ rm hook.py*
279 279 $ rm -Rf __pycache__
280 280
281 281 $ echo a >> a
282 282 >>> import os
283 283 >>> from hgclient import check, readchannel, runcommand
284 284 >>> @check
285 285 ... def outsidechanges(server):
286 286 ... readchannel(server)
287 287 ... runcommand(server, [b'status'])
288 288 ... os.system('hg ci -Am2')
289 289 ... runcommand(server, [b'tip'])
290 290 ... runcommand(server, [b'status'])
291 291 *** runcommand status
292 292 M a
293 293 *** runcommand tip
294 294 changeset: 1:d3a0a68be6de
295 295 tag: tip
296 296 user: test
297 297 date: Thu Jan 01 00:00:00 1970 +0000
298 298 summary: 2
299 299
300 300 *** runcommand status
301 301
302 302 >>> import os
303 303 >>> from hgclient import bprint, check, readchannel, runcommand
304 304 >>> @check
305 305 ... def bookmarks(server):
306 306 ... readchannel(server)
307 307 ... runcommand(server, [b'bookmarks'])
308 308 ...
309 309 ... # changes .hg/bookmarks
310 310 ... os.system('hg bookmark -i bm1')
311 311 ... os.system('hg bookmark -i bm2')
312 312 ... runcommand(server, [b'bookmarks'])
313 313 ...
314 314 ... # changes .hg/bookmarks.current
315 315 ... os.system('hg upd bm1 -q')
316 316 ... runcommand(server, [b'bookmarks'])
317 317 ...
318 318 ... runcommand(server, [b'bookmarks', b'bm3'])
319 319 ... f = open('a', 'ab')
320 320 ... f.write(b'a\n') and None
321 321 ... f.close()
322 322 ... runcommand(server, [b'commit', b'-Amm'])
323 323 ... runcommand(server, [b'bookmarks'])
324 324 ... bprint(b'')
325 325 *** runcommand bookmarks
326 326 no bookmarks set
327 327 *** runcommand bookmarks
328 328 bm1 1:d3a0a68be6de
329 329 bm2 1:d3a0a68be6de
330 330 *** runcommand bookmarks
331 331 * bm1 1:d3a0a68be6de
332 332 bm2 1:d3a0a68be6de
333 333 *** runcommand bookmarks bm3
334 334 *** runcommand commit -Amm
335 335 *** runcommand bookmarks
336 336 bm1 1:d3a0a68be6de
337 337 bm2 1:d3a0a68be6de
338 338 * bm3 2:aef17e88f5f0
339 339
340 340
341 341 >>> import os
342 342 >>> from hgclient import check, readchannel, runcommand
343 343 >>> @check
344 344 ... def tagscache(server):
345 345 ... readchannel(server)
346 346 ... runcommand(server, [b'id', b'-t', b'-r', b'0'])
347 347 ... os.system('hg tag -r 0 foo')
348 348 ... runcommand(server, [b'id', b'-t', b'-r', b'0'])
349 349 *** runcommand id -t -r 0
350 350
351 351 *** runcommand id -t -r 0
352 352 foo
353 353
354 354 >>> import os
355 355 >>> from hgclient import check, readchannel, runcommand
356 356 >>> @check
357 357 ... def setphase(server):
358 358 ... readchannel(server)
359 359 ... runcommand(server, [b'phase', b'-r', b'.'])
360 360 ... os.system('hg phase -r . -p')
361 361 ... runcommand(server, [b'phase', b'-r', b'.'])
362 362 *** runcommand phase -r .
363 363 3: draft
364 364 *** runcommand phase -r .
365 365 3: public
366 366
367 367 $ echo a >> a
368 368 >>> from hgclient import bprint, check, readchannel, runcommand
369 369 >>> @check
370 370 ... def rollback(server):
371 371 ... readchannel(server)
372 372 ... runcommand(server, [b'phase', b'-r', b'.', b'-p'])
373 373 ... runcommand(server, [b'commit', b'-Am.'])
374 374 ... runcommand(server, [b'rollback'])
375 375 ... runcommand(server, [b'phase', b'-r', b'.'])
376 376 ... bprint(b'')
377 377 *** runcommand phase -r . -p
378 378 no phases changed
379 379 *** runcommand commit -Am.
380 380 *** runcommand rollback
381 381 repository tip rolled back to revision 3 (undo commit)
382 382 working directory now based on revision 3
383 383 *** runcommand phase -r .
384 384 3: public
385 385
386 386
387 387 >>> import os
388 388 >>> from hgclient import check, readchannel, runcommand
389 389 >>> @check
390 390 ... def branch(server):
391 391 ... readchannel(server)
392 392 ... runcommand(server, [b'branch'])
393 393 ... os.system('hg branch foo')
394 394 ... runcommand(server, [b'branch'])
395 395 ... os.system('hg branch default')
396 396 *** runcommand branch
397 397 default
398 398 marked working directory as branch foo
399 399 (branches are permanent and global, did you want a bookmark?)
400 400 *** runcommand branch
401 401 foo
402 402 marked working directory as branch default
403 403 (branches are permanent and global, did you want a bookmark?)
404 404
405 405 $ touch .hgignore
406 406 >>> import os
407 407 >>> from hgclient import bprint, check, readchannel, runcommand
408 408 >>> @check
409 409 ... def hgignore(server):
410 410 ... readchannel(server)
411 411 ... runcommand(server, [b'commit', b'-Am.'])
412 412 ... f = open('ignored-file', 'ab')
413 413 ... f.write(b'') and None
414 414 ... f.close()
415 415 ... f = open('.hgignore', 'ab')
416 416 ... f.write(b'ignored-file')
417 417 ... f.close()
418 418 ... runcommand(server, [b'status', b'-i', b'-u'])
419 419 ... bprint(b'')
420 420 *** runcommand commit -Am.
421 421 adding .hgignore
422 422 *** runcommand status -i -u
423 423 I ignored-file
424 424
425 425
426 426 cache of non-public revisions should be invalidated on repository change
427 427 (issue4855):
428 428
429 429 >>> import os
430 430 >>> from hgclient import bprint, check, readchannel, runcommand
431 431 >>> @check
432 432 ... def phasesetscacheaftercommit(server):
433 433 ... readchannel(server)
434 434 ... # load _phasecache._phaserevs and _phasesets
435 435 ... runcommand(server, [b'log', b'-qr', b'draft()'])
436 436 ... # create draft commits by another process
437 437 ... for i in range(5, 7):
438 438 ... f = open('a', 'ab')
439 439 ... f.seek(0, os.SEEK_END)
440 440 ... f.write(b'a\n') and None
441 441 ... f.close()
442 442 ... os.system('hg commit -Aqm%d' % i)
443 443 ... # new commits should be listed as draft revisions
444 444 ... runcommand(server, [b'log', b'-qr', b'draft()'])
445 445 ... bprint(b'')
446 446 *** runcommand log -qr draft()
447 447 4:7966c8e3734d
448 448 *** runcommand log -qr draft()
449 449 4:7966c8e3734d
450 450 5:41f6602d1c4f
451 451 6:10501e202c35
452 452
453 453
454 454 >>> import os
455 455 >>> from hgclient import bprint, check, readchannel, runcommand
456 456 >>> @check
457 457 ... def phasesetscacheafterstrip(server):
458 458 ... readchannel(server)
459 459 ... # load _phasecache._phaserevs and _phasesets
460 460 ... runcommand(server, [b'log', b'-qr', b'draft()'])
461 461 ... # strip cached revisions by another process
462 462 ... os.system('hg --config extensions.strip= strip -q 5')
463 463 ... # shouldn't abort by "unknown revision '6'"
464 464 ... runcommand(server, [b'log', b'-qr', b'draft()'])
465 465 ... bprint(b'')
466 466 *** runcommand log -qr draft()
467 467 4:7966c8e3734d
468 468 5:41f6602d1c4f
469 469 6:10501e202c35
470 470 *** runcommand log -qr draft()
471 471 4:7966c8e3734d
472 472
473 473
474 474 cache of phase roots should be invalidated on strip (issue3827):
475 475
476 476 >>> import os
477 477 >>> from hgclient import check, readchannel, runcommand, sep
478 478 >>> @check
479 479 ... def phasecacheafterstrip(server):
480 480 ... readchannel(server)
481 481 ...
482 482 ... # create new head, 5:731265503d86
483 483 ... runcommand(server, [b'update', b'-C', b'0'])
484 484 ... f = open('a', 'ab')
485 485 ... f.write(b'a\n') and None
486 486 ... f.close()
487 487 ... runcommand(server, [b'commit', b'-Am.', b'a'])
488 488 ... runcommand(server, [b'log', b'-Gq'])
489 489 ...
490 490 ... # make it public; draft marker moves to 4:7966c8e3734d
491 491 ... runcommand(server, [b'phase', b'-p', b'.'])
492 492 ... # load _phasecache.phaseroots
493 493 ... runcommand(server, [b'phase', b'.'], outfilter=sep)
494 494 ...
495 495 ... # strip 1::4 outside server
496 496 ... os.system('hg -q --config extensions.mq= strip 1')
497 497 ...
498 498 ... # shouldn't raise "7966c8e3734d: no node!"
499 499 ... runcommand(server, [b'branches'])
500 500 *** runcommand update -C 0
501 501 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
502 502 (leaving bookmark bm3)
503 503 *** runcommand commit -Am. a
504 504 created new head
505 505 *** runcommand log -Gq
506 506 @ 5:731265503d86
507 507 |
508 508 | o 4:7966c8e3734d
509 509 | |
510 510 | o 3:b9b85890c400
511 511 | |
512 512 | o 2:aef17e88f5f0
513 513 | |
514 514 | o 1:d3a0a68be6de
515 515 |/
516 516 o 0:eff892de26ec
517 517
518 518 *** runcommand phase -p .
519 519 *** runcommand phase .
520 520 5: public
521 521 *** runcommand branches
522 522 default 1:731265503d86
523 523
524 524 in-memory cache must be reloaded if transaction is aborted. otherwise
525 525 changelog and manifest would have invalid node:
526 526
527 527 $ echo a >> a
528 528 >>> from hgclient import check, readchannel, runcommand
529 529 >>> @check
530 530 ... def txabort(server):
531 531 ... readchannel(server)
532 532 ... runcommand(server, [b'commit', b'--config', b'hooks.pretxncommit=false',
533 533 ... b'-mfoo'])
534 534 ... runcommand(server, [b'verify'])
535 535 *** runcommand commit --config hooks.pretxncommit=false -mfoo
536 536 transaction abort!
537 537 rollback completed
538 538 abort: pretxncommit hook exited with status 1
539 539 [40]
540 540 *** runcommand verify
541 541 checking changesets
542 542 checking manifests
543 543 crosschecking files in changesets and manifests
544 544 checking files
545 545 checked 2 changesets with 2 changes to 1 files
546 546 $ hg revert --no-backup -aq
547 547
548 548 $ cat >> .hg/hgrc << EOF
549 549 > [experimental]
550 550 > evolution.createmarkers=True
551 551 > EOF
552 552
553 553 >>> import os
554 554 >>> from hgclient import check, readchannel, runcommand
555 555 >>> @check
556 556 ... def obsolete(server):
557 557 ... readchannel(server)
558 558 ...
559 559 ... runcommand(server, [b'up', b'null'])
560 560 ... runcommand(server, [b'phase', b'-df', b'tip'])
561 561 ... cmd = 'hg debugobsolete `hg log -r tip --template {node}`'
562 562 ... if os.name == 'nt':
563 563 ... cmd = 'sh -c "%s"' % cmd # run in sh, not cmd.exe
564 564 ... os.system(cmd)
565 565 ... runcommand(server, [b'log', b'--hidden'])
566 566 ... runcommand(server, [b'log'])
567 567 *** runcommand up null
568 568 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
569 569 *** runcommand phase -df tip
570 570 1 new obsolescence markers
571 571 obsoleted 1 changesets
572 572 *** runcommand log --hidden
573 573 changeset: 1:731265503d86
574 574 tag: tip
575 575 user: test
576 576 date: Thu Jan 01 00:00:00 1970 +0000
577 577 obsolete: pruned
578 578 summary: .
579 579
580 580 changeset: 0:eff892de26ec
581 581 bookmark: bm1
582 582 bookmark: bm2
583 583 bookmark: bm3
584 584 user: test
585 585 date: Thu Jan 01 00:00:00 1970 +0000
586 586 summary: 1
587 587
588 588 *** runcommand log
589 589 changeset: 0:eff892de26ec
590 590 bookmark: bm1
591 591 bookmark: bm2
592 592 bookmark: bm3
593 593 tag: tip
594 594 user: test
595 595 date: Thu Jan 01 00:00:00 1970 +0000
596 596 summary: 1
597 597
598 598
599 599 $ cat <<EOF >> .hg/hgrc
600 600 > [extensions]
601 601 > mq =
602 602 > EOF
603 603
604 604 >>> import os
605 605 >>> from hgclient import check, readchannel, runcommand
606 606 >>> @check
607 607 ... def mqoutsidechanges(server):
608 608 ... readchannel(server)
609 609 ...
610 610 ... # load repo.mq
611 611 ... runcommand(server, [b'qapplied'])
612 612 ... os.system('hg qnew 0.diff')
613 613 ... # repo.mq should be invalidated
614 614 ... runcommand(server, [b'qapplied'])
615 615 ...
616 616 ... runcommand(server, [b'qpop', b'--all'])
617 617 ... os.system('hg qqueue --create foo')
618 618 ... # repo.mq should be recreated to point to new queue
619 619 ... runcommand(server, [b'qqueue', b'--active'])
620 620 *** runcommand qapplied
621 621 *** runcommand qapplied
622 622 0.diff
623 623 *** runcommand qpop --all
624 624 popping 0.diff
625 625 patch queue now empty
626 626 *** runcommand qqueue --active
627 627 foo
628 628
629 629 $ cat <<'EOF' > ../dbgui.py
630 630 > import os
631 631 > import sys
632 632 > from mercurial import commands, registrar
633 633 > cmdtable = {}
634 634 > command = registrar.command(cmdtable)
635 635 > @command(b"debuggetpass", norepo=True)
636 636 > def debuggetpass(ui):
637 637 > ui.write(b"%s\n" % ui.getpass())
638 638 > @command(b"debugprompt", norepo=True)
639 639 > def debugprompt(ui):
640 640 > ui.write(b"%s\n" % ui.prompt(b"prompt:"))
641 641 > @command(b"debugpromptchoice", norepo=True)
642 642 > def debugpromptchoice(ui):
643 643 > msg = b"promptchoice (y/n)? $$ &Yes $$ &No"
644 644 > ui.write(b"%d\n" % ui.promptchoice(msg))
645 645 > @command(b"debugreadstdin", norepo=True)
646 646 > def debugreadstdin(ui):
647 647 > ui.write(b"read: %r\n" % sys.stdin.read(1))
648 648 > @command(b"debugwritestdout", norepo=True)
649 649 > def debugwritestdout(ui):
650 650 > os.write(1, b"low-level stdout fd and\n")
651 651 > sys.stdout.write("stdout should be redirected to stderr\n")
652 652 > sys.stdout.flush()
653 653 > EOF
654 654 $ cat <<EOF >> .hg/hgrc
655 655 > [extensions]
656 656 > dbgui = ../dbgui.py
657 657 > EOF
658 658
659 659 >>> from hgclient import check, readchannel, runcommand, stringio
660 660 >>> @check
661 661 ... def getpass(server):
662 662 ... readchannel(server)
663 663 ... runcommand(server, [b'debuggetpass', b'--config',
664 664 ... b'ui.interactive=True'],
665 665 ... input=stringio(b'1234\n'))
666 666 ... runcommand(server, [b'debuggetpass', b'--config',
667 667 ... b'ui.interactive=True'],
668 668 ... input=stringio(b'\n'))
669 669 ... runcommand(server, [b'debuggetpass', b'--config',
670 670 ... b'ui.interactive=True'],
671 671 ... input=stringio(b''))
672 672 ... runcommand(server, [b'debugprompt', b'--config',
673 673 ... b'ui.interactive=True'],
674 674 ... input=stringio(b'5678\n'))
675 675 ... runcommand(server, [b'debugprompt', b'--config',
676 676 ... b'ui.interactive=True'],
677 677 ... input=stringio(b'\nremainder\nshould\nnot\nbe\nread\n'))
678 678 ... runcommand(server, [b'debugreadstdin'])
679 679 ... runcommand(server, [b'debugwritestdout'])
680 680 *** runcommand debuggetpass --config ui.interactive=True
681 681 password: 1234
682 682 *** runcommand debuggetpass --config ui.interactive=True
683 683 password:
684 684 *** runcommand debuggetpass --config ui.interactive=True
685 685 password: abort: response expected
686 686 [255]
687 687 *** runcommand debugprompt --config ui.interactive=True
688 688 prompt: 5678
689 689 *** runcommand debugprompt --config ui.interactive=True
690 690 prompt: y
691 691 *** runcommand debugreadstdin
692 692 read: ''
693 693 *** runcommand debugwritestdout
694 694 low-level stdout fd and
695 695 stdout should be redirected to stderr
696 696
697 697
698 698 run commandserver in commandserver, which is silly but should work:
699 699
700 700 >>> from hgclient import bprint, check, readchannel, runcommand, stringio
701 701 >>> @check
702 702 ... def nested(server):
703 703 ... bprint(b'%c, %r' % readchannel(server))
704 704 ... class nestedserver(object):
705 705 ... stdin = stringio(b'getencoding\n')
706 706 ... stdout = stringio()
707 707 ... runcommand(server, [b'serve', b'--cmdserver', b'pipe'],
708 708 ... output=nestedserver.stdout, input=nestedserver.stdin)
709 709 ... nestedserver.stdout.seek(0)
710 710 ... bprint(b'%c, %r' % readchannel(nestedserver)) # hello
711 711 ... bprint(b'%c, %r' % readchannel(nestedserver)) # getencoding
712 712 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
713 713 *** runcommand serve --cmdserver pipe
714 714 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
715 715 r, '*' (glob)
716 716
717 717
718 718 start without repository:
719 719
720 720 $ cd ..
721 721
722 722 >>> from hgclient import bprint, check, readchannel, runcommand
723 723 >>> @check
724 724 ... def hellomessage(server):
725 725 ... ch, data = readchannel(server)
726 726 ... bprint(b'%c, %r' % (ch, data))
727 727 ... # run an arbitrary command to make sure the next thing the server
728 728 ... # sends isn't part of the hello message
729 729 ... runcommand(server, [b'id'])
730 730 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
731 731 *** runcommand id
732 732 abort: there is no Mercurial repository here (.hg not found)
733 733 [10]
734 734
735 735 >>> from hgclient import check, readchannel, runcommand
736 736 >>> @check
737 737 ... def startwithoutrepo(server):
738 738 ... readchannel(server)
739 739 ... runcommand(server, [b'init', b'repo2'])
740 740 ... runcommand(server, [b'id', b'-R', b'repo2'])
741 741 *** runcommand init repo2
742 742 *** runcommand id -R repo2
743 743 000000000000 tip
744 744
745 745
746 746 don't fall back to cwd if invalid -R path is specified (issue4805):
747 747
748 748 $ cd repo
749 749 $ hg serve --cmdserver pipe -R ../nonexistent
750 750 abort: repository ../nonexistent not found
751 751 [255]
752 752 $ cd ..
753 753
754 754
755 755 #if no-windows
756 756
757 757 option to not shutdown on SIGINT:
758 758
759 759 $ cat <<'EOF' > dbgint.py
760 760 > import os
761 761 > import signal
762 762 > import time
763 763 > from mercurial import commands, registrar
764 764 > cmdtable = {}
765 765 > command = registrar.command(cmdtable)
766 766 > @command(b"debugsleep", norepo=True)
767 767 > def debugsleep(ui):
768 768 > time.sleep(1)
769 769 > @command(b"debugsuicide", norepo=True)
770 770 > def debugsuicide(ui):
771 771 > os.kill(os.getpid(), signal.SIGINT)
772 772 > time.sleep(1)
773 773 > EOF
774 774
775 775 >>> import signal
776 776 >>> import time
777 777 >>> from hgclient import checkwith, readchannel, runcommand
778 778 >>> @checkwith(extraargs=[b'--config', b'cmdserver.shutdown-on-interrupt=False',
779 779 ... b'--config', b'extensions.dbgint=dbgint.py'])
780 780 ... def nointr(server):
781 781 ... readchannel(server)
782 782 ... server.send_signal(signal.SIGINT) # server won't be terminated
783 783 ... time.sleep(1)
784 784 ... runcommand(server, [b'debugsleep'])
785 785 ... server.send_signal(signal.SIGINT) # server won't be terminated
786 786 ... runcommand(server, [b'debugsleep'])
787 787 ... runcommand(server, [b'debugsuicide']) # command can be interrupted
788 788 ... server.send_signal(signal.SIGTERM) # server will be terminated
789 789 ... time.sleep(1)
790 790 *** runcommand debugsleep
791 791 *** runcommand debugsleep
792 792 *** runcommand debugsuicide
793 793 interrupted!
794 794 killed!
795 [250]
795 [255]
796 796
797 797 #endif
798 798
799 799
800 800 structured message channel:
801 801
802 802 $ cat <<'EOF' >> repo2/.hg/hgrc
803 803 > [ui]
804 804 > # server --config should precede repository option
805 805 > message-output = stdio
806 806 > EOF
807 807
808 808 >>> from hgclient import bprint, checkwith, readchannel, runcommand
809 809 >>> @checkwith(extraargs=[b'--config', b'ui.message-output=channel',
810 810 ... b'--config', b'cmdserver.message-encodings=foo cbor'])
811 811 ... def verify(server):
812 812 ... _ch, data = readchannel(server)
813 813 ... bprint(data)
814 814 ... runcommand(server, [b'-R', b'repo2', b'verify'])
815 815 capabilities: getencoding runcommand
816 816 encoding: ascii
817 817 message-encoding: cbor
818 818 pid: * (glob)
819 819 pgid: * (glob) (no-windows !)
820 820 *** runcommand -R repo2 verify
821 821 message: '\xa2DdataTchecking changesets\nDtypeFstatus'
822 822 message: '\xa6Ditem@Cpos\xf6EtopicHcheckingEtotal\xf6DtypeHprogressDunit@'
823 823 message: '\xa2DdataSchecking manifests\nDtypeFstatus'
824 824 message: '\xa6Ditem@Cpos\xf6EtopicHcheckingEtotal\xf6DtypeHprogressDunit@'
825 825 message: '\xa2DdataX0crosschecking files in changesets and manifests\nDtypeFstatus'
826 826 message: '\xa6Ditem@Cpos\xf6EtopicMcrosscheckingEtotal\xf6DtypeHprogressDunit@'
827 827 message: '\xa2DdataOchecking files\nDtypeFstatus'
828 828 message: '\xa6Ditem@Cpos\xf6EtopicHcheckingEtotal\xf6DtypeHprogressDunit@'
829 829 message: '\xa2DdataX/checked 0 changesets with 0 changes to 0 files\nDtypeFstatus'
830 830
831 831 >>> from hgclient import checkwith, readchannel, runcommand, stringio
832 832 >>> @checkwith(extraargs=[b'--config', b'ui.message-output=channel',
833 833 ... b'--config', b'cmdserver.message-encodings=cbor',
834 834 ... b'--config', b'extensions.dbgui=dbgui.py'])
835 835 ... def prompt(server):
836 836 ... readchannel(server)
837 837 ... interactive = [b'--config', b'ui.interactive=True']
838 838 ... runcommand(server, [b'debuggetpass'] + interactive,
839 839 ... input=stringio(b'1234\n'))
840 840 ... runcommand(server, [b'debugprompt'] + interactive,
841 841 ... input=stringio(b'5678\n'))
842 842 ... runcommand(server, [b'debugpromptchoice'] + interactive,
843 843 ... input=stringio(b'n\n'))
844 844 *** runcommand debuggetpass --config ui.interactive=True
845 845 message: '\xa3DdataJpassword: Hpassword\xf5DtypeFprompt'
846 846 1234
847 847 *** runcommand debugprompt --config ui.interactive=True
848 848 message: '\xa3DdataGprompt:GdefaultAyDtypeFprompt'
849 849 5678
850 850 *** runcommand debugpromptchoice --config ui.interactive=True
851 851 message: '\xa4Gchoices\x82\x82AyCYes\x82AnBNoDdataTpromptchoice (y/n)? GdefaultAyDtypeFprompt'
852 852 1
853 853
854 854 bad message encoding:
855 855
856 856 $ hg serve --cmdserver pipe --config ui.message-output=channel
857 857 abort: no supported message encodings:
858 858 [255]
859 859 $ hg serve --cmdserver pipe --config ui.message-output=channel \
860 860 > --config cmdserver.message-encodings='foo bar'
861 861 abort: no supported message encodings: foo bar
862 862 [255]
863 863
864 864 unix domain socket:
865 865
866 866 $ cd repo
867 867 $ hg update -q
868 868
869 869 #if unix-socket unix-permissions
870 870
871 871 >>> from hgclient import bprint, check, readchannel, runcommand, stringio, unixserver
872 872 >>> server = unixserver(b'.hg/server.sock', b'.hg/server.log')
873 873 >>> def hellomessage(conn):
874 874 ... ch, data = readchannel(conn)
875 875 ... bprint(b'%c, %r' % (ch, data))
876 876 ... runcommand(conn, [b'id'])
877 877 >>> check(hellomessage, server.connect)
878 878 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
879 879 *** runcommand id
880 880 eff892de26ec tip bm1/bm2/bm3
881 881 >>> def unknowncommand(conn):
882 882 ... readchannel(conn)
883 883 ... conn.stdin.write(b'unknowncommand\n')
884 884 >>> check(unknowncommand, server.connect) # error sent to server.log
885 885 >>> def serverinput(conn):
886 886 ... readchannel(conn)
887 887 ... patch = b"""
888 888 ... # HG changeset patch
889 889 ... # User test
890 890 ... # Date 0 0
891 891 ... 2
892 892 ...
893 893 ... diff -r eff892de26ec -r 1ed24be7e7a0 a
894 894 ... --- a/a
895 895 ... +++ b/a
896 896 ... @@ -1,1 +1,2 @@
897 897 ... 1
898 898 ... +2
899 899 ... """
900 900 ... runcommand(conn, [b'import', b'-'], input=stringio(patch))
901 901 ... runcommand(conn, [b'log', b'-rtip', b'-q'])
902 902 >>> check(serverinput, server.connect)
903 903 *** runcommand import -
904 904 applying patch from stdin
905 905 *** runcommand log -rtip -q
906 906 2:1ed24be7e7a0
907 907 >>> server.shutdown()
908 908
909 909 $ cat .hg/server.log
910 910 listening at .hg/server.sock
911 911 abort: unknown command unknowncommand
912 912 killed!
913 913 $ rm .hg/server.log
914 914
915 915 if server crashed before hello, traceback will be sent to 'e' channel as
916 916 last ditch:
917 917
918 918 $ cat <<'EOF' > ../earlycrasher.py
919 919 > from mercurial import commandserver, extensions
920 920 > def _serverequest(orig, ui, repo, conn, createcmdserver, prereposetups):
921 921 > def createcmdserver(*args, **kwargs):
922 922 > raise Exception('crash')
923 923 > return orig(ui, repo, conn, createcmdserver, prereposetups)
924 924 > def extsetup(ui):
925 925 > extensions.wrapfunction(commandserver, b'_serverequest', _serverequest)
926 926 > EOF
927 927 $ cat <<EOF >> .hg/hgrc
928 928 > [extensions]
929 929 > earlycrasher = ../earlycrasher.py
930 930 > EOF
931 931 >>> from hgclient import bprint, check, readchannel, unixserver
932 932 >>> server = unixserver(b'.hg/server.sock', b'.hg/server.log')
933 933 >>> def earlycrash(conn):
934 934 ... while True:
935 935 ... try:
936 936 ... ch, data = readchannel(conn)
937 937 ... for l in data.splitlines(True):
938 938 ... if not l.startswith(b' '):
939 939 ... bprint(b'%c, %r' % (ch, l))
940 940 ... except EOFError:
941 941 ... break
942 942 >>> check(earlycrash, server.connect)
943 943 e, 'Traceback (most recent call last):\n'
944 944 e, 'Exception: crash\n'
945 945 >>> server.shutdown()
946 946
947 947 $ cat .hg/server.log | grep -v '^ '
948 948 listening at .hg/server.sock
949 949 Traceback (most recent call last):
950 950 Exception: crash
951 951 killed!
952 952 #endif
953 953 #if no-unix-socket
954 954
955 955 $ hg serve --cmdserver unix -a .hg/server.sock
956 956 abort: unsupported platform
957 957 [255]
958 958
959 959 #endif
960 960
961 961 $ cd ..
962 962
963 963 Test that accessing to invalid changelog cache is avoided at
964 964 subsequent operations even if repo object is reused even after failure
965 965 of transaction (see 0a7610758c42 also)
966 966
967 967 "hg log" after failure of transaction is needed to detect invalid
968 968 cache in repoview: this can't detect by "hg verify" only.
969 969
970 970 Combination of "finalization" and "empty-ness of changelog" (2 x 2 =
971 971 4) are tested, because '00changelog.i' are differently changed in each
972 972 cases.
973 973
974 974 $ cat > $TESTTMP/failafterfinalize.py <<EOF
975 975 > # extension to abort transaction after finalization forcibly
976 976 > from mercurial import commands, error, extensions, lock as lockmod
977 977 > from mercurial import registrar
978 978 > cmdtable = {}
979 979 > command = registrar.command(cmdtable)
980 980 > configtable = {}
981 981 > configitem = registrar.configitem(configtable)
982 982 > configitem(b'failafterfinalize', b'fail',
983 983 > default=None,
984 984 > )
985 985 > def fail(tr):
986 986 > raise error.Abort(b'fail after finalization')
987 987 > def reposetup(ui, repo):
988 988 > class failrepo(repo.__class__):
989 989 > def commitctx(self, ctx, error=False, origctx=None):
990 990 > if self.ui.configbool(b'failafterfinalize', b'fail'):
991 991 > # 'sorted()' by ASCII code on category names causes
992 992 > # invoking 'fail' after finalization of changelog
993 993 > # using "'cl-%i' % id(self)" as category name
994 994 > self.currenttransaction().addfinalize(b'zzzzzzzz', fail)
995 995 > return super(failrepo, self).commitctx(ctx, error, origctx)
996 996 > repo.__class__ = failrepo
997 997 > EOF
998 998
999 999 $ hg init repo3
1000 1000 $ cd repo3
1001 1001
1002 1002 $ cat <<EOF >> $HGRCPATH
1003 1003 > [command-templates]
1004 1004 > log = {rev} {desc|firstline} ({files})\n
1005 1005 >
1006 1006 > [extensions]
1007 1007 > failafterfinalize = $TESTTMP/failafterfinalize.py
1008 1008 > EOF
1009 1009
1010 1010 - test failure with "empty changelog"
1011 1011
1012 1012 $ echo foo > foo
1013 1013 $ hg add foo
1014 1014
1015 1015 (failure before finalization)
1016 1016
1017 1017 >>> from hgclient import check, readchannel, runcommand
1018 1018 >>> @check
1019 1019 ... def abort(server):
1020 1020 ... readchannel(server)
1021 1021 ... runcommand(server, [b'commit',
1022 1022 ... b'--config', b'hooks.pretxncommit=false',
1023 1023 ... b'-mfoo'])
1024 1024 ... runcommand(server, [b'log'])
1025 1025 ... runcommand(server, [b'verify', b'-q'])
1026 1026 *** runcommand commit --config hooks.pretxncommit=false -mfoo
1027 1027 transaction abort!
1028 1028 rollback completed
1029 1029 abort: pretxncommit hook exited with status 1
1030 1030 [40]
1031 1031 *** runcommand log
1032 1032 *** runcommand verify -q
1033 1033
1034 1034 (failure after finalization)
1035 1035
1036 1036 >>> from hgclient import check, readchannel, runcommand
1037 1037 >>> @check
1038 1038 ... def abort(server):
1039 1039 ... readchannel(server)
1040 1040 ... runcommand(server, [b'commit',
1041 1041 ... b'--config', b'failafterfinalize.fail=true',
1042 1042 ... b'-mfoo'])
1043 1043 ... runcommand(server, [b'log'])
1044 1044 ... runcommand(server, [b'verify', b'-q'])
1045 1045 *** runcommand commit --config failafterfinalize.fail=true -mfoo
1046 1046 transaction abort!
1047 1047 rollback completed
1048 1048 abort: fail after finalization
1049 1049 [255]
1050 1050 *** runcommand log
1051 1051 *** runcommand verify -q
1052 1052
1053 1053 - test failure with "not-empty changelog"
1054 1054
1055 1055 $ echo bar > bar
1056 1056 $ hg add bar
1057 1057 $ hg commit -mbar bar
1058 1058
1059 1059 (failure before finalization)
1060 1060
1061 1061 >>> from hgclient import check, readchannel, runcommand
1062 1062 >>> @check
1063 1063 ... def abort(server):
1064 1064 ... readchannel(server)
1065 1065 ... runcommand(server, [b'commit',
1066 1066 ... b'--config', b'hooks.pretxncommit=false',
1067 1067 ... b'-mfoo', b'foo'])
1068 1068 ... runcommand(server, [b'log'])
1069 1069 ... runcommand(server, [b'verify', b'-q'])
1070 1070 *** runcommand commit --config hooks.pretxncommit=false -mfoo foo
1071 1071 transaction abort!
1072 1072 rollback completed
1073 1073 abort: pretxncommit hook exited with status 1
1074 1074 [40]
1075 1075 *** runcommand log
1076 1076 0 bar (bar)
1077 1077 *** runcommand verify -q
1078 1078
1079 1079 (failure after finalization)
1080 1080
1081 1081 >>> from hgclient import check, readchannel, runcommand
1082 1082 >>> @check
1083 1083 ... def abort(server):
1084 1084 ... readchannel(server)
1085 1085 ... runcommand(server, [b'commit',
1086 1086 ... b'--config', b'failafterfinalize.fail=true',
1087 1087 ... b'-mfoo', b'foo'])
1088 1088 ... runcommand(server, [b'log'])
1089 1089 ... runcommand(server, [b'verify', b'-q'])
1090 1090 *** runcommand commit --config failafterfinalize.fail=true -mfoo foo
1091 1091 transaction abort!
1092 1092 rollback completed
1093 1093 abort: fail after finalization
1094 1094 [255]
1095 1095 *** runcommand log
1096 1096 0 bar (bar)
1097 1097 *** runcommand verify -q
1098 1098
1099 1099 $ cd ..
1100 1100
1101 1101 Test symlink traversal over cached audited paths:
1102 1102 -------------------------------------------------
1103 1103
1104 1104 #if symlink
1105 1105
1106 1106 set up symlink hell
1107 1107
1108 1108 $ mkdir merge-symlink-out
1109 1109 $ hg init merge-symlink
1110 1110 $ cd merge-symlink
1111 1111 $ touch base
1112 1112 $ hg commit -qAm base
1113 1113 $ ln -s ../merge-symlink-out a
1114 1114 $ hg commit -qAm 'symlink a -> ../merge-symlink-out'
1115 1115 $ hg up -q 0
1116 1116 $ mkdir a
1117 1117 $ touch a/poisoned
1118 1118 $ hg commit -qAm 'file a/poisoned'
1119 1119 $ hg log -G -T '{rev}: {desc}\n'
1120 1120 @ 2: file a/poisoned
1121 1121 |
1122 1122 | o 1: symlink a -> ../merge-symlink-out
1123 1123 |/
1124 1124 o 0: base
1125 1125
1126 1126
1127 1127 try trivial merge after update: cache of audited paths should be discarded,
1128 1128 and the merge should fail (issue5628)
1129 1129
1130 1130 $ hg up -q null
1131 1131 >>> from hgclient import check, readchannel, runcommand
1132 1132 >>> @check
1133 1133 ... def merge(server):
1134 1134 ... readchannel(server)
1135 1135 ... # audit a/poisoned as a good path
1136 1136 ... runcommand(server, [b'up', b'-qC', b'2'])
1137 1137 ... runcommand(server, [b'up', b'-qC', b'1'])
1138 1138 ... # here a is a symlink, so a/poisoned is bad
1139 1139 ... runcommand(server, [b'merge', b'2'])
1140 1140 *** runcommand up -qC 2
1141 1141 *** runcommand up -qC 1
1142 1142 *** runcommand merge 2
1143 1143 abort: path 'a/poisoned' traverses symbolic link 'a'
1144 1144 [255]
1145 1145 $ ls ../merge-symlink-out
1146 1146
1147 1147 cache of repo.auditor should be discarded, so matcher would never traverse
1148 1148 symlinks:
1149 1149
1150 1150 $ hg up -qC 0
1151 1151 $ touch ../merge-symlink-out/poisoned
1152 1152 >>> from hgclient import check, readchannel, runcommand
1153 1153 >>> @check
1154 1154 ... def files(server):
1155 1155 ... readchannel(server)
1156 1156 ... runcommand(server, [b'up', b'-qC', b'2'])
1157 1157 ... # audit a/poisoned as a good path
1158 1158 ... runcommand(server, [b'files', b'a/poisoned'])
1159 1159 ... runcommand(server, [b'up', b'-qC', b'0'])
1160 1160 ... runcommand(server, [b'up', b'-qC', b'1'])
1161 1161 ... # here 'a' is a symlink, so a/poisoned should be warned
1162 1162 ... runcommand(server, [b'files', b'a/poisoned'])
1163 1163 *** runcommand up -qC 2
1164 1164 *** runcommand files a/poisoned
1165 1165 a/poisoned
1166 1166 *** runcommand up -qC 0
1167 1167 *** runcommand up -qC 1
1168 1168 *** runcommand files a/poisoned
1169 1169 abort: path 'a/poisoned' traverses symbolic link 'a'
1170 1170 [255]
1171 1171
1172 1172 $ cd ..
1173 1173
1174 1174 #endif
@@ -1,94 +1,94 b''
1 1 #require no-windows no-rhg
2 2
3 3 XXX-RHG this test hangs if `hg` is really `rhg`. This was hidden by the use of
4 4 `alias hg=rhg` by run-tests.py. With such alias removed, this test is revealed
5 5 buggy. This need to be resolved sooner than later.
6 6
7 7 Dummy extension simulating unsafe long running command
8 8 $ SYNC_FILE="$TESTTMP/sync-file"
9 9 $ export SYNC_FILE
10 10 $ DONE_FILE="$TESTTMP/done-file"
11 11 $ export DONE_FILE
12 12 $
13 13 $ cat > wait_ext.py <<EOF
14 14 > import os
15 15 > import time
16 16 >
17 17 > from mercurial.i18n import _
18 18 > from mercurial import registrar
19 19 > from mercurial import testing
20 20 >
21 21 > cmdtable = {}
22 22 > command = registrar.command(cmdtable)
23 23 >
24 24 > @command(b'wait-signal', [], _(b'SYNC_FILE DONE_FILE'), norepo=True)
25 25 > def sleep(ui, sync_file=b"$SYNC_FILE", done_file=b"$DONE_FILE", **opts):
26 26 > start = time.time()
27 27 > with ui.uninterruptible():
28 28 > testing.write_file(sync_file, b'%d' % os.getpid())
29 29 > testing.wait_file(done_file)
30 30 > # make sure we get rescheduled and the signal get a chance to be handled
31 31 > time.sleep(0.1)
32 32 > ui.warn(b"end of unsafe operation\n")
33 33 > ui.warn(b"%d second(s) passed\n" % int(time.time() - start))
34 34 > EOF
35 35
36 36 $ cat > send-signal.sh << EOF
37 37 > #!/bin/sh
38 38 > SIG=\$1
39 39 > if [ -z "\$SIG" ]; then
40 40 > echo "send-signal.sh requires one argument" >&2
41 41 > exit 1
42 42 > fi
43 43 > "$RUNTESTDIR/testlib/wait-on-file" 10 "$SYNC_FILE" || exit 2
44 44 > kill -s \$SIG \`cat "$SYNC_FILE"\`
45 45 > sleep 1
46 46 > touch "$DONE_FILE"
47 47 > EOF
48 48
49 49 #if no-windows
50 50 $ chmod +x send-signal.sh
51 51 #endif
52 52
53 53 Kludge to emulate timeout(1) which is not generally available.
54 54
55 55 Set up repository
56 56 $ hg init repo
57 57 $ cd repo
58 58 $ cat >> $HGRCPATH << EOF
59 59 > [extensions]
60 60 > wait_ext = $TESTTMP/wait_ext.py
61 61 > EOF
62 62
63 63
64 64 Test ctrl-c
65 65 $ rm -f $SYNC_FILE $DONE_FILE
66 66 $ sh -c "../send-signal.sh INT" &
67 67 $ hg wait-signal
68 68 interrupted!
69 [250]
69 [255]
70 70
71 71 $ cat >> $HGRCPATH << EOF
72 72 > [experimental]
73 73 > nointerrupt = yes
74 74 > EOF
75 75
76 76 $ rm -f $SYNC_FILE $DONE_FILE
77 77 $ sh -c "../send-signal.sh INT" &
78 78 $ hg wait-signal
79 79 interrupted!
80 [250]
80 [255]
81 81
82 82 $ cat >> $HGRCPATH << EOF
83 83 > [experimental]
84 84 > nointerrupt-interactiveonly = False
85 85 > EOF
86 86
87 87 $ rm -f $SYNC_FILE $DONE_FILE
88 88 $ sh -c "../send-signal.sh INT" &
89 89 $ hg wait-signal
90 90 shutting down cleanly
91 91 press ^C again to terminate immediately (dangerous)
92 92 end of unsafe operation
93 93 interrupted!
94 [250]
94 [255]
General Comments 0
You need to be logged in to leave comments. Login now