##// END OF EJS Templates
dispatch: unify handling of None returned by a command function...
Yuya Nishihara -
r38015:6f9ac3cb default
parent child Browse files
Show More
@@ -1,535 +1,535 b''
1 1 # commandserver.py - communicate with Mercurial's API over a pipe
2 2 #
3 3 # Copyright Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import errno
11 11 import gc
12 12 import os
13 13 import random
14 14 import signal
15 15 import socket
16 16 import struct
17 17 import traceback
18 18
19 19 try:
20 20 import selectors
21 21 selectors.BaseSelector
22 22 except ImportError:
23 23 from .thirdparty import selectors2 as selectors
24 24
25 25 from .i18n import _
26 26 from . import (
27 27 encoding,
28 28 error,
29 29 pycompat,
30 30 util,
31 31 )
32 32 from .utils import (
33 33 procutil,
34 34 )
35 35
36 36 logfile = None
37 37
38 38 def log(*args):
39 39 if not logfile:
40 40 return
41 41
42 42 for a in args:
43 43 logfile.write(str(a))
44 44
45 45 logfile.flush()
46 46
47 47 class channeledoutput(object):
48 48 """
49 49 Write data to out in the following format:
50 50
51 51 data length (unsigned int),
52 52 data
53 53 """
54 54 def __init__(self, out, channel):
55 55 self.out = out
56 56 self.channel = channel
57 57
58 58 @property
59 59 def name(self):
60 60 return '<%c-channel>' % self.channel
61 61
62 62 def write(self, data):
63 63 if not data:
64 64 return
65 65 # single write() to guarantee the same atomicity as the underlying file
66 66 self.out.write(struct.pack('>cI', self.channel, len(data)) + data)
67 67 self.out.flush()
68 68
69 69 def __getattr__(self, attr):
70 70 if attr in ('isatty', 'fileno', 'tell', 'seek'):
71 71 raise AttributeError(attr)
72 72 return getattr(self.out, attr)
73 73
74 74 class channeledinput(object):
75 75 """
76 76 Read data from in_.
77 77
78 78 Requests for input are written to out in the following format:
79 79 channel identifier - 'I' for plain input, 'L' line based (1 byte)
80 80 how many bytes to send at most (unsigned int),
81 81
82 82 The client replies with:
83 83 data length (unsigned int), 0 meaning EOF
84 84 data
85 85 """
86 86
87 87 maxchunksize = 4 * 1024
88 88
89 89 def __init__(self, in_, out, channel):
90 90 self.in_ = in_
91 91 self.out = out
92 92 self.channel = channel
93 93
94 94 @property
95 95 def name(self):
96 96 return '<%c-channel>' % self.channel
97 97
98 98 def read(self, size=-1):
99 99 if size < 0:
100 100 # if we need to consume all the clients input, ask for 4k chunks
101 101 # so the pipe doesn't fill up risking a deadlock
102 102 size = self.maxchunksize
103 103 s = self._read(size, self.channel)
104 104 buf = s
105 105 while s:
106 106 s = self._read(size, self.channel)
107 107 buf += s
108 108
109 109 return buf
110 110 else:
111 111 return self._read(size, self.channel)
112 112
113 113 def _read(self, size, channel):
114 114 if not size:
115 115 return ''
116 116 assert size > 0
117 117
118 118 # tell the client we need at most size bytes
119 119 self.out.write(struct.pack('>cI', channel, size))
120 120 self.out.flush()
121 121
122 122 length = self.in_.read(4)
123 123 length = struct.unpack('>I', length)[0]
124 124 if not length:
125 125 return ''
126 126 else:
127 127 return self.in_.read(length)
128 128
129 129 def readline(self, size=-1):
130 130 if size < 0:
131 131 size = self.maxchunksize
132 132 s = self._read(size, 'L')
133 133 buf = s
134 134 # keep asking for more until there's either no more or
135 135 # we got a full line
136 136 while s and s[-1] != '\n':
137 137 s = self._read(size, 'L')
138 138 buf += s
139 139
140 140 return buf
141 141 else:
142 142 return self._read(size, 'L')
143 143
144 144 def __iter__(self):
145 145 return self
146 146
147 147 def next(self):
148 148 l = self.readline()
149 149 if not l:
150 150 raise StopIteration
151 151 return l
152 152
153 153 def __getattr__(self, attr):
154 154 if attr in ('isatty', 'fileno', 'tell', 'seek'):
155 155 raise AttributeError(attr)
156 156 return getattr(self.in_, attr)
157 157
158 158 class server(object):
159 159 """
160 160 Listens for commands on fin, runs them and writes the output on a channel
161 161 based stream to fout.
162 162 """
163 163 def __init__(self, ui, repo, fin, fout):
164 164 self.cwd = pycompat.getcwd()
165 165
166 166 # developer config: cmdserver.log
167 167 logpath = ui.config("cmdserver", "log")
168 168 if logpath:
169 169 global logfile
170 170 if logpath == '-':
171 171 # write log on a special 'd' (debug) channel
172 172 logfile = channeledoutput(fout, 'd')
173 173 else:
174 174 logfile = open(logpath, 'a')
175 175
176 176 if repo:
177 177 # the ui here is really the repo ui so take its baseui so we don't
178 178 # end up with its local configuration
179 179 self.ui = repo.baseui
180 180 self.repo = repo
181 181 self.repoui = repo.ui
182 182 else:
183 183 self.ui = ui
184 184 self.repo = self.repoui = None
185 185
186 186 self.cerr = channeledoutput(fout, 'e')
187 187 self.cout = channeledoutput(fout, 'o')
188 188 self.cin = channeledinput(fin, fout, 'I')
189 189 self.cresult = channeledoutput(fout, 'r')
190 190
191 191 self.client = fin
192 192
193 193 def cleanup(self):
194 194 """release and restore resources taken during server session"""
195 195
196 196 def _read(self, size):
197 197 if not size:
198 198 return ''
199 199
200 200 data = self.client.read(size)
201 201
202 202 # is the other end closed?
203 203 if not data:
204 204 raise EOFError
205 205
206 206 return data
207 207
208 208 def _readstr(self):
209 209 """read a string from the channel
210 210
211 211 format:
212 212 data length (uint32), data
213 213 """
214 214 length = struct.unpack('>I', self._read(4))[0]
215 215 if not length:
216 216 return ''
217 217 return self._read(length)
218 218
219 219 def _readlist(self):
220 220 """read a list of NULL separated strings from the channel"""
221 221 s = self._readstr()
222 222 if s:
223 223 return s.split('\0')
224 224 else:
225 225 return []
226 226
227 227 def runcommand(self):
228 228 """ reads a list of \0 terminated arguments, executes
229 229 and writes the return code to the result channel """
230 230 from . import dispatch # avoid cycle
231 231
232 232 args = self._readlist()
233 233
234 234 # copy the uis so changes (e.g. --config or --verbose) don't
235 235 # persist between requests
236 236 copiedui = self.ui.copy()
237 237 uis = [copiedui]
238 238 if self.repo:
239 239 self.repo.baseui = copiedui
240 240 # clone ui without using ui.copy because this is protected
241 241 repoui = self.repoui.__class__(self.repoui)
242 242 repoui.copy = copiedui.copy # redo copy protection
243 243 uis.append(repoui)
244 244 self.repo.ui = self.repo.dirstate._ui = repoui
245 245 self.repo.invalidateall()
246 246
247 247 for ui in uis:
248 248 ui.resetstate()
249 249 # any kind of interaction must use server channels, but chg may
250 250 # replace channels by fully functional tty files. so nontty is
251 251 # enforced only if cin is a channel.
252 252 if not util.safehasattr(self.cin, 'fileno'):
253 253 ui.setconfig('ui', 'nontty', 'true', 'commandserver')
254 254
255 255 req = dispatch.request(args[:], copiedui, self.repo, self.cin,
256 256 self.cout, self.cerr)
257 257
258 258 try:
259 ret = (dispatch.dispatch(req) or 0) & 255 # might return None
259 ret = dispatch.dispatch(req) & 255
260 260 self.cresult.write(struct.pack('>i', int(ret)))
261 261 finally:
262 262 # restore old cwd
263 263 if '--cwd' in args:
264 264 os.chdir(self.cwd)
265 265
266 266 def getencoding(self):
267 267 """ writes the current encoding to the result channel """
268 268 self.cresult.write(encoding.encoding)
269 269
270 270 def serveone(self):
271 271 cmd = self.client.readline()[:-1]
272 272 if cmd:
273 273 handler = self.capabilities.get(cmd)
274 274 if handler:
275 275 handler(self)
276 276 else:
277 277 # clients are expected to check what commands are supported by
278 278 # looking at the servers capabilities
279 279 raise error.Abort(_('unknown command %s') % cmd)
280 280
281 281 return cmd != ''
282 282
283 283 capabilities = {'runcommand': runcommand,
284 284 'getencoding': getencoding}
285 285
286 286 def serve(self):
287 287 hellomsg = 'capabilities: ' + ' '.join(sorted(self.capabilities))
288 288 hellomsg += '\n'
289 289 hellomsg += 'encoding: ' + encoding.encoding
290 290 hellomsg += '\n'
291 291 hellomsg += 'pid: %d' % procutil.getpid()
292 292 if util.safehasattr(os, 'getpgid'):
293 293 hellomsg += '\n'
294 294 hellomsg += 'pgid: %d' % os.getpgid(0)
295 295
296 296 # write the hello msg in -one- chunk
297 297 self.cout.write(hellomsg)
298 298
299 299 try:
300 300 while self.serveone():
301 301 pass
302 302 except EOFError:
303 303 # we'll get here if the client disconnected while we were reading
304 304 # its request
305 305 return 1
306 306
307 307 return 0
308 308
309 309 class pipeservice(object):
310 310 def __init__(self, ui, repo, opts):
311 311 self.ui = ui
312 312 self.repo = repo
313 313
314 314 def init(self):
315 315 pass
316 316
317 317 def run(self):
318 318 ui = self.ui
319 319 # redirect stdio to null device so that broken extensions or in-process
320 320 # hooks will never cause corruption of channel protocol.
321 321 with procutil.protectedstdio(ui.fin, ui.fout) as (fin, fout):
322 322 try:
323 323 sv = server(ui, self.repo, fin, fout)
324 324 return sv.serve()
325 325 finally:
326 326 sv.cleanup()
327 327
328 328 def _initworkerprocess():
329 329 # use a different process group from the master process, in order to:
330 330 # 1. make the current process group no longer "orphaned" (because the
331 331 # parent of this process is in a different process group while
332 332 # remains in a same session)
333 333 # according to POSIX 2.2.2.52, orphaned process group will ignore
334 334 # terminal-generated stop signals like SIGTSTP (Ctrl+Z), which will
335 335 # cause trouble for things like ncurses.
336 336 # 2. the client can use kill(-pgid, sig) to simulate terminal-generated
337 337 # SIGINT (Ctrl+C) and process-exit-generated SIGHUP. our child
338 338 # processes like ssh will be killed properly, without affecting
339 339 # unrelated processes.
340 340 os.setpgid(0, 0)
341 341 # change random state otherwise forked request handlers would have a
342 342 # same state inherited from parent.
343 343 random.seed()
344 344
345 345 def _serverequest(ui, repo, conn, createcmdserver):
346 346 fin = conn.makefile('rb')
347 347 fout = conn.makefile('wb')
348 348 sv = None
349 349 try:
350 350 sv = createcmdserver(repo, conn, fin, fout)
351 351 try:
352 352 sv.serve()
353 353 # handle exceptions that may be raised by command server. most of
354 354 # known exceptions are caught by dispatch.
355 355 except error.Abort as inst:
356 356 ui.warn(_('abort: %s\n') % inst)
357 357 except IOError as inst:
358 358 if inst.errno != errno.EPIPE:
359 359 raise
360 360 except KeyboardInterrupt:
361 361 pass
362 362 finally:
363 363 sv.cleanup()
364 364 except: # re-raises
365 365 # also write traceback to error channel. otherwise client cannot
366 366 # see it because it is written to server's stderr by default.
367 367 if sv:
368 368 cerr = sv.cerr
369 369 else:
370 370 cerr = channeledoutput(fout, 'e')
371 371 traceback.print_exc(file=cerr)
372 372 raise
373 373 finally:
374 374 fin.close()
375 375 try:
376 376 fout.close() # implicit flush() may cause another EPIPE
377 377 except IOError as inst:
378 378 if inst.errno != errno.EPIPE:
379 379 raise
380 380
381 381 class unixservicehandler(object):
382 382 """Set of pluggable operations for unix-mode services
383 383
384 384 Almost all methods except for createcmdserver() are called in the main
385 385 process. You can't pass mutable resource back from createcmdserver().
386 386 """
387 387
388 388 pollinterval = None
389 389
390 390 def __init__(self, ui):
391 391 self.ui = ui
392 392
393 393 def bindsocket(self, sock, address):
394 394 util.bindunixsocket(sock, address)
395 395 sock.listen(socket.SOMAXCONN)
396 396 self.ui.status(_('listening at %s\n') % address)
397 397 self.ui.flush() # avoid buffering of status message
398 398
399 399 def unlinksocket(self, address):
400 400 os.unlink(address)
401 401
402 402 def shouldexit(self):
403 403 """True if server should shut down; checked per pollinterval"""
404 404 return False
405 405
406 406 def newconnection(self):
407 407 """Called when main process notices new connection"""
408 408
409 409 def createcmdserver(self, repo, conn, fin, fout):
410 410 """Create new command server instance; called in the process that
411 411 serves for the current connection"""
412 412 return server(self.ui, repo, fin, fout)
413 413
414 414 class unixforkingservice(object):
415 415 """
416 416 Listens on unix domain socket and forks server per connection
417 417 """
418 418
419 419 def __init__(self, ui, repo, opts, handler=None):
420 420 self.ui = ui
421 421 self.repo = repo
422 422 self.address = opts['address']
423 423 if not util.safehasattr(socket, 'AF_UNIX'):
424 424 raise error.Abort(_('unsupported platform'))
425 425 if not self.address:
426 426 raise error.Abort(_('no socket path specified with --address'))
427 427 self._servicehandler = handler or unixservicehandler(ui)
428 428 self._sock = None
429 429 self._oldsigchldhandler = None
430 430 self._workerpids = set() # updated by signal handler; do not iterate
431 431 self._socketunlinked = None
432 432
433 433 def init(self):
434 434 self._sock = socket.socket(socket.AF_UNIX)
435 435 self._servicehandler.bindsocket(self._sock, self.address)
436 436 if util.safehasattr(procutil, 'unblocksignal'):
437 437 procutil.unblocksignal(signal.SIGCHLD)
438 438 o = signal.signal(signal.SIGCHLD, self._sigchldhandler)
439 439 self._oldsigchldhandler = o
440 440 self._socketunlinked = False
441 441
442 442 def _unlinksocket(self):
443 443 if not self._socketunlinked:
444 444 self._servicehandler.unlinksocket(self.address)
445 445 self._socketunlinked = True
446 446
447 447 def _cleanup(self):
448 448 signal.signal(signal.SIGCHLD, self._oldsigchldhandler)
449 449 self._sock.close()
450 450 self._unlinksocket()
451 451 # don't kill child processes as they have active clients, just wait
452 452 self._reapworkers(0)
453 453
454 454 def run(self):
455 455 try:
456 456 self._mainloop()
457 457 finally:
458 458 self._cleanup()
459 459
460 460 def _mainloop(self):
461 461 exiting = False
462 462 h = self._servicehandler
463 463 selector = selectors.DefaultSelector()
464 464 selector.register(self._sock, selectors.EVENT_READ)
465 465 while True:
466 466 if not exiting and h.shouldexit():
467 467 # clients can no longer connect() to the domain socket, so
468 468 # we stop queuing new requests.
469 469 # for requests that are queued (connect()-ed, but haven't been
470 470 # accept()-ed), handle them before exit. otherwise, clients
471 471 # waiting for recv() will receive ECONNRESET.
472 472 self._unlinksocket()
473 473 exiting = True
474 474 ready = selector.select(timeout=h.pollinterval)
475 475 if not ready:
476 476 # only exit if we completed all queued requests
477 477 if exiting:
478 478 break
479 479 continue
480 480 try:
481 481 conn, _addr = self._sock.accept()
482 482 except socket.error as inst:
483 483 if inst.args[0] == errno.EINTR:
484 484 continue
485 485 raise
486 486
487 487 pid = os.fork()
488 488 if pid:
489 489 try:
490 490 self.ui.debug('forked worker process (pid=%d)\n' % pid)
491 491 self._workerpids.add(pid)
492 492 h.newconnection()
493 493 finally:
494 494 conn.close() # release handle in parent process
495 495 else:
496 496 try:
497 497 self._runworker(conn)
498 498 conn.close()
499 499 os._exit(0)
500 500 except: # never return, hence no re-raises
501 501 try:
502 502 self.ui.traceback(force=True)
503 503 finally:
504 504 os._exit(255)
505 505 selector.close()
506 506
507 507 def _sigchldhandler(self, signal, frame):
508 508 self._reapworkers(os.WNOHANG)
509 509
510 510 def _reapworkers(self, options):
511 511 while self._workerpids:
512 512 try:
513 513 pid, _status = os.waitpid(-1, options)
514 514 except OSError as inst:
515 515 if inst.errno == errno.EINTR:
516 516 continue
517 517 if inst.errno != errno.ECHILD:
518 518 raise
519 519 # no child processes at all (reaped by other waitpid()?)
520 520 self._workerpids.clear()
521 521 return
522 522 if pid == 0:
523 523 # no waitable child processes
524 524 return
525 525 self.ui.debug('worker process exited (pid=%d)\n' % pid)
526 526 self._workerpids.discard(pid)
527 527
528 528 def _runworker(self, conn):
529 529 signal.signal(signal.SIGCHLD, self._oldsigchldhandler)
530 530 _initworkerprocess()
531 531 h = self._servicehandler
532 532 try:
533 533 _serverequest(self.ui, self.repo, conn, h.createcmdserver)
534 534 finally:
535 535 gc.collect() # trigger __del__ since worker process uses os._exit
@@ -1,1056 +1,1056 b''
1 1 # dispatch.py - command dispatching for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import, print_function
9 9
10 10 import difflib
11 11 import errno
12 12 import getopt
13 13 import os
14 14 import pdb
15 15 import re
16 16 import signal
17 17 import sys
18 18 import time
19 19 import traceback
20 20
21 21
22 22 from .i18n import _
23 23
24 24 from . import (
25 25 cmdutil,
26 26 color,
27 27 commands,
28 28 demandimport,
29 29 encoding,
30 30 error,
31 31 extensions,
32 32 fancyopts,
33 33 help,
34 34 hg,
35 35 hook,
36 36 profiling,
37 37 pycompat,
38 38 scmutil,
39 39 ui as uimod,
40 40 util,
41 41 )
42 42
43 43 from .utils import (
44 44 procutil,
45 45 stringutil,
46 46 )
47 47
48 48 class request(object):
49 49 def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
50 50 ferr=None, prereposetups=None):
51 51 self.args = args
52 52 self.ui = ui
53 53 self.repo = repo
54 54
55 55 # input/output/error streams
56 56 self.fin = fin
57 57 self.fout = fout
58 58 self.ferr = ferr
59 59
60 60 # remember options pre-parsed by _earlyparseopts()
61 61 self.earlyoptions = {}
62 62
63 63 # reposetups which run before extensions, useful for chg to pre-fill
64 64 # low-level repo state (for example, changelog) before extensions.
65 65 self.prereposetups = prereposetups or []
66 66
67 67 def _runexithandlers(self):
68 68 exc = None
69 69 handlers = self.ui._exithandlers
70 70 try:
71 71 while handlers:
72 72 func, args, kwargs = handlers.pop()
73 73 try:
74 74 func(*args, **kwargs)
75 75 except: # re-raises below
76 76 if exc is None:
77 77 exc = sys.exc_info()[1]
78 78 self.ui.warn(('error in exit handlers:\n'))
79 79 self.ui.traceback(force=True)
80 80 finally:
81 81 if exc is not None:
82 82 raise exc
83 83
84 84 def run():
85 85 "run the command in sys.argv"
86 86 initstdio()
87 87 req = request(pycompat.sysargv[1:])
88 88 err = None
89 89 try:
90 status = dispatch(req) or 0
90 status = dispatch(req)
91 91 except error.StdioError as e:
92 92 err = e
93 93 status = -1
94 94
95 95 # In all cases we try to flush stdio streams.
96 96 if util.safehasattr(req.ui, 'fout'):
97 97 try:
98 98 req.ui.fout.flush()
99 99 except IOError as e:
100 100 err = e
101 101 status = -1
102 102
103 103 if util.safehasattr(req.ui, 'ferr'):
104 104 try:
105 105 if err is not None and err.errno != errno.EPIPE:
106 106 req.ui.ferr.write('abort: %s\n' %
107 107 encoding.strtolocal(err.strerror))
108 108 req.ui.ferr.flush()
109 109 # There's not much we can do about an I/O error here. So (possibly)
110 110 # change the status code and move on.
111 111 except IOError:
112 112 status = -1
113 113
114 114 _silencestdio()
115 115 sys.exit(status & 255)
116 116
117 117 if pycompat.ispy3:
118 118 def initstdio():
119 119 pass
120 120
121 121 def _silencestdio():
122 122 for fp in (sys.stdout, sys.stderr):
123 123 # Check if the file is okay
124 124 try:
125 125 fp.flush()
126 126 continue
127 127 except IOError:
128 128 pass
129 129 # Otherwise mark it as closed to silence "Exception ignored in"
130 130 # message emitted by the interpreter finalizer. Be careful to
131 131 # not close procutil.stdout, which may be a fdopen-ed file object
132 132 # and its close() actually closes the underlying file descriptor.
133 133 try:
134 134 fp.close()
135 135 except IOError:
136 136 pass
137 137 else:
138 138 def initstdio():
139 139 for fp in (sys.stdin, sys.stdout, sys.stderr):
140 140 procutil.setbinary(fp)
141 141
142 142 def _silencestdio():
143 143 pass
144 144
145 145 def _getsimilar(symbols, value):
146 146 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
147 147 # The cutoff for similarity here is pretty arbitrary. It should
148 148 # probably be investigated and tweaked.
149 149 return [s for s in symbols if sim(s) > 0.6]
150 150
151 151 def _reportsimilar(write, similar):
152 152 if len(similar) == 1:
153 153 write(_("(did you mean %s?)\n") % similar[0])
154 154 elif similar:
155 155 ss = ", ".join(sorted(similar))
156 156 write(_("(did you mean one of %s?)\n") % ss)
157 157
158 158 def _formatparse(write, inst):
159 159 similar = []
160 160 if isinstance(inst, error.UnknownIdentifier):
161 161 # make sure to check fileset first, as revset can invoke fileset
162 162 similar = _getsimilar(inst.symbols, inst.function)
163 163 if len(inst.args) > 1:
164 164 write(_("hg: parse error at %s: %s\n") %
165 165 (pycompat.bytestr(inst.args[1]), inst.args[0]))
166 166 if inst.args[0].startswith(' '):
167 167 write(_("unexpected leading whitespace\n"))
168 168 else:
169 169 write(_("hg: parse error: %s\n") % inst.args[0])
170 170 _reportsimilar(write, similar)
171 171 if inst.hint:
172 172 write(_("(%s)\n") % inst.hint)
173 173
174 174 def _formatargs(args):
175 175 return ' '.join(procutil.shellquote(a) for a in args)
176 176
177 177 def dispatch(req):
178 "run the command specified in req.args"
178 """run the command specified in req.args; returns an integer status code"""
179 179 if req.ferr:
180 180 ferr = req.ferr
181 181 elif req.ui:
182 182 ferr = req.ui.ferr
183 183 else:
184 184 ferr = procutil.stderr
185 185
186 186 try:
187 187 if not req.ui:
188 188 req.ui = uimod.ui.load()
189 189 req.earlyoptions.update(_earlyparseopts(req.ui, req.args))
190 190 if req.earlyoptions['traceback']:
191 191 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
192 192
193 193 # set ui streams from the request
194 194 if req.fin:
195 195 req.ui.fin = req.fin
196 196 if req.fout:
197 197 req.ui.fout = req.fout
198 198 if req.ferr:
199 199 req.ui.ferr = req.ferr
200 200 except error.Abort as inst:
201 201 ferr.write(_("abort: %s\n") % inst)
202 202 if inst.hint:
203 203 ferr.write(_("(%s)\n") % inst.hint)
204 204 return -1
205 205 except error.ParseError as inst:
206 206 _formatparse(ferr.write, inst)
207 207 return -1
208 208
209 209 msg = _formatargs(req.args)
210 210 starttime = util.timer()
211 ret = None
211 ret = -1
212 212 try:
213 ret = _runcatch(req)
213 ret = _runcatch(req) or 0
214 214 except error.ProgrammingError as inst:
215 215 req.ui.warn(_('** ProgrammingError: %s\n') % inst)
216 216 if inst.hint:
217 217 req.ui.warn(_('** (%s)\n') % inst.hint)
218 218 raise
219 219 except KeyboardInterrupt as inst:
220 220 try:
221 221 if isinstance(inst, error.SignalInterrupt):
222 222 msg = _("killed!\n")
223 223 else:
224 224 msg = _("interrupted!\n")
225 225 req.ui.warn(msg)
226 226 except error.SignalInterrupt:
227 227 # maybe pager would quit without consuming all the output, and
228 228 # SIGPIPE was raised. we cannot print anything in this case.
229 229 pass
230 230 except IOError as inst:
231 231 if inst.errno != errno.EPIPE:
232 232 raise
233 233 ret = -1
234 234 finally:
235 235 duration = util.timer() - starttime
236 236 req.ui.flush()
237 237 if req.ui.logblockedtimes:
238 238 req.ui._blockedtimes['command_duration'] = duration * 1000
239 239 req.ui.log('uiblocked', 'ui blocked ms',
240 240 **pycompat.strkwargs(req.ui._blockedtimes))
241 241 req.ui.log("commandfinish", "%s exited %d after %0.2f seconds\n",
242 242 msg, ret or 0, duration)
243 243 try:
244 244 req._runexithandlers()
245 245 except: # exiting, so no re-raises
246 246 ret = ret or -1
247 247 return ret
248 248
249 249 def _runcatch(req):
250 250 def catchterm(*args):
251 251 raise error.SignalInterrupt
252 252
253 253 ui = req.ui
254 254 try:
255 255 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
256 256 num = getattr(signal, name, None)
257 257 if num:
258 258 signal.signal(num, catchterm)
259 259 except ValueError:
260 260 pass # happens if called in a thread
261 261
262 262 def _runcatchfunc():
263 263 realcmd = None
264 264 try:
265 265 cmdargs = fancyopts.fancyopts(req.args[:], commands.globalopts, {})
266 266 cmd = cmdargs[0]
267 267 aliases, entry = cmdutil.findcmd(cmd, commands.table, False)
268 268 realcmd = aliases[0]
269 269 except (error.UnknownCommand, error.AmbiguousCommand,
270 270 IndexError, getopt.GetoptError):
271 271 # Don't handle this here. We know the command is
272 272 # invalid, but all we're worried about for now is that
273 273 # it's not a command that server operators expect to
274 274 # be safe to offer to users in a sandbox.
275 275 pass
276 276 if realcmd == 'serve' and '--stdio' in cmdargs:
277 277 # We want to constrain 'hg serve --stdio' instances pretty
278 278 # closely, as many shared-ssh access tools want to grant
279 279 # access to run *only* 'hg -R $repo serve --stdio'. We
280 280 # restrict to exactly that set of arguments, and prohibit
281 281 # any repo name that starts with '--' to prevent
282 282 # shenanigans wherein a user does something like pass
283 283 # --debugger or --config=ui.debugger=1 as a repo
284 284 # name. This used to actually run the debugger.
285 285 if (len(req.args) != 4 or
286 286 req.args[0] != '-R' or
287 287 req.args[1].startswith('--') or
288 288 req.args[2] != 'serve' or
289 289 req.args[3] != '--stdio'):
290 290 raise error.Abort(
291 291 _('potentially unsafe serve --stdio invocation: %r') %
292 292 (req.args,))
293 293
294 294 try:
295 295 debugger = 'pdb'
296 296 debugtrace = {
297 297 'pdb': pdb.set_trace
298 298 }
299 299 debugmortem = {
300 300 'pdb': pdb.post_mortem
301 301 }
302 302
303 303 # read --config before doing anything else
304 304 # (e.g. to change trust settings for reading .hg/hgrc)
305 305 cfgs = _parseconfig(req.ui, req.earlyoptions['config'])
306 306
307 307 if req.repo:
308 308 # copy configs that were passed on the cmdline (--config) to
309 309 # the repo ui
310 310 for sec, name, val in cfgs:
311 311 req.repo.ui.setconfig(sec, name, val, source='--config')
312 312
313 313 # developer config: ui.debugger
314 314 debugger = ui.config("ui", "debugger")
315 315 debugmod = pdb
316 316 if not debugger or ui.plain():
317 317 # if we are in HGPLAIN mode, then disable custom debugging
318 318 debugger = 'pdb'
319 319 elif req.earlyoptions['debugger']:
320 320 # This import can be slow for fancy debuggers, so only
321 321 # do it when absolutely necessary, i.e. when actual
322 322 # debugging has been requested
323 323 with demandimport.deactivated():
324 324 try:
325 325 debugmod = __import__(debugger)
326 326 except ImportError:
327 327 pass # Leave debugmod = pdb
328 328
329 329 debugtrace[debugger] = debugmod.set_trace
330 330 debugmortem[debugger] = debugmod.post_mortem
331 331
332 332 # enter the debugger before command execution
333 333 if req.earlyoptions['debugger']:
334 334 ui.warn(_("entering debugger - "
335 335 "type c to continue starting hg or h for help\n"))
336 336
337 337 if (debugger != 'pdb' and
338 338 debugtrace[debugger] == debugtrace['pdb']):
339 339 ui.warn(_("%s debugger specified "
340 340 "but its module was not found\n") % debugger)
341 341 with demandimport.deactivated():
342 342 debugtrace[debugger]()
343 343 try:
344 344 return _dispatch(req)
345 345 finally:
346 346 ui.flush()
347 347 except: # re-raises
348 348 # enter the debugger when we hit an exception
349 349 if req.earlyoptions['debugger']:
350 350 traceback.print_exc()
351 351 debugmortem[debugger](sys.exc_info()[2])
352 352 raise
353 353
354 354 return _callcatch(ui, _runcatchfunc)
355 355
356 356 def _callcatch(ui, func):
357 357 """like scmutil.callcatch but handles more high-level exceptions about
358 358 config parsing and commands. besides, use handlecommandexception to handle
359 359 uncaught exceptions.
360 360 """
361 361 try:
362 362 return scmutil.callcatch(ui, func)
363 363 except error.AmbiguousCommand as inst:
364 364 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
365 365 (inst.args[0], " ".join(inst.args[1])))
366 366 except error.CommandError as inst:
367 367 if inst.args[0]:
368 368 ui.pager('help')
369 369 msgbytes = pycompat.bytestr(inst.args[1])
370 370 ui.warn(_("hg %s: %s\n") % (inst.args[0], msgbytes))
371 371 commands.help_(ui, inst.args[0], full=False, command=True)
372 372 else:
373 373 ui.pager('help')
374 374 ui.warn(_("hg: %s\n") % inst.args[1])
375 375 commands.help_(ui, 'shortlist')
376 376 except error.ParseError as inst:
377 377 _formatparse(ui.warn, inst)
378 378 return -1
379 379 except error.UnknownCommand as inst:
380 380 nocmdmsg = _("hg: unknown command '%s'\n") % inst.args[0]
381 381 try:
382 382 # check if the command is in a disabled extension
383 383 # (but don't check for extensions themselves)
384 384 formatted = help.formattedhelp(ui, commands, inst.args[0],
385 385 unknowncmd=True)
386 386 ui.warn(nocmdmsg)
387 387 ui.write(formatted)
388 388 except (error.UnknownCommand, error.Abort):
389 389 suggested = False
390 390 if len(inst.args) == 2:
391 391 sim = _getsimilar(inst.args[1], inst.args[0])
392 392 if sim:
393 393 ui.warn(nocmdmsg)
394 394 _reportsimilar(ui.warn, sim)
395 395 suggested = True
396 396 if not suggested:
397 397 ui.pager('help')
398 398 ui.warn(nocmdmsg)
399 399 commands.help_(ui, 'shortlist')
400 400 except IOError:
401 401 raise
402 402 except KeyboardInterrupt:
403 403 raise
404 404 except: # probably re-raises
405 405 if not handlecommandexception(ui):
406 406 raise
407 407
408 408 return -1
409 409
410 410 def aliasargs(fn, givenargs):
411 411 args = []
412 412 # only care about alias 'args', ignore 'args' set by extensions.wrapfunction
413 413 if not util.safehasattr(fn, '_origfunc'):
414 414 args = getattr(fn, 'args', args)
415 415 if args:
416 416 cmd = ' '.join(map(procutil.shellquote, args))
417 417
418 418 nums = []
419 419 def replacer(m):
420 420 num = int(m.group(1)) - 1
421 421 nums.append(num)
422 422 if num < len(givenargs):
423 423 return givenargs[num]
424 424 raise error.Abort(_('too few arguments for command alias'))
425 425 cmd = re.sub(br'\$(\d+|\$)', replacer, cmd)
426 426 givenargs = [x for i, x in enumerate(givenargs)
427 427 if i not in nums]
428 428 args = pycompat.shlexsplit(cmd)
429 429 return args + givenargs
430 430
431 431 def aliasinterpolate(name, args, cmd):
432 432 '''interpolate args into cmd for shell aliases
433 433
434 434 This also handles $0, $@ and "$@".
435 435 '''
436 436 # util.interpolate can't deal with "$@" (with quotes) because it's only
437 437 # built to match prefix + patterns.
438 438 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
439 439 replacemap['$0'] = name
440 440 replacemap['$$'] = '$'
441 441 replacemap['$@'] = ' '.join(args)
442 442 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
443 443 # parameters, separated out into words. Emulate the same behavior here by
444 444 # quoting the arguments individually. POSIX shells will then typically
445 445 # tokenize each argument into exactly one word.
446 446 replacemap['"$@"'] = ' '.join(procutil.shellquote(arg) for arg in args)
447 447 # escape '\$' for regex
448 448 regex = '|'.join(replacemap.keys()).replace('$', br'\$')
449 449 r = re.compile(regex)
450 450 return r.sub(lambda x: replacemap[x.group()], cmd)
451 451
452 452 class cmdalias(object):
453 453 def __init__(self, ui, name, definition, cmdtable, source):
454 454 self.name = self.cmd = name
455 455 self.cmdname = ''
456 456 self.definition = definition
457 457 self.fn = None
458 458 self.givenargs = []
459 459 self.opts = []
460 460 self.help = ''
461 461 self.badalias = None
462 462 self.unknowncmd = False
463 463 self.source = source
464 464
465 465 try:
466 466 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
467 467 for alias, e in cmdtable.iteritems():
468 468 if e is entry:
469 469 self.cmd = alias
470 470 break
471 471 self.shadows = True
472 472 except error.UnknownCommand:
473 473 self.shadows = False
474 474
475 475 if not self.definition:
476 476 self.badalias = _("no definition for alias '%s'") % self.name
477 477 return
478 478
479 479 if self.definition.startswith('!'):
480 480 shdef = self.definition[1:]
481 481 self.shell = True
482 482 def fn(ui, *args):
483 483 env = {'HG_ARGS': ' '.join((self.name,) + args)}
484 484 def _checkvar(m):
485 485 if m.groups()[0] == '$':
486 486 return m.group()
487 487 elif int(m.groups()[0]) <= len(args):
488 488 return m.group()
489 489 else:
490 490 ui.debug("No argument found for substitution "
491 491 "of %i variable in alias '%s' definition.\n"
492 492 % (int(m.groups()[0]), self.name))
493 493 return ''
494 494 cmd = re.sub(br'\$(\d+|\$)', _checkvar, shdef)
495 495 cmd = aliasinterpolate(self.name, args, cmd)
496 496 return ui.system(cmd, environ=env,
497 497 blockedtag='alias_%s' % self.name)
498 498 self.fn = fn
499 499 self._populatehelp(ui, name, shdef, self.fn)
500 500 return
501 501
502 502 try:
503 503 args = pycompat.shlexsplit(self.definition)
504 504 except ValueError as inst:
505 505 self.badalias = (_("error in definition for alias '%s': %s")
506 506 % (self.name, stringutil.forcebytestr(inst)))
507 507 return
508 508 earlyopts, args = _earlysplitopts(args)
509 509 if earlyopts:
510 510 self.badalias = (_("error in definition for alias '%s': %s may "
511 511 "only be given on the command line")
512 512 % (self.name, '/'.join(pycompat.ziplist(*earlyopts)
513 513 [0])))
514 514 return
515 515 self.cmdname = cmd = args.pop(0)
516 516 self.givenargs = args
517 517
518 518 try:
519 519 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
520 520 if len(tableentry) > 2:
521 521 self.fn, self.opts, cmdhelp = tableentry
522 522 else:
523 523 self.fn, self.opts = tableentry
524 524 cmdhelp = None
525 525
526 526 self._populatehelp(ui, name, cmd, self.fn, cmdhelp)
527 527
528 528 except error.UnknownCommand:
529 529 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
530 530 % (self.name, cmd))
531 531 self.unknowncmd = True
532 532 except error.AmbiguousCommand:
533 533 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
534 534 % (self.name, cmd))
535 535
536 536 def _populatehelp(self, ui, name, cmd, fn, defaulthelp=None):
537 537 # confine strings to be passed to i18n.gettext()
538 538 cfg = {}
539 539 for k in ('doc', 'help'):
540 540 v = ui.config('alias', '%s:%s' % (name, k), None)
541 541 if v is None:
542 542 continue
543 543 if not encoding.isasciistr(v):
544 544 self.badalias = (_("non-ASCII character in alias definition "
545 545 "'%s:%s'") % (name, k))
546 546 return
547 547 cfg[k] = v
548 548
549 549 self.help = cfg.get('help', defaulthelp or '')
550 550 if self.help and self.help.startswith("hg " + cmd):
551 551 # drop prefix in old-style help lines so hg shows the alias
552 552 self.help = self.help[4 + len(cmd):]
553 553
554 554 doc = cfg.get('doc', pycompat.getdoc(fn))
555 555 if doc is not None:
556 556 doc = pycompat.sysstr(doc)
557 557 self.__doc__ = doc
558 558
559 559 @property
560 560 def args(self):
561 561 args = pycompat.maplist(util.expandpath, self.givenargs)
562 562 return aliasargs(self.fn, args)
563 563
564 564 def __getattr__(self, name):
565 565 adefaults = {r'norepo': True, r'intents': set(),
566 566 r'optionalrepo': False, r'inferrepo': False}
567 567 if name not in adefaults:
568 568 raise AttributeError(name)
569 569 if self.badalias or util.safehasattr(self, 'shell'):
570 570 return adefaults[name]
571 571 return getattr(self.fn, name)
572 572
573 573 def __call__(self, ui, *args, **opts):
574 574 if self.badalias:
575 575 hint = None
576 576 if self.unknowncmd:
577 577 try:
578 578 # check if the command is in a disabled extension
579 579 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
580 580 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
581 581 except error.UnknownCommand:
582 582 pass
583 583 raise error.Abort(self.badalias, hint=hint)
584 584 if self.shadows:
585 585 ui.debug("alias '%s' shadows command '%s'\n" %
586 586 (self.name, self.cmdname))
587 587
588 588 ui.log('commandalias', "alias '%s' expands to '%s'\n",
589 589 self.name, self.definition)
590 590 if util.safehasattr(self, 'shell'):
591 591 return self.fn(ui, *args, **opts)
592 592 else:
593 593 try:
594 594 return util.checksignature(self.fn)(ui, *args, **opts)
595 595 except error.SignatureError:
596 596 args = ' '.join([self.cmdname] + self.args)
597 597 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
598 598 raise
599 599
600 600 class lazyaliasentry(object):
601 601 """like a typical command entry (func, opts, help), but is lazy"""
602 602
603 603 def __init__(self, ui, name, definition, cmdtable, source):
604 604 self.ui = ui
605 605 self.name = name
606 606 self.definition = definition
607 607 self.cmdtable = cmdtable.copy()
608 608 self.source = source
609 609
610 610 @util.propertycache
611 611 def _aliasdef(self):
612 612 return cmdalias(self.ui, self.name, self.definition, self.cmdtable,
613 613 self.source)
614 614
615 615 def __getitem__(self, n):
616 616 aliasdef = self._aliasdef
617 617 if n == 0:
618 618 return aliasdef
619 619 elif n == 1:
620 620 return aliasdef.opts
621 621 elif n == 2:
622 622 return aliasdef.help
623 623 else:
624 624 raise IndexError
625 625
626 626 def __iter__(self):
627 627 for i in range(3):
628 628 yield self[i]
629 629
630 630 def __len__(self):
631 631 return 3
632 632
633 633 def addaliases(ui, cmdtable):
634 634 # aliases are processed after extensions have been loaded, so they
635 635 # may use extension commands. Aliases can also use other alias definitions,
636 636 # but only if they have been defined prior to the current definition.
637 637 for alias, definition in ui.configitems('alias', ignoresub=True):
638 638 try:
639 639 if cmdtable[alias].definition == definition:
640 640 continue
641 641 except (KeyError, AttributeError):
642 642 # definition might not exist or it might not be a cmdalias
643 643 pass
644 644
645 645 source = ui.configsource('alias', alias)
646 646 entry = lazyaliasentry(ui, alias, definition, cmdtable, source)
647 647 cmdtable[alias] = entry
648 648
649 649 def _parse(ui, args):
650 650 options = {}
651 651 cmdoptions = {}
652 652
653 653 try:
654 654 args = fancyopts.fancyopts(args, commands.globalopts, options)
655 655 except getopt.GetoptError as inst:
656 656 raise error.CommandError(None, stringutil.forcebytestr(inst))
657 657
658 658 if args:
659 659 cmd, args = args[0], args[1:]
660 660 aliases, entry = cmdutil.findcmd(cmd, commands.table,
661 661 ui.configbool("ui", "strict"))
662 662 cmd = aliases[0]
663 663 args = aliasargs(entry[0], args)
664 664 defaults = ui.config("defaults", cmd)
665 665 if defaults:
666 666 args = pycompat.maplist(
667 667 util.expandpath, pycompat.shlexsplit(defaults)) + args
668 668 c = list(entry[1])
669 669 else:
670 670 cmd = None
671 671 c = []
672 672
673 673 # combine global options into local
674 674 for o in commands.globalopts:
675 675 c.append((o[0], o[1], options[o[1]], o[3]))
676 676
677 677 try:
678 678 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
679 679 except getopt.GetoptError as inst:
680 680 raise error.CommandError(cmd, stringutil.forcebytestr(inst))
681 681
682 682 # separate global options back out
683 683 for o in commands.globalopts:
684 684 n = o[1]
685 685 options[n] = cmdoptions[n]
686 686 del cmdoptions[n]
687 687
688 688 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
689 689
690 690 def _parseconfig(ui, config):
691 691 """parse the --config options from the command line"""
692 692 configs = []
693 693
694 694 for cfg in config:
695 695 try:
696 696 name, value = [cfgelem.strip()
697 697 for cfgelem in cfg.split('=', 1)]
698 698 section, name = name.split('.', 1)
699 699 if not section or not name:
700 700 raise IndexError
701 701 ui.setconfig(section, name, value, '--config')
702 702 configs.append((section, name, value))
703 703 except (IndexError, ValueError):
704 704 raise error.Abort(_('malformed --config option: %r '
705 705 '(use --config section.name=value)')
706 706 % pycompat.bytestr(cfg))
707 707
708 708 return configs
709 709
710 710 def _earlyparseopts(ui, args):
711 711 options = {}
712 712 fancyopts.fancyopts(args, commands.globalopts, options,
713 713 gnu=not ui.plain('strictflags'), early=True,
714 714 optaliases={'repository': ['repo']})
715 715 return options
716 716
717 717 def _earlysplitopts(args):
718 718 """Split args into a list of possible early options and remainder args"""
719 719 shortoptions = 'R:'
720 720 # TODO: perhaps 'debugger' should be included
721 721 longoptions = ['cwd=', 'repository=', 'repo=', 'config=']
722 722 return fancyopts.earlygetopt(args, shortoptions, longoptions,
723 723 gnu=True, keepsep=True)
724 724
725 725 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
726 726 # run pre-hook, and abort if it fails
727 727 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
728 728 pats=cmdpats, opts=cmdoptions)
729 729 try:
730 730 ret = _runcommand(ui, options, cmd, d)
731 731 # run post-hook, passing command result
732 732 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
733 733 result=ret, pats=cmdpats, opts=cmdoptions)
734 734 except Exception:
735 735 # run failure hook and re-raise
736 736 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
737 737 pats=cmdpats, opts=cmdoptions)
738 738 raise
739 739 return ret
740 740
741 741 def _getlocal(ui, rpath, wd=None):
742 742 """Return (path, local ui object) for the given target path.
743 743
744 744 Takes paths in [cwd]/.hg/hgrc into account."
745 745 """
746 746 if wd is None:
747 747 try:
748 748 wd = pycompat.getcwd()
749 749 except OSError as e:
750 750 raise error.Abort(_("error getting current working directory: %s") %
751 751 encoding.strtolocal(e.strerror))
752 752 path = cmdutil.findrepo(wd) or ""
753 753 if not path:
754 754 lui = ui
755 755 else:
756 756 lui = ui.copy()
757 757 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
758 758
759 759 if rpath:
760 760 path = lui.expandpath(rpath)
761 761 lui = ui.copy()
762 762 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
763 763
764 764 return path, lui
765 765
766 766 def _checkshellalias(lui, ui, args):
767 767 """Return the function to run the shell alias, if it is required"""
768 768 options = {}
769 769
770 770 try:
771 771 args = fancyopts.fancyopts(args, commands.globalopts, options)
772 772 except getopt.GetoptError:
773 773 return
774 774
775 775 if not args:
776 776 return
777 777
778 778 cmdtable = commands.table
779 779
780 780 cmd = args[0]
781 781 try:
782 782 strict = ui.configbool("ui", "strict")
783 783 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
784 784 except (error.AmbiguousCommand, error.UnknownCommand):
785 785 return
786 786
787 787 cmd = aliases[0]
788 788 fn = entry[0]
789 789
790 790 if cmd and util.safehasattr(fn, 'shell'):
791 791 # shell alias shouldn't receive early options which are consumed by hg
792 792 _earlyopts, args = _earlysplitopts(args)
793 793 d = lambda: fn(ui, *args[1:])
794 794 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
795 795 [], {})
796 796
797 797 def _dispatch(req):
798 798 args = req.args
799 799 ui = req.ui
800 800
801 801 # check for cwd
802 802 cwd = req.earlyoptions['cwd']
803 803 if cwd:
804 804 os.chdir(cwd)
805 805
806 806 rpath = req.earlyoptions['repository']
807 807 path, lui = _getlocal(ui, rpath)
808 808
809 809 uis = {ui, lui}
810 810
811 811 if req.repo:
812 812 uis.add(req.repo.ui)
813 813
814 814 if req.earlyoptions['profile']:
815 815 for ui_ in uis:
816 816 ui_.setconfig('profiling', 'enabled', 'true', '--profile')
817 817
818 818 profile = lui.configbool('profiling', 'enabled')
819 819 with profiling.profile(lui, enabled=profile) as profiler:
820 820 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
821 821 # reposetup
822 822 extensions.loadall(lui)
823 823 # Propagate any changes to lui.__class__ by extensions
824 824 ui.__class__ = lui.__class__
825 825
826 826 # (uisetup and extsetup are handled in extensions.loadall)
827 827
828 828 # (reposetup is handled in hg.repository)
829 829
830 830 addaliases(lui, commands.table)
831 831
832 832 # All aliases and commands are completely defined, now.
833 833 # Check abbreviation/ambiguity of shell alias.
834 834 shellaliasfn = _checkshellalias(lui, ui, args)
835 835 if shellaliasfn:
836 836 return shellaliasfn()
837 837
838 838 # check for fallback encoding
839 839 fallback = lui.config('ui', 'fallbackencoding')
840 840 if fallback:
841 841 encoding.fallbackencoding = fallback
842 842
843 843 fullargs = args
844 844 cmd, func, args, options, cmdoptions = _parse(lui, args)
845 845
846 846 if options["config"] != req.earlyoptions["config"]:
847 847 raise error.Abort(_("option --config may not be abbreviated!"))
848 848 if options["cwd"] != req.earlyoptions["cwd"]:
849 849 raise error.Abort(_("option --cwd may not be abbreviated!"))
850 850 if options["repository"] != req.earlyoptions["repository"]:
851 851 raise error.Abort(_(
852 852 "option -R has to be separated from other options (e.g. not "
853 853 "-qR) and --repository may only be abbreviated as --repo!"))
854 854 if options["debugger"] != req.earlyoptions["debugger"]:
855 855 raise error.Abort(_("option --debugger may not be abbreviated!"))
856 856 # don't validate --profile/--traceback, which can be enabled from now
857 857
858 858 if options["encoding"]:
859 859 encoding.encoding = options["encoding"]
860 860 if options["encodingmode"]:
861 861 encoding.encodingmode = options["encodingmode"]
862 862 if options["time"]:
863 863 def get_times():
864 864 t = os.times()
865 865 if t[4] == 0.0:
866 866 # Windows leaves this as zero, so use time.clock()
867 867 t = (t[0], t[1], t[2], t[3], time.clock())
868 868 return t
869 869 s = get_times()
870 870 def print_time():
871 871 t = get_times()
872 872 ui.warn(
873 873 _("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
874 874 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
875 875 ui.atexit(print_time)
876 876 if options["profile"]:
877 877 profiler.start()
878 878
879 879 if options['verbose'] or options['debug'] or options['quiet']:
880 880 for opt in ('verbose', 'debug', 'quiet'):
881 881 val = pycompat.bytestr(bool(options[opt]))
882 882 for ui_ in uis:
883 883 ui_.setconfig('ui', opt, val, '--' + opt)
884 884
885 885 if options['traceback']:
886 886 for ui_ in uis:
887 887 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
888 888
889 889 if options['noninteractive']:
890 890 for ui_ in uis:
891 891 ui_.setconfig('ui', 'interactive', 'off', '-y')
892 892
893 893 if cmdoptions.get('insecure', False):
894 894 for ui_ in uis:
895 895 ui_.insecureconnections = True
896 896
897 897 # setup color handling before pager, because setting up pager
898 898 # might cause incorrect console information
899 899 coloropt = options['color']
900 900 for ui_ in uis:
901 901 if coloropt:
902 902 ui_.setconfig('ui', 'color', coloropt, '--color')
903 903 color.setup(ui_)
904 904
905 905 if stringutil.parsebool(options['pager']):
906 906 # ui.pager() expects 'internal-always-' prefix in this case
907 907 ui.pager('internal-always-' + cmd)
908 908 elif options['pager'] != 'auto':
909 909 for ui_ in uis:
910 910 ui_.disablepager()
911 911
912 912 if options['version']:
913 913 return commands.version_(ui)
914 914 if options['help']:
915 915 return commands.help_(ui, cmd, command=cmd is not None)
916 916 elif not cmd:
917 917 return commands.help_(ui, 'shortlist')
918 918
919 919 repo = None
920 920 cmdpats = args[:]
921 921 if not func.norepo:
922 922 # use the repo from the request only if we don't have -R
923 923 if not rpath and not cwd:
924 924 repo = req.repo
925 925
926 926 if repo:
927 927 # set the descriptors of the repo ui to those of ui
928 928 repo.ui.fin = ui.fin
929 929 repo.ui.fout = ui.fout
930 930 repo.ui.ferr = ui.ferr
931 931 else:
932 932 try:
933 933 repo = hg.repository(ui, path=path,
934 934 presetupfuncs=req.prereposetups,
935 935 intents=func.intents)
936 936 if not repo.local():
937 937 raise error.Abort(_("repository '%s' is not local")
938 938 % path)
939 939 repo.ui.setconfig("bundle", "mainreporoot", repo.root,
940 940 'repo')
941 941 except error.RequirementError:
942 942 raise
943 943 except error.RepoError:
944 944 if rpath: # invalid -R path
945 945 raise
946 946 if not func.optionalrepo:
947 947 if func.inferrepo and args and not path:
948 948 # try to infer -R from command args
949 949 repos = pycompat.maplist(cmdutil.findrepo, args)
950 950 guess = repos[0]
951 951 if guess and repos.count(guess) == len(repos):
952 952 req.args = ['--repository', guess] + fullargs
953 953 req.earlyoptions['repository'] = guess
954 954 return _dispatch(req)
955 955 if not path:
956 956 raise error.RepoError(_("no repository found in"
957 957 " '%s' (.hg not found)")
958 958 % pycompat.getcwd())
959 959 raise
960 960 if repo:
961 961 ui = repo.ui
962 962 if options['hidden']:
963 963 repo = repo.unfiltered()
964 964 args.insert(0, repo)
965 965 elif rpath:
966 966 ui.warn(_("warning: --repository ignored\n"))
967 967
968 968 msg = _formatargs(fullargs)
969 969 ui.log("command", '%s\n', msg)
970 970 strcmdopt = pycompat.strkwargs(cmdoptions)
971 971 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
972 972 try:
973 973 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
974 974 cmdpats, cmdoptions)
975 975 finally:
976 976 if repo and repo != req.repo:
977 977 repo.close()
978 978
979 979 def _runcommand(ui, options, cmd, cmdfunc):
980 980 """Run a command function, possibly with profiling enabled."""
981 981 try:
982 982 return cmdfunc()
983 983 except error.SignatureError:
984 984 raise error.CommandError(cmd, _('invalid arguments'))
985 985
986 986 def _exceptionwarning(ui):
987 987 """Produce a warning message for the current active exception"""
988 988
989 989 # For compatibility checking, we discard the portion of the hg
990 990 # version after the + on the assumption that if a "normal
991 991 # user" is running a build with a + in it the packager
992 992 # probably built from fairly close to a tag and anyone with a
993 993 # 'make local' copy of hg (where the version number can be out
994 994 # of date) will be clueful enough to notice the implausible
995 995 # version number and try updating.
996 996 ct = util.versiontuple(n=2)
997 997 worst = None, ct, ''
998 998 if ui.config('ui', 'supportcontact') is None:
999 999 for name, mod in extensions.extensions():
1000 1000 # 'testedwith' should be bytes, but not all extensions are ported
1001 1001 # to py3 and we don't want UnicodeException because of that.
1002 1002 testedwith = stringutil.forcebytestr(getattr(mod, 'testedwith', ''))
1003 1003 report = getattr(mod, 'buglink', _('the extension author.'))
1004 1004 if not testedwith.strip():
1005 1005 # We found an untested extension. It's likely the culprit.
1006 1006 worst = name, 'unknown', report
1007 1007 break
1008 1008
1009 1009 # Never blame on extensions bundled with Mercurial.
1010 1010 if extensions.ismoduleinternal(mod):
1011 1011 continue
1012 1012
1013 1013 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
1014 1014 if ct in tested:
1015 1015 continue
1016 1016
1017 1017 lower = [t for t in tested if t < ct]
1018 1018 nearest = max(lower or tested)
1019 1019 if worst[0] is None or nearest < worst[1]:
1020 1020 worst = name, nearest, report
1021 1021 if worst[0] is not None:
1022 1022 name, testedwith, report = worst
1023 1023 if not isinstance(testedwith, (bytes, str)):
1024 1024 testedwith = '.'.join([stringutil.forcebytestr(c)
1025 1025 for c in testedwith])
1026 1026 warning = (_('** Unknown exception encountered with '
1027 1027 'possibly-broken third-party extension %s\n'
1028 1028 '** which supports versions %s of Mercurial.\n'
1029 1029 '** Please disable %s and try your action again.\n'
1030 1030 '** If that fixes the bug please report it to %s\n')
1031 1031 % (name, testedwith, name, report))
1032 1032 else:
1033 1033 bugtracker = ui.config('ui', 'supportcontact')
1034 1034 if bugtracker is None:
1035 1035 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
1036 1036 warning = (_("** unknown exception encountered, "
1037 1037 "please report by visiting\n** ") + bugtracker + '\n')
1038 1038 sysversion = pycompat.sysbytes(sys.version).replace('\n', '')
1039 1039 warning += ((_("** Python %s\n") % sysversion) +
1040 1040 (_("** Mercurial Distributed SCM (version %s)\n") %
1041 1041 util.version()) +
1042 1042 (_("** Extensions loaded: %s\n") %
1043 1043 ", ".join([x[0] for x in extensions.extensions()])))
1044 1044 return warning
1045 1045
1046 1046 def handlecommandexception(ui):
1047 1047 """Produce a warning message for broken commands
1048 1048
1049 1049 Called when handling an exception; the exception is reraised if
1050 1050 this function returns False, ignored otherwise.
1051 1051 """
1052 1052 warning = _exceptionwarning(ui)
1053 1053 ui.log("commandexception", "%s\n%s\n", warning,
1054 1054 pycompat.sysbytes(traceback.format_exc()))
1055 1055 ui.warn(warning)
1056 1056 return False # re-raise the exception
@@ -1,375 +1,375 b''
1 1 setup
2 2 $ cat >> $HGRCPATH <<EOF
3 3 > [extensions]
4 4 > blackbox=
5 5 > mock=$TESTDIR/mockblackbox.py
6 6 > mq=
7 7 > [alias]
8 8 > confuse = log --limit 3
9 9 > so-confusing = confuse --style compact
10 10 > EOF
11 11 $ hg init blackboxtest
12 12 $ cd blackboxtest
13 13
14 14 command, exit codes, and duration
15 15
16 16 $ echo a > a
17 17 $ hg add a
18 18 $ hg blackbox --config blackbox.dirty=True
19 19 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> init blackboxtest exited 0 after * seconds (glob)
20 20 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> add a
21 21 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> add a exited 0 after * seconds (glob)
22 22 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000+ (5000)> blackbox --config *blackbox.dirty=True* (glob)
23 23
24 24 alias expansion is logged
25 25 $ rm ./.hg/blackbox.log
26 26 $ hg confuse
27 27 $ hg blackbox
28 28 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> confuse
29 29 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> alias 'confuse' expands to 'log --limit 3'
30 30 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> confuse exited 0 after * seconds (glob)
31 31 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> blackbox
32 32
33 33 recursive aliases work correctly
34 34 $ rm ./.hg/blackbox.log
35 35 $ hg so-confusing
36 36 $ hg blackbox
37 37 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> so-confusing
38 38 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> alias 'so-confusing' expands to 'confuse --style compact'
39 39 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> alias 'confuse' expands to 'log --limit 3'
40 40 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> so-confusing exited 0 after * seconds (glob)
41 41 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> blackbox
42 42
43 43 incoming change tracking
44 44
45 45 create two heads to verify that we only see one change in the log later
46 46 $ hg commit -ma
47 47 $ hg up null
48 48 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
49 49 $ echo b > b
50 50 $ hg commit -Amb
51 51 adding b
52 52 created new head
53 53
54 54 clone, commit, pull
55 55 $ hg clone . ../blackboxtest2
56 56 updating to branch default
57 57 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
58 58 $ echo c > c
59 59 $ hg commit -Amc
60 60 adding c
61 61 $ cd ../blackboxtest2
62 62 $ hg pull
63 63 pulling from $TESTTMP/blackboxtest
64 64 searching for changes
65 65 adding changesets
66 66 adding manifests
67 67 adding file changes
68 68 added 1 changesets with 1 changes to 1 files
69 69 new changesets d02f48003e62
70 70 (run 'hg update' to get a working copy)
71 71 $ hg blackbox -l 6
72 72 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> pull
73 73 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> updated served branch cache in * seconds (glob)
74 74 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> wrote served branch cache with 1 labels and 2 nodes
75 75 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> 1 incoming changes - new heads: d02f48003e62
76 76 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> pull exited 0 after * seconds (glob)
77 77 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> blackbox -l 6
78 78
79 79 we must not cause a failure if we cannot write to the log
80 80
81 81 $ hg rollback
82 82 repository tip rolled back to revision 1 (undo pull)
83 83
84 84 $ mv .hg/blackbox.log .hg/blackbox.log-
85 85 $ mkdir .hg/blackbox.log
86 86 $ hg --debug incoming
87 87 warning: cannot write to blackbox.log: * (glob)
88 88 comparing with $TESTTMP/blackboxtest
89 89 query 1; heads
90 90 searching for changes
91 91 all local heads known remotely
92 92 changeset: 2:d02f48003e62c24e2659d97d30f2a83abe5d5d51
93 93 tag: tip
94 94 phase: draft
95 95 parent: 1:6563da9dcf87b1949716e38ff3e3dfaa3198eb06
96 96 parent: -1:0000000000000000000000000000000000000000
97 97 manifest: 2:ab9d46b053ebf45b7996f2922b9893ff4b63d892
98 98 user: test
99 99 date: Thu Jan 01 00:00:00 1970 +0000
100 100 files+: c
101 101 extra: branch=default
102 102 description:
103 103 c
104 104
105 105
106 106 $ hg pull
107 107 pulling from $TESTTMP/blackboxtest
108 108 searching for changes
109 109 adding changesets
110 110 adding manifests
111 111 adding file changes
112 112 added 1 changesets with 1 changes to 1 files
113 113 new changesets d02f48003e62
114 114 (run 'hg update' to get a working copy)
115 115
116 116 a failure reading from the log is fatal
117 117
118 118 $ hg blackbox -l 3
119 119 abort: *$TESTTMP/blackboxtest2/.hg/blackbox.log* (glob)
120 120 [255]
121 121
122 122 $ rmdir .hg/blackbox.log
123 123 $ mv .hg/blackbox.log- .hg/blackbox.log
124 124
125 125 backup bundles get logged
126 126
127 127 $ touch d
128 128 $ hg commit -Amd
129 129 adding d
130 130 created new head
131 131 $ hg strip tip
132 132 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
133 133 saved backup bundle to $TESTTMP/blackboxtest2/.hg/strip-backup/*-backup.hg (glob)
134 134 $ hg blackbox -l 6
135 135 1970/01/01 00:00:00 bob @73f6ee326b27d820b0472f1a825e3a50f3dc489b (5000)> strip tip
136 136 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> saved backup bundle to $TESTTMP/blackboxtest2/.hg/strip-backup/73f6ee326b27-7612e004-backup.hg
137 137 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> updated base branch cache in * seconds (glob)
138 138 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> wrote base branch cache with 1 labels and 2 nodes
139 139 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> strip tip exited 0 after * seconds (glob)
140 140 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> blackbox -l 6
141 141
142 142 extension and python hooks - use the eol extension for a pythonhook
143 143
144 144 $ echo '[extensions]' >> .hg/hgrc
145 145 $ echo 'eol=' >> .hg/hgrc
146 146 $ echo '[hooks]' >> .hg/hgrc
147 147 $ echo 'update = echo hooked' >> .hg/hgrc
148 148 $ hg update
149 149 The fsmonitor extension is incompatible with the eol extension and has been disabled. (fsmonitor !)
150 150 hooked
151 151 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
152 152 updated to "d02f48003e62: c"
153 153 1 other heads for branch "default"
154 154 $ cat >> .hg/hgrc <<EOF
155 155 > [extensions]
156 156 > # disable eol, because it is not needed for subsequent tests
157 157 > # (in addition, keeping it requires extra care for fsmonitor)
158 158 > eol=!
159 159 > EOF
160 160 $ hg blackbox -l 5
161 161 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> update (no-chg !)
162 162 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> pythonhook-preupdate: hgext.eol.preupdate finished in * seconds (glob)
163 163 1970/01/01 00:00:00 bob @d02f48003e62c24e2659d97d30f2a83abe5d5d51 (5000)> exthook-update: echo hooked finished in * seconds (glob)
164 164 1970/01/01 00:00:00 bob @d02f48003e62c24e2659d97d30f2a83abe5d5d51 (5000)> update exited 0 after * seconds (glob)
165 165 1970/01/01 00:00:00 bob @d02f48003e62c24e2659d97d30f2a83abe5d5d51 (5000)> serve --cmdserver chgunix --address $TESTTMP.chgsock/server.* --daemon-postexec 'chdir:/' (glob) (chg !)
166 166 1970/01/01 00:00:00 bob @d02f48003e62c24e2659d97d30f2a83abe5d5d51 (5000)> blackbox -l 5
167 167
168 168 log rotation
169 169
170 170 $ echo '[blackbox]' >> .hg/hgrc
171 171 $ echo 'maxsize = 20 b' >> .hg/hgrc
172 172 $ echo 'maxfiles = 3' >> .hg/hgrc
173 173 $ hg status
174 174 $ hg status
175 175 $ hg status
176 176 $ hg tip -q
177 177 2:d02f48003e62
178 178 $ ls .hg/blackbox.log*
179 179 .hg/blackbox.log
180 180 .hg/blackbox.log.1
181 181 .hg/blackbox.log.2
182 182 $ cd ..
183 183
184 184 $ hg init blackboxtest3
185 185 $ cd blackboxtest3
186 186 $ hg blackbox
187 187 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> init blackboxtest3 exited 0 after * seconds (glob)
188 188 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> blackbox
189 189 $ mv .hg/blackbox.log .hg/blackbox.log-
190 190 $ mkdir .hg/blackbox.log
191 191 $ sed -e 's/\(.*test1.*\)/#\1/; s#\(.*commit2.*\)#os.rmdir(".hg/blackbox.log")\
192 192 > os.rename(".hg/blackbox.log-", ".hg/blackbox.log")\
193 193 > \1#' $TESTDIR/test-dispatch.py > ../test-dispatch.py
194 194 $ $PYTHON $TESTDIR/blackbox-readonly-dispatch.py
195 195 running: --debug add foo
196 196 warning: cannot write to blackbox.log: Is a directory (no-windows !)
197 197 warning: cannot write to blackbox.log: $TESTTMP/blackboxtest3/.hg/blackbox.log: Access is denied (windows !)
198 198 adding foo
199 199 result: 0
200 200 running: --debug commit -m commit1 -d 2000-01-01 foo
201 201 warning: cannot write to blackbox.log: Is a directory (no-windows !)
202 202 warning: cannot write to blackbox.log: $TESTTMP/blackboxtest3/.hg/blackbox.log: Access is denied (windows !)
203 203 committing files:
204 204 foo
205 205 committing manifest
206 206 committing changelog
207 207 updating the branch cache
208 208 committed changeset 0:0e46349438790c460c5c9f7546bfcd39b267bbd2
209 result: None
209 result: 0
210 210 running: --debug commit -m commit2 -d 2000-01-02 foo
211 211 committing files:
212 212 foo
213 213 committing manifest
214 214 committing changelog
215 215 updating the branch cache
216 216 committed changeset 1:45589e459b2edfbf3dbde7e01f611d2c1e7453d7
217 result: None
217 result: 0
218 218 running: --debug log -r 0
219 219 changeset: 0:0e46349438790c460c5c9f7546bfcd39b267bbd2
220 220 phase: draft
221 221 parent: -1:0000000000000000000000000000000000000000
222 222 parent: -1:0000000000000000000000000000000000000000
223 223 manifest: 0:9091aa5df980aea60860a2e39c95182e68d1ddec
224 224 user: test
225 225 date: Sat Jan 01 00:00:00 2000 +0000
226 226 files+: foo
227 227 extra: branch=default
228 228 description:
229 229 commit1
230 230
231 231
232 result: None
232 result: 0
233 233 running: --debug log -r tip
234 234 changeset: 1:45589e459b2edfbf3dbde7e01f611d2c1e7453d7
235 235 tag: tip
236 236 phase: draft
237 237 parent: 0:0e46349438790c460c5c9f7546bfcd39b267bbd2
238 238 parent: -1:0000000000000000000000000000000000000000
239 239 manifest: 1:895aa9b7886f89dd017a6d62524e1f9180b04df9
240 240 user: test
241 241 date: Sun Jan 02 00:00:00 2000 +0000
242 242 files: foo
243 243 extra: branch=default
244 244 description:
245 245 commit2
246 246
247 247
248 result: None
248 result: 0
249 249 $ hg blackbox
250 250 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> updating the branch cache
251 251 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> updated served branch cache in * seconds (glob)
252 252 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> wrote served branch cache with 1 labels and 1 nodes
253 253 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> --debug commit -m commit2 -d 2000-01-02 foo exited 0 after *.?? seconds (glob)
254 254 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> --debug log -r 0
255 255 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> writing .hg/cache/tags2-visible with 0 tags
256 256 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> --debug log -r 0 exited 0 after *.?? seconds (glob)
257 257 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> --debug log -r tip
258 258 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> --debug log -r tip exited 0 after *.?? seconds (glob)
259 259 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> blackbox
260 260
261 261 Test log recursion from dirty status check
262 262
263 263 $ cat > ../r.py <<EOF
264 264 > from mercurial import context, error, extensions
265 265 > x=[False]
266 266 > def status(orig, *args, **opts):
267 267 > args[0].repo().ui.log(b"broken", b"recursion?")
268 268 > return orig(*args, **opts)
269 269 > def reposetup(ui, repo):
270 270 > extensions.wrapfunction(context.basectx, 'status', status)
271 271 > EOF
272 272 $ hg id --config extensions.x=../r.py --config blackbox.dirty=True
273 273 45589e459b2e tip
274 274
275 275 cleanup
276 276 $ cd ..
277 277
278 278 #if chg
279 279
280 280 when using chg, blackbox.log should get rotated correctly
281 281
282 282 $ cat > $TESTTMP/noop.py << EOF
283 283 > from __future__ import absolute_import
284 284 > import time
285 285 > from mercurial import registrar, scmutil
286 286 > cmdtable = {}
287 287 > command = registrar.command(cmdtable)
288 288 > @command('noop')
289 289 > def noop(ui, repo):
290 290 > pass
291 291 > EOF
292 292
293 293 $ hg init blackbox-chg
294 294 $ cd blackbox-chg
295 295
296 296 $ cat > .hg/hgrc << EOF
297 297 > [blackbox]
298 298 > maxsize = 500B
299 299 > [extensions]
300 300 > # extension change forces chg to restart
301 301 > noop=$TESTTMP/noop.py
302 302 > EOF
303 303
304 304 $ $PYTHON -c 'print("a" * 400)' > .hg/blackbox.log
305 305 $ chg noop
306 306 $ chg noop
307 307 $ chg noop
308 308 $ chg noop
309 309 $ chg noop
310 310
311 311 $ cat > showsize.py << 'EOF'
312 312 > import os, sys
313 313 > limit = 500
314 314 > for p in sys.argv[1:]:
315 315 > size = os.stat(p).st_size
316 316 > if size >= limit:
317 317 > desc = '>='
318 318 > else:
319 319 > desc = '<'
320 320 > print('%s: %s %d' % (p, desc, limit))
321 321 > EOF
322 322
323 323 $ $PYTHON showsize.py .hg/blackbox*
324 324 .hg/blackbox.log: < 500
325 325 .hg/blackbox.log.1: >= 500
326 326 .hg/blackbox.log.2: >= 500
327 327
328 328 $ cd ..
329 329
330 330 With chg, blackbox should not create the log file if the repo is gone
331 331
332 332 $ hg init repo1
333 333 $ hg --config extensions.a=! -R repo1 log
334 334 $ rm -rf $TESTTMP/repo1
335 335 $ hg --config extensions.a=! init repo1
336 336
337 337 #endif
338 338
339 339 blackbox should work if repo.ui.log is not called (issue5518)
340 340
341 341 $ cat > $TESTTMP/raise.py << EOF
342 342 > from __future__ import absolute_import
343 343 > from mercurial import registrar, scmutil
344 344 > cmdtable = {}
345 345 > command = registrar.command(cmdtable)
346 346 > @command(b'raise')
347 347 > def raisecmd(*args):
348 348 > raise RuntimeError('raise')
349 349 > EOF
350 350
351 351 $ cat >> $HGRCPATH << EOF
352 352 > [blackbox]
353 353 > track = commandexception
354 354 > [extensions]
355 355 > raise=$TESTTMP/raise.py
356 356 > EOF
357 357
358 358 $ hg init $TESTTMP/blackbox-exception-only
359 359 $ cd $TESTTMP/blackbox-exception-only
360 360
361 361 #if chg
362 362 (chg exits 255 because it fails to receive an exit code)
363 363 $ hg raise 2>/dev/null
364 364 [255]
365 365 #else
366 366 (hg exits 1 because Python default exit code for uncaught exception is 1)
367 367 $ hg raise 2>/dev/null
368 368 [1]
369 369 #endif
370 370
371 371 $ head -1 .hg/blackbox.log
372 372 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> ** Unknown exception encountered with possibly-broken third-party extension mock
373 373 $ tail -2 .hg/blackbox.log
374 374 RuntimeError: raise
375 375
@@ -1,23 +1,23 b''
1 1 running: init test1
2 result: None
2 result: 0
3 3 running: add foo
4 4 result: 0
5 5 running: commit -m commit1 -d 2000-01-01 foo
6 result: None
6 result: 0
7 7 running: commit -m commit2 -d 2000-01-02 foo
8 result: None
8 result: 0
9 9 running: log -r 0
10 10 changeset: 0:0e4634943879
11 11 user: test
12 12 date: Sat Jan 01 00:00:00 2000 +0000
13 13 summary: commit1
14 14
15 result: None
15 result: 0
16 16 running: log -r tip
17 17 changeset: 1:45589e459b2e
18 18 tag: tip
19 19 user: test
20 20 date: Sun Jan 02 00:00:00 2000 +0000
21 21 summary: commit2
22 22
23 result: None
23 result: 0
General Comments 0
You need to be logged in to leave comments. Login now