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