##// END OF EJS Templates
dispatch: move IOError handling and flushing of streams to `dispatch()`...
Pulkit Goyal -
r46717:49b69102 default
parent child Browse files
Show More
@@ -1,782 +1,771
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
21
22 selectors.BaseSelector
22 selectors.BaseSelector
23 except ImportError:
23 except ImportError:
24 from .thirdparty import selectors2 as selectors
24 from .thirdparty import selectors2 as selectors
25
25
26 from .i18n import _
26 from .i18n import _
27 from .pycompat import getattr
27 from .pycompat import getattr
28 from . import (
28 from . import (
29 encoding,
29 encoding,
30 error,
30 error,
31 loggingutil,
31 loggingutil,
32 pycompat,
32 pycompat,
33 repocache,
33 repocache,
34 util,
34 util,
35 vfs as vfsmod,
35 vfs as vfsmod,
36 )
36 )
37 from .utils import (
37 from .utils import (
38 cborutil,
38 cborutil,
39 procutil,
39 procutil,
40 )
40 )
41
41
42
42
43 class channeledoutput(object):
43 class channeledoutput(object):
44 """
44 """
45 Write data to out in the following format:
45 Write data to out in the following format:
46
46
47 data length (unsigned int),
47 data length (unsigned int),
48 data
48 data
49 """
49 """
50
50
51 def __init__(self, out, channel):
51 def __init__(self, out, channel):
52 self.out = out
52 self.out = out
53 self.channel = channel
53 self.channel = channel
54
54
55 @property
55 @property
56 def name(self):
56 def name(self):
57 return b'<%c-channel>' % self.channel
57 return b'<%c-channel>' % self.channel
58
58
59 def write(self, data):
59 def write(self, data):
60 if not data:
60 if not data:
61 return
61 return
62 # single write() to guarantee the same atomicity as the underlying file
62 # single write() to guarantee the same atomicity as the underlying file
63 self.out.write(struct.pack(b'>cI', self.channel, len(data)) + data)
63 self.out.write(struct.pack(b'>cI', self.channel, len(data)) + data)
64 self.out.flush()
64 self.out.flush()
65
65
66 def __getattr__(self, attr):
66 def __getattr__(self, attr):
67 if attr in ('isatty', 'fileno', 'tell', 'seek'):
67 if attr in ('isatty', 'fileno', 'tell', 'seek'):
68 raise AttributeError(attr)
68 raise AttributeError(attr)
69 return getattr(self.out, attr)
69 return getattr(self.out, attr)
70
70
71
71
72 class channeledmessage(object):
72 class channeledmessage(object):
73 """
73 """
74 Write encoded message and metadata to out in the following format:
74 Write encoded message and metadata to out in the following format:
75
75
76 data length (unsigned int),
76 data length (unsigned int),
77 encoded message and metadata, as a flat key-value dict.
77 encoded message and metadata, as a flat key-value dict.
78
78
79 Each message should have 'type' attribute. Messages of unknown type
79 Each message should have 'type' attribute. Messages of unknown type
80 should be ignored.
80 should be ignored.
81 """
81 """
82
82
83 # teach ui that write() can take **opts
83 # teach ui that write() can take **opts
84 structured = True
84 structured = True
85
85
86 def __init__(self, out, channel, encodename, encodefn):
86 def __init__(self, out, channel, encodename, encodefn):
87 self._cout = channeledoutput(out, channel)
87 self._cout = channeledoutput(out, channel)
88 self.encoding = encodename
88 self.encoding = encodename
89 self._encodefn = encodefn
89 self._encodefn = encodefn
90
90
91 def write(self, data, **opts):
91 def write(self, data, **opts):
92 opts = pycompat.byteskwargs(opts)
92 opts = pycompat.byteskwargs(opts)
93 if data is not None:
93 if data is not None:
94 opts[b'data'] = data
94 opts[b'data'] = data
95 self._cout.write(self._encodefn(opts))
95 self._cout.write(self._encodefn(opts))
96
96
97 def __getattr__(self, attr):
97 def __getattr__(self, attr):
98 return getattr(self._cout, attr)
98 return getattr(self._cout, attr)
99
99
100
100
101 class channeledinput(object):
101 class channeledinput(object):
102 """
102 """
103 Read data from in_.
103 Read data from in_.
104
104
105 Requests for input are written to out in the following format:
105 Requests for input are written to out in the following format:
106 channel identifier - 'I' for plain input, 'L' line based (1 byte)
106 channel identifier - 'I' for plain input, 'L' line based (1 byte)
107 how many bytes to send at most (unsigned int),
107 how many bytes to send at most (unsigned int),
108
108
109 The client replies with:
109 The client replies with:
110 data length (unsigned int), 0 meaning EOF
110 data length (unsigned int), 0 meaning EOF
111 data
111 data
112 """
112 """
113
113
114 maxchunksize = 4 * 1024
114 maxchunksize = 4 * 1024
115
115
116 def __init__(self, in_, out, channel):
116 def __init__(self, in_, out, channel):
117 self.in_ = in_
117 self.in_ = in_
118 self.out = out
118 self.out = out
119 self.channel = channel
119 self.channel = channel
120
120
121 @property
121 @property
122 def name(self):
122 def name(self):
123 return b'<%c-channel>' % self.channel
123 return b'<%c-channel>' % self.channel
124
124
125 def read(self, size=-1):
125 def read(self, size=-1):
126 if size < 0:
126 if size < 0:
127 # if we need to consume all the clients input, ask for 4k chunks
127 # if we need to consume all the clients input, ask for 4k chunks
128 # so the pipe doesn't fill up risking a deadlock
128 # so the pipe doesn't fill up risking a deadlock
129 size = self.maxchunksize
129 size = self.maxchunksize
130 s = self._read(size, self.channel)
130 s = self._read(size, self.channel)
131 buf = s
131 buf = s
132 while s:
132 while s:
133 s = self._read(size, self.channel)
133 s = self._read(size, self.channel)
134 buf += s
134 buf += s
135
135
136 return buf
136 return buf
137 else:
137 else:
138 return self._read(size, self.channel)
138 return self._read(size, self.channel)
139
139
140 def _read(self, size, channel):
140 def _read(self, size, channel):
141 if not size:
141 if not size:
142 return b''
142 return b''
143 assert size > 0
143 assert size > 0
144
144
145 # tell the client we need at most size bytes
145 # tell the client we need at most size bytes
146 self.out.write(struct.pack(b'>cI', channel, size))
146 self.out.write(struct.pack(b'>cI', channel, size))
147 self.out.flush()
147 self.out.flush()
148
148
149 length = self.in_.read(4)
149 length = self.in_.read(4)
150 length = struct.unpack(b'>I', length)[0]
150 length = struct.unpack(b'>I', length)[0]
151 if not length:
151 if not length:
152 return b''
152 return b''
153 else:
153 else:
154 return self.in_.read(length)
154 return self.in_.read(length)
155
155
156 def readline(self, size=-1):
156 def readline(self, size=-1):
157 if size < 0:
157 if size < 0:
158 size = self.maxchunksize
158 size = self.maxchunksize
159 s = self._read(size, b'L')
159 s = self._read(size, b'L')
160 buf = s
160 buf = s
161 # keep asking for more until there's either no more or
161 # keep asking for more until there's either no more or
162 # we got a full line
162 # we got a full line
163 while s and not s.endswith(b'\n'):
163 while s and not s.endswith(b'\n'):
164 s = self._read(size, b'L')
164 s = self._read(size, b'L')
165 buf += s
165 buf += s
166
166
167 return buf
167 return buf
168 else:
168 else:
169 return self._read(size, b'L')
169 return self._read(size, b'L')
170
170
171 def __iter__(self):
171 def __iter__(self):
172 return self
172 return self
173
173
174 def next(self):
174 def next(self):
175 l = self.readline()
175 l = self.readline()
176 if not l:
176 if not l:
177 raise StopIteration
177 raise StopIteration
178 return l
178 return l
179
179
180 __next__ = next
180 __next__ = next
181
181
182 def __getattr__(self, attr):
182 def __getattr__(self, attr):
183 if attr in ('isatty', 'fileno', 'tell', 'seek'):
183 if attr in ('isatty', 'fileno', 'tell', 'seek'):
184 raise AttributeError(attr)
184 raise AttributeError(attr)
185 return getattr(self.in_, attr)
185 return getattr(self.in_, attr)
186
186
187
187
188 _messageencoders = {
188 _messageencoders = {
189 b'cbor': lambda v: b''.join(cborutil.streamencode(v)),
189 b'cbor': lambda v: b''.join(cborutil.streamencode(v)),
190 }
190 }
191
191
192
192
193 def _selectmessageencoder(ui):
193 def _selectmessageencoder(ui):
194 encnames = ui.configlist(b'cmdserver', b'message-encodings')
194 encnames = ui.configlist(b'cmdserver', b'message-encodings')
195 for n in encnames:
195 for n in encnames:
196 f = _messageencoders.get(n)
196 f = _messageencoders.get(n)
197 if f:
197 if f:
198 return n, f
198 return n, f
199 raise error.Abort(
199 raise error.Abort(
200 b'no supported message encodings: %s' % b' '.join(encnames)
200 b'no supported message encodings: %s' % b' '.join(encnames)
201 )
201 )
202
202
203
203
204 class server(object):
204 class server(object):
205 """
205 """
206 Listens for commands on fin, runs them and writes the output on a channel
206 Listens for commands on fin, runs them and writes the output on a channel
207 based stream to fout.
207 based stream to fout.
208 """
208 """
209
209
210 def __init__(self, ui, repo, fin, fout, prereposetups=None):
210 def __init__(self, ui, repo, fin, fout, prereposetups=None):
211 self.cwd = encoding.getcwd()
211 self.cwd = encoding.getcwd()
212
212
213 if repo:
213 if repo:
214 # the ui here is really the repo ui so take its baseui so we don't
214 # the ui here is really the repo ui so take its baseui so we don't
215 # end up with its local configuration
215 # end up with its local configuration
216 self.ui = repo.baseui
216 self.ui = repo.baseui
217 self.repo = repo
217 self.repo = repo
218 self.repoui = repo.ui
218 self.repoui = repo.ui
219 else:
219 else:
220 self.ui = ui
220 self.ui = ui
221 self.repo = self.repoui = None
221 self.repo = self.repoui = None
222 self._prereposetups = prereposetups
222 self._prereposetups = prereposetups
223
223
224 self.cdebug = channeledoutput(fout, b'd')
224 self.cdebug = channeledoutput(fout, b'd')
225 self.cerr = channeledoutput(fout, b'e')
225 self.cerr = channeledoutput(fout, b'e')
226 self.cout = channeledoutput(fout, b'o')
226 self.cout = channeledoutput(fout, b'o')
227 self.cin = channeledinput(fin, fout, b'I')
227 self.cin = channeledinput(fin, fout, b'I')
228 self.cresult = channeledoutput(fout, b'r')
228 self.cresult = channeledoutput(fout, b'r')
229
229
230 if self.ui.config(b'cmdserver', b'log') == b'-':
230 if self.ui.config(b'cmdserver', b'log') == b'-':
231 # switch log stream of server's ui to the 'd' (debug) channel
231 # switch log stream of server's ui to the 'd' (debug) channel
232 # (don't touch repo.ui as its lifetime is longer than the server)
232 # (don't touch repo.ui as its lifetime is longer than the server)
233 self.ui = self.ui.copy()
233 self.ui = self.ui.copy()
234 setuplogging(self.ui, repo=None, fp=self.cdebug)
234 setuplogging(self.ui, repo=None, fp=self.cdebug)
235
235
236 self.cmsg = None
236 self.cmsg = None
237 if ui.config(b'ui', b'message-output') == b'channel':
237 if ui.config(b'ui', b'message-output') == b'channel':
238 encname, encfn = _selectmessageencoder(ui)
238 encname, encfn = _selectmessageencoder(ui)
239 self.cmsg = channeledmessage(fout, b'm', encname, encfn)
239 self.cmsg = channeledmessage(fout, b'm', encname, encfn)
240
240
241 self.client = fin
241 self.client = fin
242
242
243 # If shutdown-on-interrupt is off, the default SIGINT handler is
243 # If shutdown-on-interrupt is off, the default SIGINT handler is
244 # removed so that client-server communication wouldn't be interrupted.
244 # removed so that client-server communication wouldn't be interrupted.
245 # For example, 'runcommand' handler will issue three short read()s.
245 # For example, 'runcommand' handler will issue three short read()s.
246 # If one of the first two read()s were interrupted, the communication
246 # If one of the first two read()s were interrupted, the communication
247 # channel would be left at dirty state and the subsequent request
247 # channel would be left at dirty state and the subsequent request
248 # wouldn't be parsed. So catching KeyboardInterrupt isn't enough.
248 # wouldn't be parsed. So catching KeyboardInterrupt isn't enough.
249 self._shutdown_on_interrupt = ui.configbool(
249 self._shutdown_on_interrupt = ui.configbool(
250 b'cmdserver', b'shutdown-on-interrupt'
250 b'cmdserver', b'shutdown-on-interrupt'
251 )
251 )
252 self._old_inthandler = None
252 self._old_inthandler = None
253 if not self._shutdown_on_interrupt:
253 if not self._shutdown_on_interrupt:
254 self._old_inthandler = signal.signal(signal.SIGINT, signal.SIG_IGN)
254 self._old_inthandler = signal.signal(signal.SIGINT, signal.SIG_IGN)
255
255
256 def cleanup(self):
256 def cleanup(self):
257 """release and restore resources taken during server session"""
257 """release and restore resources taken during server session"""
258 if not self._shutdown_on_interrupt:
258 if not self._shutdown_on_interrupt:
259 signal.signal(signal.SIGINT, self._old_inthandler)
259 signal.signal(signal.SIGINT, self._old_inthandler)
260
260
261 def _read(self, size):
261 def _read(self, size):
262 if not size:
262 if not size:
263 return b''
263 return b''
264
264
265 data = self.client.read(size)
265 data = self.client.read(size)
266
266
267 # is the other end closed?
267 # is the other end closed?
268 if not data:
268 if not data:
269 raise EOFError
269 raise EOFError
270
270
271 return data
271 return data
272
272
273 def _readstr(self):
273 def _readstr(self):
274 """read a string from the channel
274 """read a string from the channel
275
275
276 format:
276 format:
277 data length (uint32), data
277 data length (uint32), data
278 """
278 """
279 length = struct.unpack(b'>I', self._read(4))[0]
279 length = struct.unpack(b'>I', self._read(4))[0]
280 if not length:
280 if not length:
281 return b''
281 return b''
282 return self._read(length)
282 return self._read(length)
283
283
284 def _readlist(self):
284 def _readlist(self):
285 """read a list of NULL separated strings from the channel"""
285 """read a list of NULL separated strings from the channel"""
286 s = self._readstr()
286 s = self._readstr()
287 if s:
287 if s:
288 return s.split(b'\0')
288 return s.split(b'\0')
289 else:
289 else:
290 return []
290 return []
291
291
292 def _dispatchcommand(self, req):
292 def _dispatchcommand(self, req):
293 from . import dispatch # avoid cycle
293 from . import dispatch # avoid cycle
294
294
295 if self._shutdown_on_interrupt:
295 if self._shutdown_on_interrupt:
296 # no need to restore SIGINT handler as it is unmodified.
296 # no need to restore SIGINT handler as it is unmodified.
297 return dispatch.dispatch(req)
297 return dispatch.dispatch(req)
298
298
299 try:
299 try:
300 signal.signal(signal.SIGINT, self._old_inthandler)
300 signal.signal(signal.SIGINT, self._old_inthandler)
301 return dispatch.dispatch(req)
301 return dispatch.dispatch(req)
302 except error.SignalInterrupt:
302 except error.SignalInterrupt:
303 # propagate SIGBREAK, SIGHUP, or SIGTERM.
303 # propagate SIGBREAK, SIGHUP, or SIGTERM.
304 raise
304 raise
305 except KeyboardInterrupt:
305 except KeyboardInterrupt:
306 # SIGINT may be received out of the try-except block of dispatch(),
306 # SIGINT may be received out of the try-except block of dispatch(),
307 # so catch it as last ditch. Another KeyboardInterrupt may be
307 # so catch it as last ditch. Another KeyboardInterrupt may be
308 # raised while handling exceptions here, but there's no way to
308 # raised while handling exceptions here, but there's no way to
309 # avoid that except for doing everything in C.
309 # avoid that except for doing everything in C.
310 pass
310 pass
311 finally:
311 finally:
312 signal.signal(signal.SIGINT, signal.SIG_IGN)
312 signal.signal(signal.SIGINT, signal.SIG_IGN)
313 # On KeyboardInterrupt, print error message and exit *after* SIGINT
313 # On KeyboardInterrupt, print error message and exit *after* SIGINT
314 # handler removed.
314 # handler removed.
315 req.ui.error(_(b'interrupted!\n'))
315 req.ui.error(_(b'interrupted!\n'))
316 return -1
316 return -1
317
317
318 def runcommand(self):
318 def runcommand(self):
319 """reads a list of \0 terminated arguments, executes
319 """reads a list of \0 terminated arguments, executes
320 and writes the return code to the result channel"""
320 and writes the return code to the result channel"""
321 from . import dispatch # avoid cycle
321 from . import dispatch # avoid cycle
322
322
323 args = self._readlist()
323 args = self._readlist()
324
324
325 # copy the uis so changes (e.g. --config or --verbose) don't
325 # copy the uis so changes (e.g. --config or --verbose) don't
326 # persist between requests
326 # persist between requests
327 copiedui = self.ui.copy()
327 copiedui = self.ui.copy()
328 uis = [copiedui]
328 uis = [copiedui]
329 if self.repo:
329 if self.repo:
330 self.repo.baseui = copiedui
330 self.repo.baseui = copiedui
331 # clone ui without using ui.copy because this is protected
331 # clone ui without using ui.copy because this is protected
332 repoui = self.repoui.__class__(self.repoui)
332 repoui = self.repoui.__class__(self.repoui)
333 repoui.copy = copiedui.copy # redo copy protection
333 repoui.copy = copiedui.copy # redo copy protection
334 uis.append(repoui)
334 uis.append(repoui)
335 self.repo.ui = self.repo.dirstate._ui = repoui
335 self.repo.ui = self.repo.dirstate._ui = repoui
336 self.repo.invalidateall()
336 self.repo.invalidateall()
337
337
338 for ui in uis:
338 for ui in uis:
339 ui.resetstate()
339 ui.resetstate()
340 # any kind of interaction must use server channels, but chg may
340 # any kind of interaction must use server channels, but chg may
341 # replace channels by fully functional tty files. so nontty is
341 # replace channels by fully functional tty files. so nontty is
342 # enforced only if cin is a channel.
342 # enforced only if cin is a channel.
343 if not util.safehasattr(self.cin, b'fileno'):
343 if not util.safehasattr(self.cin, b'fileno'):
344 ui.setconfig(b'ui', b'nontty', b'true', b'commandserver')
344 ui.setconfig(b'ui', b'nontty', b'true', b'commandserver')
345
345
346 req = dispatch.request(
346 req = dispatch.request(
347 args[:],
347 args[:],
348 copiedui,
348 copiedui,
349 self.repo,
349 self.repo,
350 self.cin,
350 self.cin,
351 self.cout,
351 self.cout,
352 self.cerr,
352 self.cerr,
353 self.cmsg,
353 self.cmsg,
354 prereposetups=self._prereposetups,
354 prereposetups=self._prereposetups,
355 )
355 )
356
356
357 try:
357 try:
358 err = None
358 ret = self._dispatchcommand(req) & 255
359 try:
360 status = self._dispatchcommand(req)
361 except error.StdioError as e:
362 status = -1
363 err = e
364
365 retval = dispatch.closestdio(req.ui, err)
366 if retval:
367 status = retval
368
369 ret = status & 255
370 # If shutdown-on-interrupt is off, it's important to write the
359 # If shutdown-on-interrupt is off, it's important to write the
371 # result code *after* SIGINT handler removed. If the result code
360 # result code *after* SIGINT handler removed. If the result code
372 # were lost, the client wouldn't be able to continue processing.
361 # were lost, the client wouldn't be able to continue processing.
373 self.cresult.write(struct.pack(b'>i', int(ret)))
362 self.cresult.write(struct.pack(b'>i', int(ret)))
374 finally:
363 finally:
375 # restore old cwd
364 # restore old cwd
376 if b'--cwd' in args:
365 if b'--cwd' in args:
377 os.chdir(self.cwd)
366 os.chdir(self.cwd)
378
367
379 def getencoding(self):
368 def getencoding(self):
380 """ writes the current encoding to the result channel """
369 """ writes the current encoding to the result channel """
381 self.cresult.write(encoding.encoding)
370 self.cresult.write(encoding.encoding)
382
371
383 def serveone(self):
372 def serveone(self):
384 cmd = self.client.readline()[:-1]
373 cmd = self.client.readline()[:-1]
385 if cmd:
374 if cmd:
386 handler = self.capabilities.get(cmd)
375 handler = self.capabilities.get(cmd)
387 if handler:
376 if handler:
388 handler(self)
377 handler(self)
389 else:
378 else:
390 # clients are expected to check what commands are supported by
379 # clients are expected to check what commands are supported by
391 # looking at the servers capabilities
380 # looking at the servers capabilities
392 raise error.Abort(_(b'unknown command %s') % cmd)
381 raise error.Abort(_(b'unknown command %s') % cmd)
393
382
394 return cmd != b''
383 return cmd != b''
395
384
396 capabilities = {b'runcommand': runcommand, b'getencoding': getencoding}
385 capabilities = {b'runcommand': runcommand, b'getencoding': getencoding}
397
386
398 def serve(self):
387 def serve(self):
399 hellomsg = b'capabilities: ' + b' '.join(sorted(self.capabilities))
388 hellomsg = b'capabilities: ' + b' '.join(sorted(self.capabilities))
400 hellomsg += b'\n'
389 hellomsg += b'\n'
401 hellomsg += b'encoding: ' + encoding.encoding
390 hellomsg += b'encoding: ' + encoding.encoding
402 hellomsg += b'\n'
391 hellomsg += b'\n'
403 if self.cmsg:
392 if self.cmsg:
404 hellomsg += b'message-encoding: %s\n' % self.cmsg.encoding
393 hellomsg += b'message-encoding: %s\n' % self.cmsg.encoding
405 hellomsg += b'pid: %d' % procutil.getpid()
394 hellomsg += b'pid: %d' % procutil.getpid()
406 if util.safehasattr(os, b'getpgid'):
395 if util.safehasattr(os, b'getpgid'):
407 hellomsg += b'\n'
396 hellomsg += b'\n'
408 hellomsg += b'pgid: %d' % os.getpgid(0)
397 hellomsg += b'pgid: %d' % os.getpgid(0)
409
398
410 # write the hello msg in -one- chunk
399 # write the hello msg in -one- chunk
411 self.cout.write(hellomsg)
400 self.cout.write(hellomsg)
412
401
413 try:
402 try:
414 while self.serveone():
403 while self.serveone():
415 pass
404 pass
416 except EOFError:
405 except EOFError:
417 # we'll get here if the client disconnected while we were reading
406 # we'll get here if the client disconnected while we were reading
418 # its request
407 # its request
419 return 1
408 return 1
420
409
421 return 0
410 return 0
422
411
423
412
424 def setuplogging(ui, repo=None, fp=None):
413 def setuplogging(ui, repo=None, fp=None):
425 """Set up server logging facility
414 """Set up server logging facility
426
415
427 If cmdserver.log is '-', log messages will be sent to the given fp.
416 If cmdserver.log is '-', log messages will be sent to the given fp.
428 It should be the 'd' channel while a client is connected, and otherwise
417 It should be the 'd' channel while a client is connected, and otherwise
429 is the stderr of the server process.
418 is the stderr of the server process.
430 """
419 """
431 # developer config: cmdserver.log
420 # developer config: cmdserver.log
432 logpath = ui.config(b'cmdserver', b'log')
421 logpath = ui.config(b'cmdserver', b'log')
433 if not logpath:
422 if not logpath:
434 return
423 return
435 # developer config: cmdserver.track-log
424 # developer config: cmdserver.track-log
436 tracked = set(ui.configlist(b'cmdserver', b'track-log'))
425 tracked = set(ui.configlist(b'cmdserver', b'track-log'))
437
426
438 if logpath == b'-' and fp:
427 if logpath == b'-' and fp:
439 logger = loggingutil.fileobjectlogger(fp, tracked)
428 logger = loggingutil.fileobjectlogger(fp, tracked)
440 elif logpath == b'-':
429 elif logpath == b'-':
441 logger = loggingutil.fileobjectlogger(ui.ferr, tracked)
430 logger = loggingutil.fileobjectlogger(ui.ferr, tracked)
442 else:
431 else:
443 logpath = os.path.abspath(util.expandpath(logpath))
432 logpath = os.path.abspath(util.expandpath(logpath))
444 # developer config: cmdserver.max-log-files
433 # developer config: cmdserver.max-log-files
445 maxfiles = ui.configint(b'cmdserver', b'max-log-files')
434 maxfiles = ui.configint(b'cmdserver', b'max-log-files')
446 # developer config: cmdserver.max-log-size
435 # developer config: cmdserver.max-log-size
447 maxsize = ui.configbytes(b'cmdserver', b'max-log-size')
436 maxsize = ui.configbytes(b'cmdserver', b'max-log-size')
448 vfs = vfsmod.vfs(os.path.dirname(logpath))
437 vfs = vfsmod.vfs(os.path.dirname(logpath))
449 logger = loggingutil.filelogger(
438 logger = loggingutil.filelogger(
450 vfs,
439 vfs,
451 os.path.basename(logpath),
440 os.path.basename(logpath),
452 tracked,
441 tracked,
453 maxfiles=maxfiles,
442 maxfiles=maxfiles,
454 maxsize=maxsize,
443 maxsize=maxsize,
455 )
444 )
456
445
457 targetuis = {ui}
446 targetuis = {ui}
458 if repo:
447 if repo:
459 targetuis.add(repo.baseui)
448 targetuis.add(repo.baseui)
460 targetuis.add(repo.ui)
449 targetuis.add(repo.ui)
461 for u in targetuis:
450 for u in targetuis:
462 u.setlogger(b'cmdserver', logger)
451 u.setlogger(b'cmdserver', logger)
463
452
464
453
465 class pipeservice(object):
454 class pipeservice(object):
466 def __init__(self, ui, repo, opts):
455 def __init__(self, ui, repo, opts):
467 self.ui = ui
456 self.ui = ui
468 self.repo = repo
457 self.repo = repo
469
458
470 def init(self):
459 def init(self):
471 pass
460 pass
472
461
473 def run(self):
462 def run(self):
474 ui = self.ui
463 ui = self.ui
475 # redirect stdio to null device so that broken extensions or in-process
464 # redirect stdio to null device so that broken extensions or in-process
476 # hooks will never cause corruption of channel protocol.
465 # hooks will never cause corruption of channel protocol.
477 with ui.protectedfinout() as (fin, fout):
466 with ui.protectedfinout() as (fin, fout):
478 sv = server(ui, self.repo, fin, fout)
467 sv = server(ui, self.repo, fin, fout)
479 try:
468 try:
480 return sv.serve()
469 return sv.serve()
481 finally:
470 finally:
482 sv.cleanup()
471 sv.cleanup()
483
472
484
473
485 def _initworkerprocess():
474 def _initworkerprocess():
486 # use a different process group from the master process, in order to:
475 # use a different process group from the master process, in order to:
487 # 1. make the current process group no longer "orphaned" (because the
476 # 1. make the current process group no longer "orphaned" (because the
488 # parent of this process is in a different process group while
477 # parent of this process is in a different process group while
489 # remains in a same session)
478 # remains in a same session)
490 # according to POSIX 2.2.2.52, orphaned process group will ignore
479 # according to POSIX 2.2.2.52, orphaned process group will ignore
491 # terminal-generated stop signals like SIGTSTP (Ctrl+Z), which will
480 # terminal-generated stop signals like SIGTSTP (Ctrl+Z), which will
492 # cause trouble for things like ncurses.
481 # cause trouble for things like ncurses.
493 # 2. the client can use kill(-pgid, sig) to simulate terminal-generated
482 # 2. the client can use kill(-pgid, sig) to simulate terminal-generated
494 # SIGINT (Ctrl+C) and process-exit-generated SIGHUP. our child
483 # SIGINT (Ctrl+C) and process-exit-generated SIGHUP. our child
495 # processes like ssh will be killed properly, without affecting
484 # processes like ssh will be killed properly, without affecting
496 # unrelated processes.
485 # unrelated processes.
497 os.setpgid(0, 0)
486 os.setpgid(0, 0)
498 # change random state otherwise forked request handlers would have a
487 # change random state otherwise forked request handlers would have a
499 # same state inherited from parent.
488 # same state inherited from parent.
500 random.seed()
489 random.seed()
501
490
502
491
503 def _serverequest(ui, repo, conn, createcmdserver, prereposetups):
492 def _serverequest(ui, repo, conn, createcmdserver, prereposetups):
504 fin = conn.makefile('rb')
493 fin = conn.makefile('rb')
505 fout = conn.makefile('wb')
494 fout = conn.makefile('wb')
506 sv = None
495 sv = None
507 try:
496 try:
508 sv = createcmdserver(repo, conn, fin, fout, prereposetups)
497 sv = createcmdserver(repo, conn, fin, fout, prereposetups)
509 try:
498 try:
510 sv.serve()
499 sv.serve()
511 # handle exceptions that may be raised by command server. most of
500 # handle exceptions that may be raised by command server. most of
512 # known exceptions are caught by dispatch.
501 # known exceptions are caught by dispatch.
513 except error.Abort as inst:
502 except error.Abort as inst:
514 ui.error(_(b'abort: %s\n') % inst.message)
503 ui.error(_(b'abort: %s\n') % inst.message)
515 except IOError as inst:
504 except IOError as inst:
516 if inst.errno != errno.EPIPE:
505 if inst.errno != errno.EPIPE:
517 raise
506 raise
518 except KeyboardInterrupt:
507 except KeyboardInterrupt:
519 pass
508 pass
520 finally:
509 finally:
521 sv.cleanup()
510 sv.cleanup()
522 except: # re-raises
511 except: # re-raises
523 # also write traceback to error channel. otherwise client cannot
512 # also write traceback to error channel. otherwise client cannot
524 # see it because it is written to server's stderr by default.
513 # see it because it is written to server's stderr by default.
525 if sv:
514 if sv:
526 cerr = sv.cerr
515 cerr = sv.cerr
527 else:
516 else:
528 cerr = channeledoutput(fout, b'e')
517 cerr = channeledoutput(fout, b'e')
529 cerr.write(encoding.strtolocal(traceback.format_exc()))
518 cerr.write(encoding.strtolocal(traceback.format_exc()))
530 raise
519 raise
531 finally:
520 finally:
532 fin.close()
521 fin.close()
533 try:
522 try:
534 fout.close() # implicit flush() may cause another EPIPE
523 fout.close() # implicit flush() may cause another EPIPE
535 except IOError as inst:
524 except IOError as inst:
536 if inst.errno != errno.EPIPE:
525 if inst.errno != errno.EPIPE:
537 raise
526 raise
538
527
539
528
540 class unixservicehandler(object):
529 class unixservicehandler(object):
541 """Set of pluggable operations for unix-mode services
530 """Set of pluggable operations for unix-mode services
542
531
543 Almost all methods except for createcmdserver() are called in the main
532 Almost all methods except for createcmdserver() are called in the main
544 process. You can't pass mutable resource back from createcmdserver().
533 process. You can't pass mutable resource back from createcmdserver().
545 """
534 """
546
535
547 pollinterval = None
536 pollinterval = None
548
537
549 def __init__(self, ui):
538 def __init__(self, ui):
550 self.ui = ui
539 self.ui = ui
551
540
552 def bindsocket(self, sock, address):
541 def bindsocket(self, sock, address):
553 util.bindunixsocket(sock, address)
542 util.bindunixsocket(sock, address)
554 sock.listen(socket.SOMAXCONN)
543 sock.listen(socket.SOMAXCONN)
555 self.ui.status(_(b'listening at %s\n') % address)
544 self.ui.status(_(b'listening at %s\n') % address)
556 self.ui.flush() # avoid buffering of status message
545 self.ui.flush() # avoid buffering of status message
557
546
558 def unlinksocket(self, address):
547 def unlinksocket(self, address):
559 os.unlink(address)
548 os.unlink(address)
560
549
561 def shouldexit(self):
550 def shouldexit(self):
562 """True if server should shut down; checked per pollinterval"""
551 """True if server should shut down; checked per pollinterval"""
563 return False
552 return False
564
553
565 def newconnection(self):
554 def newconnection(self):
566 """Called when main process notices new connection"""
555 """Called when main process notices new connection"""
567
556
568 def createcmdserver(self, repo, conn, fin, fout, prereposetups):
557 def createcmdserver(self, repo, conn, fin, fout, prereposetups):
569 """Create new command server instance; called in the process that
558 """Create new command server instance; called in the process that
570 serves for the current connection"""
559 serves for the current connection"""
571 return server(self.ui, repo, fin, fout, prereposetups)
560 return server(self.ui, repo, fin, fout, prereposetups)
572
561
573
562
574 class unixforkingservice(object):
563 class unixforkingservice(object):
575 """
564 """
576 Listens on unix domain socket and forks server per connection
565 Listens on unix domain socket and forks server per connection
577 """
566 """
578
567
579 def __init__(self, ui, repo, opts, handler=None):
568 def __init__(self, ui, repo, opts, handler=None):
580 self.ui = ui
569 self.ui = ui
581 self.repo = repo
570 self.repo = repo
582 self.address = opts[b'address']
571 self.address = opts[b'address']
583 if not util.safehasattr(socket, b'AF_UNIX'):
572 if not util.safehasattr(socket, b'AF_UNIX'):
584 raise error.Abort(_(b'unsupported platform'))
573 raise error.Abort(_(b'unsupported platform'))
585 if not self.address:
574 if not self.address:
586 raise error.Abort(_(b'no socket path specified with --address'))
575 raise error.Abort(_(b'no socket path specified with --address'))
587 self._servicehandler = handler or unixservicehandler(ui)
576 self._servicehandler = handler or unixservicehandler(ui)
588 self._sock = None
577 self._sock = None
589 self._mainipc = None
578 self._mainipc = None
590 self._workeripc = None
579 self._workeripc = None
591 self._oldsigchldhandler = None
580 self._oldsigchldhandler = None
592 self._workerpids = set() # updated by signal handler; do not iterate
581 self._workerpids = set() # updated by signal handler; do not iterate
593 self._socketunlinked = None
582 self._socketunlinked = None
594 # experimental config: cmdserver.max-repo-cache
583 # experimental config: cmdserver.max-repo-cache
595 maxlen = ui.configint(b'cmdserver', b'max-repo-cache')
584 maxlen = ui.configint(b'cmdserver', b'max-repo-cache')
596 if maxlen < 0:
585 if maxlen < 0:
597 raise error.Abort(_(b'negative max-repo-cache size not allowed'))
586 raise error.Abort(_(b'negative max-repo-cache size not allowed'))
598 self._repoloader = repocache.repoloader(ui, maxlen)
587 self._repoloader = repocache.repoloader(ui, maxlen)
599 # attempt to avoid crash in CoreFoundation when using chg after fix in
588 # attempt to avoid crash in CoreFoundation when using chg after fix in
600 # a89381e04c58
589 # a89381e04c58
601 if pycompat.isdarwin:
590 if pycompat.isdarwin:
602 procutil.gui()
591 procutil.gui()
603
592
604 def init(self):
593 def init(self):
605 self._sock = socket.socket(socket.AF_UNIX)
594 self._sock = socket.socket(socket.AF_UNIX)
606 # IPC channel from many workers to one main process; this is actually
595 # IPC channel from many workers to one main process; this is actually
607 # a uni-directional pipe, but is backed by a DGRAM socket so each
596 # a uni-directional pipe, but is backed by a DGRAM socket so each
608 # message can be easily separated.
597 # message can be easily separated.
609 o = socket.socketpair(socket.AF_UNIX, socket.SOCK_DGRAM)
598 o = socket.socketpair(socket.AF_UNIX, socket.SOCK_DGRAM)
610 self._mainipc, self._workeripc = o
599 self._mainipc, self._workeripc = o
611 self._servicehandler.bindsocket(self._sock, self.address)
600 self._servicehandler.bindsocket(self._sock, self.address)
612 if util.safehasattr(procutil, b'unblocksignal'):
601 if util.safehasattr(procutil, b'unblocksignal'):
613 procutil.unblocksignal(signal.SIGCHLD)
602 procutil.unblocksignal(signal.SIGCHLD)
614 o = signal.signal(signal.SIGCHLD, self._sigchldhandler)
603 o = signal.signal(signal.SIGCHLD, self._sigchldhandler)
615 self._oldsigchldhandler = o
604 self._oldsigchldhandler = o
616 self._socketunlinked = False
605 self._socketunlinked = False
617 self._repoloader.start()
606 self._repoloader.start()
618
607
619 def _unlinksocket(self):
608 def _unlinksocket(self):
620 if not self._socketunlinked:
609 if not self._socketunlinked:
621 self._servicehandler.unlinksocket(self.address)
610 self._servicehandler.unlinksocket(self.address)
622 self._socketunlinked = True
611 self._socketunlinked = True
623
612
624 def _cleanup(self):
613 def _cleanup(self):
625 signal.signal(signal.SIGCHLD, self._oldsigchldhandler)
614 signal.signal(signal.SIGCHLD, self._oldsigchldhandler)
626 self._sock.close()
615 self._sock.close()
627 self._mainipc.close()
616 self._mainipc.close()
628 self._workeripc.close()
617 self._workeripc.close()
629 self._unlinksocket()
618 self._unlinksocket()
630 self._repoloader.stop()
619 self._repoloader.stop()
631 # don't kill child processes as they have active clients, just wait
620 # don't kill child processes as they have active clients, just wait
632 self._reapworkers(0)
621 self._reapworkers(0)
633
622
634 def run(self):
623 def run(self):
635 try:
624 try:
636 self._mainloop()
625 self._mainloop()
637 finally:
626 finally:
638 self._cleanup()
627 self._cleanup()
639
628
640 def _mainloop(self):
629 def _mainloop(self):
641 exiting = False
630 exiting = False
642 h = self._servicehandler
631 h = self._servicehandler
643 selector = selectors.DefaultSelector()
632 selector = selectors.DefaultSelector()
644 selector.register(
633 selector.register(
645 self._sock, selectors.EVENT_READ, self._acceptnewconnection
634 self._sock, selectors.EVENT_READ, self._acceptnewconnection
646 )
635 )
647 selector.register(
636 selector.register(
648 self._mainipc, selectors.EVENT_READ, self._handlemainipc
637 self._mainipc, selectors.EVENT_READ, self._handlemainipc
649 )
638 )
650 while True:
639 while True:
651 if not exiting and h.shouldexit():
640 if not exiting and h.shouldexit():
652 # clients can no longer connect() to the domain socket, so
641 # clients can no longer connect() to the domain socket, so
653 # we stop queuing new requests.
642 # we stop queuing new requests.
654 # for requests that are queued (connect()-ed, but haven't been
643 # for requests that are queued (connect()-ed, but haven't been
655 # accept()-ed), handle them before exit. otherwise, clients
644 # accept()-ed), handle them before exit. otherwise, clients
656 # waiting for recv() will receive ECONNRESET.
645 # waiting for recv() will receive ECONNRESET.
657 self._unlinksocket()
646 self._unlinksocket()
658 exiting = True
647 exiting = True
659 try:
648 try:
660 events = selector.select(timeout=h.pollinterval)
649 events = selector.select(timeout=h.pollinterval)
661 except OSError as inst:
650 except OSError as inst:
662 # selectors2 raises ETIMEDOUT if timeout exceeded while
651 # selectors2 raises ETIMEDOUT if timeout exceeded while
663 # handling signal interrupt. That's probably wrong, but
652 # handling signal interrupt. That's probably wrong, but
664 # we can easily get around it.
653 # we can easily get around it.
665 if inst.errno != errno.ETIMEDOUT:
654 if inst.errno != errno.ETIMEDOUT:
666 raise
655 raise
667 events = []
656 events = []
668 if not events:
657 if not events:
669 # only exit if we completed all queued requests
658 # only exit if we completed all queued requests
670 if exiting:
659 if exiting:
671 break
660 break
672 continue
661 continue
673 for key, _mask in events:
662 for key, _mask in events:
674 key.data(key.fileobj, selector)
663 key.data(key.fileobj, selector)
675 selector.close()
664 selector.close()
676
665
677 def _acceptnewconnection(self, sock, selector):
666 def _acceptnewconnection(self, sock, selector):
678 h = self._servicehandler
667 h = self._servicehandler
679 try:
668 try:
680 conn, _addr = sock.accept()
669 conn, _addr = sock.accept()
681 except socket.error as inst:
670 except socket.error as inst:
682 if inst.args[0] == errno.EINTR:
671 if inst.args[0] == errno.EINTR:
683 return
672 return
684 raise
673 raise
685
674
686 # Future improvement: On Python 3.7, maybe gc.freeze() can be used
675 # Future improvement: On Python 3.7, maybe gc.freeze() can be used
687 # to prevent COW memory from being touched by GC.
676 # to prevent COW memory from being touched by GC.
688 # https://instagram-engineering.com/
677 # https://instagram-engineering.com/
689 # copy-on-write-friendly-python-garbage-collection-ad6ed5233ddf
678 # copy-on-write-friendly-python-garbage-collection-ad6ed5233ddf
690 pid = os.fork()
679 pid = os.fork()
691 if pid:
680 if pid:
692 try:
681 try:
693 self.ui.log(
682 self.ui.log(
694 b'cmdserver', b'forked worker process (pid=%d)\n', pid
683 b'cmdserver', b'forked worker process (pid=%d)\n', pid
695 )
684 )
696 self._workerpids.add(pid)
685 self._workerpids.add(pid)
697 h.newconnection()
686 h.newconnection()
698 finally:
687 finally:
699 conn.close() # release handle in parent process
688 conn.close() # release handle in parent process
700 else:
689 else:
701 try:
690 try:
702 selector.close()
691 selector.close()
703 sock.close()
692 sock.close()
704 self._mainipc.close()
693 self._mainipc.close()
705 self._runworker(conn)
694 self._runworker(conn)
706 conn.close()
695 conn.close()
707 self._workeripc.close()
696 self._workeripc.close()
708 os._exit(0)
697 os._exit(0)
709 except: # never return, hence no re-raises
698 except: # never return, hence no re-raises
710 try:
699 try:
711 self.ui.traceback(force=True)
700 self.ui.traceback(force=True)
712 finally:
701 finally:
713 os._exit(255)
702 os._exit(255)
714
703
715 def _handlemainipc(self, sock, selector):
704 def _handlemainipc(self, sock, selector):
716 """Process messages sent from a worker"""
705 """Process messages sent from a worker"""
717 try:
706 try:
718 path = sock.recv(32768) # large enough to receive path
707 path = sock.recv(32768) # large enough to receive path
719 except socket.error as inst:
708 except socket.error as inst:
720 if inst.args[0] == errno.EINTR:
709 if inst.args[0] == errno.EINTR:
721 return
710 return
722 raise
711 raise
723 self._repoloader.load(path)
712 self._repoloader.load(path)
724
713
725 def _sigchldhandler(self, signal, frame):
714 def _sigchldhandler(self, signal, frame):
726 self._reapworkers(os.WNOHANG)
715 self._reapworkers(os.WNOHANG)
727
716
728 def _reapworkers(self, options):
717 def _reapworkers(self, options):
729 while self._workerpids:
718 while self._workerpids:
730 try:
719 try:
731 pid, _status = os.waitpid(-1, options)
720 pid, _status = os.waitpid(-1, options)
732 except OSError as inst:
721 except OSError as inst:
733 if inst.errno == errno.EINTR:
722 if inst.errno == errno.EINTR:
734 continue
723 continue
735 if inst.errno != errno.ECHILD:
724 if inst.errno != errno.ECHILD:
736 raise
725 raise
737 # no child processes at all (reaped by other waitpid()?)
726 # no child processes at all (reaped by other waitpid()?)
738 self._workerpids.clear()
727 self._workerpids.clear()
739 return
728 return
740 if pid == 0:
729 if pid == 0:
741 # no waitable child processes
730 # no waitable child processes
742 return
731 return
743 self.ui.log(b'cmdserver', b'worker process exited (pid=%d)\n', pid)
732 self.ui.log(b'cmdserver', b'worker process exited (pid=%d)\n', pid)
744 self._workerpids.discard(pid)
733 self._workerpids.discard(pid)
745
734
746 def _runworker(self, conn):
735 def _runworker(self, conn):
747 signal.signal(signal.SIGCHLD, self._oldsigchldhandler)
736 signal.signal(signal.SIGCHLD, self._oldsigchldhandler)
748 _initworkerprocess()
737 _initworkerprocess()
749 h = self._servicehandler
738 h = self._servicehandler
750 try:
739 try:
751 _serverequest(
740 _serverequest(
752 self.ui,
741 self.ui,
753 self.repo,
742 self.repo,
754 conn,
743 conn,
755 h.createcmdserver,
744 h.createcmdserver,
756 prereposetups=[self._reposetup],
745 prereposetups=[self._reposetup],
757 )
746 )
758 finally:
747 finally:
759 gc.collect() # trigger __del__ since worker process uses os._exit
748 gc.collect() # trigger __del__ since worker process uses os._exit
760
749
761 def _reposetup(self, ui, repo):
750 def _reposetup(self, ui, repo):
762 if not repo.local():
751 if not repo.local():
763 return
752 return
764
753
765 class unixcmdserverrepo(repo.__class__):
754 class unixcmdserverrepo(repo.__class__):
766 def close(self):
755 def close(self):
767 super(unixcmdserverrepo, self).close()
756 super(unixcmdserverrepo, self).close()
768 try:
757 try:
769 self._cmdserveripc.send(self.root)
758 self._cmdserveripc.send(self.root)
770 except socket.error:
759 except socket.error:
771 self.ui.log(
760 self.ui.log(
772 b'cmdserver', b'failed to send repo root to master\n'
761 b'cmdserver', b'failed to send repo root to master\n'
773 )
762 )
774
763
775 repo.__class__ = unixcmdserverrepo
764 repo.__class__ = unixcmdserverrepo
776 repo._cmdserveripc = self._workeripc
765 repo._cmdserveripc = self._workeripc
777
766
778 cachedrepo = self._repoloader.get(repo.root)
767 cachedrepo = self._repoloader.get(repo.root)
779 if cachedrepo is None:
768 if cachedrepo is None:
780 return
769 return
781 repo.ui.log(b'repocache', b'repo from cache: %s\n', repo.root)
770 repo.ui.log(b'repocache', b'repo from cache: %s\n', repo.root)
782 repocache.copycache(cachedrepo, repo)
771 repocache.copycache(cachedrepo, repo)
@@ -1,1354 +1,1360
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 errno
10 import errno
11 import getopt
11 import getopt
12 import io
12 import io
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 traceback
18 import traceback
19
19
20
20
21 from .i18n import _
21 from .i18n import _
22 from .pycompat import getattr
22 from .pycompat import getattr
23
23
24 from hgdemandimport import tracing
24 from hgdemandimport import tracing
25
25
26 from . import (
26 from . import (
27 cmdutil,
27 cmdutil,
28 color,
28 color,
29 commands,
29 commands,
30 demandimport,
30 demandimport,
31 encoding,
31 encoding,
32 error,
32 error,
33 extensions,
33 extensions,
34 fancyopts,
34 fancyopts,
35 help,
35 help,
36 hg,
36 hg,
37 hook,
37 hook,
38 localrepo,
38 localrepo,
39 profiling,
39 profiling,
40 pycompat,
40 pycompat,
41 rcutil,
41 rcutil,
42 registrar,
42 registrar,
43 requirements as requirementsmod,
43 requirements as requirementsmod,
44 scmutil,
44 scmutil,
45 ui as uimod,
45 ui as uimod,
46 util,
46 util,
47 vfs,
47 vfs,
48 )
48 )
49
49
50 from .utils import (
50 from .utils import (
51 procutil,
51 procutil,
52 stringutil,
52 stringutil,
53 )
53 )
54
54
55
55
56 class request(object):
56 class request(object):
57 def __init__(
57 def __init__(
58 self,
58 self,
59 args,
59 args,
60 ui=None,
60 ui=None,
61 repo=None,
61 repo=None,
62 fin=None,
62 fin=None,
63 fout=None,
63 fout=None,
64 ferr=None,
64 ferr=None,
65 fmsg=None,
65 fmsg=None,
66 prereposetups=None,
66 prereposetups=None,
67 ):
67 ):
68 self.args = args
68 self.args = args
69 self.ui = ui
69 self.ui = ui
70 self.repo = repo
70 self.repo = repo
71
71
72 # input/output/error streams
72 # input/output/error streams
73 self.fin = fin
73 self.fin = fin
74 self.fout = fout
74 self.fout = fout
75 self.ferr = ferr
75 self.ferr = ferr
76 # separate stream for status/error messages
76 # separate stream for status/error messages
77 self.fmsg = fmsg
77 self.fmsg = fmsg
78
78
79 # remember options pre-parsed by _earlyparseopts()
79 # remember options pre-parsed by _earlyparseopts()
80 self.earlyoptions = {}
80 self.earlyoptions = {}
81
81
82 # reposetups which run before extensions, useful for chg to pre-fill
82 # reposetups which run before extensions, useful for chg to pre-fill
83 # low-level repo state (for example, changelog) before extensions.
83 # low-level repo state (for example, changelog) before extensions.
84 self.prereposetups = prereposetups or []
84 self.prereposetups = prereposetups or []
85
85
86 # store the parsed and canonical command
86 # store the parsed and canonical command
87 self.canonical_command = None
87 self.canonical_command = None
88
88
89 def _runexithandlers(self):
89 def _runexithandlers(self):
90 exc = None
90 exc = None
91 handlers = self.ui._exithandlers
91 handlers = self.ui._exithandlers
92 try:
92 try:
93 while handlers:
93 while handlers:
94 func, args, kwargs = handlers.pop()
94 func, args, kwargs = handlers.pop()
95 try:
95 try:
96 func(*args, **kwargs)
96 func(*args, **kwargs)
97 except: # re-raises below
97 except: # re-raises below
98 if exc is None:
98 if exc is None:
99 exc = sys.exc_info()[1]
99 exc = sys.exc_info()[1]
100 self.ui.warnnoi18n(b'error in exit handlers:\n')
100 self.ui.warnnoi18n(b'error in exit handlers:\n')
101 self.ui.traceback(force=True)
101 self.ui.traceback(force=True)
102 finally:
102 finally:
103 if exc is not None:
103 if exc is not None:
104 raise exc
104 raise exc
105
105
106
106
107 def closestdio(ui, err):
107 def _flushstdio(ui, err):
108 status = None
108 status = None
109 # In all cases we try to flush stdio streams.
109 # In all cases we try to flush stdio streams.
110 if util.safehasattr(ui, b'fout'):
110 if util.safehasattr(ui, b'fout'):
111 assert ui is not None # help pytype
111 assert ui is not None # help pytype
112 assert ui.fout is not None # help pytype
112 assert ui.fout is not None # help pytype
113 try:
113 try:
114 ui.fout.flush()
114 ui.fout.flush()
115 except IOError as e:
115 except IOError as e:
116 err = e
116 err = e
117 status = -1
117 status = -1
118
118
119 if util.safehasattr(ui, b'ferr'):
119 if util.safehasattr(ui, b'ferr'):
120 assert ui is not None # help pytype
120 assert ui is not None # help pytype
121 assert ui.ferr is not None # help pytype
121 assert ui.ferr is not None # help pytype
122 try:
122 try:
123 if err is not None and err.errno != errno.EPIPE:
123 if err is not None and err.errno != errno.EPIPE:
124 ui.ferr.write(
124 ui.ferr.write(
125 b'abort: %s\n' % encoding.strtolocal(err.strerror)
125 b'abort: %s\n' % encoding.strtolocal(err.strerror)
126 )
126 )
127 ui.ferr.flush()
127 ui.ferr.flush()
128 # There's not much we can do about an I/O error here. So (possibly)
128 # There's not much we can do about an I/O error here. So (possibly)
129 # change the status code and move on.
129 # change the status code and move on.
130 except IOError:
130 except IOError:
131 status = -1
131 status = -1
132
132
133 return status
133 return status
134
134
135
135
136 def run():
136 def run():
137 """run the command in sys.argv"""
137 """run the command in sys.argv"""
138 try:
138 try:
139 initstdio()
139 initstdio()
140 with tracing.log('parse args into request'):
140 with tracing.log('parse args into request'):
141 req = request(pycompat.sysargv[1:])
141 req = request(pycompat.sysargv[1:])
142 err = None
143 try:
144 status = dispatch(req)
145 except error.StdioError as e:
146 err = e
147 status = -1
148
142
149 ret = closestdio(req.ui, err)
143 status = dispatch(req)
150 if ret:
151 status = ret
152 _silencestdio()
144 _silencestdio()
153 except KeyboardInterrupt:
145 except KeyboardInterrupt:
154 # Catch early/late KeyboardInterrupt as last ditch. Here nothing will
146 # Catch early/late KeyboardInterrupt as last ditch. Here nothing will
155 # be printed to console to avoid another IOError/KeyboardInterrupt.
147 # be printed to console to avoid another IOError/KeyboardInterrupt.
156 status = -1
148 status = -1
157 sys.exit(status & 255)
149 sys.exit(status & 255)
158
150
159
151
160 if pycompat.ispy3:
152 if pycompat.ispy3:
161
153
162 def initstdio():
154 def initstdio():
163 # stdio streams on Python 3 are io.TextIOWrapper instances proxying another
155 # stdio streams on Python 3 are io.TextIOWrapper instances proxying another
164 # buffer. These streams will normalize \n to \r\n by default. Mercurial's
156 # buffer. These streams will normalize \n to \r\n by default. Mercurial's
165 # preferred mechanism for writing output (ui.write()) uses io.BufferedWriter
157 # preferred mechanism for writing output (ui.write()) uses io.BufferedWriter
166 # instances, which write to the underlying stdio file descriptor in binary
158 # instances, which write to the underlying stdio file descriptor in binary
167 # mode. ui.write() uses \n for line endings and no line ending normalization
159 # mode. ui.write() uses \n for line endings and no line ending normalization
168 # is attempted through this interface. This "just works," even if the system
160 # is attempted through this interface. This "just works," even if the system
169 # preferred line ending is not \n.
161 # preferred line ending is not \n.
170 #
162 #
171 # But some parts of Mercurial (e.g. hooks) can still send data to sys.stdout
163 # But some parts of Mercurial (e.g. hooks) can still send data to sys.stdout
172 # and sys.stderr. They will inherit the line ending normalization settings,
164 # and sys.stderr. They will inherit the line ending normalization settings,
173 # potentially causing e.g. \r\n to be emitted. Since emitting \n should
165 # potentially causing e.g. \r\n to be emitted. Since emitting \n should
174 # "just work," here we change the sys.* streams to disable line ending
166 # "just work," here we change the sys.* streams to disable line ending
175 # normalization, ensuring compatibility with our ui type.
167 # normalization, ensuring compatibility with our ui type.
176
168
177 # write_through is new in Python 3.7.
169 # write_through is new in Python 3.7.
178 kwargs = {
170 kwargs = {
179 "newline": "\n",
171 "newline": "\n",
180 "line_buffering": sys.stdout.line_buffering,
172 "line_buffering": sys.stdout.line_buffering,
181 }
173 }
182 if util.safehasattr(sys.stdout, "write_through"):
174 if util.safehasattr(sys.stdout, "write_through"):
183 kwargs["write_through"] = sys.stdout.write_through
175 kwargs["write_through"] = sys.stdout.write_through
184 sys.stdout = io.TextIOWrapper(
176 sys.stdout = io.TextIOWrapper(
185 sys.stdout.buffer, sys.stdout.encoding, sys.stdout.errors, **kwargs
177 sys.stdout.buffer, sys.stdout.encoding, sys.stdout.errors, **kwargs
186 )
178 )
187
179
188 kwargs = {
180 kwargs = {
189 "newline": "\n",
181 "newline": "\n",
190 "line_buffering": sys.stderr.line_buffering,
182 "line_buffering": sys.stderr.line_buffering,
191 }
183 }
192 if util.safehasattr(sys.stderr, "write_through"):
184 if util.safehasattr(sys.stderr, "write_through"):
193 kwargs["write_through"] = sys.stderr.write_through
185 kwargs["write_through"] = sys.stderr.write_through
194 sys.stderr = io.TextIOWrapper(
186 sys.stderr = io.TextIOWrapper(
195 sys.stderr.buffer, sys.stderr.encoding, sys.stderr.errors, **kwargs
187 sys.stderr.buffer, sys.stderr.encoding, sys.stderr.errors, **kwargs
196 )
188 )
197
189
198 if sys.stdin is not None:
190 if sys.stdin is not None:
199 # No write_through on read-only stream.
191 # No write_through on read-only stream.
200 sys.stdin = io.TextIOWrapper(
192 sys.stdin = io.TextIOWrapper(
201 sys.stdin.buffer,
193 sys.stdin.buffer,
202 sys.stdin.encoding,
194 sys.stdin.encoding,
203 sys.stdin.errors,
195 sys.stdin.errors,
204 # None is universal newlines mode.
196 # None is universal newlines mode.
205 newline=None,
197 newline=None,
206 line_buffering=sys.stdin.line_buffering,
198 line_buffering=sys.stdin.line_buffering,
207 )
199 )
208
200
209 def _silencestdio():
201 def _silencestdio():
210 for fp in (sys.stdout, sys.stderr):
202 for fp in (sys.stdout, sys.stderr):
211 # Check if the file is okay
203 # Check if the file is okay
212 try:
204 try:
213 fp.flush()
205 fp.flush()
214 continue
206 continue
215 except IOError:
207 except IOError:
216 pass
208 pass
217 # Otherwise mark it as closed to silence "Exception ignored in"
209 # Otherwise mark it as closed to silence "Exception ignored in"
218 # message emitted by the interpreter finalizer. Be careful to
210 # message emitted by the interpreter finalizer. Be careful to
219 # not close procutil.stdout, which may be a fdopen-ed file object
211 # not close procutil.stdout, which may be a fdopen-ed file object
220 # and its close() actually closes the underlying file descriptor.
212 # and its close() actually closes the underlying file descriptor.
221 try:
213 try:
222 fp.close()
214 fp.close()
223 except IOError:
215 except IOError:
224 pass
216 pass
225
217
226
218
227 else:
219 else:
228
220
229 def initstdio():
221 def initstdio():
230 for fp in (sys.stdin, sys.stdout, sys.stderr):
222 for fp in (sys.stdin, sys.stdout, sys.stderr):
231 procutil.setbinary(fp)
223 procutil.setbinary(fp)
232
224
233 def _silencestdio():
225 def _silencestdio():
234 pass
226 pass
235
227
236
228
237 def _formatargs(args):
229 def _formatargs(args):
238 return b' '.join(procutil.shellquote(a) for a in args)
230 return b' '.join(procutil.shellquote(a) for a in args)
239
231
240
232
241 def dispatch(req):
233 def dispatch(req):
242 """run the command specified in req.args; returns an integer status code"""
234 """run the command specified in req.args; returns an integer status code"""
243 with tracing.log('dispatch.dispatch'):
235 err = None
236 try:
237 status = _rundispatch(req)
238 except error.StdioError as e:
239 err = e
240 status = -1
241
242 ret = _flushstdio(req.ui, err)
243 if ret:
244 status = ret
245 return status
246
247
248 def _rundispatch(req):
249 with tracing.log('dispatch._rundispatch'):
244 if req.ferr:
250 if req.ferr:
245 ferr = req.ferr
251 ferr = req.ferr
246 elif req.ui:
252 elif req.ui:
247 ferr = req.ui.ferr
253 ferr = req.ui.ferr
248 else:
254 else:
249 ferr = procutil.stderr
255 ferr = procutil.stderr
250
256
251 try:
257 try:
252 if not req.ui:
258 if not req.ui:
253 req.ui = uimod.ui.load()
259 req.ui = uimod.ui.load()
254 req.earlyoptions.update(_earlyparseopts(req.ui, req.args))
260 req.earlyoptions.update(_earlyparseopts(req.ui, req.args))
255 if req.earlyoptions[b'traceback']:
261 if req.earlyoptions[b'traceback']:
256 req.ui.setconfig(b'ui', b'traceback', b'on', b'--traceback')
262 req.ui.setconfig(b'ui', b'traceback', b'on', b'--traceback')
257
263
258 # set ui streams from the request
264 # set ui streams from the request
259 if req.fin:
265 if req.fin:
260 req.ui.fin = req.fin
266 req.ui.fin = req.fin
261 if req.fout:
267 if req.fout:
262 req.ui.fout = req.fout
268 req.ui.fout = req.fout
263 if req.ferr:
269 if req.ferr:
264 req.ui.ferr = req.ferr
270 req.ui.ferr = req.ferr
265 if req.fmsg:
271 if req.fmsg:
266 req.ui.fmsg = req.fmsg
272 req.ui.fmsg = req.fmsg
267 except error.Abort as inst:
273 except error.Abort as inst:
268 ferr.write(inst.format())
274 ferr.write(inst.format())
269 return -1
275 return -1
270
276
271 msg = _formatargs(req.args)
277 msg = _formatargs(req.args)
272 starttime = util.timer()
278 starttime = util.timer()
273 ret = 1 # default of Python exit code on unhandled exception
279 ret = 1 # default of Python exit code on unhandled exception
274 try:
280 try:
275 ret = _runcatch(req) or 0
281 ret = _runcatch(req) or 0
276 except error.ProgrammingError as inst:
282 except error.ProgrammingError as inst:
277 req.ui.error(_(b'** ProgrammingError: %s\n') % inst)
283 req.ui.error(_(b'** ProgrammingError: %s\n') % inst)
278 if inst.hint:
284 if inst.hint:
279 req.ui.error(_(b'** (%s)\n') % inst.hint)
285 req.ui.error(_(b'** (%s)\n') % inst.hint)
280 raise
286 raise
281 except KeyboardInterrupt as inst:
287 except KeyboardInterrupt as inst:
282 try:
288 try:
283 if isinstance(inst, error.SignalInterrupt):
289 if isinstance(inst, error.SignalInterrupt):
284 msg = _(b"killed!\n")
290 msg = _(b"killed!\n")
285 else:
291 else:
286 msg = _(b"interrupted!\n")
292 msg = _(b"interrupted!\n")
287 req.ui.error(msg)
293 req.ui.error(msg)
288 except error.SignalInterrupt:
294 except error.SignalInterrupt:
289 # maybe pager would quit without consuming all the output, and
295 # maybe pager would quit without consuming all the output, and
290 # SIGPIPE was raised. we cannot print anything in this case.
296 # SIGPIPE was raised. we cannot print anything in this case.
291 pass
297 pass
292 except IOError as inst:
298 except IOError as inst:
293 if inst.errno != errno.EPIPE:
299 if inst.errno != errno.EPIPE:
294 raise
300 raise
295 ret = -1
301 ret = -1
296 finally:
302 finally:
297 duration = util.timer() - starttime
303 duration = util.timer() - starttime
298 req.ui.flush() # record blocked times
304 req.ui.flush() # record blocked times
299 if req.ui.logblockedtimes:
305 if req.ui.logblockedtimes:
300 req.ui._blockedtimes[b'command_duration'] = duration * 1000
306 req.ui._blockedtimes[b'command_duration'] = duration * 1000
301 req.ui.log(
307 req.ui.log(
302 b'uiblocked',
308 b'uiblocked',
303 b'ui blocked ms\n',
309 b'ui blocked ms\n',
304 **pycompat.strkwargs(req.ui._blockedtimes)
310 **pycompat.strkwargs(req.ui._blockedtimes)
305 )
311 )
306 return_code = ret & 255
312 return_code = ret & 255
307 req.ui.log(
313 req.ui.log(
308 b"commandfinish",
314 b"commandfinish",
309 b"%s exited %d after %0.2f seconds\n",
315 b"%s exited %d after %0.2f seconds\n",
310 msg,
316 msg,
311 return_code,
317 return_code,
312 duration,
318 duration,
313 return_code=return_code,
319 return_code=return_code,
314 duration=duration,
320 duration=duration,
315 canonical_command=req.canonical_command,
321 canonical_command=req.canonical_command,
316 )
322 )
317 try:
323 try:
318 req._runexithandlers()
324 req._runexithandlers()
319 except: # exiting, so no re-raises
325 except: # exiting, so no re-raises
320 ret = ret or -1
326 ret = ret or -1
321 # do flush again since ui.log() and exit handlers may write to ui
327 # do flush again since ui.log() and exit handlers may write to ui
322 req.ui.flush()
328 req.ui.flush()
323 return ret
329 return ret
324
330
325
331
326 def _runcatch(req):
332 def _runcatch(req):
327 with tracing.log('dispatch._runcatch'):
333 with tracing.log('dispatch._runcatch'):
328
334
329 def catchterm(*args):
335 def catchterm(*args):
330 raise error.SignalInterrupt
336 raise error.SignalInterrupt
331
337
332 ui = req.ui
338 ui = req.ui
333 try:
339 try:
334 for name in b'SIGBREAK', b'SIGHUP', b'SIGTERM':
340 for name in b'SIGBREAK', b'SIGHUP', b'SIGTERM':
335 num = getattr(signal, name, None)
341 num = getattr(signal, name, None)
336 if num:
342 if num:
337 signal.signal(num, catchterm)
343 signal.signal(num, catchterm)
338 except ValueError:
344 except ValueError:
339 pass # happens if called in a thread
345 pass # happens if called in a thread
340
346
341 def _runcatchfunc():
347 def _runcatchfunc():
342 realcmd = None
348 realcmd = None
343 try:
349 try:
344 cmdargs = fancyopts.fancyopts(
350 cmdargs = fancyopts.fancyopts(
345 req.args[:], commands.globalopts, {}
351 req.args[:], commands.globalopts, {}
346 )
352 )
347 cmd = cmdargs[0]
353 cmd = cmdargs[0]
348 aliases, entry = cmdutil.findcmd(cmd, commands.table, False)
354 aliases, entry = cmdutil.findcmd(cmd, commands.table, False)
349 realcmd = aliases[0]
355 realcmd = aliases[0]
350 except (
356 except (
351 error.UnknownCommand,
357 error.UnknownCommand,
352 error.AmbiguousCommand,
358 error.AmbiguousCommand,
353 IndexError,
359 IndexError,
354 getopt.GetoptError,
360 getopt.GetoptError,
355 ):
361 ):
356 # Don't handle this here. We know the command is
362 # Don't handle this here. We know the command is
357 # invalid, but all we're worried about for now is that
363 # invalid, but all we're worried about for now is that
358 # it's not a command that server operators expect to
364 # it's not a command that server operators expect to
359 # be safe to offer to users in a sandbox.
365 # be safe to offer to users in a sandbox.
360 pass
366 pass
361 if realcmd == b'serve' and b'--stdio' in cmdargs:
367 if realcmd == b'serve' and b'--stdio' in cmdargs:
362 # We want to constrain 'hg serve --stdio' instances pretty
368 # We want to constrain 'hg serve --stdio' instances pretty
363 # closely, as many shared-ssh access tools want to grant
369 # closely, as many shared-ssh access tools want to grant
364 # access to run *only* 'hg -R $repo serve --stdio'. We
370 # access to run *only* 'hg -R $repo serve --stdio'. We
365 # restrict to exactly that set of arguments, and prohibit
371 # restrict to exactly that set of arguments, and prohibit
366 # any repo name that starts with '--' to prevent
372 # any repo name that starts with '--' to prevent
367 # shenanigans wherein a user does something like pass
373 # shenanigans wherein a user does something like pass
368 # --debugger or --config=ui.debugger=1 as a repo
374 # --debugger or --config=ui.debugger=1 as a repo
369 # name. This used to actually run the debugger.
375 # name. This used to actually run the debugger.
370 if (
376 if (
371 len(req.args) != 4
377 len(req.args) != 4
372 or req.args[0] != b'-R'
378 or req.args[0] != b'-R'
373 or req.args[1].startswith(b'--')
379 or req.args[1].startswith(b'--')
374 or req.args[2] != b'serve'
380 or req.args[2] != b'serve'
375 or req.args[3] != b'--stdio'
381 or req.args[3] != b'--stdio'
376 ):
382 ):
377 raise error.Abort(
383 raise error.Abort(
378 _(b'potentially unsafe serve --stdio invocation: %s')
384 _(b'potentially unsafe serve --stdio invocation: %s')
379 % (stringutil.pprint(req.args),)
385 % (stringutil.pprint(req.args),)
380 )
386 )
381
387
382 try:
388 try:
383 debugger = b'pdb'
389 debugger = b'pdb'
384 debugtrace = {b'pdb': pdb.set_trace}
390 debugtrace = {b'pdb': pdb.set_trace}
385 debugmortem = {b'pdb': pdb.post_mortem}
391 debugmortem = {b'pdb': pdb.post_mortem}
386
392
387 # read --config before doing anything else
393 # read --config before doing anything else
388 # (e.g. to change trust settings for reading .hg/hgrc)
394 # (e.g. to change trust settings for reading .hg/hgrc)
389 cfgs = _parseconfig(req.ui, req.earlyoptions[b'config'])
395 cfgs = _parseconfig(req.ui, req.earlyoptions[b'config'])
390
396
391 if req.repo:
397 if req.repo:
392 # copy configs that were passed on the cmdline (--config) to
398 # copy configs that were passed on the cmdline (--config) to
393 # the repo ui
399 # the repo ui
394 for sec, name, val in cfgs:
400 for sec, name, val in cfgs:
395 req.repo.ui.setconfig(
401 req.repo.ui.setconfig(
396 sec, name, val, source=b'--config'
402 sec, name, val, source=b'--config'
397 )
403 )
398
404
399 # developer config: ui.debugger
405 # developer config: ui.debugger
400 debugger = ui.config(b"ui", b"debugger")
406 debugger = ui.config(b"ui", b"debugger")
401 debugmod = pdb
407 debugmod = pdb
402 if not debugger or ui.plain():
408 if not debugger or ui.plain():
403 # if we are in HGPLAIN mode, then disable custom debugging
409 # if we are in HGPLAIN mode, then disable custom debugging
404 debugger = b'pdb'
410 debugger = b'pdb'
405 elif req.earlyoptions[b'debugger']:
411 elif req.earlyoptions[b'debugger']:
406 # This import can be slow for fancy debuggers, so only
412 # This import can be slow for fancy debuggers, so only
407 # do it when absolutely necessary, i.e. when actual
413 # do it when absolutely necessary, i.e. when actual
408 # debugging has been requested
414 # debugging has been requested
409 with demandimport.deactivated():
415 with demandimport.deactivated():
410 try:
416 try:
411 debugmod = __import__(debugger)
417 debugmod = __import__(debugger)
412 except ImportError:
418 except ImportError:
413 pass # Leave debugmod = pdb
419 pass # Leave debugmod = pdb
414
420
415 debugtrace[debugger] = debugmod.set_trace
421 debugtrace[debugger] = debugmod.set_trace
416 debugmortem[debugger] = debugmod.post_mortem
422 debugmortem[debugger] = debugmod.post_mortem
417
423
418 # enter the debugger before command execution
424 # enter the debugger before command execution
419 if req.earlyoptions[b'debugger']:
425 if req.earlyoptions[b'debugger']:
420 ui.warn(
426 ui.warn(
421 _(
427 _(
422 b"entering debugger - "
428 b"entering debugger - "
423 b"type c to continue starting hg or h for help\n"
429 b"type c to continue starting hg or h for help\n"
424 )
430 )
425 )
431 )
426
432
427 if (
433 if (
428 debugger != b'pdb'
434 debugger != b'pdb'
429 and debugtrace[debugger] == debugtrace[b'pdb']
435 and debugtrace[debugger] == debugtrace[b'pdb']
430 ):
436 ):
431 ui.warn(
437 ui.warn(
432 _(
438 _(
433 b"%s debugger specified "
439 b"%s debugger specified "
434 b"but its module was not found\n"
440 b"but its module was not found\n"
435 )
441 )
436 % debugger
442 % debugger
437 )
443 )
438 with demandimport.deactivated():
444 with demandimport.deactivated():
439 debugtrace[debugger]()
445 debugtrace[debugger]()
440 try:
446 try:
441 return _dispatch(req)
447 return _dispatch(req)
442 finally:
448 finally:
443 ui.flush()
449 ui.flush()
444 except: # re-raises
450 except: # re-raises
445 # enter the debugger when we hit an exception
451 # enter the debugger when we hit an exception
446 if req.earlyoptions[b'debugger']:
452 if req.earlyoptions[b'debugger']:
447 traceback.print_exc()
453 traceback.print_exc()
448 debugmortem[debugger](sys.exc_info()[2])
454 debugmortem[debugger](sys.exc_info()[2])
449 raise
455 raise
450
456
451 return _callcatch(ui, _runcatchfunc)
457 return _callcatch(ui, _runcatchfunc)
452
458
453
459
454 def _callcatch(ui, func):
460 def _callcatch(ui, func):
455 """like scmutil.callcatch but handles more high-level exceptions about
461 """like scmutil.callcatch but handles more high-level exceptions about
456 config parsing and commands. besides, use handlecommandexception to handle
462 config parsing and commands. besides, use handlecommandexception to handle
457 uncaught exceptions.
463 uncaught exceptions.
458 """
464 """
459 try:
465 try:
460 return scmutil.callcatch(ui, func)
466 return scmutil.callcatch(ui, func)
461 except error.AmbiguousCommand as inst:
467 except error.AmbiguousCommand as inst:
462 ui.warn(
468 ui.warn(
463 _(b"hg: command '%s' is ambiguous:\n %s\n")
469 _(b"hg: command '%s' is ambiguous:\n %s\n")
464 % (inst.prefix, b" ".join(inst.matches))
470 % (inst.prefix, b" ".join(inst.matches))
465 )
471 )
466 except error.CommandError as inst:
472 except error.CommandError as inst:
467 if inst.command:
473 if inst.command:
468 ui.pager(b'help')
474 ui.pager(b'help')
469 msgbytes = pycompat.bytestr(inst.message)
475 msgbytes = pycompat.bytestr(inst.message)
470 ui.warn(_(b"hg %s: %s\n") % (inst.command, msgbytes))
476 ui.warn(_(b"hg %s: %s\n") % (inst.command, msgbytes))
471 commands.help_(ui, inst.command, full=False, command=True)
477 commands.help_(ui, inst.command, full=False, command=True)
472 else:
478 else:
473 ui.warn(_(b"hg: %s\n") % inst.message)
479 ui.warn(_(b"hg: %s\n") % inst.message)
474 ui.warn(_(b"(use 'hg help -v' for a list of global options)\n"))
480 ui.warn(_(b"(use 'hg help -v' for a list of global options)\n"))
475 except error.UnknownCommand as inst:
481 except error.UnknownCommand as inst:
476 nocmdmsg = _(b"hg: unknown command '%s'\n") % inst.command
482 nocmdmsg = _(b"hg: unknown command '%s'\n") % inst.command
477 try:
483 try:
478 # check if the command is in a disabled extension
484 # check if the command is in a disabled extension
479 # (but don't check for extensions themselves)
485 # (but don't check for extensions themselves)
480 formatted = help.formattedhelp(
486 formatted = help.formattedhelp(
481 ui, commands, inst.command, unknowncmd=True
487 ui, commands, inst.command, unknowncmd=True
482 )
488 )
483 ui.warn(nocmdmsg)
489 ui.warn(nocmdmsg)
484 ui.write(formatted)
490 ui.write(formatted)
485 except (error.UnknownCommand, error.Abort):
491 except (error.UnknownCommand, error.Abort):
486 suggested = False
492 suggested = False
487 if inst.all_commands:
493 if inst.all_commands:
488 sim = error.getsimilar(inst.all_commands, inst.command)
494 sim = error.getsimilar(inst.all_commands, inst.command)
489 if sim:
495 if sim:
490 ui.warn(nocmdmsg)
496 ui.warn(nocmdmsg)
491 ui.warn(b"(%s)\n" % error.similarity_hint(sim))
497 ui.warn(b"(%s)\n" % error.similarity_hint(sim))
492 suggested = True
498 suggested = True
493 if not suggested:
499 if not suggested:
494 ui.warn(nocmdmsg)
500 ui.warn(nocmdmsg)
495 ui.warn(_(b"(use 'hg help' for a list of commands)\n"))
501 ui.warn(_(b"(use 'hg help' for a list of commands)\n"))
496 except IOError:
502 except IOError:
497 raise
503 raise
498 except KeyboardInterrupt:
504 except KeyboardInterrupt:
499 raise
505 raise
500 except: # probably re-raises
506 except: # probably re-raises
501 if not handlecommandexception(ui):
507 if not handlecommandexception(ui):
502 raise
508 raise
503
509
504 return -1
510 return -1
505
511
506
512
507 def aliasargs(fn, givenargs):
513 def aliasargs(fn, givenargs):
508 args = []
514 args = []
509 # only care about alias 'args', ignore 'args' set by extensions.wrapfunction
515 # only care about alias 'args', ignore 'args' set by extensions.wrapfunction
510 if not util.safehasattr(fn, b'_origfunc'):
516 if not util.safehasattr(fn, b'_origfunc'):
511 args = getattr(fn, 'args', args)
517 args = getattr(fn, 'args', args)
512 if args:
518 if args:
513 cmd = b' '.join(map(procutil.shellquote, args))
519 cmd = b' '.join(map(procutil.shellquote, args))
514
520
515 nums = []
521 nums = []
516
522
517 def replacer(m):
523 def replacer(m):
518 num = int(m.group(1)) - 1
524 num = int(m.group(1)) - 1
519 nums.append(num)
525 nums.append(num)
520 if num < len(givenargs):
526 if num < len(givenargs):
521 return givenargs[num]
527 return givenargs[num]
522 raise error.InputError(_(b'too few arguments for command alias'))
528 raise error.InputError(_(b'too few arguments for command alias'))
523
529
524 cmd = re.sub(br'\$(\d+|\$)', replacer, cmd)
530 cmd = re.sub(br'\$(\d+|\$)', replacer, cmd)
525 givenargs = [x for i, x in enumerate(givenargs) if i not in nums]
531 givenargs = [x for i, x in enumerate(givenargs) if i not in nums]
526 args = pycompat.shlexsplit(cmd)
532 args = pycompat.shlexsplit(cmd)
527 return args + givenargs
533 return args + givenargs
528
534
529
535
530 def aliasinterpolate(name, args, cmd):
536 def aliasinterpolate(name, args, cmd):
531 """interpolate args into cmd for shell aliases
537 """interpolate args into cmd for shell aliases
532
538
533 This also handles $0, $@ and "$@".
539 This also handles $0, $@ and "$@".
534 """
540 """
535 # util.interpolate can't deal with "$@" (with quotes) because it's only
541 # util.interpolate can't deal with "$@" (with quotes) because it's only
536 # built to match prefix + patterns.
542 # built to match prefix + patterns.
537 replacemap = {b'$%d' % (i + 1): arg for i, arg in enumerate(args)}
543 replacemap = {b'$%d' % (i + 1): arg for i, arg in enumerate(args)}
538 replacemap[b'$0'] = name
544 replacemap[b'$0'] = name
539 replacemap[b'$$'] = b'$'
545 replacemap[b'$$'] = b'$'
540 replacemap[b'$@'] = b' '.join(args)
546 replacemap[b'$@'] = b' '.join(args)
541 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
547 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
542 # parameters, separated out into words. Emulate the same behavior here by
548 # parameters, separated out into words. Emulate the same behavior here by
543 # quoting the arguments individually. POSIX shells will then typically
549 # quoting the arguments individually. POSIX shells will then typically
544 # tokenize each argument into exactly one word.
550 # tokenize each argument into exactly one word.
545 replacemap[b'"$@"'] = b' '.join(procutil.shellquote(arg) for arg in args)
551 replacemap[b'"$@"'] = b' '.join(procutil.shellquote(arg) for arg in args)
546 # escape '\$' for regex
552 # escape '\$' for regex
547 regex = b'|'.join(replacemap.keys()).replace(b'$', br'\$')
553 regex = b'|'.join(replacemap.keys()).replace(b'$', br'\$')
548 r = re.compile(regex)
554 r = re.compile(regex)
549 return r.sub(lambda x: replacemap[x.group()], cmd)
555 return r.sub(lambda x: replacemap[x.group()], cmd)
550
556
551
557
552 class cmdalias(object):
558 class cmdalias(object):
553 def __init__(self, ui, name, definition, cmdtable, source):
559 def __init__(self, ui, name, definition, cmdtable, source):
554 self.name = self.cmd = name
560 self.name = self.cmd = name
555 self.cmdname = b''
561 self.cmdname = b''
556 self.definition = definition
562 self.definition = definition
557 self.fn = None
563 self.fn = None
558 self.givenargs = []
564 self.givenargs = []
559 self.opts = []
565 self.opts = []
560 self.help = b''
566 self.help = b''
561 self.badalias = None
567 self.badalias = None
562 self.unknowncmd = False
568 self.unknowncmd = False
563 self.source = source
569 self.source = source
564
570
565 try:
571 try:
566 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
572 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
567 for alias, e in pycompat.iteritems(cmdtable):
573 for alias, e in pycompat.iteritems(cmdtable):
568 if e is entry:
574 if e is entry:
569 self.cmd = alias
575 self.cmd = alias
570 break
576 break
571 self.shadows = True
577 self.shadows = True
572 except error.UnknownCommand:
578 except error.UnknownCommand:
573 self.shadows = False
579 self.shadows = False
574
580
575 if not self.definition:
581 if not self.definition:
576 self.badalias = _(b"no definition for alias '%s'") % self.name
582 self.badalias = _(b"no definition for alias '%s'") % self.name
577 return
583 return
578
584
579 if self.definition.startswith(b'!'):
585 if self.definition.startswith(b'!'):
580 shdef = self.definition[1:]
586 shdef = self.definition[1:]
581 self.shell = True
587 self.shell = True
582
588
583 def fn(ui, *args):
589 def fn(ui, *args):
584 env = {b'HG_ARGS': b' '.join((self.name,) + args)}
590 env = {b'HG_ARGS': b' '.join((self.name,) + args)}
585
591
586 def _checkvar(m):
592 def _checkvar(m):
587 if m.groups()[0] == b'$':
593 if m.groups()[0] == b'$':
588 return m.group()
594 return m.group()
589 elif int(m.groups()[0]) <= len(args):
595 elif int(m.groups()[0]) <= len(args):
590 return m.group()
596 return m.group()
591 else:
597 else:
592 ui.debug(
598 ui.debug(
593 b"No argument found for substitution "
599 b"No argument found for substitution "
594 b"of %i variable in alias '%s' definition.\n"
600 b"of %i variable in alias '%s' definition.\n"
595 % (int(m.groups()[0]), self.name)
601 % (int(m.groups()[0]), self.name)
596 )
602 )
597 return b''
603 return b''
598
604
599 cmd = re.sub(br'\$(\d+|\$)', _checkvar, shdef)
605 cmd = re.sub(br'\$(\d+|\$)', _checkvar, shdef)
600 cmd = aliasinterpolate(self.name, args, cmd)
606 cmd = aliasinterpolate(self.name, args, cmd)
601 return ui.system(
607 return ui.system(
602 cmd, environ=env, blockedtag=b'alias_%s' % self.name
608 cmd, environ=env, blockedtag=b'alias_%s' % self.name
603 )
609 )
604
610
605 self.fn = fn
611 self.fn = fn
606 self.alias = True
612 self.alias = True
607 self._populatehelp(ui, name, shdef, self.fn)
613 self._populatehelp(ui, name, shdef, self.fn)
608 return
614 return
609
615
610 try:
616 try:
611 args = pycompat.shlexsplit(self.definition)
617 args = pycompat.shlexsplit(self.definition)
612 except ValueError as inst:
618 except ValueError as inst:
613 self.badalias = _(b"error in definition for alias '%s': %s") % (
619 self.badalias = _(b"error in definition for alias '%s': %s") % (
614 self.name,
620 self.name,
615 stringutil.forcebytestr(inst),
621 stringutil.forcebytestr(inst),
616 )
622 )
617 return
623 return
618 earlyopts, args = _earlysplitopts(args)
624 earlyopts, args = _earlysplitopts(args)
619 if earlyopts:
625 if earlyopts:
620 self.badalias = _(
626 self.badalias = _(
621 b"error in definition for alias '%s': %s may "
627 b"error in definition for alias '%s': %s may "
622 b"only be given on the command line"
628 b"only be given on the command line"
623 ) % (self.name, b'/'.join(pycompat.ziplist(*earlyopts)[0]))
629 ) % (self.name, b'/'.join(pycompat.ziplist(*earlyopts)[0]))
624 return
630 return
625 self.cmdname = cmd = args.pop(0)
631 self.cmdname = cmd = args.pop(0)
626 self.givenargs = args
632 self.givenargs = args
627
633
628 try:
634 try:
629 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
635 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
630 if len(tableentry) > 2:
636 if len(tableentry) > 2:
631 self.fn, self.opts, cmdhelp = tableentry
637 self.fn, self.opts, cmdhelp = tableentry
632 else:
638 else:
633 self.fn, self.opts = tableentry
639 self.fn, self.opts = tableentry
634 cmdhelp = None
640 cmdhelp = None
635
641
636 self.alias = True
642 self.alias = True
637 self._populatehelp(ui, name, cmd, self.fn, cmdhelp)
643 self._populatehelp(ui, name, cmd, self.fn, cmdhelp)
638
644
639 except error.UnknownCommand:
645 except error.UnknownCommand:
640 self.badalias = _(
646 self.badalias = _(
641 b"alias '%s' resolves to unknown command '%s'"
647 b"alias '%s' resolves to unknown command '%s'"
642 ) % (
648 ) % (
643 self.name,
649 self.name,
644 cmd,
650 cmd,
645 )
651 )
646 self.unknowncmd = True
652 self.unknowncmd = True
647 except error.AmbiguousCommand:
653 except error.AmbiguousCommand:
648 self.badalias = _(
654 self.badalias = _(
649 b"alias '%s' resolves to ambiguous command '%s'"
655 b"alias '%s' resolves to ambiguous command '%s'"
650 ) % (
656 ) % (
651 self.name,
657 self.name,
652 cmd,
658 cmd,
653 )
659 )
654
660
655 def _populatehelp(self, ui, name, cmd, fn, defaulthelp=None):
661 def _populatehelp(self, ui, name, cmd, fn, defaulthelp=None):
656 # confine strings to be passed to i18n.gettext()
662 # confine strings to be passed to i18n.gettext()
657 cfg = {}
663 cfg = {}
658 for k in (b'doc', b'help', b'category'):
664 for k in (b'doc', b'help', b'category'):
659 v = ui.config(b'alias', b'%s:%s' % (name, k), None)
665 v = ui.config(b'alias', b'%s:%s' % (name, k), None)
660 if v is None:
666 if v is None:
661 continue
667 continue
662 if not encoding.isasciistr(v):
668 if not encoding.isasciistr(v):
663 self.badalias = _(
669 self.badalias = _(
664 b"non-ASCII character in alias definition '%s:%s'"
670 b"non-ASCII character in alias definition '%s:%s'"
665 ) % (name, k)
671 ) % (name, k)
666 return
672 return
667 cfg[k] = v
673 cfg[k] = v
668
674
669 self.help = cfg.get(b'help', defaulthelp or b'')
675 self.help = cfg.get(b'help', defaulthelp or b'')
670 if self.help and self.help.startswith(b"hg " + cmd):
676 if self.help and self.help.startswith(b"hg " + cmd):
671 # drop prefix in old-style help lines so hg shows the alias
677 # drop prefix in old-style help lines so hg shows the alias
672 self.help = self.help[4 + len(cmd) :]
678 self.help = self.help[4 + len(cmd) :]
673
679
674 self.owndoc = b'doc' in cfg
680 self.owndoc = b'doc' in cfg
675 doc = cfg.get(b'doc', pycompat.getdoc(fn))
681 doc = cfg.get(b'doc', pycompat.getdoc(fn))
676 if doc is not None:
682 if doc is not None:
677 doc = pycompat.sysstr(doc)
683 doc = pycompat.sysstr(doc)
678 self.__doc__ = doc
684 self.__doc__ = doc
679
685
680 self.helpcategory = cfg.get(
686 self.helpcategory = cfg.get(
681 b'category', registrar.command.CATEGORY_NONE
687 b'category', registrar.command.CATEGORY_NONE
682 )
688 )
683
689
684 @property
690 @property
685 def args(self):
691 def args(self):
686 args = pycompat.maplist(util.expandpath, self.givenargs)
692 args = pycompat.maplist(util.expandpath, self.givenargs)
687 return aliasargs(self.fn, args)
693 return aliasargs(self.fn, args)
688
694
689 def __getattr__(self, name):
695 def __getattr__(self, name):
690 adefaults = {
696 adefaults = {
691 'norepo': True,
697 'norepo': True,
692 'intents': set(),
698 'intents': set(),
693 'optionalrepo': False,
699 'optionalrepo': False,
694 'inferrepo': False,
700 'inferrepo': False,
695 }
701 }
696 if name not in adefaults:
702 if name not in adefaults:
697 raise AttributeError(name)
703 raise AttributeError(name)
698 if self.badalias or util.safehasattr(self, b'shell'):
704 if self.badalias or util.safehasattr(self, b'shell'):
699 return adefaults[name]
705 return adefaults[name]
700 return getattr(self.fn, name)
706 return getattr(self.fn, name)
701
707
702 def __call__(self, ui, *args, **opts):
708 def __call__(self, ui, *args, **opts):
703 if self.badalias:
709 if self.badalias:
704 hint = None
710 hint = None
705 if self.unknowncmd:
711 if self.unknowncmd:
706 try:
712 try:
707 # check if the command is in a disabled extension
713 # check if the command is in a disabled extension
708 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
714 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
709 hint = _(b"'%s' is provided by '%s' extension") % (cmd, ext)
715 hint = _(b"'%s' is provided by '%s' extension") % (cmd, ext)
710 except error.UnknownCommand:
716 except error.UnknownCommand:
711 pass
717 pass
712 raise error.ConfigError(self.badalias, hint=hint)
718 raise error.ConfigError(self.badalias, hint=hint)
713 if self.shadows:
719 if self.shadows:
714 ui.debug(
720 ui.debug(
715 b"alias '%s' shadows command '%s'\n" % (self.name, self.cmdname)
721 b"alias '%s' shadows command '%s'\n" % (self.name, self.cmdname)
716 )
722 )
717
723
718 ui.log(
724 ui.log(
719 b'commandalias',
725 b'commandalias',
720 b"alias '%s' expands to '%s'\n",
726 b"alias '%s' expands to '%s'\n",
721 self.name,
727 self.name,
722 self.definition,
728 self.definition,
723 )
729 )
724 if util.safehasattr(self, b'shell'):
730 if util.safehasattr(self, b'shell'):
725 return self.fn(ui, *args, **opts)
731 return self.fn(ui, *args, **opts)
726 else:
732 else:
727 try:
733 try:
728 return util.checksignature(self.fn)(ui, *args, **opts)
734 return util.checksignature(self.fn)(ui, *args, **opts)
729 except error.SignatureError:
735 except error.SignatureError:
730 args = b' '.join([self.cmdname] + self.args)
736 args = b' '.join([self.cmdname] + self.args)
731 ui.debug(b"alias '%s' expands to '%s'\n" % (self.name, args))
737 ui.debug(b"alias '%s' expands to '%s'\n" % (self.name, args))
732 raise
738 raise
733
739
734
740
735 class lazyaliasentry(object):
741 class lazyaliasentry(object):
736 """like a typical command entry (func, opts, help), but is lazy"""
742 """like a typical command entry (func, opts, help), but is lazy"""
737
743
738 def __init__(self, ui, name, definition, cmdtable, source):
744 def __init__(self, ui, name, definition, cmdtable, source):
739 self.ui = ui
745 self.ui = ui
740 self.name = name
746 self.name = name
741 self.definition = definition
747 self.definition = definition
742 self.cmdtable = cmdtable.copy()
748 self.cmdtable = cmdtable.copy()
743 self.source = source
749 self.source = source
744 self.alias = True
750 self.alias = True
745
751
746 @util.propertycache
752 @util.propertycache
747 def _aliasdef(self):
753 def _aliasdef(self):
748 return cmdalias(
754 return cmdalias(
749 self.ui, self.name, self.definition, self.cmdtable, self.source
755 self.ui, self.name, self.definition, self.cmdtable, self.source
750 )
756 )
751
757
752 def __getitem__(self, n):
758 def __getitem__(self, n):
753 aliasdef = self._aliasdef
759 aliasdef = self._aliasdef
754 if n == 0:
760 if n == 0:
755 return aliasdef
761 return aliasdef
756 elif n == 1:
762 elif n == 1:
757 return aliasdef.opts
763 return aliasdef.opts
758 elif n == 2:
764 elif n == 2:
759 return aliasdef.help
765 return aliasdef.help
760 else:
766 else:
761 raise IndexError
767 raise IndexError
762
768
763 def __iter__(self):
769 def __iter__(self):
764 for i in range(3):
770 for i in range(3):
765 yield self[i]
771 yield self[i]
766
772
767 def __len__(self):
773 def __len__(self):
768 return 3
774 return 3
769
775
770
776
771 def addaliases(ui, cmdtable):
777 def addaliases(ui, cmdtable):
772 # aliases are processed after extensions have been loaded, so they
778 # aliases are processed after extensions have been loaded, so they
773 # may use extension commands. Aliases can also use other alias definitions,
779 # may use extension commands. Aliases can also use other alias definitions,
774 # but only if they have been defined prior to the current definition.
780 # but only if they have been defined prior to the current definition.
775 for alias, definition in ui.configitems(b'alias', ignoresub=True):
781 for alias, definition in ui.configitems(b'alias', ignoresub=True):
776 try:
782 try:
777 if cmdtable[alias].definition == definition:
783 if cmdtable[alias].definition == definition:
778 continue
784 continue
779 except (KeyError, AttributeError):
785 except (KeyError, AttributeError):
780 # definition might not exist or it might not be a cmdalias
786 # definition might not exist or it might not be a cmdalias
781 pass
787 pass
782
788
783 source = ui.configsource(b'alias', alias)
789 source = ui.configsource(b'alias', alias)
784 entry = lazyaliasentry(ui, alias, definition, cmdtable, source)
790 entry = lazyaliasentry(ui, alias, definition, cmdtable, source)
785 cmdtable[alias] = entry
791 cmdtable[alias] = entry
786
792
787
793
788 def _parse(ui, args):
794 def _parse(ui, args):
789 options = {}
795 options = {}
790 cmdoptions = {}
796 cmdoptions = {}
791
797
792 try:
798 try:
793 args = fancyopts.fancyopts(args, commands.globalopts, options)
799 args = fancyopts.fancyopts(args, commands.globalopts, options)
794 except getopt.GetoptError as inst:
800 except getopt.GetoptError as inst:
795 raise error.CommandError(None, stringutil.forcebytestr(inst))
801 raise error.CommandError(None, stringutil.forcebytestr(inst))
796
802
797 if args:
803 if args:
798 cmd, args = args[0], args[1:]
804 cmd, args = args[0], args[1:]
799 aliases, entry = cmdutil.findcmd(
805 aliases, entry = cmdutil.findcmd(
800 cmd, commands.table, ui.configbool(b"ui", b"strict")
806 cmd, commands.table, ui.configbool(b"ui", b"strict")
801 )
807 )
802 cmd = aliases[0]
808 cmd = aliases[0]
803 args = aliasargs(entry[0], args)
809 args = aliasargs(entry[0], args)
804 defaults = ui.config(b"defaults", cmd)
810 defaults = ui.config(b"defaults", cmd)
805 if defaults:
811 if defaults:
806 args = (
812 args = (
807 pycompat.maplist(util.expandpath, pycompat.shlexsplit(defaults))
813 pycompat.maplist(util.expandpath, pycompat.shlexsplit(defaults))
808 + args
814 + args
809 )
815 )
810 c = list(entry[1])
816 c = list(entry[1])
811 else:
817 else:
812 cmd = None
818 cmd = None
813 c = []
819 c = []
814
820
815 # combine global options into local
821 # combine global options into local
816 for o in commands.globalopts:
822 for o in commands.globalopts:
817 c.append((o[0], o[1], options[o[1]], o[3]))
823 c.append((o[0], o[1], options[o[1]], o[3]))
818
824
819 try:
825 try:
820 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
826 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
821 except getopt.GetoptError as inst:
827 except getopt.GetoptError as inst:
822 raise error.CommandError(cmd, stringutil.forcebytestr(inst))
828 raise error.CommandError(cmd, stringutil.forcebytestr(inst))
823
829
824 # separate global options back out
830 # separate global options back out
825 for o in commands.globalopts:
831 for o in commands.globalopts:
826 n = o[1]
832 n = o[1]
827 options[n] = cmdoptions[n]
833 options[n] = cmdoptions[n]
828 del cmdoptions[n]
834 del cmdoptions[n]
829
835
830 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
836 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
831
837
832
838
833 def _parseconfig(ui, config):
839 def _parseconfig(ui, config):
834 """parse the --config options from the command line"""
840 """parse the --config options from the command line"""
835 configs = []
841 configs = []
836
842
837 for cfg in config:
843 for cfg in config:
838 try:
844 try:
839 name, value = [cfgelem.strip() for cfgelem in cfg.split(b'=', 1)]
845 name, value = [cfgelem.strip() for cfgelem in cfg.split(b'=', 1)]
840 section, name = name.split(b'.', 1)
846 section, name = name.split(b'.', 1)
841 if not section or not name:
847 if not section or not name:
842 raise IndexError
848 raise IndexError
843 ui.setconfig(section, name, value, b'--config')
849 ui.setconfig(section, name, value, b'--config')
844 configs.append((section, name, value))
850 configs.append((section, name, value))
845 except (IndexError, ValueError):
851 except (IndexError, ValueError):
846 raise error.Abort(
852 raise error.Abort(
847 _(
853 _(
848 b'malformed --config option: %r '
854 b'malformed --config option: %r '
849 b'(use --config section.name=value)'
855 b'(use --config section.name=value)'
850 )
856 )
851 % pycompat.bytestr(cfg)
857 % pycompat.bytestr(cfg)
852 )
858 )
853
859
854 return configs
860 return configs
855
861
856
862
857 def _earlyparseopts(ui, args):
863 def _earlyparseopts(ui, args):
858 options = {}
864 options = {}
859 fancyopts.fancyopts(
865 fancyopts.fancyopts(
860 args,
866 args,
861 commands.globalopts,
867 commands.globalopts,
862 options,
868 options,
863 gnu=not ui.plain(b'strictflags'),
869 gnu=not ui.plain(b'strictflags'),
864 early=True,
870 early=True,
865 optaliases={b'repository': [b'repo']},
871 optaliases={b'repository': [b'repo']},
866 )
872 )
867 return options
873 return options
868
874
869
875
870 def _earlysplitopts(args):
876 def _earlysplitopts(args):
871 """Split args into a list of possible early options and remainder args"""
877 """Split args into a list of possible early options and remainder args"""
872 shortoptions = b'R:'
878 shortoptions = b'R:'
873 # TODO: perhaps 'debugger' should be included
879 # TODO: perhaps 'debugger' should be included
874 longoptions = [b'cwd=', b'repository=', b'repo=', b'config=']
880 longoptions = [b'cwd=', b'repository=', b'repo=', b'config=']
875 return fancyopts.earlygetopt(
881 return fancyopts.earlygetopt(
876 args, shortoptions, longoptions, gnu=True, keepsep=True
882 args, shortoptions, longoptions, gnu=True, keepsep=True
877 )
883 )
878
884
879
885
880 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
886 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
881 # run pre-hook, and abort if it fails
887 # run pre-hook, and abort if it fails
882 hook.hook(
888 hook.hook(
883 lui,
889 lui,
884 repo,
890 repo,
885 b"pre-%s" % cmd,
891 b"pre-%s" % cmd,
886 True,
892 True,
887 args=b" ".join(fullargs),
893 args=b" ".join(fullargs),
888 pats=cmdpats,
894 pats=cmdpats,
889 opts=cmdoptions,
895 opts=cmdoptions,
890 )
896 )
891 try:
897 try:
892 ret = _runcommand(ui, options, cmd, d)
898 ret = _runcommand(ui, options, cmd, d)
893 # run post-hook, passing command result
899 # run post-hook, passing command result
894 hook.hook(
900 hook.hook(
895 lui,
901 lui,
896 repo,
902 repo,
897 b"post-%s" % cmd,
903 b"post-%s" % cmd,
898 False,
904 False,
899 args=b" ".join(fullargs),
905 args=b" ".join(fullargs),
900 result=ret,
906 result=ret,
901 pats=cmdpats,
907 pats=cmdpats,
902 opts=cmdoptions,
908 opts=cmdoptions,
903 )
909 )
904 except Exception:
910 except Exception:
905 # run failure hook and re-raise
911 # run failure hook and re-raise
906 hook.hook(
912 hook.hook(
907 lui,
913 lui,
908 repo,
914 repo,
909 b"fail-%s" % cmd,
915 b"fail-%s" % cmd,
910 False,
916 False,
911 args=b" ".join(fullargs),
917 args=b" ".join(fullargs),
912 pats=cmdpats,
918 pats=cmdpats,
913 opts=cmdoptions,
919 opts=cmdoptions,
914 )
920 )
915 raise
921 raise
916 return ret
922 return ret
917
923
918
924
919 def _readsharedsourceconfig(ui, path):
925 def _readsharedsourceconfig(ui, path):
920 """if the current repository is shared one, this tries to read
926 """if the current repository is shared one, this tries to read
921 .hg/hgrc of shared source if we are in share-safe mode
927 .hg/hgrc of shared source if we are in share-safe mode
922
928
923 Config read is loaded into the ui object passed
929 Config read is loaded into the ui object passed
924
930
925 This should be called before reading .hg/hgrc or the main repo
931 This should be called before reading .hg/hgrc or the main repo
926 as that overrides config set in shared source"""
932 as that overrides config set in shared source"""
927 try:
933 try:
928 with open(os.path.join(path, b".hg", b"requires"), "rb") as fp:
934 with open(os.path.join(path, b".hg", b"requires"), "rb") as fp:
929 requirements = set(fp.read().splitlines())
935 requirements = set(fp.read().splitlines())
930 if not (
936 if not (
931 requirementsmod.SHARESAFE_REQUIREMENT in requirements
937 requirementsmod.SHARESAFE_REQUIREMENT in requirements
932 and requirementsmod.SHARED_REQUIREMENT in requirements
938 and requirementsmod.SHARED_REQUIREMENT in requirements
933 ):
939 ):
934 return
940 return
935 hgvfs = vfs.vfs(os.path.join(path, b".hg"))
941 hgvfs = vfs.vfs(os.path.join(path, b".hg"))
936 sharedvfs = localrepo._getsharedvfs(hgvfs, requirements)
942 sharedvfs = localrepo._getsharedvfs(hgvfs, requirements)
937 root = sharedvfs.base
943 root = sharedvfs.base
938 ui.readconfig(sharedvfs.join(b"hgrc"), root)
944 ui.readconfig(sharedvfs.join(b"hgrc"), root)
939 except IOError:
945 except IOError:
940 pass
946 pass
941
947
942
948
943 def _getlocal(ui, rpath, wd=None):
949 def _getlocal(ui, rpath, wd=None):
944 """Return (path, local ui object) for the given target path.
950 """Return (path, local ui object) for the given target path.
945
951
946 Takes paths in [cwd]/.hg/hgrc into account."
952 Takes paths in [cwd]/.hg/hgrc into account."
947 """
953 """
948 if wd is None:
954 if wd is None:
949 try:
955 try:
950 wd = encoding.getcwd()
956 wd = encoding.getcwd()
951 except OSError as e:
957 except OSError as e:
952 raise error.Abort(
958 raise error.Abort(
953 _(b"error getting current working directory: %s")
959 _(b"error getting current working directory: %s")
954 % encoding.strtolocal(e.strerror)
960 % encoding.strtolocal(e.strerror)
955 )
961 )
956
962
957 path = cmdutil.findrepo(wd) or b""
963 path = cmdutil.findrepo(wd) or b""
958 if not path:
964 if not path:
959 lui = ui
965 lui = ui
960 else:
966 else:
961 lui = ui.copy()
967 lui = ui.copy()
962 if rcutil.use_repo_hgrc():
968 if rcutil.use_repo_hgrc():
963 _readsharedsourceconfig(lui, path)
969 _readsharedsourceconfig(lui, path)
964 lui.readconfig(os.path.join(path, b".hg", b"hgrc"), path)
970 lui.readconfig(os.path.join(path, b".hg", b"hgrc"), path)
965 lui.readconfig(os.path.join(path, b".hg", b"hgrc-not-shared"), path)
971 lui.readconfig(os.path.join(path, b".hg", b"hgrc-not-shared"), path)
966
972
967 if rpath:
973 if rpath:
968 path = lui.expandpath(rpath)
974 path = lui.expandpath(rpath)
969 lui = ui.copy()
975 lui = ui.copy()
970 if rcutil.use_repo_hgrc():
976 if rcutil.use_repo_hgrc():
971 _readsharedsourceconfig(lui, path)
977 _readsharedsourceconfig(lui, path)
972 lui.readconfig(os.path.join(path, b".hg", b"hgrc"), path)
978 lui.readconfig(os.path.join(path, b".hg", b"hgrc"), path)
973 lui.readconfig(os.path.join(path, b".hg", b"hgrc-not-shared"), path)
979 lui.readconfig(os.path.join(path, b".hg", b"hgrc-not-shared"), path)
974
980
975 return path, lui
981 return path, lui
976
982
977
983
978 def _checkshellalias(lui, ui, args):
984 def _checkshellalias(lui, ui, args):
979 """Return the function to run the shell alias, if it is required"""
985 """Return the function to run the shell alias, if it is required"""
980 options = {}
986 options = {}
981
987
982 try:
988 try:
983 args = fancyopts.fancyopts(args, commands.globalopts, options)
989 args = fancyopts.fancyopts(args, commands.globalopts, options)
984 except getopt.GetoptError:
990 except getopt.GetoptError:
985 return
991 return
986
992
987 if not args:
993 if not args:
988 return
994 return
989
995
990 cmdtable = commands.table
996 cmdtable = commands.table
991
997
992 cmd = args[0]
998 cmd = args[0]
993 try:
999 try:
994 strict = ui.configbool(b"ui", b"strict")
1000 strict = ui.configbool(b"ui", b"strict")
995 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
1001 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
996 except (error.AmbiguousCommand, error.UnknownCommand):
1002 except (error.AmbiguousCommand, error.UnknownCommand):
997 return
1003 return
998
1004
999 cmd = aliases[0]
1005 cmd = aliases[0]
1000 fn = entry[0]
1006 fn = entry[0]
1001
1007
1002 if cmd and util.safehasattr(fn, b'shell'):
1008 if cmd and util.safehasattr(fn, b'shell'):
1003 # shell alias shouldn't receive early options which are consumed by hg
1009 # shell alias shouldn't receive early options which are consumed by hg
1004 _earlyopts, args = _earlysplitopts(args)
1010 _earlyopts, args = _earlysplitopts(args)
1005 d = lambda: fn(ui, *args[1:])
1011 d = lambda: fn(ui, *args[1:])
1006 return lambda: runcommand(
1012 return lambda: runcommand(
1007 lui, None, cmd, args[:1], ui, options, d, [], {}
1013 lui, None, cmd, args[:1], ui, options, d, [], {}
1008 )
1014 )
1009
1015
1010
1016
1011 def _dispatch(req):
1017 def _dispatch(req):
1012 args = req.args
1018 args = req.args
1013 ui = req.ui
1019 ui = req.ui
1014
1020
1015 # check for cwd
1021 # check for cwd
1016 cwd = req.earlyoptions[b'cwd']
1022 cwd = req.earlyoptions[b'cwd']
1017 if cwd:
1023 if cwd:
1018 os.chdir(cwd)
1024 os.chdir(cwd)
1019
1025
1020 rpath = req.earlyoptions[b'repository']
1026 rpath = req.earlyoptions[b'repository']
1021 path, lui = _getlocal(ui, rpath)
1027 path, lui = _getlocal(ui, rpath)
1022
1028
1023 uis = {ui, lui}
1029 uis = {ui, lui}
1024
1030
1025 if req.repo:
1031 if req.repo:
1026 uis.add(req.repo.ui)
1032 uis.add(req.repo.ui)
1027
1033
1028 if (
1034 if (
1029 req.earlyoptions[b'verbose']
1035 req.earlyoptions[b'verbose']
1030 or req.earlyoptions[b'debug']
1036 or req.earlyoptions[b'debug']
1031 or req.earlyoptions[b'quiet']
1037 or req.earlyoptions[b'quiet']
1032 ):
1038 ):
1033 for opt in (b'verbose', b'debug', b'quiet'):
1039 for opt in (b'verbose', b'debug', b'quiet'):
1034 val = pycompat.bytestr(bool(req.earlyoptions[opt]))
1040 val = pycompat.bytestr(bool(req.earlyoptions[opt]))
1035 for ui_ in uis:
1041 for ui_ in uis:
1036 ui_.setconfig(b'ui', opt, val, b'--' + opt)
1042 ui_.setconfig(b'ui', opt, val, b'--' + opt)
1037
1043
1038 if req.earlyoptions[b'profile']:
1044 if req.earlyoptions[b'profile']:
1039 for ui_ in uis:
1045 for ui_ in uis:
1040 ui_.setconfig(b'profiling', b'enabled', b'true', b'--profile')
1046 ui_.setconfig(b'profiling', b'enabled', b'true', b'--profile')
1041
1047
1042 profile = lui.configbool(b'profiling', b'enabled')
1048 profile = lui.configbool(b'profiling', b'enabled')
1043 with profiling.profile(lui, enabled=profile) as profiler:
1049 with profiling.profile(lui, enabled=profile) as profiler:
1044 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
1050 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
1045 # reposetup
1051 # reposetup
1046 extensions.loadall(lui)
1052 extensions.loadall(lui)
1047 # Propagate any changes to lui.__class__ by extensions
1053 # Propagate any changes to lui.__class__ by extensions
1048 ui.__class__ = lui.__class__
1054 ui.__class__ = lui.__class__
1049
1055
1050 # (uisetup and extsetup are handled in extensions.loadall)
1056 # (uisetup and extsetup are handled in extensions.loadall)
1051
1057
1052 # (reposetup is handled in hg.repository)
1058 # (reposetup is handled in hg.repository)
1053
1059
1054 addaliases(lui, commands.table)
1060 addaliases(lui, commands.table)
1055
1061
1056 # All aliases and commands are completely defined, now.
1062 # All aliases and commands are completely defined, now.
1057 # Check abbreviation/ambiguity of shell alias.
1063 # Check abbreviation/ambiguity of shell alias.
1058 shellaliasfn = _checkshellalias(lui, ui, args)
1064 shellaliasfn = _checkshellalias(lui, ui, args)
1059 if shellaliasfn:
1065 if shellaliasfn:
1060 # no additional configs will be set, set up the ui instances
1066 # no additional configs will be set, set up the ui instances
1061 for ui_ in uis:
1067 for ui_ in uis:
1062 extensions.populateui(ui_)
1068 extensions.populateui(ui_)
1063 return shellaliasfn()
1069 return shellaliasfn()
1064
1070
1065 # check for fallback encoding
1071 # check for fallback encoding
1066 fallback = lui.config(b'ui', b'fallbackencoding')
1072 fallback = lui.config(b'ui', b'fallbackencoding')
1067 if fallback:
1073 if fallback:
1068 encoding.fallbackencoding = fallback
1074 encoding.fallbackencoding = fallback
1069
1075
1070 fullargs = args
1076 fullargs = args
1071 cmd, func, args, options, cmdoptions = _parse(lui, args)
1077 cmd, func, args, options, cmdoptions = _parse(lui, args)
1072
1078
1073 # store the canonical command name in request object for later access
1079 # store the canonical command name in request object for later access
1074 req.canonical_command = cmd
1080 req.canonical_command = cmd
1075
1081
1076 if options[b"config"] != req.earlyoptions[b"config"]:
1082 if options[b"config"] != req.earlyoptions[b"config"]:
1077 raise error.InputError(_(b"option --config may not be abbreviated"))
1083 raise error.InputError(_(b"option --config may not be abbreviated"))
1078 if options[b"cwd"] != req.earlyoptions[b"cwd"]:
1084 if options[b"cwd"] != req.earlyoptions[b"cwd"]:
1079 raise error.InputError(_(b"option --cwd may not be abbreviated"))
1085 raise error.InputError(_(b"option --cwd may not be abbreviated"))
1080 if options[b"repository"] != req.earlyoptions[b"repository"]:
1086 if options[b"repository"] != req.earlyoptions[b"repository"]:
1081 raise error.InputError(
1087 raise error.InputError(
1082 _(
1088 _(
1083 b"option -R has to be separated from other options (e.g. not "
1089 b"option -R has to be separated from other options (e.g. not "
1084 b"-qR) and --repository may only be abbreviated as --repo"
1090 b"-qR) and --repository may only be abbreviated as --repo"
1085 )
1091 )
1086 )
1092 )
1087 if options[b"debugger"] != req.earlyoptions[b"debugger"]:
1093 if options[b"debugger"] != req.earlyoptions[b"debugger"]:
1088 raise error.InputError(
1094 raise error.InputError(
1089 _(b"option --debugger may not be abbreviated")
1095 _(b"option --debugger may not be abbreviated")
1090 )
1096 )
1091 # don't validate --profile/--traceback, which can be enabled from now
1097 # don't validate --profile/--traceback, which can be enabled from now
1092
1098
1093 if options[b"encoding"]:
1099 if options[b"encoding"]:
1094 encoding.encoding = options[b"encoding"]
1100 encoding.encoding = options[b"encoding"]
1095 if options[b"encodingmode"]:
1101 if options[b"encodingmode"]:
1096 encoding.encodingmode = options[b"encodingmode"]
1102 encoding.encodingmode = options[b"encodingmode"]
1097 if options[b"time"]:
1103 if options[b"time"]:
1098
1104
1099 def get_times():
1105 def get_times():
1100 t = os.times()
1106 t = os.times()
1101 if t[4] == 0.0:
1107 if t[4] == 0.0:
1102 # Windows leaves this as zero, so use time.perf_counter()
1108 # Windows leaves this as zero, so use time.perf_counter()
1103 t = (t[0], t[1], t[2], t[3], util.timer())
1109 t = (t[0], t[1], t[2], t[3], util.timer())
1104 return t
1110 return t
1105
1111
1106 s = get_times()
1112 s = get_times()
1107
1113
1108 def print_time():
1114 def print_time():
1109 t = get_times()
1115 t = get_times()
1110 ui.warn(
1116 ui.warn(
1111 _(b"time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n")
1117 _(b"time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n")
1112 % (
1118 % (
1113 t[4] - s[4],
1119 t[4] - s[4],
1114 t[0] - s[0],
1120 t[0] - s[0],
1115 t[2] - s[2],
1121 t[2] - s[2],
1116 t[1] - s[1],
1122 t[1] - s[1],
1117 t[3] - s[3],
1123 t[3] - s[3],
1118 )
1124 )
1119 )
1125 )
1120
1126
1121 ui.atexit(print_time)
1127 ui.atexit(print_time)
1122 if options[b"profile"]:
1128 if options[b"profile"]:
1123 profiler.start()
1129 profiler.start()
1124
1130
1125 # if abbreviated version of this were used, take them in account, now
1131 # if abbreviated version of this were used, take them in account, now
1126 if options[b'verbose'] or options[b'debug'] or options[b'quiet']:
1132 if options[b'verbose'] or options[b'debug'] or options[b'quiet']:
1127 for opt in (b'verbose', b'debug', b'quiet'):
1133 for opt in (b'verbose', b'debug', b'quiet'):
1128 if options[opt] == req.earlyoptions[opt]:
1134 if options[opt] == req.earlyoptions[opt]:
1129 continue
1135 continue
1130 val = pycompat.bytestr(bool(options[opt]))
1136 val = pycompat.bytestr(bool(options[opt]))
1131 for ui_ in uis:
1137 for ui_ in uis:
1132 ui_.setconfig(b'ui', opt, val, b'--' + opt)
1138 ui_.setconfig(b'ui', opt, val, b'--' + opt)
1133
1139
1134 if options[b'traceback']:
1140 if options[b'traceback']:
1135 for ui_ in uis:
1141 for ui_ in uis:
1136 ui_.setconfig(b'ui', b'traceback', b'on', b'--traceback')
1142 ui_.setconfig(b'ui', b'traceback', b'on', b'--traceback')
1137
1143
1138 if options[b'noninteractive']:
1144 if options[b'noninteractive']:
1139 for ui_ in uis:
1145 for ui_ in uis:
1140 ui_.setconfig(b'ui', b'interactive', b'off', b'-y')
1146 ui_.setconfig(b'ui', b'interactive', b'off', b'-y')
1141
1147
1142 if cmdoptions.get(b'insecure', False):
1148 if cmdoptions.get(b'insecure', False):
1143 for ui_ in uis:
1149 for ui_ in uis:
1144 ui_.insecureconnections = True
1150 ui_.insecureconnections = True
1145
1151
1146 # setup color handling before pager, because setting up pager
1152 # setup color handling before pager, because setting up pager
1147 # might cause incorrect console information
1153 # might cause incorrect console information
1148 coloropt = options[b'color']
1154 coloropt = options[b'color']
1149 for ui_ in uis:
1155 for ui_ in uis:
1150 if coloropt:
1156 if coloropt:
1151 ui_.setconfig(b'ui', b'color', coloropt, b'--color')
1157 ui_.setconfig(b'ui', b'color', coloropt, b'--color')
1152 color.setup(ui_)
1158 color.setup(ui_)
1153
1159
1154 if stringutil.parsebool(options[b'pager']):
1160 if stringutil.parsebool(options[b'pager']):
1155 # ui.pager() expects 'internal-always-' prefix in this case
1161 # ui.pager() expects 'internal-always-' prefix in this case
1156 ui.pager(b'internal-always-' + cmd)
1162 ui.pager(b'internal-always-' + cmd)
1157 elif options[b'pager'] != b'auto':
1163 elif options[b'pager'] != b'auto':
1158 for ui_ in uis:
1164 for ui_ in uis:
1159 ui_.disablepager()
1165 ui_.disablepager()
1160
1166
1161 # configs are fully loaded, set up the ui instances
1167 # configs are fully loaded, set up the ui instances
1162 for ui_ in uis:
1168 for ui_ in uis:
1163 extensions.populateui(ui_)
1169 extensions.populateui(ui_)
1164
1170
1165 if options[b'version']:
1171 if options[b'version']:
1166 return commands.version_(ui)
1172 return commands.version_(ui)
1167 if options[b'help']:
1173 if options[b'help']:
1168 return commands.help_(ui, cmd, command=cmd is not None)
1174 return commands.help_(ui, cmd, command=cmd is not None)
1169 elif not cmd:
1175 elif not cmd:
1170 return commands.help_(ui, b'shortlist')
1176 return commands.help_(ui, b'shortlist')
1171
1177
1172 repo = None
1178 repo = None
1173 cmdpats = args[:]
1179 cmdpats = args[:]
1174 assert func is not None # help out pytype
1180 assert func is not None # help out pytype
1175 if not func.norepo:
1181 if not func.norepo:
1176 # use the repo from the request only if we don't have -R
1182 # use the repo from the request only if we don't have -R
1177 if not rpath and not cwd:
1183 if not rpath and not cwd:
1178 repo = req.repo
1184 repo = req.repo
1179
1185
1180 if repo:
1186 if repo:
1181 # set the descriptors of the repo ui to those of ui
1187 # set the descriptors of the repo ui to those of ui
1182 repo.ui.fin = ui.fin
1188 repo.ui.fin = ui.fin
1183 repo.ui.fout = ui.fout
1189 repo.ui.fout = ui.fout
1184 repo.ui.ferr = ui.ferr
1190 repo.ui.ferr = ui.ferr
1185 repo.ui.fmsg = ui.fmsg
1191 repo.ui.fmsg = ui.fmsg
1186 else:
1192 else:
1187 try:
1193 try:
1188 repo = hg.repository(
1194 repo = hg.repository(
1189 ui,
1195 ui,
1190 path=path,
1196 path=path,
1191 presetupfuncs=req.prereposetups,
1197 presetupfuncs=req.prereposetups,
1192 intents=func.intents,
1198 intents=func.intents,
1193 )
1199 )
1194 if not repo.local():
1200 if not repo.local():
1195 raise error.InputError(
1201 raise error.InputError(
1196 _(b"repository '%s' is not local") % path
1202 _(b"repository '%s' is not local") % path
1197 )
1203 )
1198 repo.ui.setconfig(
1204 repo.ui.setconfig(
1199 b"bundle", b"mainreporoot", repo.root, b'repo'
1205 b"bundle", b"mainreporoot", repo.root, b'repo'
1200 )
1206 )
1201 except error.RequirementError:
1207 except error.RequirementError:
1202 raise
1208 raise
1203 except error.RepoError:
1209 except error.RepoError:
1204 if rpath: # invalid -R path
1210 if rpath: # invalid -R path
1205 raise
1211 raise
1206 if not func.optionalrepo:
1212 if not func.optionalrepo:
1207 if func.inferrepo and args and not path:
1213 if func.inferrepo and args and not path:
1208 # try to infer -R from command args
1214 # try to infer -R from command args
1209 repos = pycompat.maplist(cmdutil.findrepo, args)
1215 repos = pycompat.maplist(cmdutil.findrepo, args)
1210 guess = repos[0]
1216 guess = repos[0]
1211 if guess and repos.count(guess) == len(repos):
1217 if guess and repos.count(guess) == len(repos):
1212 req.args = [b'--repository', guess] + fullargs
1218 req.args = [b'--repository', guess] + fullargs
1213 req.earlyoptions[b'repository'] = guess
1219 req.earlyoptions[b'repository'] = guess
1214 return _dispatch(req)
1220 return _dispatch(req)
1215 if not path:
1221 if not path:
1216 raise error.InputError(
1222 raise error.InputError(
1217 _(
1223 _(
1218 b"no repository found in"
1224 b"no repository found in"
1219 b" '%s' (.hg not found)"
1225 b" '%s' (.hg not found)"
1220 )
1226 )
1221 % encoding.getcwd()
1227 % encoding.getcwd()
1222 )
1228 )
1223 raise
1229 raise
1224 if repo:
1230 if repo:
1225 ui = repo.ui
1231 ui = repo.ui
1226 if options[b'hidden']:
1232 if options[b'hidden']:
1227 repo = repo.unfiltered()
1233 repo = repo.unfiltered()
1228 args.insert(0, repo)
1234 args.insert(0, repo)
1229 elif rpath:
1235 elif rpath:
1230 ui.warn(_(b"warning: --repository ignored\n"))
1236 ui.warn(_(b"warning: --repository ignored\n"))
1231
1237
1232 msg = _formatargs(fullargs)
1238 msg = _formatargs(fullargs)
1233 ui.log(b"command", b'%s\n', msg)
1239 ui.log(b"command", b'%s\n', msg)
1234 strcmdopt = pycompat.strkwargs(cmdoptions)
1240 strcmdopt = pycompat.strkwargs(cmdoptions)
1235 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
1241 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
1236 try:
1242 try:
1237 return runcommand(
1243 return runcommand(
1238 lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions
1244 lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions
1239 )
1245 )
1240 finally:
1246 finally:
1241 if repo and repo != req.repo:
1247 if repo and repo != req.repo:
1242 repo.close()
1248 repo.close()
1243
1249
1244
1250
1245 def _runcommand(ui, options, cmd, cmdfunc):
1251 def _runcommand(ui, options, cmd, cmdfunc):
1246 """Run a command function, possibly with profiling enabled."""
1252 """Run a command function, possibly with profiling enabled."""
1247 try:
1253 try:
1248 with tracing.log("Running %s command" % cmd):
1254 with tracing.log("Running %s command" % cmd):
1249 return cmdfunc()
1255 return cmdfunc()
1250 except error.SignatureError:
1256 except error.SignatureError:
1251 raise error.CommandError(cmd, _(b'invalid arguments'))
1257 raise error.CommandError(cmd, _(b'invalid arguments'))
1252
1258
1253
1259
1254 def _exceptionwarning(ui):
1260 def _exceptionwarning(ui):
1255 """Produce a warning message for the current active exception"""
1261 """Produce a warning message for the current active exception"""
1256
1262
1257 # For compatibility checking, we discard the portion of the hg
1263 # For compatibility checking, we discard the portion of the hg
1258 # version after the + on the assumption that if a "normal
1264 # version after the + on the assumption that if a "normal
1259 # user" is running a build with a + in it the packager
1265 # user" is running a build with a + in it the packager
1260 # probably built from fairly close to a tag and anyone with a
1266 # probably built from fairly close to a tag and anyone with a
1261 # 'make local' copy of hg (where the version number can be out
1267 # 'make local' copy of hg (where the version number can be out
1262 # of date) will be clueful enough to notice the implausible
1268 # of date) will be clueful enough to notice the implausible
1263 # version number and try updating.
1269 # version number and try updating.
1264 ct = util.versiontuple(n=2)
1270 ct = util.versiontuple(n=2)
1265 worst = None, ct, b'', b''
1271 worst = None, ct, b'', b''
1266 if ui.config(b'ui', b'supportcontact') is None:
1272 if ui.config(b'ui', b'supportcontact') is None:
1267 for name, mod in extensions.extensions():
1273 for name, mod in extensions.extensions():
1268 # 'testedwith' should be bytes, but not all extensions are ported
1274 # 'testedwith' should be bytes, but not all extensions are ported
1269 # to py3 and we don't want UnicodeException because of that.
1275 # to py3 and we don't want UnicodeException because of that.
1270 testedwith = stringutil.forcebytestr(
1276 testedwith = stringutil.forcebytestr(
1271 getattr(mod, 'testedwith', b'')
1277 getattr(mod, 'testedwith', b'')
1272 )
1278 )
1273 version = extensions.moduleversion(mod)
1279 version = extensions.moduleversion(mod)
1274 report = getattr(mod, 'buglink', _(b'the extension author.'))
1280 report = getattr(mod, 'buglink', _(b'the extension author.'))
1275 if not testedwith.strip():
1281 if not testedwith.strip():
1276 # We found an untested extension. It's likely the culprit.
1282 # We found an untested extension. It's likely the culprit.
1277 worst = name, b'unknown', report, version
1283 worst = name, b'unknown', report, version
1278 break
1284 break
1279
1285
1280 # Never blame on extensions bundled with Mercurial.
1286 # Never blame on extensions bundled with Mercurial.
1281 if extensions.ismoduleinternal(mod):
1287 if extensions.ismoduleinternal(mod):
1282 continue
1288 continue
1283
1289
1284 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
1290 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
1285 if ct in tested:
1291 if ct in tested:
1286 continue
1292 continue
1287
1293
1288 lower = [t for t in tested if t < ct]
1294 lower = [t for t in tested if t < ct]
1289 nearest = max(lower or tested)
1295 nearest = max(lower or tested)
1290 if worst[0] is None or nearest < worst[1]:
1296 if worst[0] is None or nearest < worst[1]:
1291 worst = name, nearest, report, version
1297 worst = name, nearest, report, version
1292 if worst[0] is not None:
1298 if worst[0] is not None:
1293 name, testedwith, report, version = worst
1299 name, testedwith, report, version = worst
1294 if not isinstance(testedwith, (bytes, str)):
1300 if not isinstance(testedwith, (bytes, str)):
1295 testedwith = b'.'.join(
1301 testedwith = b'.'.join(
1296 [stringutil.forcebytestr(c) for c in testedwith]
1302 [stringutil.forcebytestr(c) for c in testedwith]
1297 )
1303 )
1298 extver = version or _(b"(version N/A)")
1304 extver = version or _(b"(version N/A)")
1299 warning = _(
1305 warning = _(
1300 b'** Unknown exception encountered with '
1306 b'** Unknown exception encountered with '
1301 b'possibly-broken third-party extension "%s" %s\n'
1307 b'possibly-broken third-party extension "%s" %s\n'
1302 b'** which supports versions %s of Mercurial.\n'
1308 b'** which supports versions %s of Mercurial.\n'
1303 b'** Please disable "%s" and try your action again.\n'
1309 b'** Please disable "%s" and try your action again.\n'
1304 b'** If that fixes the bug please report it to %s\n'
1310 b'** If that fixes the bug please report it to %s\n'
1305 ) % (name, extver, testedwith, name, stringutil.forcebytestr(report))
1311 ) % (name, extver, testedwith, name, stringutil.forcebytestr(report))
1306 else:
1312 else:
1307 bugtracker = ui.config(b'ui', b'supportcontact')
1313 bugtracker = ui.config(b'ui', b'supportcontact')
1308 if bugtracker is None:
1314 if bugtracker is None:
1309 bugtracker = _(b"https://mercurial-scm.org/wiki/BugTracker")
1315 bugtracker = _(b"https://mercurial-scm.org/wiki/BugTracker")
1310 warning = (
1316 warning = (
1311 _(
1317 _(
1312 b"** unknown exception encountered, "
1318 b"** unknown exception encountered, "
1313 b"please report by visiting\n** "
1319 b"please report by visiting\n** "
1314 )
1320 )
1315 + bugtracker
1321 + bugtracker
1316 + b'\n'
1322 + b'\n'
1317 )
1323 )
1318 sysversion = pycompat.sysbytes(sys.version).replace(b'\n', b'')
1324 sysversion = pycompat.sysbytes(sys.version).replace(b'\n', b'')
1319
1325
1320 def ext_with_ver(x):
1326 def ext_with_ver(x):
1321 ext = x[0]
1327 ext = x[0]
1322 ver = extensions.moduleversion(x[1])
1328 ver = extensions.moduleversion(x[1])
1323 if ver:
1329 if ver:
1324 ext += b' ' + ver
1330 ext += b' ' + ver
1325 return ext
1331 return ext
1326
1332
1327 warning += (
1333 warning += (
1328 (_(b"** Python %s\n") % sysversion)
1334 (_(b"** Python %s\n") % sysversion)
1329 + (_(b"** Mercurial Distributed SCM (version %s)\n") % util.version())
1335 + (_(b"** Mercurial Distributed SCM (version %s)\n") % util.version())
1330 + (
1336 + (
1331 _(b"** Extensions loaded: %s\n")
1337 _(b"** Extensions loaded: %s\n")
1332 % b", ".join(
1338 % b", ".join(
1333 [ext_with_ver(x) for x in sorted(extensions.extensions())]
1339 [ext_with_ver(x) for x in sorted(extensions.extensions())]
1334 )
1340 )
1335 )
1341 )
1336 )
1342 )
1337 return warning
1343 return warning
1338
1344
1339
1345
1340 def handlecommandexception(ui):
1346 def handlecommandexception(ui):
1341 """Produce a warning message for broken commands
1347 """Produce a warning message for broken commands
1342
1348
1343 Called when handling an exception; the exception is reraised if
1349 Called when handling an exception; the exception is reraised if
1344 this function returns False, ignored otherwise.
1350 this function returns False, ignored otherwise.
1345 """
1351 """
1346 warning = _exceptionwarning(ui)
1352 warning = _exceptionwarning(ui)
1347 ui.log(
1353 ui.log(
1348 b"commandexception",
1354 b"commandexception",
1349 b"%s\n%s\n",
1355 b"%s\n%s\n",
1350 warning,
1356 warning,
1351 pycompat.sysbytes(traceback.format_exc()),
1357 pycompat.sysbytes(traceback.format_exc()),
1352 )
1358 )
1353 ui.warn(warning)
1359 ui.warn(warning)
1354 return False # re-raise the exception
1360 return False # re-raise the exception
@@ -1,393 +1,402
1
1
2 $ cat << EOF > buggylocking.py
2 $ cat << EOF > buggylocking.py
3 > """A small extension that tests our developer warnings
3 > """A small extension that tests our developer warnings
4 > """
4 > """
5 >
5 >
6 > from mercurial import error, registrar, repair, util
6 > from mercurial import error, registrar, repair, util
7 >
7 >
8 > cmdtable = {}
8 > cmdtable = {}
9 > command = registrar.command(cmdtable)
9 > command = registrar.command(cmdtable)
10 >
10 >
11 > @command(b'buggylocking', [], '')
11 > @command(b'buggylocking', [], '')
12 > def buggylocking(ui, repo):
12 > def buggylocking(ui, repo):
13 > lo = repo.lock()
13 > lo = repo.lock()
14 > wl = repo.wlock()
14 > wl = repo.wlock()
15 > wl.release()
15 > wl.release()
16 > lo.release()
16 > lo.release()
17 >
17 >
18 > @command(b'buggytransaction', [], '')
18 > @command(b'buggytransaction', [], '')
19 > def buggylocking(ui, repo):
19 > def buggylocking(ui, repo):
20 > tr = repo.transaction(b'buggy')
20 > tr = repo.transaction(b'buggy')
21 > # make sure we rollback the transaction as we don't want to rely on the__del__
21 > # make sure we rollback the transaction as we don't want to rely on the__del__
22 > tr.release()
22 > tr.release()
23 >
23 >
24 > @command(b'properlocking', [], '')
24 > @command(b'properlocking', [], '')
25 > def properlocking(ui, repo):
25 > def properlocking(ui, repo):
26 > """check that reentrance is fine"""
26 > """check that reentrance is fine"""
27 > wl = repo.wlock()
27 > wl = repo.wlock()
28 > lo = repo.lock()
28 > lo = repo.lock()
29 > tr = repo.transaction(b'proper')
29 > tr = repo.transaction(b'proper')
30 > tr2 = repo.transaction(b'proper')
30 > tr2 = repo.transaction(b'proper')
31 > lo2 = repo.lock()
31 > lo2 = repo.lock()
32 > wl2 = repo.wlock()
32 > wl2 = repo.wlock()
33 > wl2.release()
33 > wl2.release()
34 > lo2.release()
34 > lo2.release()
35 > tr2.close()
35 > tr2.close()
36 > tr.close()
36 > tr.close()
37 > lo.release()
37 > lo.release()
38 > wl.release()
38 > wl.release()
39 >
39 >
40 > @command(b'nowaitlocking', [], '')
40 > @command(b'nowaitlocking', [], '')
41 > def nowaitlocking(ui, repo):
41 > def nowaitlocking(ui, repo):
42 > lo = repo.lock()
42 > lo = repo.lock()
43 > wl = repo.wlock(wait=False)
43 > wl = repo.wlock(wait=False)
44 > wl.release()
44 > wl.release()
45 > lo.release()
45 > lo.release()
46 >
46 >
47 > @command(b'no-wlock-write', [], '')
47 > @command(b'no-wlock-write', [], '')
48 > def nowlockwrite(ui, repo):
48 > def nowlockwrite(ui, repo):
49 > with repo.vfs(b'branch', b'a'):
49 > with repo.vfs(b'branch', b'a'):
50 > pass
50 > pass
51 >
51 >
52 > @command(b'no-lock-write', [], '')
52 > @command(b'no-lock-write', [], '')
53 > def nolockwrite(ui, repo):
53 > def nolockwrite(ui, repo):
54 > with repo.svfs(b'fncache', b'a'):
54 > with repo.svfs(b'fncache', b'a'):
55 > pass
55 > pass
56 >
56 >
57 > @command(b'stripintr', [], '')
57 > @command(b'stripintr', [], '')
58 > def stripintr(ui, repo):
58 > def stripintr(ui, repo):
59 > lo = repo.lock()
59 > lo = repo.lock()
60 > tr = repo.transaction(b'foobar')
60 > tr = repo.transaction(b'foobar')
61 > try:
61 > try:
62 > repair.strip(repo.ui, repo, [repo[b'.'].node()])
62 > repair.strip(repo.ui, repo, [repo[b'.'].node()])
63 > finally:
63 > finally:
64 > lo.release()
64 > lo.release()
65 > @command(b'oldanddeprecated', [], '')
65 > @command(b'oldanddeprecated', [], '')
66 > def oldanddeprecated(ui, repo):
66 > def oldanddeprecated(ui, repo):
67 > """test deprecation warning API"""
67 > """test deprecation warning API"""
68 > def foobar(ui):
68 > def foobar(ui):
69 > ui.deprecwarn(b'foorbar is deprecated, go shopping', b'42.1337')
69 > ui.deprecwarn(b'foorbar is deprecated, go shopping', b'42.1337')
70 > foobar(ui)
70 > foobar(ui)
71 > @command(b'nouiwarning', [], '')
71 > @command(b'nouiwarning', [], '')
72 > def nouiwarning(ui, repo):
72 > def nouiwarning(ui, repo):
73 > util.nouideprecwarn(b'this is a test', b'13.37')
73 > util.nouideprecwarn(b'this is a test', b'13.37')
74 > @command(b'programmingerror', [], '')
74 > @command(b'programmingerror', [], '')
75 > def programmingerror(ui, repo):
75 > def programmingerror(ui, repo):
76 > raise error.ProgrammingError(b'something went wrong', hint=b'try again')
76 > raise error.ProgrammingError(b'something went wrong', hint=b'try again')
77 > EOF
77 > EOF
78
78
79 $ cat << EOF >> $HGRCPATH
79 $ cat << EOF >> $HGRCPATH
80 > [extensions]
80 > [extensions]
81 > buggylocking=$TESTTMP/buggylocking.py
81 > buggylocking=$TESTTMP/buggylocking.py
82 > mock=$TESTDIR/mockblackbox.py
82 > mock=$TESTDIR/mockblackbox.py
83 > blackbox=
83 > blackbox=
84 > [devel]
84 > [devel]
85 > all-warnings=1
85 > all-warnings=1
86 > [blackbox]
86 > [blackbox]
87 > track = command, commandexception, commandfinish, develwarn
87 > track = command, commandexception, commandfinish, develwarn
88 > EOF
88 > EOF
89
89
90 $ hg init lock-checker
90 $ hg init lock-checker
91 $ cd lock-checker
91 $ cd lock-checker
92 $ hg buggylocking
92 $ hg buggylocking
93 devel-warn: "wlock" acquired after "lock" at: $TESTTMP/buggylocking.py:* (buggylocking) (glob)
93 devel-warn: "wlock" acquired after "lock" at: $TESTTMP/buggylocking.py:* (buggylocking) (glob)
94 $ cat << EOF >> $HGRCPATH
94 $ cat << EOF >> $HGRCPATH
95 > [devel]
95 > [devel]
96 > all=0
96 > all=0
97 > check-locks=1
97 > check-locks=1
98 > EOF
98 > EOF
99 $ hg buggylocking
99 $ hg buggylocking
100 devel-warn: "wlock" acquired after "lock" at: $TESTTMP/buggylocking.py:* (buggylocking) (glob)
100 devel-warn: "wlock" acquired after "lock" at: $TESTTMP/buggylocking.py:* (buggylocking) (glob)
101 #if no-chg
101 #if no-chg
102 $ hg buggylocking --traceback
102 $ hg buggylocking --traceback
103 devel-warn: "wlock" acquired after "lock" at:
103 devel-warn: "wlock" acquired after "lock" at:
104 */hg:* in <module> (glob) (?)
104 */hg:* in <module> (glob) (?)
105 */mercurial/dispatch.py:* in run (glob)
105 */mercurial/dispatch.py:* in run (glob)
106 */mercurial/dispatch.py:* in dispatch (glob)
106 */mercurial/dispatch.py:* in dispatch (glob)
107 */mercurial/dispatch.py:* in _rundispatch (glob)
107 */mercurial/dispatch.py:* in _runcatch (glob)
108 */mercurial/dispatch.py:* in _runcatch (glob)
108 */mercurial/dispatch.py:* in _callcatch (glob)
109 */mercurial/dispatch.py:* in _callcatch (glob)
109 */mercurial/scmutil.py* in callcatch (glob)
110 */mercurial/scmutil.py* in callcatch (glob)
110 */mercurial/dispatch.py:* in _runcatchfunc (glob)
111 */mercurial/dispatch.py:* in _runcatchfunc (glob)
111 */mercurial/dispatch.py:* in _dispatch (glob)
112 */mercurial/dispatch.py:* in _dispatch (glob)
112 */mercurial/dispatch.py:* in runcommand (glob)
113 */mercurial/dispatch.py:* in runcommand (glob)
113 */mercurial/dispatch.py:* in _runcommand (glob)
114 */mercurial/dispatch.py:* in _runcommand (glob)
114 */mercurial/dispatch.py:* in <lambda> (glob)
115 */mercurial/dispatch.py:* in <lambda> (glob)
115 */mercurial/util.py:* in check (glob)
116 */mercurial/util.py:* in check (glob)
116 $TESTTMP/buggylocking.py:* in buggylocking (glob)
117 $TESTTMP/buggylocking.py:* in buggylocking (glob)
117 #else
118 #else
118 $ hg buggylocking --traceback
119 $ hg buggylocking --traceback
119 devel-warn: "wlock" acquired after "lock" at:
120 devel-warn: "wlock" acquired after "lock" at:
120 */hg:* in <module> (glob) (?)
121 */hg:* in <module> (glob) (?)
121 */mercurial/dispatch.py:* in run (glob)
122 */mercurial/dispatch.py:* in run (glob)
122 */mercurial/dispatch.py:* in dispatch (glob)
123 */mercurial/dispatch.py:* in dispatch (glob)
124 */mercurial/dispatch.py:* in _rundispatch (glob)
123 */mercurial/dispatch.py:* in _runcatch (glob)
125 */mercurial/dispatch.py:* in _runcatch (glob)
124 */mercurial/dispatch.py:* in _callcatch (glob)
126 */mercurial/dispatch.py:* in _callcatch (glob)
125 */mercurial/scmutil.py:* in callcatch (glob)
127 */mercurial/scmutil.py:* in callcatch (glob)
126 */mercurial/dispatch.py:* in _runcatchfunc (glob)
128 */mercurial/dispatch.py:* in _runcatchfunc (glob)
127 */mercurial/dispatch.py:* in _dispatch (glob)
129 */mercurial/dispatch.py:* in _dispatch (glob)
128 */mercurial/dispatch.py:* in runcommand (glob)
130 */mercurial/dispatch.py:* in runcommand (glob)
129 */mercurial/dispatch.py:* in _runcommand (glob)
131 */mercurial/dispatch.py:* in _runcommand (glob)
130 */mercurial/dispatch.py:* in <lambda> (glob)
132 */mercurial/dispatch.py:* in <lambda> (glob)
131 */mercurial/util.py:* in check (glob)
133 */mercurial/util.py:* in check (glob)
132 */mercurial/commands.py:* in serve (glob)
134 */mercurial/commands.py:* in serve (glob)
133 */mercurial/server.py:* in runservice (glob)
135 */mercurial/server.py:* in runservice (glob)
134 */mercurial/commandserver.py:* in run (glob)
136 */mercurial/commandserver.py:* in run (glob)
135 */mercurial/commandserver.py:* in _mainloop (glob)
137 */mercurial/commandserver.py:* in _mainloop (glob)
136 */mercurial/commandserver.py:* in _acceptnewconnection (glob)
138 */mercurial/commandserver.py:* in _acceptnewconnection (glob)
137 */mercurial/commandserver.py:* in _runworker (glob)
139 */mercurial/commandserver.py:* in _runworker (glob)
138 */mercurial/commandserver.py:* in _serverequest (glob)
140 */mercurial/commandserver.py:* in _serverequest (glob)
139 */mercurial/commandserver.py:* in serve (glob)
141 */mercurial/commandserver.py:* in serve (glob)
140 */mercurial/commandserver.py:* in serveone (glob)
142 */mercurial/commandserver.py:* in serveone (glob)
141 */mercurial/chgserver.py:* in runcommand (glob)
143 */mercurial/chgserver.py:* in runcommand (glob)
142 */mercurial/commandserver.py:* in runcommand (glob)
144 */mercurial/commandserver.py:* in runcommand (glob)
143 */mercurial/commandserver.py:* in _dispatchcommand (glob)
145 */mercurial/commandserver.py:* in _dispatchcommand (glob)
144 */mercurial/dispatch.py:* in dispatch (glob)
146 */mercurial/dispatch.py:* in dispatch (glob)
147 */mercurial/dispatch.py:* in _rundispatch (glob)
145 */mercurial/dispatch.py:* in _runcatch (glob)
148 */mercurial/dispatch.py:* in _runcatch (glob)
146 */mercurial/dispatch.py:* in _callcatch (glob)
149 */mercurial/dispatch.py:* in _callcatch (glob)
147 */mercurial/scmutil.py:* in callcatch (glob)
150 */mercurial/scmutil.py:* in callcatch (glob)
148 */mercurial/dispatch.py:* in _runcatchfunc (glob)
151 */mercurial/dispatch.py:* in _runcatchfunc (glob)
149 */mercurial/dispatch.py:* in _dispatch (glob)
152 */mercurial/dispatch.py:* in _dispatch (glob)
150 */mercurial/dispatch.py:* in runcommand (glob)
153 */mercurial/dispatch.py:* in runcommand (glob)
151 */mercurial/dispatch.py:* in _runcommand (glob)
154 */mercurial/dispatch.py:* in _runcommand (glob)
152 */mercurial/dispatch.py:* in <lambda> (glob)
155 */mercurial/dispatch.py:* in <lambda> (glob)
153 */mercurial/util.py:* in check (glob)
156 */mercurial/util.py:* in check (glob)
154 $TESTTMP/buggylocking.py:* in buggylocking (glob)
157 $TESTTMP/buggylocking.py:* in buggylocking (glob)
155 #endif
158 #endif
156 $ hg properlocking
159 $ hg properlocking
157 $ hg nowaitlocking
160 $ hg nowaitlocking
158
161
159 Writing without lock
162 Writing without lock
160
163
161 $ hg no-wlock-write
164 $ hg no-wlock-write
162 devel-warn: write with no wlock: "branch" at: $TESTTMP/buggylocking.py:* (nowlockwrite) (glob)
165 devel-warn: write with no wlock: "branch" at: $TESTTMP/buggylocking.py:* (nowlockwrite) (glob)
163
166
164 $ hg no-lock-write
167 $ hg no-lock-write
165 devel-warn: write with no lock: "fncache" at: $TESTTMP/buggylocking.py:* (nolockwrite) (glob)
168 devel-warn: write with no lock: "fncache" at: $TESTTMP/buggylocking.py:* (nolockwrite) (glob)
166
169
167 Stripping from a transaction
170 Stripping from a transaction
168
171
169 $ echo a > a
172 $ echo a > a
170 $ hg add a
173 $ hg add a
171 $ hg commit -m a
174 $ hg commit -m a
172 $ hg stripintr 2>&1 | egrep -v '^(\*\*| )'
175 $ hg stripintr 2>&1 | egrep -v '^(\*\*| )'
173 Traceback (most recent call last):
176 Traceback (most recent call last):
174 *ProgrammingError: cannot strip from inside a transaction (glob)
177 *ProgrammingError: cannot strip from inside a transaction (glob)
175
178
176 $ hg oldanddeprecated
179 $ hg oldanddeprecated
177 devel-warn: foorbar is deprecated, go shopping
180 devel-warn: foorbar is deprecated, go shopping
178 (compatibility will be dropped after Mercurial-42.1337, update your code.) at: $TESTTMP/buggylocking.py:* (oldanddeprecated) (glob)
181 (compatibility will be dropped after Mercurial-42.1337, update your code.) at: $TESTTMP/buggylocking.py:* (oldanddeprecated) (glob)
179
182
180 #if no-chg
183 #if no-chg
181 $ hg oldanddeprecated --traceback
184 $ hg oldanddeprecated --traceback
182 devel-warn: foorbar is deprecated, go shopping
185 devel-warn: foorbar is deprecated, go shopping
183 (compatibility will be dropped after Mercurial-42.1337, update your code.) at:
186 (compatibility will be dropped after Mercurial-42.1337, update your code.) at:
184 */hg:* in <module> (glob) (?)
187 */hg:* in <module> (glob) (?)
185 */mercurial/dispatch.py:* in run (glob)
188 */mercurial/dispatch.py:* in run (glob)
186 */mercurial/dispatch.py:* in dispatch (glob)
189 */mercurial/dispatch.py:* in dispatch (glob)
190 */mercurial/dispatch.py:* in _rundispatch (glob)
187 */mercurial/dispatch.py:* in _runcatch (glob)
191 */mercurial/dispatch.py:* in _runcatch (glob)
188 */mercurial/dispatch.py:* in _callcatch (glob)
192 */mercurial/dispatch.py:* in _callcatch (glob)
189 */mercurial/scmutil.py* in callcatch (glob)
193 */mercurial/scmutil.py* in callcatch (glob)
190 */mercurial/dispatch.py:* in _runcatchfunc (glob)
194 */mercurial/dispatch.py:* in _runcatchfunc (glob)
191 */mercurial/dispatch.py:* in _dispatch (glob)
195 */mercurial/dispatch.py:* in _dispatch (glob)
192 */mercurial/dispatch.py:* in runcommand (glob)
196 */mercurial/dispatch.py:* in runcommand (glob)
193 */mercurial/dispatch.py:* in _runcommand (glob)
197 */mercurial/dispatch.py:* in _runcommand (glob)
194 */mercurial/dispatch.py:* in <lambda> (glob)
198 */mercurial/dispatch.py:* in <lambda> (glob)
195 */mercurial/util.py:* in check (glob)
199 */mercurial/util.py:* in check (glob)
196 $TESTTMP/buggylocking.py:* in oldanddeprecated (glob)
200 $TESTTMP/buggylocking.py:* in oldanddeprecated (glob)
197 #else
201 #else
198 $ hg oldanddeprecated --traceback
202 $ hg oldanddeprecated --traceback
199 devel-warn: foorbar is deprecated, go shopping
203 devel-warn: foorbar is deprecated, go shopping
200 (compatibility will be dropped after Mercurial-42.1337, update your code.) at:
204 (compatibility will be dropped after Mercurial-42.1337, update your code.) at:
201 */hg:* in <module> (glob)
205 */hg:* in <module> (glob)
202 */mercurial/dispatch.py:* in run (glob)
206 */mercurial/dispatch.py:* in run (glob)
203 */mercurial/dispatch.py:* in dispatch (glob)
207 */mercurial/dispatch.py:* in dispatch (glob)
208 */mercurial/dispatch.py:* in _rundispatch (glob)
204 */mercurial/dispatch.py:* in _runcatch (glob)
209 */mercurial/dispatch.py:* in _runcatch (glob)
205 */mercurial/dispatch.py:* in _callcatch (glob)
210 */mercurial/dispatch.py:* in _callcatch (glob)
206 */mercurial/scmutil.py:* in callcatch (glob)
211 */mercurial/scmutil.py:* in callcatch (glob)
207 */mercurial/dispatch.py:* in _runcatchfunc (glob)
212 */mercurial/dispatch.py:* in _runcatchfunc (glob)
208 */mercurial/dispatch.py:* in _dispatch (glob)
213 */mercurial/dispatch.py:* in _dispatch (glob)
209 */mercurial/dispatch.py:* in runcommand (glob)
214 */mercurial/dispatch.py:* in runcommand (glob)
210 */mercurial/dispatch.py:* in _runcommand (glob)
215 */mercurial/dispatch.py:* in _runcommand (glob)
211 */mercurial/dispatch.py:* in <lambda> (glob)
216 */mercurial/dispatch.py:* in <lambda> (glob)
212 */mercurial/util.py:* in check (glob)
217 */mercurial/util.py:* in check (glob)
213 */mercurial/commands.py:* in serve (glob)
218 */mercurial/commands.py:* in serve (glob)
214 */mercurial/server.py:* in runservice (glob)
219 */mercurial/server.py:* in runservice (glob)
215 */mercurial/commandserver.py:* in run (glob)
220 */mercurial/commandserver.py:* in run (glob)
216 */mercurial/commandserver.py:* in _mainloop (glob)
221 */mercurial/commandserver.py:* in _mainloop (glob)
217 */mercurial/commandserver.py:* in _acceptnewconnection (glob)
222 */mercurial/commandserver.py:* in _acceptnewconnection (glob)
218 */mercurial/commandserver.py:* in _runworker (glob)
223 */mercurial/commandserver.py:* in _runworker (glob)
219 */mercurial/commandserver.py:* in _serverequest (glob)
224 */mercurial/commandserver.py:* in _serverequest (glob)
220 */mercurial/commandserver.py:* in serve (glob)
225 */mercurial/commandserver.py:* in serve (glob)
221 */mercurial/commandserver.py:* in serveone (glob)
226 */mercurial/commandserver.py:* in serveone (glob)
222 */mercurial/chgserver.py:* in runcommand (glob)
227 */mercurial/chgserver.py:* in runcommand (glob)
223 */mercurial/commandserver.py:* in runcommand (glob)
228 */mercurial/commandserver.py:* in runcommand (glob)
224 */mercurial/commandserver.py:* in _dispatchcommand (glob)
229 */mercurial/commandserver.py:* in _dispatchcommand (glob)
225 */mercurial/dispatch.py:* in dispatch (glob)
230 */mercurial/dispatch.py:* in dispatch (glob)
231 */mercurial/dispatch.py:* in _rundispatch (glob)
226 */mercurial/dispatch.py:* in _runcatch (glob)
232 */mercurial/dispatch.py:* in _runcatch (glob)
227 */mercurial/dispatch.py:* in _callcatch (glob)
233 */mercurial/dispatch.py:* in _callcatch (glob)
228 */mercurial/scmutil.py:* in callcatch (glob)
234 */mercurial/scmutil.py:* in callcatch (glob)
229 */mercurial/dispatch.py:* in _runcatchfunc (glob)
235 */mercurial/dispatch.py:* in _runcatchfunc (glob)
230 */mercurial/dispatch.py:* in _dispatch (glob)
236 */mercurial/dispatch.py:* in _dispatch (glob)
231 */mercurial/dispatch.py:* in runcommand (glob)
237 */mercurial/dispatch.py:* in runcommand (glob)
232 */mercurial/dispatch.py:* in _runcommand (glob)
238 */mercurial/dispatch.py:* in _runcommand (glob)
233 */mercurial/dispatch.py:* in <lambda> (glob)
239 */mercurial/dispatch.py:* in <lambda> (glob)
234 */mercurial/util.py:* in check (glob)
240 */mercurial/util.py:* in check (glob)
235 $TESTTMP/buggylocking.py:* in oldanddeprecated (glob)
241 $TESTTMP/buggylocking.py:* in oldanddeprecated (glob)
236 #endif
242 #endif
237
243
238 #if no-chg
244 #if no-chg
239 $ hg blackbox -l 7
245 $ hg blackbox -l 7
240 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated
246 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated
241 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> devel-warn: foorbar is deprecated, go shopping
247 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> devel-warn: foorbar is deprecated, go shopping
242 (compatibility will be dropped after Mercurial-42.1337, update your code.) at: $TESTTMP/buggylocking.py:* (oldanddeprecated) (glob)
248 (compatibility will be dropped after Mercurial-42.1337, update your code.) at: $TESTTMP/buggylocking.py:* (oldanddeprecated) (glob)
243 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated exited 0 after * seconds (glob)
249 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated exited 0 after * seconds (glob)
244 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated --traceback
250 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated --traceback
245 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> devel-warn: foorbar is deprecated, go shopping
251 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> devel-warn: foorbar is deprecated, go shopping
246 (compatibility will be dropped after Mercurial-42.1337, update your code.) at:
252 (compatibility will be dropped after Mercurial-42.1337, update your code.) at:
247 */hg:* in <module> (glob) (?)
253 */hg:* in <module> (glob) (?)
248 */mercurial/dispatch.py:* in run (glob)
254 */mercurial/dispatch.py:* in run (glob)
249 */mercurial/dispatch.py:* in dispatch (glob)
255 */mercurial/dispatch.py:* in dispatch (glob)
256 */mercurial/dispatch.py:* in _rundispatch (glob)
250 */mercurial/dispatch.py:* in _runcatch (glob)
257 */mercurial/dispatch.py:* in _runcatch (glob)
251 */mercurial/dispatch.py:* in _callcatch (glob)
258 */mercurial/dispatch.py:* in _callcatch (glob)
252 */mercurial/scmutil.py* in callcatch (glob)
259 */mercurial/scmutil.py* in callcatch (glob)
253 */mercurial/dispatch.py:* in _runcatchfunc (glob)
260 */mercurial/dispatch.py:* in _runcatchfunc (glob)
254 */mercurial/dispatch.py:* in _dispatch (glob)
261 */mercurial/dispatch.py:* in _dispatch (glob)
255 */mercurial/dispatch.py:* in runcommand (glob)
262 */mercurial/dispatch.py:* in runcommand (glob)
256 */mercurial/dispatch.py:* in _runcommand (glob)
263 */mercurial/dispatch.py:* in _runcommand (glob)
257 */mercurial/dispatch.py:* in <lambda> (glob)
264 */mercurial/dispatch.py:* in <lambda> (glob)
258 */mercurial/util.py:* in check (glob)
265 */mercurial/util.py:* in check (glob)
259 $TESTTMP/buggylocking.py:* in oldanddeprecated (glob)
266 $TESTTMP/buggylocking.py:* in oldanddeprecated (glob)
260 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated --traceback exited 0 after * seconds (glob)
267 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated --traceback exited 0 after * seconds (glob)
261 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> blackbox -l 7
268 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> blackbox -l 7
262 #else
269 #else
263 $ hg blackbox -l 7
270 $ hg blackbox -l 7
264 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated
271 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated
265 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> devel-warn: foorbar is deprecated, go shopping
272 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> devel-warn: foorbar is deprecated, go shopping
266 (compatibility will be dropped after Mercurial-42.1337, update your code.) at: $TESTTMP/buggylocking.py:* (oldanddeprecated) (glob)
273 (compatibility will be dropped after Mercurial-42.1337, update your code.) at: $TESTTMP/buggylocking.py:* (oldanddeprecated) (glob)
267 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated exited 0 after * seconds (glob)
274 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated exited 0 after * seconds (glob)
268 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated --traceback
275 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated --traceback
269 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> devel-warn: foorbar is deprecated, go shopping
276 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> devel-warn: foorbar is deprecated, go shopping
270 (compatibility will be dropped after Mercurial-42.1337, update your code.) at:
277 (compatibility will be dropped after Mercurial-42.1337, update your code.) at:
271 */hg:* in <module> (glob)
278 */hg:* in <module> (glob)
272 */mercurial/dispatch.py:* in run (glob)
279 */mercurial/dispatch.py:* in run (glob)
273 */mercurial/dispatch.py:* in dispatch (glob)
280 */mercurial/dispatch.py:* in dispatch (glob)
281 */mercurial/dispatch.py:* in _rundispatch (glob)
274 */mercurial/dispatch.py:* in _runcatch (glob)
282 */mercurial/dispatch.py:* in _runcatch (glob)
275 */mercurial/dispatch.py:* in _callcatch (glob)
283 */mercurial/dispatch.py:* in _callcatch (glob)
276 */mercurial/scmutil.py:* in callcatch (glob)
284 */mercurial/scmutil.py:* in callcatch (glob)
277 */mercurial/dispatch.py:* in _runcatchfunc (glob)
285 */mercurial/dispatch.py:* in _runcatchfunc (glob)
278 */mercurial/dispatch.py:* in _dispatch (glob)
286 */mercurial/dispatch.py:* in _dispatch (glob)
279 */mercurial/dispatch.py:* in runcommand (glob)
287 */mercurial/dispatch.py:* in runcommand (glob)
280 */mercurial/dispatch.py:* in _runcommand (glob)
288 */mercurial/dispatch.py:* in _runcommand (glob)
281 */mercurial/dispatch.py:* in <lambda> (glob)
289 */mercurial/dispatch.py:* in <lambda> (glob)
282 */mercurial/util.py:* in check (glob)
290 */mercurial/util.py:* in check (glob)
283 */mercurial/commands.py:* in serve (glob)
291 */mercurial/commands.py:* in serve (glob)
284 */mercurial/server.py:* in runservice (glob)
292 */mercurial/server.py:* in runservice (glob)
285 */mercurial/commandserver.py:* in run (glob)
293 */mercurial/commandserver.py:* in run (glob)
286 */mercurial/commandserver.py:* in _mainloop (glob)
294 */mercurial/commandserver.py:* in _mainloop (glob)
287 */mercurial/commandserver.py:* in _acceptnewconnection (glob)
295 */mercurial/commandserver.py:* in _acceptnewconnection (glob)
288 */mercurial/commandserver.py:* in _runworker (glob)
296 */mercurial/commandserver.py:* in _runworker (glob)
289 */mercurial/commandserver.py:* in _serverequest (glob)
297 */mercurial/commandserver.py:* in _serverequest (glob)
290 */mercurial/commandserver.py:* in serve (glob)
298 */mercurial/commandserver.py:* in serve (glob)
291 */mercurial/commandserver.py:* in serveone (glob)
299 */mercurial/commandserver.py:* in serveone (glob)
292 */mercurial/chgserver.py:* in runcommand (glob)
300 */mercurial/chgserver.py:* in runcommand (glob)
293 */mercurial/commandserver.py:* in runcommand (glob)
301 */mercurial/commandserver.py:* in runcommand (glob)
294 */mercurial/commandserver.py:* in _dispatchcommand (glob)
302 */mercurial/commandserver.py:* in _dispatchcommand (glob)
295 */mercurial/dispatch.py:* in dispatch (glob)
303 */mercurial/dispatch.py:* in dispatch (glob)
304 */mercurial/dispatch.py:* in _rundispatch (glob)
296 */mercurial/dispatch.py:* in _runcatch (glob)
305 */mercurial/dispatch.py:* in _runcatch (glob)
297 */mercurial/dispatch.py:* in _callcatch (glob)
306 */mercurial/dispatch.py:* in _callcatch (glob)
298 */mercurial/scmutil.py:* in callcatch (glob)
307 */mercurial/scmutil.py:* in callcatch (glob)
299 */mercurial/dispatch.py:* in _runcatchfunc (glob)
308 */mercurial/dispatch.py:* in _runcatchfunc (glob)
300 */mercurial/dispatch.py:* in _dispatch (glob)
309 */mercurial/dispatch.py:* in _dispatch (glob)
301 */mercurial/dispatch.py:* in runcommand (glob)
310 */mercurial/dispatch.py:* in runcommand (glob)
302 */mercurial/dispatch.py:* in _runcommand (glob)
311 */mercurial/dispatch.py:* in _runcommand (glob)
303 */mercurial/dispatch.py:* in <lambda> (glob)
312 */mercurial/dispatch.py:* in <lambda> (glob)
304 */mercurial/util.py:* in check (glob)
313 */mercurial/util.py:* in check (glob)
305 $TESTTMP/buggylocking.py:* in oldanddeprecated (glob)
314 $TESTTMP/buggylocking.py:* in oldanddeprecated (glob)
306 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated --traceback exited 0 after * seconds (glob)
315 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated --traceback exited 0 after * seconds (glob)
307 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> blackbox -l 7
316 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> blackbox -l 7
308 #endif
317 #endif
309
318
310 Test programming error failure:
319 Test programming error failure:
311
320
312 $ hg buggytransaction 2>&1 | egrep -v '^ '
321 $ hg buggytransaction 2>&1 | egrep -v '^ '
313 ** Unknown exception encountered with possibly-broken third-party extension "buggylocking" (version N/A)
322 ** Unknown exception encountered with possibly-broken third-party extension "buggylocking" (version N/A)
314 ** which supports versions unknown of Mercurial.
323 ** which supports versions unknown of Mercurial.
315 ** Please disable "buggylocking" and try your action again.
324 ** Please disable "buggylocking" and try your action again.
316 ** If that fixes the bug please report it to the extension author.
325 ** If that fixes the bug please report it to the extension author.
317 ** Python * (glob)
326 ** Python * (glob)
318 ** Mercurial Distributed SCM (*) (glob)
327 ** Mercurial Distributed SCM (*) (glob)
319 ** Extensions loaded: * (glob)
328 ** Extensions loaded: * (glob)
320 ** ProgrammingError: transaction requires locking
329 ** ProgrammingError: transaction requires locking
321 Traceback (most recent call last):
330 Traceback (most recent call last):
322 *ProgrammingError: transaction requires locking (glob)
331 *ProgrammingError: transaction requires locking (glob)
323
332
324 $ hg programmingerror 2>&1 | egrep -v '^ '
333 $ hg programmingerror 2>&1 | egrep -v '^ '
325 ** Unknown exception encountered with possibly-broken third-party extension "buggylocking" (version N/A)
334 ** Unknown exception encountered with possibly-broken third-party extension "buggylocking" (version N/A)
326 ** which supports versions unknown of Mercurial.
335 ** which supports versions unknown of Mercurial.
327 ** Please disable "buggylocking" and try your action again.
336 ** Please disable "buggylocking" and try your action again.
328 ** If that fixes the bug please report it to the extension author.
337 ** If that fixes the bug please report it to the extension author.
329 ** Python * (glob)
338 ** Python * (glob)
330 ** Mercurial Distributed SCM (*) (glob)
339 ** Mercurial Distributed SCM (*) (glob)
331 ** Extensions loaded: * (glob)
340 ** Extensions loaded: * (glob)
332 ** ProgrammingError: something went wrong
341 ** ProgrammingError: something went wrong
333 ** (try again)
342 ** (try again)
334 Traceback (most recent call last):
343 Traceback (most recent call last):
335 *ProgrammingError: something went wrong (glob)
344 *ProgrammingError: something went wrong (glob)
336
345
337 Old style deprecation warning
346 Old style deprecation warning
338
347
339 $ hg nouiwarning
348 $ hg nouiwarning
340 $TESTTMP/buggylocking.py:*: DeprecationWarning: this is a test (glob)
349 $TESTTMP/buggylocking.py:*: DeprecationWarning: this is a test (glob)
341 (compatibility will be dropped after Mercurial-13.37, update your code.)
350 (compatibility will be dropped after Mercurial-13.37, update your code.)
342 util.nouideprecwarn(b'this is a test', b'13.37')
351 util.nouideprecwarn(b'this is a test', b'13.37')
343
352
344 (disabled outside of test run)
353 (disabled outside of test run)
345
354
346 $ HGEMITWARNINGS= hg nouiwarning
355 $ HGEMITWARNINGS= hg nouiwarning
347
356
348 Test warning on config option access and registration
357 Test warning on config option access and registration
349
358
350 $ cat << EOF > ${TESTTMP}/buggyconfig.py
359 $ cat << EOF > ${TESTTMP}/buggyconfig.py
351 > """A small extension that tests our developer warnings for config"""
360 > """A small extension that tests our developer warnings for config"""
352 >
361 >
353 > from mercurial import configitems, registrar
362 > from mercurial import configitems, registrar
354 >
363 >
355 > cmdtable = {}
364 > cmdtable = {}
356 > command = registrar.command(cmdtable)
365 > command = registrar.command(cmdtable)
357 >
366 >
358 > configtable = {}
367 > configtable = {}
359 > configitem = registrar.configitem(configtable)
368 > configitem = registrar.configitem(configtable)
360 >
369 >
361 > configitem(b'test', b'some', default=b'foo')
370 > configitem(b'test', b'some', default=b'foo')
362 > configitem(b'test', b'dynamic', default=configitems.dynamicdefault)
371 > configitem(b'test', b'dynamic', default=configitems.dynamicdefault)
363 > configitem(b'test', b'callable', default=list)
372 > configitem(b'test', b'callable', default=list)
364 > # overwrite a core config
373 > # overwrite a core config
365 > configitem(b'ui', b'quiet', default=False)
374 > configitem(b'ui', b'quiet', default=False)
366 > configitem(b'ui', b'interactive', default=None)
375 > configitem(b'ui', b'interactive', default=None)
367 >
376 >
368 > @command(b'buggyconfig')
377 > @command(b'buggyconfig')
369 > def cmdbuggyconfig(ui, repo):
378 > def cmdbuggyconfig(ui, repo):
370 > repo.ui.config(b'ui', b'quiet', True)
379 > repo.ui.config(b'ui', b'quiet', True)
371 > repo.ui.config(b'ui', b'interactive', False)
380 > repo.ui.config(b'ui', b'interactive', False)
372 > repo.ui.config(b'test', b'some', b'bar')
381 > repo.ui.config(b'test', b'some', b'bar')
373 > repo.ui.config(b'test', b'some', b'foo')
382 > repo.ui.config(b'test', b'some', b'foo')
374 > repo.ui.config(b'test', b'dynamic', b'some-required-default')
383 > repo.ui.config(b'test', b'dynamic', b'some-required-default')
375 > repo.ui.config(b'test', b'dynamic')
384 > repo.ui.config(b'test', b'dynamic')
376 > repo.ui.config(b'test', b'callable', [])
385 > repo.ui.config(b'test', b'callable', [])
377 > repo.ui.config(b'test', b'callable', b'foo')
386 > repo.ui.config(b'test', b'callable', b'foo')
378 > repo.ui.config(b'test', b'unregistered')
387 > repo.ui.config(b'test', b'unregistered')
379 > repo.ui.config(b'unregistered', b'unregistered')
388 > repo.ui.config(b'unregistered', b'unregistered')
380 > EOF
389 > EOF
381
390
382 $ hg --config "extensions.buggyconfig=${TESTTMP}/buggyconfig.py" buggyconfig
391 $ hg --config "extensions.buggyconfig=${TESTTMP}/buggyconfig.py" buggyconfig
383 devel-warn: extension 'buggyconfig' overwrite config item 'ui.interactive' at: */mercurial/extensions.py:* (_loadextra) (glob)
392 devel-warn: extension 'buggyconfig' overwrite config item 'ui.interactive' at: */mercurial/extensions.py:* (_loadextra) (glob)
384 devel-warn: extension 'buggyconfig' overwrite config item 'ui.quiet' at: */mercurial/extensions.py:* (_loadextra) (glob)
393 devel-warn: extension 'buggyconfig' overwrite config item 'ui.quiet' at: */mercurial/extensions.py:* (_loadextra) (glob)
385 devel-warn: specifying a mismatched default value for a registered config item: 'ui.quiet' 'True' at: $TESTTMP/buggyconfig.py:* (cmdbuggyconfig) (glob)
394 devel-warn: specifying a mismatched default value for a registered config item: 'ui.quiet' 'True' at: $TESTTMP/buggyconfig.py:* (cmdbuggyconfig) (glob)
386 devel-warn: specifying a mismatched default value for a registered config item: 'ui.interactive' 'False' at: $TESTTMP/buggyconfig.py:* (cmdbuggyconfig) (glob)
395 devel-warn: specifying a mismatched default value for a registered config item: 'ui.interactive' 'False' at: $TESTTMP/buggyconfig.py:* (cmdbuggyconfig) (glob)
387 devel-warn: specifying a mismatched default value for a registered config item: 'test.some' 'bar' at: $TESTTMP/buggyconfig.py:* (cmdbuggyconfig) (glob)
396 devel-warn: specifying a mismatched default value for a registered config item: 'test.some' 'bar' at: $TESTTMP/buggyconfig.py:* (cmdbuggyconfig) (glob)
388 devel-warn: config item requires an explicit default value: 'test.dynamic' at: $TESTTMP/buggyconfig.py:* (cmdbuggyconfig) (glob)
397 devel-warn: config item requires an explicit default value: 'test.dynamic' at: $TESTTMP/buggyconfig.py:* (cmdbuggyconfig) (glob)
389 devel-warn: specifying a mismatched default value for a registered config item: 'test.callable' 'foo' at: $TESTTMP/buggyconfig.py:* (cmdbuggyconfig) (glob)
398 devel-warn: specifying a mismatched default value for a registered config item: 'test.callable' 'foo' at: $TESTTMP/buggyconfig.py:* (cmdbuggyconfig) (glob)
390 devel-warn: accessing unregistered config item: 'test.unregistered' at: $TESTTMP/buggyconfig.py:* (cmdbuggyconfig) (glob)
399 devel-warn: accessing unregistered config item: 'test.unregistered' at: $TESTTMP/buggyconfig.py:* (cmdbuggyconfig) (glob)
391 devel-warn: accessing unregistered config item: 'unregistered.unregistered' at: $TESTTMP/buggyconfig.py:* (cmdbuggyconfig) (glob)
400 devel-warn: accessing unregistered config item: 'unregistered.unregistered' at: $TESTTMP/buggyconfig.py:* (cmdbuggyconfig) (glob)
392
401
393 $ cd ..
402 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now