##// END OF EJS Templates
dispatch: print traceback in scmutil.callcatch() if --traceback specified...
Yuya Nishihara -
r32041:38963a53 default
parent child Browse files
Show More
@@ -1,961 +1,960 b''
1 # dispatch.py - command dispatching for mercurial
1 # dispatch.py - command dispatching for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import, print_function
8 from __future__ import absolute_import, print_function
9
9
10 import difflib
10 import difflib
11 import errno
11 import errno
12 import getopt
12 import getopt
13 import os
13 import os
14 import pdb
14 import pdb
15 import re
15 import re
16 import signal
16 import signal
17 import sys
17 import sys
18 import time
18 import time
19 import traceback
19 import traceback
20
20
21
21
22 from .i18n import _
22 from .i18n import _
23
23
24 from . import (
24 from . import (
25 cmdutil,
25 cmdutil,
26 color,
26 color,
27 commands,
27 commands,
28 debugcommands,
28 debugcommands,
29 demandimport,
29 demandimport,
30 encoding,
30 encoding,
31 error,
31 error,
32 extensions,
32 extensions,
33 fancyopts,
33 fancyopts,
34 fileset,
34 fileset,
35 help,
35 help,
36 hg,
36 hg,
37 hook,
37 hook,
38 profiling,
38 profiling,
39 pycompat,
39 pycompat,
40 revset,
40 revset,
41 scmutil,
41 scmutil,
42 templatefilters,
42 templatefilters,
43 templatekw,
43 templatekw,
44 templater,
44 templater,
45 ui as uimod,
45 ui as uimod,
46 util,
46 util,
47 )
47 )
48
48
49 class request(object):
49 class request(object):
50 def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
50 def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
51 ferr=None):
51 ferr=None):
52 self.args = args
52 self.args = args
53 self.ui = ui
53 self.ui = ui
54 self.repo = repo
54 self.repo = repo
55
55
56 # input/output/error streams
56 # input/output/error streams
57 self.fin = fin
57 self.fin = fin
58 self.fout = fout
58 self.fout = fout
59 self.ferr = ferr
59 self.ferr = ferr
60
60
61 def _runexithandlers(self):
61 def _runexithandlers(self):
62 exc = None
62 exc = None
63 handlers = self.ui._exithandlers
63 handlers = self.ui._exithandlers
64 try:
64 try:
65 while handlers:
65 while handlers:
66 func, args, kwargs = handlers.pop()
66 func, args, kwargs = handlers.pop()
67 try:
67 try:
68 func(*args, **kwargs)
68 func(*args, **kwargs)
69 except: # re-raises below
69 except: # re-raises below
70 if exc is None:
70 if exc is None:
71 exc = sys.exc_info()[1]
71 exc = sys.exc_info()[1]
72 self.ui.warn(('error in exit handlers:\n'))
72 self.ui.warn(('error in exit handlers:\n'))
73 self.ui.traceback(force=True)
73 self.ui.traceback(force=True)
74 finally:
74 finally:
75 if exc is not None:
75 if exc is not None:
76 raise exc
76 raise exc
77
77
78 def run():
78 def run():
79 "run the command in sys.argv"
79 "run the command in sys.argv"
80 req = request(pycompat.sysargv[1:])
80 req = request(pycompat.sysargv[1:])
81 err = None
81 err = None
82 try:
82 try:
83 status = (dispatch(req) or 0) & 255
83 status = (dispatch(req) or 0) & 255
84 except error.StdioError as err:
84 except error.StdioError as err:
85 status = -1
85 status = -1
86 if util.safehasattr(req.ui, 'fout'):
86 if util.safehasattr(req.ui, 'fout'):
87 try:
87 try:
88 req.ui.fout.close()
88 req.ui.fout.close()
89 except IOError as err:
89 except IOError as err:
90 status = -1
90 status = -1
91 if util.safehasattr(req.ui, 'ferr'):
91 if util.safehasattr(req.ui, 'ferr'):
92 if err is not None and err.errno != errno.EPIPE:
92 if err is not None and err.errno != errno.EPIPE:
93 req.ui.ferr.write('abort: %s\n' % err.strerror)
93 req.ui.ferr.write('abort: %s\n' % err.strerror)
94 req.ui.ferr.close()
94 req.ui.ferr.close()
95 sys.exit(status & 255)
95 sys.exit(status & 255)
96
96
97 def _getsimilar(symbols, value):
97 def _getsimilar(symbols, value):
98 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
98 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
99 # The cutoff for similarity here is pretty arbitrary. It should
99 # The cutoff for similarity here is pretty arbitrary. It should
100 # probably be investigated and tweaked.
100 # probably be investigated and tweaked.
101 return [s for s in symbols if sim(s) > 0.6]
101 return [s for s in symbols if sim(s) > 0.6]
102
102
103 def _reportsimilar(write, similar):
103 def _reportsimilar(write, similar):
104 if len(similar) == 1:
104 if len(similar) == 1:
105 write(_("(did you mean %s?)\n") % similar[0])
105 write(_("(did you mean %s?)\n") % similar[0])
106 elif similar:
106 elif similar:
107 ss = ", ".join(sorted(similar))
107 ss = ", ".join(sorted(similar))
108 write(_("(did you mean one of %s?)\n") % ss)
108 write(_("(did you mean one of %s?)\n") % ss)
109
109
110 def _formatparse(write, inst):
110 def _formatparse(write, inst):
111 similar = []
111 similar = []
112 if isinstance(inst, error.UnknownIdentifier):
112 if isinstance(inst, error.UnknownIdentifier):
113 # make sure to check fileset first, as revset can invoke fileset
113 # make sure to check fileset first, as revset can invoke fileset
114 similar = _getsimilar(inst.symbols, inst.function)
114 similar = _getsimilar(inst.symbols, inst.function)
115 if len(inst.args) > 1:
115 if len(inst.args) > 1:
116 write(_("hg: parse error at %s: %s\n") %
116 write(_("hg: parse error at %s: %s\n") %
117 (inst.args[1], inst.args[0]))
117 (inst.args[1], inst.args[0]))
118 if (inst.args[0][0] == ' '):
118 if (inst.args[0][0] == ' '):
119 write(_("unexpected leading whitespace\n"))
119 write(_("unexpected leading whitespace\n"))
120 else:
120 else:
121 write(_("hg: parse error: %s\n") % inst.args[0])
121 write(_("hg: parse error: %s\n") % inst.args[0])
122 _reportsimilar(write, similar)
122 _reportsimilar(write, similar)
123 if inst.hint:
123 if inst.hint:
124 write(_("(%s)\n") % inst.hint)
124 write(_("(%s)\n") % inst.hint)
125
125
126 def _formatargs(args):
126 def _formatargs(args):
127 return ' '.join(util.shellquote(a) for a in args)
127 return ' '.join(util.shellquote(a) for a in args)
128
128
129 def dispatch(req):
129 def dispatch(req):
130 "run the command specified in req.args"
130 "run the command specified in req.args"
131 if req.ferr:
131 if req.ferr:
132 ferr = req.ferr
132 ferr = req.ferr
133 elif req.ui:
133 elif req.ui:
134 ferr = req.ui.ferr
134 ferr = req.ui.ferr
135 else:
135 else:
136 ferr = util.stderr
136 ferr = util.stderr
137
137
138 try:
138 try:
139 if not req.ui:
139 if not req.ui:
140 req.ui = uimod.ui.load()
140 req.ui = uimod.ui.load()
141 if '--traceback' in req.args:
141 if '--traceback' in req.args:
142 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
142 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
143
143
144 # set ui streams from the request
144 # set ui streams from the request
145 if req.fin:
145 if req.fin:
146 req.ui.fin = req.fin
146 req.ui.fin = req.fin
147 if req.fout:
147 if req.fout:
148 req.ui.fout = req.fout
148 req.ui.fout = req.fout
149 if req.ferr:
149 if req.ferr:
150 req.ui.ferr = req.ferr
150 req.ui.ferr = req.ferr
151 except error.Abort as inst:
151 except error.Abort as inst:
152 ferr.write(_("abort: %s\n") % inst)
152 ferr.write(_("abort: %s\n") % inst)
153 if inst.hint:
153 if inst.hint:
154 ferr.write(_("(%s)\n") % inst.hint)
154 ferr.write(_("(%s)\n") % inst.hint)
155 return -1
155 return -1
156 except error.ParseError as inst:
156 except error.ParseError as inst:
157 _formatparse(ferr.write, inst)
157 _formatparse(ferr.write, inst)
158 return -1
158 return -1
159
159
160 msg = _formatargs(req.args)
160 msg = _formatargs(req.args)
161 starttime = util.timer()
161 starttime = util.timer()
162 ret = None
162 ret = None
163 try:
163 try:
164 ret = _runcatch(req)
164 ret = _runcatch(req)
165 except KeyboardInterrupt:
165 except KeyboardInterrupt:
166 try:
166 try:
167 req.ui.warn(_("interrupted!\n"))
167 req.ui.warn(_("interrupted!\n"))
168 except IOError as inst:
168 except IOError as inst:
169 if inst.errno != errno.EPIPE:
169 if inst.errno != errno.EPIPE:
170 raise
170 raise
171 ret = -1
171 ret = -1
172 finally:
172 finally:
173 duration = util.timer() - starttime
173 duration = util.timer() - starttime
174 req.ui.flush()
174 req.ui.flush()
175 if req.ui.logblockedtimes:
175 if req.ui.logblockedtimes:
176 req.ui._blockedtimes['command_duration'] = duration * 1000
176 req.ui._blockedtimes['command_duration'] = duration * 1000
177 req.ui.log('uiblocked', 'ui blocked ms', **req.ui._blockedtimes)
177 req.ui.log('uiblocked', 'ui blocked ms', **req.ui._blockedtimes)
178 req.ui.log("commandfinish", "%s exited %s after %0.2f seconds\n",
178 req.ui.log("commandfinish", "%s exited %s after %0.2f seconds\n",
179 msg, ret or 0, duration)
179 msg, ret or 0, duration)
180 try:
180 try:
181 req._runexithandlers()
181 req._runexithandlers()
182 except: # exiting, so no re-raises
182 except: # exiting, so no re-raises
183 ret = ret or -1
183 ret = ret or -1
184 return ret
184 return ret
185
185
186 def _runcatch(req):
186 def _runcatch(req):
187 def catchterm(*args):
187 def catchterm(*args):
188 raise error.SignalInterrupt
188 raise error.SignalInterrupt
189
189
190 ui = req.ui
190 ui = req.ui
191 try:
191 try:
192 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
192 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
193 num = getattr(signal, name, None)
193 num = getattr(signal, name, None)
194 if num:
194 if num:
195 signal.signal(num, catchterm)
195 signal.signal(num, catchterm)
196 except ValueError:
196 except ValueError:
197 pass # happens if called in a thread
197 pass # happens if called in a thread
198
198
199 def _runcatchfunc():
199 def _runcatchfunc():
200 try:
200 try:
201 debugger = 'pdb'
201 debugger = 'pdb'
202 debugtrace = {
202 debugtrace = {
203 'pdb' : pdb.set_trace
203 'pdb' : pdb.set_trace
204 }
204 }
205 debugmortem = {
205 debugmortem = {
206 'pdb' : pdb.post_mortem
206 'pdb' : pdb.post_mortem
207 }
207 }
208
208
209 # read --config before doing anything else
209 # read --config before doing anything else
210 # (e.g. to change trust settings for reading .hg/hgrc)
210 # (e.g. to change trust settings for reading .hg/hgrc)
211 cfgs = _parseconfig(req.ui, _earlygetopt(['--config'], req.args))
211 cfgs = _parseconfig(req.ui, _earlygetopt(['--config'], req.args))
212
212
213 if req.repo:
213 if req.repo:
214 # copy configs that were passed on the cmdline (--config) to
214 # copy configs that were passed on the cmdline (--config) to
215 # the repo ui
215 # the repo ui
216 for sec, name, val in cfgs:
216 for sec, name, val in cfgs:
217 req.repo.ui.setconfig(sec, name, val, source='--config')
217 req.repo.ui.setconfig(sec, name, val, source='--config')
218
218
219 # developer config: ui.debugger
219 # developer config: ui.debugger
220 debugger = ui.config("ui", "debugger")
220 debugger = ui.config("ui", "debugger")
221 debugmod = pdb
221 debugmod = pdb
222 if not debugger or ui.plain():
222 if not debugger or ui.plain():
223 # if we are in HGPLAIN mode, then disable custom debugging
223 # if we are in HGPLAIN mode, then disable custom debugging
224 debugger = 'pdb'
224 debugger = 'pdb'
225 elif '--debugger' in req.args:
225 elif '--debugger' in req.args:
226 # This import can be slow for fancy debuggers, so only
226 # This import can be slow for fancy debuggers, so only
227 # do it when absolutely necessary, i.e. when actual
227 # do it when absolutely necessary, i.e. when actual
228 # debugging has been requested
228 # debugging has been requested
229 with demandimport.deactivated():
229 with demandimport.deactivated():
230 try:
230 try:
231 debugmod = __import__(debugger)
231 debugmod = __import__(debugger)
232 except ImportError:
232 except ImportError:
233 pass # Leave debugmod = pdb
233 pass # Leave debugmod = pdb
234
234
235 debugtrace[debugger] = debugmod.set_trace
235 debugtrace[debugger] = debugmod.set_trace
236 debugmortem[debugger] = debugmod.post_mortem
236 debugmortem[debugger] = debugmod.post_mortem
237
237
238 # enter the debugger before command execution
238 # enter the debugger before command execution
239 if '--debugger' in req.args:
239 if '--debugger' in req.args:
240 ui.warn(_("entering debugger - "
240 ui.warn(_("entering debugger - "
241 "type c to continue starting hg or h for help\n"))
241 "type c to continue starting hg or h for help\n"))
242
242
243 if (debugger != 'pdb' and
243 if (debugger != 'pdb' and
244 debugtrace[debugger] == debugtrace['pdb']):
244 debugtrace[debugger] == debugtrace['pdb']):
245 ui.warn(_("%s debugger specified "
245 ui.warn(_("%s debugger specified "
246 "but its module was not found\n") % debugger)
246 "but its module was not found\n") % debugger)
247 with demandimport.deactivated():
247 with demandimport.deactivated():
248 debugtrace[debugger]()
248 debugtrace[debugger]()
249 try:
249 try:
250 return _dispatch(req)
250 return _dispatch(req)
251 finally:
251 finally:
252 ui.flush()
252 ui.flush()
253 except: # re-raises
253 except: # re-raises
254 # enter the debugger when we hit an exception
254 # enter the debugger when we hit an exception
255 if '--debugger' in req.args:
255 if '--debugger' in req.args:
256 traceback.print_exc()
256 traceback.print_exc()
257 debugmortem[debugger](sys.exc_info()[2])
257 debugmortem[debugger](sys.exc_info()[2])
258 ui.traceback()
259 raise
258 raise
260
259
261 return _callcatch(ui, _runcatchfunc)
260 return _callcatch(ui, _runcatchfunc)
262
261
263 def _callcatch(ui, func):
262 def _callcatch(ui, func):
264 """like scmutil.callcatch but handles more high-level exceptions about
263 """like scmutil.callcatch but handles more high-level exceptions about
265 config parsing and commands. besides, use handlecommandexception to handle
264 config parsing and commands. besides, use handlecommandexception to handle
266 uncaught exceptions.
265 uncaught exceptions.
267 """
266 """
268 try:
267 try:
269 return scmutil.callcatch(ui, func)
268 return scmutil.callcatch(ui, func)
270 except error.AmbiguousCommand as inst:
269 except error.AmbiguousCommand as inst:
271 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
270 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
272 (inst.args[0], " ".join(inst.args[1])))
271 (inst.args[0], " ".join(inst.args[1])))
273 except error.CommandError as inst:
272 except error.CommandError as inst:
274 if inst.args[0]:
273 if inst.args[0]:
275 ui.pager('help')
274 ui.pager('help')
276 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
275 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
277 commands.help_(ui, inst.args[0], full=False, command=True)
276 commands.help_(ui, inst.args[0], full=False, command=True)
278 else:
277 else:
279 ui.pager('help')
278 ui.pager('help')
280 ui.warn(_("hg: %s\n") % inst.args[1])
279 ui.warn(_("hg: %s\n") % inst.args[1])
281 commands.help_(ui, 'shortlist')
280 commands.help_(ui, 'shortlist')
282 except error.ParseError as inst:
281 except error.ParseError as inst:
283 _formatparse(ui.warn, inst)
282 _formatparse(ui.warn, inst)
284 return -1
283 return -1
285 except error.UnknownCommand as inst:
284 except error.UnknownCommand as inst:
286 nocmdmsg = _("hg: unknown command '%s'\n") % inst.args[0]
285 nocmdmsg = _("hg: unknown command '%s'\n") % inst.args[0]
287 try:
286 try:
288 # check if the command is in a disabled extension
287 # check if the command is in a disabled extension
289 # (but don't check for extensions themselves)
288 # (but don't check for extensions themselves)
290 formatted = help.formattedhelp(ui, inst.args[0], unknowncmd=True)
289 formatted = help.formattedhelp(ui, inst.args[0], unknowncmd=True)
291 ui.warn(nocmdmsg)
290 ui.warn(nocmdmsg)
292 ui.write(formatted)
291 ui.write(formatted)
293 except (error.UnknownCommand, error.Abort):
292 except (error.UnknownCommand, error.Abort):
294 suggested = False
293 suggested = False
295 if len(inst.args) == 2:
294 if len(inst.args) == 2:
296 sim = _getsimilar(inst.args[1], inst.args[0])
295 sim = _getsimilar(inst.args[1], inst.args[0])
297 if sim:
296 if sim:
298 ui.warn(nocmdmsg)
297 ui.warn(nocmdmsg)
299 _reportsimilar(ui.warn, sim)
298 _reportsimilar(ui.warn, sim)
300 suggested = True
299 suggested = True
301 if not suggested:
300 if not suggested:
302 ui.pager('help')
301 ui.pager('help')
303 ui.warn(nocmdmsg)
302 ui.warn(nocmdmsg)
304 commands.help_(ui, 'shortlist')
303 commands.help_(ui, 'shortlist')
305 except IOError:
304 except IOError:
306 raise
305 raise
307 except KeyboardInterrupt:
306 except KeyboardInterrupt:
308 raise
307 raise
309 except: # probably re-raises
308 except: # probably re-raises
310 if not handlecommandexception(ui):
309 if not handlecommandexception(ui):
311 raise
310 raise
312
311
313 return -1
312 return -1
314
313
315 def aliasargs(fn, givenargs):
314 def aliasargs(fn, givenargs):
316 args = getattr(fn, 'args', [])
315 args = getattr(fn, 'args', [])
317 if args:
316 if args:
318 cmd = ' '.join(map(util.shellquote, args))
317 cmd = ' '.join(map(util.shellquote, args))
319
318
320 nums = []
319 nums = []
321 def replacer(m):
320 def replacer(m):
322 num = int(m.group(1)) - 1
321 num = int(m.group(1)) - 1
323 nums.append(num)
322 nums.append(num)
324 if num < len(givenargs):
323 if num < len(givenargs):
325 return givenargs[num]
324 return givenargs[num]
326 raise error.Abort(_('too few arguments for command alias'))
325 raise error.Abort(_('too few arguments for command alias'))
327 cmd = re.sub(br'\$(\d+|\$)', replacer, cmd)
326 cmd = re.sub(br'\$(\d+|\$)', replacer, cmd)
328 givenargs = [x for i, x in enumerate(givenargs)
327 givenargs = [x for i, x in enumerate(givenargs)
329 if i not in nums]
328 if i not in nums]
330 args = pycompat.shlexsplit(cmd)
329 args = pycompat.shlexsplit(cmd)
331 return args + givenargs
330 return args + givenargs
332
331
333 def aliasinterpolate(name, args, cmd):
332 def aliasinterpolate(name, args, cmd):
334 '''interpolate args into cmd for shell aliases
333 '''interpolate args into cmd for shell aliases
335
334
336 This also handles $0, $@ and "$@".
335 This also handles $0, $@ and "$@".
337 '''
336 '''
338 # util.interpolate can't deal with "$@" (with quotes) because it's only
337 # util.interpolate can't deal with "$@" (with quotes) because it's only
339 # built to match prefix + patterns.
338 # built to match prefix + patterns.
340 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
339 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
341 replacemap['$0'] = name
340 replacemap['$0'] = name
342 replacemap['$$'] = '$'
341 replacemap['$$'] = '$'
343 replacemap['$@'] = ' '.join(args)
342 replacemap['$@'] = ' '.join(args)
344 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
343 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
345 # parameters, separated out into words. Emulate the same behavior here by
344 # parameters, separated out into words. Emulate the same behavior here by
346 # quoting the arguments individually. POSIX shells will then typically
345 # quoting the arguments individually. POSIX shells will then typically
347 # tokenize each argument into exactly one word.
346 # tokenize each argument into exactly one word.
348 replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
347 replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
349 # escape '\$' for regex
348 # escape '\$' for regex
350 regex = '|'.join(replacemap.keys()).replace('$', r'\$')
349 regex = '|'.join(replacemap.keys()).replace('$', r'\$')
351 r = re.compile(regex)
350 r = re.compile(regex)
352 return r.sub(lambda x: replacemap[x.group()], cmd)
351 return r.sub(lambda x: replacemap[x.group()], cmd)
353
352
354 class cmdalias(object):
353 class cmdalias(object):
355 def __init__(self, name, definition, cmdtable, source):
354 def __init__(self, name, definition, cmdtable, source):
356 self.name = self.cmd = name
355 self.name = self.cmd = name
357 self.cmdname = ''
356 self.cmdname = ''
358 self.definition = definition
357 self.definition = definition
359 self.fn = None
358 self.fn = None
360 self.givenargs = []
359 self.givenargs = []
361 self.opts = []
360 self.opts = []
362 self.help = ''
361 self.help = ''
363 self.badalias = None
362 self.badalias = None
364 self.unknowncmd = False
363 self.unknowncmd = False
365 self.source = source
364 self.source = source
366
365
367 try:
366 try:
368 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
367 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
369 for alias, e in cmdtable.iteritems():
368 for alias, e in cmdtable.iteritems():
370 if e is entry:
369 if e is entry:
371 self.cmd = alias
370 self.cmd = alias
372 break
371 break
373 self.shadows = True
372 self.shadows = True
374 except error.UnknownCommand:
373 except error.UnknownCommand:
375 self.shadows = False
374 self.shadows = False
376
375
377 if not self.definition:
376 if not self.definition:
378 self.badalias = _("no definition for alias '%s'") % self.name
377 self.badalias = _("no definition for alias '%s'") % self.name
379 return
378 return
380
379
381 if self.definition.startswith('!'):
380 if self.definition.startswith('!'):
382 self.shell = True
381 self.shell = True
383 def fn(ui, *args):
382 def fn(ui, *args):
384 env = {'HG_ARGS': ' '.join((self.name,) + args)}
383 env = {'HG_ARGS': ' '.join((self.name,) + args)}
385 def _checkvar(m):
384 def _checkvar(m):
386 if m.groups()[0] == '$':
385 if m.groups()[0] == '$':
387 return m.group()
386 return m.group()
388 elif int(m.groups()[0]) <= len(args):
387 elif int(m.groups()[0]) <= len(args):
389 return m.group()
388 return m.group()
390 else:
389 else:
391 ui.debug("No argument found for substitution "
390 ui.debug("No argument found for substitution "
392 "of %i variable in alias '%s' definition."
391 "of %i variable in alias '%s' definition."
393 % (int(m.groups()[0]), self.name))
392 % (int(m.groups()[0]), self.name))
394 return ''
393 return ''
395 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
394 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
396 cmd = aliasinterpolate(self.name, args, cmd)
395 cmd = aliasinterpolate(self.name, args, cmd)
397 return ui.system(cmd, environ=env,
396 return ui.system(cmd, environ=env,
398 blockedtag='alias_%s' % self.name)
397 blockedtag='alias_%s' % self.name)
399 self.fn = fn
398 self.fn = fn
400 return
399 return
401
400
402 try:
401 try:
403 args = pycompat.shlexsplit(self.definition)
402 args = pycompat.shlexsplit(self.definition)
404 except ValueError as inst:
403 except ValueError as inst:
405 self.badalias = (_("error in definition for alias '%s': %s")
404 self.badalias = (_("error in definition for alias '%s': %s")
406 % (self.name, inst))
405 % (self.name, inst))
407 return
406 return
408 self.cmdname = cmd = args.pop(0)
407 self.cmdname = cmd = args.pop(0)
409 self.givenargs = args
408 self.givenargs = args
410
409
411 for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"):
410 for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"):
412 if _earlygetopt([invalidarg], args):
411 if _earlygetopt([invalidarg], args):
413 self.badalias = (_("error in definition for alias '%s': %s may "
412 self.badalias = (_("error in definition for alias '%s': %s may "
414 "only be given on the command line")
413 "only be given on the command line")
415 % (self.name, invalidarg))
414 % (self.name, invalidarg))
416 return
415 return
417
416
418 try:
417 try:
419 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
418 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
420 if len(tableentry) > 2:
419 if len(tableentry) > 2:
421 self.fn, self.opts, self.help = tableentry
420 self.fn, self.opts, self.help = tableentry
422 else:
421 else:
423 self.fn, self.opts = tableentry
422 self.fn, self.opts = tableentry
424
423
425 if self.help.startswith("hg " + cmd):
424 if self.help.startswith("hg " + cmd):
426 # drop prefix in old-style help lines so hg shows the alias
425 # drop prefix in old-style help lines so hg shows the alias
427 self.help = self.help[4 + len(cmd):]
426 self.help = self.help[4 + len(cmd):]
428 self.__doc__ = self.fn.__doc__
427 self.__doc__ = self.fn.__doc__
429
428
430 except error.UnknownCommand:
429 except error.UnknownCommand:
431 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
430 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
432 % (self.name, cmd))
431 % (self.name, cmd))
433 self.unknowncmd = True
432 self.unknowncmd = True
434 except error.AmbiguousCommand:
433 except error.AmbiguousCommand:
435 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
434 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
436 % (self.name, cmd))
435 % (self.name, cmd))
437
436
438 @property
437 @property
439 def args(self):
438 def args(self):
440 args = pycompat.maplist(util.expandpath, self.givenargs)
439 args = pycompat.maplist(util.expandpath, self.givenargs)
441 return aliasargs(self.fn, args)
440 return aliasargs(self.fn, args)
442
441
443 def __getattr__(self, name):
442 def __getattr__(self, name):
444 adefaults = {'norepo': True, 'optionalrepo': False, 'inferrepo': False}
443 adefaults = {'norepo': True, 'optionalrepo': False, 'inferrepo': False}
445 if name not in adefaults:
444 if name not in adefaults:
446 raise AttributeError(name)
445 raise AttributeError(name)
447 if self.badalias or util.safehasattr(self, 'shell'):
446 if self.badalias or util.safehasattr(self, 'shell'):
448 return adefaults[name]
447 return adefaults[name]
449 return getattr(self.fn, name)
448 return getattr(self.fn, name)
450
449
451 def __call__(self, ui, *args, **opts):
450 def __call__(self, ui, *args, **opts):
452 if self.badalias:
451 if self.badalias:
453 hint = None
452 hint = None
454 if self.unknowncmd:
453 if self.unknowncmd:
455 try:
454 try:
456 # check if the command is in a disabled extension
455 # check if the command is in a disabled extension
457 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
456 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
458 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
457 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
459 except error.UnknownCommand:
458 except error.UnknownCommand:
460 pass
459 pass
461 raise error.Abort(self.badalias, hint=hint)
460 raise error.Abort(self.badalias, hint=hint)
462 if self.shadows:
461 if self.shadows:
463 ui.debug("alias '%s' shadows command '%s'\n" %
462 ui.debug("alias '%s' shadows command '%s'\n" %
464 (self.name, self.cmdname))
463 (self.name, self.cmdname))
465
464
466 ui.log('commandalias', "alias '%s' expands to '%s'\n",
465 ui.log('commandalias', "alias '%s' expands to '%s'\n",
467 self.name, self.definition)
466 self.name, self.definition)
468 if util.safehasattr(self, 'shell'):
467 if util.safehasattr(self, 'shell'):
469 return self.fn(ui, *args, **opts)
468 return self.fn(ui, *args, **opts)
470 else:
469 else:
471 try:
470 try:
472 return util.checksignature(self.fn)(ui, *args, **opts)
471 return util.checksignature(self.fn)(ui, *args, **opts)
473 except error.SignatureError:
472 except error.SignatureError:
474 args = ' '.join([self.cmdname] + self.args)
473 args = ' '.join([self.cmdname] + self.args)
475 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
474 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
476 raise
475 raise
477
476
478 def addaliases(ui, cmdtable):
477 def addaliases(ui, cmdtable):
479 # aliases are processed after extensions have been loaded, so they
478 # aliases are processed after extensions have been loaded, so they
480 # may use extension commands. Aliases can also use other alias definitions,
479 # may use extension commands. Aliases can also use other alias definitions,
481 # but only if they have been defined prior to the current definition.
480 # but only if they have been defined prior to the current definition.
482 for alias, definition in ui.configitems('alias'):
481 for alias, definition in ui.configitems('alias'):
483 source = ui.configsource('alias', alias)
482 source = ui.configsource('alias', alias)
484 aliasdef = cmdalias(alias, definition, cmdtable, source)
483 aliasdef = cmdalias(alias, definition, cmdtable, source)
485
484
486 try:
485 try:
487 olddef = cmdtable[aliasdef.cmd][0]
486 olddef = cmdtable[aliasdef.cmd][0]
488 if olddef.definition == aliasdef.definition:
487 if olddef.definition == aliasdef.definition:
489 continue
488 continue
490 except (KeyError, AttributeError):
489 except (KeyError, AttributeError):
491 # definition might not exist or it might not be a cmdalias
490 # definition might not exist or it might not be a cmdalias
492 pass
491 pass
493
492
494 cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help)
493 cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help)
495
494
496 def _parse(ui, args):
495 def _parse(ui, args):
497 options = {}
496 options = {}
498 cmdoptions = {}
497 cmdoptions = {}
499
498
500 try:
499 try:
501 args = fancyopts.fancyopts(args, commands.globalopts, options)
500 args = fancyopts.fancyopts(args, commands.globalopts, options)
502 except getopt.GetoptError as inst:
501 except getopt.GetoptError as inst:
503 raise error.CommandError(None, inst)
502 raise error.CommandError(None, inst)
504
503
505 if args:
504 if args:
506 cmd, args = args[0], args[1:]
505 cmd, args = args[0], args[1:]
507 aliases, entry = cmdutil.findcmd(cmd, commands.table,
506 aliases, entry = cmdutil.findcmd(cmd, commands.table,
508 ui.configbool("ui", "strict"))
507 ui.configbool("ui", "strict"))
509 cmd = aliases[0]
508 cmd = aliases[0]
510 args = aliasargs(entry[0], args)
509 args = aliasargs(entry[0], args)
511 defaults = ui.config("defaults", cmd)
510 defaults = ui.config("defaults", cmd)
512 if defaults:
511 if defaults:
513 args = pycompat.maplist(
512 args = pycompat.maplist(
514 util.expandpath, pycompat.shlexsplit(defaults)) + args
513 util.expandpath, pycompat.shlexsplit(defaults)) + args
515 c = list(entry[1])
514 c = list(entry[1])
516 else:
515 else:
517 cmd = None
516 cmd = None
518 c = []
517 c = []
519
518
520 # combine global options into local
519 # combine global options into local
521 for o in commands.globalopts:
520 for o in commands.globalopts:
522 c.append((o[0], o[1], options[o[1]], o[3]))
521 c.append((o[0], o[1], options[o[1]], o[3]))
523
522
524 try:
523 try:
525 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
524 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
526 except getopt.GetoptError as inst:
525 except getopt.GetoptError as inst:
527 raise error.CommandError(cmd, inst)
526 raise error.CommandError(cmd, inst)
528
527
529 # separate global options back out
528 # separate global options back out
530 for o in commands.globalopts:
529 for o in commands.globalopts:
531 n = o[1]
530 n = o[1]
532 options[n] = cmdoptions[n]
531 options[n] = cmdoptions[n]
533 del cmdoptions[n]
532 del cmdoptions[n]
534
533
535 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
534 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
536
535
537 def _parseconfig(ui, config):
536 def _parseconfig(ui, config):
538 """parse the --config options from the command line"""
537 """parse the --config options from the command line"""
539 configs = []
538 configs = []
540
539
541 for cfg in config:
540 for cfg in config:
542 try:
541 try:
543 name, value = [cfgelem.strip()
542 name, value = [cfgelem.strip()
544 for cfgelem in cfg.split('=', 1)]
543 for cfgelem in cfg.split('=', 1)]
545 section, name = name.split('.', 1)
544 section, name = name.split('.', 1)
546 if not section or not name:
545 if not section or not name:
547 raise IndexError
546 raise IndexError
548 ui.setconfig(section, name, value, '--config')
547 ui.setconfig(section, name, value, '--config')
549 configs.append((section, name, value))
548 configs.append((section, name, value))
550 except (IndexError, ValueError):
549 except (IndexError, ValueError):
551 raise error.Abort(_('malformed --config option: %r '
550 raise error.Abort(_('malformed --config option: %r '
552 '(use --config section.name=value)') % cfg)
551 '(use --config section.name=value)') % cfg)
553
552
554 return configs
553 return configs
555
554
556 def _earlygetopt(aliases, args):
555 def _earlygetopt(aliases, args):
557 """Return list of values for an option (or aliases).
556 """Return list of values for an option (or aliases).
558
557
559 The values are listed in the order they appear in args.
558 The values are listed in the order they appear in args.
560 The options and values are removed from args.
559 The options and values are removed from args.
561
560
562 >>> args = ['x', '--cwd', 'foo', 'y']
561 >>> args = ['x', '--cwd', 'foo', 'y']
563 >>> _earlygetopt(['--cwd'], args), args
562 >>> _earlygetopt(['--cwd'], args), args
564 (['foo'], ['x', 'y'])
563 (['foo'], ['x', 'y'])
565
564
566 >>> args = ['x', '--cwd=bar', 'y']
565 >>> args = ['x', '--cwd=bar', 'y']
567 >>> _earlygetopt(['--cwd'], args), args
566 >>> _earlygetopt(['--cwd'], args), args
568 (['bar'], ['x', 'y'])
567 (['bar'], ['x', 'y'])
569
568
570 >>> args = ['x', '-R', 'foo', 'y']
569 >>> args = ['x', '-R', 'foo', 'y']
571 >>> _earlygetopt(['-R'], args), args
570 >>> _earlygetopt(['-R'], args), args
572 (['foo'], ['x', 'y'])
571 (['foo'], ['x', 'y'])
573
572
574 >>> args = ['x', '-Rbar', 'y']
573 >>> args = ['x', '-Rbar', 'y']
575 >>> _earlygetopt(['-R'], args), args
574 >>> _earlygetopt(['-R'], args), args
576 (['bar'], ['x', 'y'])
575 (['bar'], ['x', 'y'])
577 """
576 """
578 try:
577 try:
579 argcount = args.index("--")
578 argcount = args.index("--")
580 except ValueError:
579 except ValueError:
581 argcount = len(args)
580 argcount = len(args)
582 shortopts = [opt for opt in aliases if len(opt) == 2]
581 shortopts = [opt for opt in aliases if len(opt) == 2]
583 values = []
582 values = []
584 pos = 0
583 pos = 0
585 while pos < argcount:
584 while pos < argcount:
586 fullarg = arg = args[pos]
585 fullarg = arg = args[pos]
587 equals = arg.find('=')
586 equals = arg.find('=')
588 if equals > -1:
587 if equals > -1:
589 arg = arg[:equals]
588 arg = arg[:equals]
590 if arg in aliases:
589 if arg in aliases:
591 del args[pos]
590 del args[pos]
592 if equals > -1:
591 if equals > -1:
593 values.append(fullarg[equals + 1:])
592 values.append(fullarg[equals + 1:])
594 argcount -= 1
593 argcount -= 1
595 else:
594 else:
596 if pos + 1 >= argcount:
595 if pos + 1 >= argcount:
597 # ignore and let getopt report an error if there is no value
596 # ignore and let getopt report an error if there is no value
598 break
597 break
599 values.append(args.pop(pos))
598 values.append(args.pop(pos))
600 argcount -= 2
599 argcount -= 2
601 elif arg[:2] in shortopts:
600 elif arg[:2] in shortopts:
602 # short option can have no following space, e.g. hg log -Rfoo
601 # short option can have no following space, e.g. hg log -Rfoo
603 values.append(args.pop(pos)[2:])
602 values.append(args.pop(pos)[2:])
604 argcount -= 1
603 argcount -= 1
605 else:
604 else:
606 pos += 1
605 pos += 1
607 return values
606 return values
608
607
609 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
608 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
610 # run pre-hook, and abort if it fails
609 # run pre-hook, and abort if it fails
611 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
610 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
612 pats=cmdpats, opts=cmdoptions)
611 pats=cmdpats, opts=cmdoptions)
613 try:
612 try:
614 ret = _runcommand(ui, options, cmd, d)
613 ret = _runcommand(ui, options, cmd, d)
615 # run post-hook, passing command result
614 # run post-hook, passing command result
616 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
615 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
617 result=ret, pats=cmdpats, opts=cmdoptions)
616 result=ret, pats=cmdpats, opts=cmdoptions)
618 except Exception:
617 except Exception:
619 # run failure hook and re-raise
618 # run failure hook and re-raise
620 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
619 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
621 pats=cmdpats, opts=cmdoptions)
620 pats=cmdpats, opts=cmdoptions)
622 raise
621 raise
623 return ret
622 return ret
624
623
625 def _getlocal(ui, rpath, wd=None):
624 def _getlocal(ui, rpath, wd=None):
626 """Return (path, local ui object) for the given target path.
625 """Return (path, local ui object) for the given target path.
627
626
628 Takes paths in [cwd]/.hg/hgrc into account."
627 Takes paths in [cwd]/.hg/hgrc into account."
629 """
628 """
630 if wd is None:
629 if wd is None:
631 try:
630 try:
632 wd = pycompat.getcwd()
631 wd = pycompat.getcwd()
633 except OSError as e:
632 except OSError as e:
634 raise error.Abort(_("error getting current working directory: %s") %
633 raise error.Abort(_("error getting current working directory: %s") %
635 e.strerror)
634 e.strerror)
636 path = cmdutil.findrepo(wd) or ""
635 path = cmdutil.findrepo(wd) or ""
637 if not path:
636 if not path:
638 lui = ui
637 lui = ui
639 else:
638 else:
640 lui = ui.copy()
639 lui = ui.copy()
641 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
640 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
642
641
643 if rpath and rpath[-1]:
642 if rpath and rpath[-1]:
644 path = lui.expandpath(rpath[-1])
643 path = lui.expandpath(rpath[-1])
645 lui = ui.copy()
644 lui = ui.copy()
646 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
645 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
647
646
648 return path, lui
647 return path, lui
649
648
650 def _checkshellalias(lui, ui, args):
649 def _checkshellalias(lui, ui, args):
651 """Return the function to run the shell alias, if it is required"""
650 """Return the function to run the shell alias, if it is required"""
652 options = {}
651 options = {}
653
652
654 try:
653 try:
655 args = fancyopts.fancyopts(args, commands.globalopts, options)
654 args = fancyopts.fancyopts(args, commands.globalopts, options)
656 except getopt.GetoptError:
655 except getopt.GetoptError:
657 return
656 return
658
657
659 if not args:
658 if not args:
660 return
659 return
661
660
662 cmdtable = commands.table
661 cmdtable = commands.table
663
662
664 cmd = args[0]
663 cmd = args[0]
665 try:
664 try:
666 strict = ui.configbool("ui", "strict")
665 strict = ui.configbool("ui", "strict")
667 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
666 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
668 except (error.AmbiguousCommand, error.UnknownCommand):
667 except (error.AmbiguousCommand, error.UnknownCommand):
669 return
668 return
670
669
671 cmd = aliases[0]
670 cmd = aliases[0]
672 fn = entry[0]
671 fn = entry[0]
673
672
674 if cmd and util.safehasattr(fn, 'shell'):
673 if cmd and util.safehasattr(fn, 'shell'):
675 d = lambda: fn(ui, *args[1:])
674 d = lambda: fn(ui, *args[1:])
676 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
675 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
677 [], {})
676 [], {})
678
677
679 _loaded = set()
678 _loaded = set()
680
679
681 # list of (objname, loadermod, loadername) tuple:
680 # list of (objname, loadermod, loadername) tuple:
682 # - objname is the name of an object in extension module, from which
681 # - objname is the name of an object in extension module, from which
683 # extra information is loaded
682 # extra information is loaded
684 # - loadermod is the module where loader is placed
683 # - loadermod is the module where loader is placed
685 # - loadername is the name of the function, which takes (ui, extensionname,
684 # - loadername is the name of the function, which takes (ui, extensionname,
686 # extraobj) arguments
685 # extraobj) arguments
687 extraloaders = [
686 extraloaders = [
688 ('cmdtable', commands, 'loadcmdtable'),
687 ('cmdtable', commands, 'loadcmdtable'),
689 ('colortable', color, 'loadcolortable'),
688 ('colortable', color, 'loadcolortable'),
690 ('filesetpredicate', fileset, 'loadpredicate'),
689 ('filesetpredicate', fileset, 'loadpredicate'),
691 ('revsetpredicate', revset, 'loadpredicate'),
690 ('revsetpredicate', revset, 'loadpredicate'),
692 ('templatefilter', templatefilters, 'loadfilter'),
691 ('templatefilter', templatefilters, 'loadfilter'),
693 ('templatefunc', templater, 'loadfunction'),
692 ('templatefunc', templater, 'loadfunction'),
694 ('templatekeyword', templatekw, 'loadkeyword'),
693 ('templatekeyword', templatekw, 'loadkeyword'),
695 ]
694 ]
696
695
697 def _dispatch(req):
696 def _dispatch(req):
698 args = req.args
697 args = req.args
699 ui = req.ui
698 ui = req.ui
700
699
701 # check for cwd
700 # check for cwd
702 cwd = _earlygetopt(['--cwd'], args)
701 cwd = _earlygetopt(['--cwd'], args)
703 if cwd:
702 if cwd:
704 os.chdir(cwd[-1])
703 os.chdir(cwd[-1])
705
704
706 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
705 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
707 path, lui = _getlocal(ui, rpath)
706 path, lui = _getlocal(ui, rpath)
708
707
709 # Side-effect of accessing is debugcommands module is guaranteed to be
708 # Side-effect of accessing is debugcommands module is guaranteed to be
710 # imported and commands.table is populated.
709 # imported and commands.table is populated.
711 debugcommands.command
710 debugcommands.command
712
711
713 uis = set([ui, lui])
712 uis = set([ui, lui])
714
713
715 if req.repo:
714 if req.repo:
716 uis.add(req.repo.ui)
715 uis.add(req.repo.ui)
717
716
718 if '--profile' in args:
717 if '--profile' in args:
719 for ui_ in uis:
718 for ui_ in uis:
720 ui_.setconfig('profiling', 'enabled', 'true', '--profile')
719 ui_.setconfig('profiling', 'enabled', 'true', '--profile')
721
720
722 with profiling.maybeprofile(lui):
721 with profiling.maybeprofile(lui):
723 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
722 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
724 # reposetup. Programs like TortoiseHg will call _dispatch several
723 # reposetup. Programs like TortoiseHg will call _dispatch several
725 # times so we keep track of configured extensions in _loaded.
724 # times so we keep track of configured extensions in _loaded.
726 extensions.loadall(lui)
725 extensions.loadall(lui)
727 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
726 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
728 # Propagate any changes to lui.__class__ by extensions
727 # Propagate any changes to lui.__class__ by extensions
729 ui.__class__ = lui.__class__
728 ui.__class__ = lui.__class__
730
729
731 # (uisetup and extsetup are handled in extensions.loadall)
730 # (uisetup and extsetup are handled in extensions.loadall)
732
731
733 for name, module in exts:
732 for name, module in exts:
734 for objname, loadermod, loadername in extraloaders:
733 for objname, loadermod, loadername in extraloaders:
735 extraobj = getattr(module, objname, None)
734 extraobj = getattr(module, objname, None)
736 if extraobj is not None:
735 if extraobj is not None:
737 getattr(loadermod, loadername)(ui, name, extraobj)
736 getattr(loadermod, loadername)(ui, name, extraobj)
738 _loaded.add(name)
737 _loaded.add(name)
739
738
740 # (reposetup is handled in hg.repository)
739 # (reposetup is handled in hg.repository)
741
740
742 addaliases(lui, commands.table)
741 addaliases(lui, commands.table)
743
742
744 # All aliases and commands are completely defined, now.
743 # All aliases and commands are completely defined, now.
745 # Check abbreviation/ambiguity of shell alias.
744 # Check abbreviation/ambiguity of shell alias.
746 shellaliasfn = _checkshellalias(lui, ui, args)
745 shellaliasfn = _checkshellalias(lui, ui, args)
747 if shellaliasfn:
746 if shellaliasfn:
748 return shellaliasfn()
747 return shellaliasfn()
749
748
750 # check for fallback encoding
749 # check for fallback encoding
751 fallback = lui.config('ui', 'fallbackencoding')
750 fallback = lui.config('ui', 'fallbackencoding')
752 if fallback:
751 if fallback:
753 encoding.fallbackencoding = fallback
752 encoding.fallbackencoding = fallback
754
753
755 fullargs = args
754 fullargs = args
756 cmd, func, args, options, cmdoptions = _parse(lui, args)
755 cmd, func, args, options, cmdoptions = _parse(lui, args)
757
756
758 if options["config"]:
757 if options["config"]:
759 raise error.Abort(_("option --config may not be abbreviated!"))
758 raise error.Abort(_("option --config may not be abbreviated!"))
760 if options["cwd"]:
759 if options["cwd"]:
761 raise error.Abort(_("option --cwd may not be abbreviated!"))
760 raise error.Abort(_("option --cwd may not be abbreviated!"))
762 if options["repository"]:
761 if options["repository"]:
763 raise error.Abort(_(
762 raise error.Abort(_(
764 "option -R has to be separated from other options (e.g. not "
763 "option -R has to be separated from other options (e.g. not "
765 "-qR) and --repository may only be abbreviated as --repo!"))
764 "-qR) and --repository may only be abbreviated as --repo!"))
766
765
767 if options["encoding"]:
766 if options["encoding"]:
768 encoding.encoding = options["encoding"]
767 encoding.encoding = options["encoding"]
769 if options["encodingmode"]:
768 if options["encodingmode"]:
770 encoding.encodingmode = options["encodingmode"]
769 encoding.encodingmode = options["encodingmode"]
771 if options["time"]:
770 if options["time"]:
772 def get_times():
771 def get_times():
773 t = os.times()
772 t = os.times()
774 if t[4] == 0.0:
773 if t[4] == 0.0:
775 # Windows leaves this as zero, so use time.clock()
774 # Windows leaves this as zero, so use time.clock()
776 t = (t[0], t[1], t[2], t[3], time.clock())
775 t = (t[0], t[1], t[2], t[3], time.clock())
777 return t
776 return t
778 s = get_times()
777 s = get_times()
779 def print_time():
778 def print_time():
780 t = get_times()
779 t = get_times()
781 ui.warn(
780 ui.warn(
782 _("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
781 _("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
783 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
782 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
784 ui.atexit(print_time)
783 ui.atexit(print_time)
785
784
786 if options['verbose'] or options['debug'] or options['quiet']:
785 if options['verbose'] or options['debug'] or options['quiet']:
787 for opt in ('verbose', 'debug', 'quiet'):
786 for opt in ('verbose', 'debug', 'quiet'):
788 val = str(bool(options[opt]))
787 val = str(bool(options[opt]))
789 if pycompat.ispy3:
788 if pycompat.ispy3:
790 val = val.encode('ascii')
789 val = val.encode('ascii')
791 for ui_ in uis:
790 for ui_ in uis:
792 ui_.setconfig('ui', opt, val, '--' + opt)
791 ui_.setconfig('ui', opt, val, '--' + opt)
793
792
794 if options['traceback']:
793 if options['traceback']:
795 for ui_ in uis:
794 for ui_ in uis:
796 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
795 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
797
796
798 if options['noninteractive']:
797 if options['noninteractive']:
799 for ui_ in uis:
798 for ui_ in uis:
800 ui_.setconfig('ui', 'interactive', 'off', '-y')
799 ui_.setconfig('ui', 'interactive', 'off', '-y')
801
800
802 if util.parsebool(options['pager']):
801 if util.parsebool(options['pager']):
803 ui.pager('internal-always-' + cmd)
802 ui.pager('internal-always-' + cmd)
804 elif options['pager'] != 'auto':
803 elif options['pager'] != 'auto':
805 ui.disablepager()
804 ui.disablepager()
806
805
807 if cmdoptions.get('insecure', False):
806 if cmdoptions.get('insecure', False):
808 for ui_ in uis:
807 for ui_ in uis:
809 ui_.insecureconnections = True
808 ui_.insecureconnections = True
810
809
811 # setup color handling
810 # setup color handling
812 coloropt = options['color']
811 coloropt = options['color']
813 for ui_ in uis:
812 for ui_ in uis:
814 if coloropt:
813 if coloropt:
815 ui_.setconfig('ui', 'color', coloropt, '--color')
814 ui_.setconfig('ui', 'color', coloropt, '--color')
816 color.setup(ui_)
815 color.setup(ui_)
817
816
818 if options['version']:
817 if options['version']:
819 return commands.version_(ui)
818 return commands.version_(ui)
820 if options['help']:
819 if options['help']:
821 return commands.help_(ui, cmd, command=cmd is not None)
820 return commands.help_(ui, cmd, command=cmd is not None)
822 elif not cmd:
821 elif not cmd:
823 return commands.help_(ui, 'shortlist')
822 return commands.help_(ui, 'shortlist')
824
823
825 repo = None
824 repo = None
826 cmdpats = args[:]
825 cmdpats = args[:]
827 if not func.norepo:
826 if not func.norepo:
828 # use the repo from the request only if we don't have -R
827 # use the repo from the request only if we don't have -R
829 if not rpath and not cwd:
828 if not rpath and not cwd:
830 repo = req.repo
829 repo = req.repo
831
830
832 if repo:
831 if repo:
833 # set the descriptors of the repo ui to those of ui
832 # set the descriptors of the repo ui to those of ui
834 repo.ui.fin = ui.fin
833 repo.ui.fin = ui.fin
835 repo.ui.fout = ui.fout
834 repo.ui.fout = ui.fout
836 repo.ui.ferr = ui.ferr
835 repo.ui.ferr = ui.ferr
837 else:
836 else:
838 try:
837 try:
839 repo = hg.repository(ui, path=path)
838 repo = hg.repository(ui, path=path)
840 if not repo.local():
839 if not repo.local():
841 raise error.Abort(_("repository '%s' is not local")
840 raise error.Abort(_("repository '%s' is not local")
842 % path)
841 % path)
843 repo.ui.setconfig("bundle", "mainreporoot", repo.root,
842 repo.ui.setconfig("bundle", "mainreporoot", repo.root,
844 'repo')
843 'repo')
845 except error.RequirementError:
844 except error.RequirementError:
846 raise
845 raise
847 except error.RepoError:
846 except error.RepoError:
848 if rpath and rpath[-1]: # invalid -R path
847 if rpath and rpath[-1]: # invalid -R path
849 raise
848 raise
850 if not func.optionalrepo:
849 if not func.optionalrepo:
851 if func.inferrepo and args and not path:
850 if func.inferrepo and args and not path:
852 # try to infer -R from command args
851 # try to infer -R from command args
853 repos = map(cmdutil.findrepo, args)
852 repos = map(cmdutil.findrepo, args)
854 guess = repos[0]
853 guess = repos[0]
855 if guess and repos.count(guess) == len(repos):
854 if guess and repos.count(guess) == len(repos):
856 req.args = ['--repository', guess] + fullargs
855 req.args = ['--repository', guess] + fullargs
857 return _dispatch(req)
856 return _dispatch(req)
858 if not path:
857 if not path:
859 raise error.RepoError(_("no repository found in"
858 raise error.RepoError(_("no repository found in"
860 " '%s' (.hg not found)")
859 " '%s' (.hg not found)")
861 % pycompat.getcwd())
860 % pycompat.getcwd())
862 raise
861 raise
863 if repo:
862 if repo:
864 ui = repo.ui
863 ui = repo.ui
865 if options['hidden']:
864 if options['hidden']:
866 repo = repo.unfiltered()
865 repo = repo.unfiltered()
867 args.insert(0, repo)
866 args.insert(0, repo)
868 elif rpath:
867 elif rpath:
869 ui.warn(_("warning: --repository ignored\n"))
868 ui.warn(_("warning: --repository ignored\n"))
870
869
871 msg = _formatargs(fullargs)
870 msg = _formatargs(fullargs)
872 ui.log("command", '%s\n', msg)
871 ui.log("command", '%s\n', msg)
873 strcmdopt = pycompat.strkwargs(cmdoptions)
872 strcmdopt = pycompat.strkwargs(cmdoptions)
874 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
873 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
875 try:
874 try:
876 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
875 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
877 cmdpats, cmdoptions)
876 cmdpats, cmdoptions)
878 finally:
877 finally:
879 if repo and repo != req.repo:
878 if repo and repo != req.repo:
880 repo.close()
879 repo.close()
881
880
882 def _runcommand(ui, options, cmd, cmdfunc):
881 def _runcommand(ui, options, cmd, cmdfunc):
883 """Run a command function, possibly with profiling enabled."""
882 """Run a command function, possibly with profiling enabled."""
884 try:
883 try:
885 return cmdfunc()
884 return cmdfunc()
886 except error.SignatureError:
885 except error.SignatureError:
887 raise error.CommandError(cmd, _('invalid arguments'))
886 raise error.CommandError(cmd, _('invalid arguments'))
888
887
889 def _exceptionwarning(ui):
888 def _exceptionwarning(ui):
890 """Produce a warning message for the current active exception"""
889 """Produce a warning message for the current active exception"""
891
890
892 # For compatibility checking, we discard the portion of the hg
891 # For compatibility checking, we discard the portion of the hg
893 # version after the + on the assumption that if a "normal
892 # version after the + on the assumption that if a "normal
894 # user" is running a build with a + in it the packager
893 # user" is running a build with a + in it the packager
895 # probably built from fairly close to a tag and anyone with a
894 # probably built from fairly close to a tag and anyone with a
896 # 'make local' copy of hg (where the version number can be out
895 # 'make local' copy of hg (where the version number can be out
897 # of date) will be clueful enough to notice the implausible
896 # of date) will be clueful enough to notice the implausible
898 # version number and try updating.
897 # version number and try updating.
899 ct = util.versiontuple(n=2)
898 ct = util.versiontuple(n=2)
900 worst = None, ct, ''
899 worst = None, ct, ''
901 if ui.config('ui', 'supportcontact', None) is None:
900 if ui.config('ui', 'supportcontact', None) is None:
902 for name, mod in extensions.extensions():
901 for name, mod in extensions.extensions():
903 testedwith = getattr(mod, 'testedwith', '')
902 testedwith = getattr(mod, 'testedwith', '')
904 if pycompat.ispy3 and isinstance(testedwith, str):
903 if pycompat.ispy3 and isinstance(testedwith, str):
905 testedwith = testedwith.encode(u'utf-8')
904 testedwith = testedwith.encode(u'utf-8')
906 report = getattr(mod, 'buglink', _('the extension author.'))
905 report = getattr(mod, 'buglink', _('the extension author.'))
907 if not testedwith.strip():
906 if not testedwith.strip():
908 # We found an untested extension. It's likely the culprit.
907 # We found an untested extension. It's likely the culprit.
909 worst = name, 'unknown', report
908 worst = name, 'unknown', report
910 break
909 break
911
910
912 # Never blame on extensions bundled with Mercurial.
911 # Never blame on extensions bundled with Mercurial.
913 if extensions.ismoduleinternal(mod):
912 if extensions.ismoduleinternal(mod):
914 continue
913 continue
915
914
916 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
915 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
917 if ct in tested:
916 if ct in tested:
918 continue
917 continue
919
918
920 lower = [t for t in tested if t < ct]
919 lower = [t for t in tested if t < ct]
921 nearest = max(lower or tested)
920 nearest = max(lower or tested)
922 if worst[0] is None or nearest < worst[1]:
921 if worst[0] is None or nearest < worst[1]:
923 worst = name, nearest, report
922 worst = name, nearest, report
924 if worst[0] is not None:
923 if worst[0] is not None:
925 name, testedwith, report = worst
924 name, testedwith, report = worst
926 if not isinstance(testedwith, (bytes, str)):
925 if not isinstance(testedwith, (bytes, str)):
927 testedwith = '.'.join([str(c) for c in testedwith])
926 testedwith = '.'.join([str(c) for c in testedwith])
928 warning = (_('** Unknown exception encountered with '
927 warning = (_('** Unknown exception encountered with '
929 'possibly-broken third-party extension %s\n'
928 'possibly-broken third-party extension %s\n'
930 '** which supports versions %s of Mercurial.\n'
929 '** which supports versions %s of Mercurial.\n'
931 '** Please disable %s and try your action again.\n'
930 '** Please disable %s and try your action again.\n'
932 '** If that fixes the bug please report it to %s\n')
931 '** If that fixes the bug please report it to %s\n')
933 % (name, testedwith, name, report))
932 % (name, testedwith, name, report))
934 else:
933 else:
935 bugtracker = ui.config('ui', 'supportcontact', None)
934 bugtracker = ui.config('ui', 'supportcontact', None)
936 if bugtracker is None:
935 if bugtracker is None:
937 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
936 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
938 warning = (_("** unknown exception encountered, "
937 warning = (_("** unknown exception encountered, "
939 "please report by visiting\n** ") + bugtracker + '\n')
938 "please report by visiting\n** ") + bugtracker + '\n')
940 if pycompat.ispy3:
939 if pycompat.ispy3:
941 sysversion = sys.version.encode(u'utf-8')
940 sysversion = sys.version.encode(u'utf-8')
942 else:
941 else:
943 sysversion = sys.version
942 sysversion = sys.version
944 sysversion = sysversion.replace('\n', '')
943 sysversion = sysversion.replace('\n', '')
945 warning += ((_("** Python %s\n") % sysversion) +
944 warning += ((_("** Python %s\n") % sysversion) +
946 (_("** Mercurial Distributed SCM (version %s)\n") %
945 (_("** Mercurial Distributed SCM (version %s)\n") %
947 util.version()) +
946 util.version()) +
948 (_("** Extensions loaded: %s\n") %
947 (_("** Extensions loaded: %s\n") %
949 ", ".join([x[0] for x in extensions.extensions()])))
948 ", ".join([x[0] for x in extensions.extensions()])))
950 return warning
949 return warning
951
950
952 def handlecommandexception(ui):
951 def handlecommandexception(ui):
953 """Produce a warning message for broken commands
952 """Produce a warning message for broken commands
954
953
955 Called when handling an exception; the exception is reraised if
954 Called when handling an exception; the exception is reraised if
956 this function returns False, ignored otherwise.
955 this function returns False, ignored otherwise.
957 """
956 """
958 warning = _exceptionwarning(ui)
957 warning = _exceptionwarning(ui)
959 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
958 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
960 ui.warn(warning)
959 ui.warn(warning)
961 return False # re-raise the exception
960 return False # re-raise the exception
@@ -1,972 +1,976 b''
1 # scmutil.py - Mercurial core utility functions
1 # scmutil.py - Mercurial core utility functions
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 glob
11 import glob
12 import hashlib
12 import hashlib
13 import os
13 import os
14 import re
14 import re
15 import socket
15 import socket
16
16
17 from .i18n import _
17 from .i18n import _
18 from .node import wdirrev
18 from .node import wdirrev
19 from . import (
19 from . import (
20 encoding,
20 encoding,
21 error,
21 error,
22 match as matchmod,
22 match as matchmod,
23 pathutil,
23 pathutil,
24 phases,
24 phases,
25 pycompat,
25 pycompat,
26 revsetlang,
26 revsetlang,
27 similar,
27 similar,
28 util,
28 util,
29 vfs as vfsmod,
29 vfs as vfsmod,
30 )
30 )
31
31
32 if pycompat.osname == 'nt':
32 if pycompat.osname == 'nt':
33 from . import scmwindows as scmplatform
33 from . import scmwindows as scmplatform
34 else:
34 else:
35 from . import scmposix as scmplatform
35 from . import scmposix as scmplatform
36
36
37 termsize = scmplatform.termsize
37 termsize = scmplatform.termsize
38
38
39 class status(tuple):
39 class status(tuple):
40 '''Named tuple with a list of files per status. The 'deleted', 'unknown'
40 '''Named tuple with a list of files per status. The 'deleted', 'unknown'
41 and 'ignored' properties are only relevant to the working copy.
41 and 'ignored' properties are only relevant to the working copy.
42 '''
42 '''
43
43
44 __slots__ = ()
44 __slots__ = ()
45
45
46 def __new__(cls, modified, added, removed, deleted, unknown, ignored,
46 def __new__(cls, modified, added, removed, deleted, unknown, ignored,
47 clean):
47 clean):
48 return tuple.__new__(cls, (modified, added, removed, deleted, unknown,
48 return tuple.__new__(cls, (modified, added, removed, deleted, unknown,
49 ignored, clean))
49 ignored, clean))
50
50
51 @property
51 @property
52 def modified(self):
52 def modified(self):
53 '''files that have been modified'''
53 '''files that have been modified'''
54 return self[0]
54 return self[0]
55
55
56 @property
56 @property
57 def added(self):
57 def added(self):
58 '''files that have been added'''
58 '''files that have been added'''
59 return self[1]
59 return self[1]
60
60
61 @property
61 @property
62 def removed(self):
62 def removed(self):
63 '''files that have been removed'''
63 '''files that have been removed'''
64 return self[2]
64 return self[2]
65
65
66 @property
66 @property
67 def deleted(self):
67 def deleted(self):
68 '''files that are in the dirstate, but have been deleted from the
68 '''files that are in the dirstate, but have been deleted from the
69 working copy (aka "missing")
69 working copy (aka "missing")
70 '''
70 '''
71 return self[3]
71 return self[3]
72
72
73 @property
73 @property
74 def unknown(self):
74 def unknown(self):
75 '''files not in the dirstate that are not ignored'''
75 '''files not in the dirstate that are not ignored'''
76 return self[4]
76 return self[4]
77
77
78 @property
78 @property
79 def ignored(self):
79 def ignored(self):
80 '''files not in the dirstate that are ignored (by _dirignore())'''
80 '''files not in the dirstate that are ignored (by _dirignore())'''
81 return self[5]
81 return self[5]
82
82
83 @property
83 @property
84 def clean(self):
84 def clean(self):
85 '''files that have not been modified'''
85 '''files that have not been modified'''
86 return self[6]
86 return self[6]
87
87
88 def __repr__(self, *args, **kwargs):
88 def __repr__(self, *args, **kwargs):
89 return (('<status modified=%r, added=%r, removed=%r, deleted=%r, '
89 return (('<status modified=%r, added=%r, removed=%r, deleted=%r, '
90 'unknown=%r, ignored=%r, clean=%r>') % self)
90 'unknown=%r, ignored=%r, clean=%r>') % self)
91
91
92 def itersubrepos(ctx1, ctx2):
92 def itersubrepos(ctx1, ctx2):
93 """find subrepos in ctx1 or ctx2"""
93 """find subrepos in ctx1 or ctx2"""
94 # Create a (subpath, ctx) mapping where we prefer subpaths from
94 # Create a (subpath, ctx) mapping where we prefer subpaths from
95 # ctx1. The subpaths from ctx2 are important when the .hgsub file
95 # ctx1. The subpaths from ctx2 are important when the .hgsub file
96 # has been modified (in ctx2) but not yet committed (in ctx1).
96 # has been modified (in ctx2) but not yet committed (in ctx1).
97 subpaths = dict.fromkeys(ctx2.substate, ctx2)
97 subpaths = dict.fromkeys(ctx2.substate, ctx2)
98 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
98 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
99
99
100 missing = set()
100 missing = set()
101
101
102 for subpath in ctx2.substate:
102 for subpath in ctx2.substate:
103 if subpath not in ctx1.substate:
103 if subpath not in ctx1.substate:
104 del subpaths[subpath]
104 del subpaths[subpath]
105 missing.add(subpath)
105 missing.add(subpath)
106
106
107 for subpath, ctx in sorted(subpaths.iteritems()):
107 for subpath, ctx in sorted(subpaths.iteritems()):
108 yield subpath, ctx.sub(subpath)
108 yield subpath, ctx.sub(subpath)
109
109
110 # Yield an empty subrepo based on ctx1 for anything only in ctx2. That way,
110 # Yield an empty subrepo based on ctx1 for anything only in ctx2. That way,
111 # status and diff will have an accurate result when it does
111 # status and diff will have an accurate result when it does
112 # 'sub.{status|diff}(rev2)'. Otherwise, the ctx2 subrepo is compared
112 # 'sub.{status|diff}(rev2)'. Otherwise, the ctx2 subrepo is compared
113 # against itself.
113 # against itself.
114 for subpath in missing:
114 for subpath in missing:
115 yield subpath, ctx2.nullsub(subpath, ctx1)
115 yield subpath, ctx2.nullsub(subpath, ctx1)
116
116
117 def nochangesfound(ui, repo, excluded=None):
117 def nochangesfound(ui, repo, excluded=None):
118 '''Report no changes for push/pull, excluded is None or a list of
118 '''Report no changes for push/pull, excluded is None or a list of
119 nodes excluded from the push/pull.
119 nodes excluded from the push/pull.
120 '''
120 '''
121 secretlist = []
121 secretlist = []
122 if excluded:
122 if excluded:
123 for n in excluded:
123 for n in excluded:
124 if n not in repo:
124 if n not in repo:
125 # discovery should not have included the filtered revision,
125 # discovery should not have included the filtered revision,
126 # we have to explicitly exclude it until discovery is cleanup.
126 # we have to explicitly exclude it until discovery is cleanup.
127 continue
127 continue
128 ctx = repo[n]
128 ctx = repo[n]
129 if ctx.phase() >= phases.secret and not ctx.extinct():
129 if ctx.phase() >= phases.secret and not ctx.extinct():
130 secretlist.append(n)
130 secretlist.append(n)
131
131
132 if secretlist:
132 if secretlist:
133 ui.status(_("no changes found (ignored %d secret changesets)\n")
133 ui.status(_("no changes found (ignored %d secret changesets)\n")
134 % len(secretlist))
134 % len(secretlist))
135 else:
135 else:
136 ui.status(_("no changes found\n"))
136 ui.status(_("no changes found\n"))
137
137
138 def callcatch(ui, func):
138 def callcatch(ui, func):
139 """call func() with global exception handling
139 """call func() with global exception handling
140
140
141 return func() if no exception happens. otherwise do some error handling
141 return func() if no exception happens. otherwise do some error handling
142 and return an exit code accordingly. does not handle all exceptions.
142 and return an exit code accordingly. does not handle all exceptions.
143 """
143 """
144 try:
144 try:
145 return func()
145 try:
146 return func()
147 except: # re-raises
148 ui.traceback()
149 raise
146 # Global exception handling, alphabetically
150 # Global exception handling, alphabetically
147 # Mercurial-specific first, followed by built-in and library exceptions
151 # Mercurial-specific first, followed by built-in and library exceptions
148 except error.LockHeld as inst:
152 except error.LockHeld as inst:
149 if inst.errno == errno.ETIMEDOUT:
153 if inst.errno == errno.ETIMEDOUT:
150 reason = _('timed out waiting for lock held by %s') % inst.locker
154 reason = _('timed out waiting for lock held by %s') % inst.locker
151 else:
155 else:
152 reason = _('lock held by %s') % inst.locker
156 reason = _('lock held by %s') % inst.locker
153 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
157 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
154 except error.LockUnavailable as inst:
158 except error.LockUnavailable as inst:
155 ui.warn(_("abort: could not lock %s: %s\n") %
159 ui.warn(_("abort: could not lock %s: %s\n") %
156 (inst.desc or inst.filename, inst.strerror))
160 (inst.desc or inst.filename, inst.strerror))
157 except error.OutOfBandError as inst:
161 except error.OutOfBandError as inst:
158 if inst.args:
162 if inst.args:
159 msg = _("abort: remote error:\n")
163 msg = _("abort: remote error:\n")
160 else:
164 else:
161 msg = _("abort: remote error\n")
165 msg = _("abort: remote error\n")
162 ui.warn(msg)
166 ui.warn(msg)
163 if inst.args:
167 if inst.args:
164 ui.warn(''.join(inst.args))
168 ui.warn(''.join(inst.args))
165 if inst.hint:
169 if inst.hint:
166 ui.warn('(%s)\n' % inst.hint)
170 ui.warn('(%s)\n' % inst.hint)
167 except error.RepoError as inst:
171 except error.RepoError as inst:
168 ui.warn(_("abort: %s!\n") % inst)
172 ui.warn(_("abort: %s!\n") % inst)
169 if inst.hint:
173 if inst.hint:
170 ui.warn(_("(%s)\n") % inst.hint)
174 ui.warn(_("(%s)\n") % inst.hint)
171 except error.ResponseError as inst:
175 except error.ResponseError as inst:
172 ui.warn(_("abort: %s") % inst.args[0])
176 ui.warn(_("abort: %s") % inst.args[0])
173 if not isinstance(inst.args[1], basestring):
177 if not isinstance(inst.args[1], basestring):
174 ui.warn(" %r\n" % (inst.args[1],))
178 ui.warn(" %r\n" % (inst.args[1],))
175 elif not inst.args[1]:
179 elif not inst.args[1]:
176 ui.warn(_(" empty string\n"))
180 ui.warn(_(" empty string\n"))
177 else:
181 else:
178 ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
182 ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
179 except error.CensoredNodeError as inst:
183 except error.CensoredNodeError as inst:
180 ui.warn(_("abort: file censored %s!\n") % inst)
184 ui.warn(_("abort: file censored %s!\n") % inst)
181 except error.RevlogError as inst:
185 except error.RevlogError as inst:
182 ui.warn(_("abort: %s!\n") % inst)
186 ui.warn(_("abort: %s!\n") % inst)
183 except error.SignalInterrupt:
187 except error.SignalInterrupt:
184 ui.warn(_("killed!\n"))
188 ui.warn(_("killed!\n"))
185 except error.InterventionRequired as inst:
189 except error.InterventionRequired as inst:
186 ui.warn("%s\n" % inst)
190 ui.warn("%s\n" % inst)
187 if inst.hint:
191 if inst.hint:
188 ui.warn(_("(%s)\n") % inst.hint)
192 ui.warn(_("(%s)\n") % inst.hint)
189 return 1
193 return 1
190 except error.Abort as inst:
194 except error.Abort as inst:
191 ui.warn(_("abort: %s\n") % inst)
195 ui.warn(_("abort: %s\n") % inst)
192 if inst.hint:
196 if inst.hint:
193 ui.warn(_("(%s)\n") % inst.hint)
197 ui.warn(_("(%s)\n") % inst.hint)
194 except ImportError as inst:
198 except ImportError as inst:
195 ui.warn(_("abort: %s!\n") % inst)
199 ui.warn(_("abort: %s!\n") % inst)
196 m = str(inst).split()[-1]
200 m = str(inst).split()[-1]
197 if m in "mpatch bdiff".split():
201 if m in "mpatch bdiff".split():
198 ui.warn(_("(did you forget to compile extensions?)\n"))
202 ui.warn(_("(did you forget to compile extensions?)\n"))
199 elif m in "zlib".split():
203 elif m in "zlib".split():
200 ui.warn(_("(is your Python install correct?)\n"))
204 ui.warn(_("(is your Python install correct?)\n"))
201 except IOError as inst:
205 except IOError as inst:
202 if util.safehasattr(inst, "code"):
206 if util.safehasattr(inst, "code"):
203 ui.warn(_("abort: %s\n") % inst)
207 ui.warn(_("abort: %s\n") % inst)
204 elif util.safehasattr(inst, "reason"):
208 elif util.safehasattr(inst, "reason"):
205 try: # usually it is in the form (errno, strerror)
209 try: # usually it is in the form (errno, strerror)
206 reason = inst.reason.args[1]
210 reason = inst.reason.args[1]
207 except (AttributeError, IndexError):
211 except (AttributeError, IndexError):
208 # it might be anything, for example a string
212 # it might be anything, for example a string
209 reason = inst.reason
213 reason = inst.reason
210 if isinstance(reason, unicode):
214 if isinstance(reason, unicode):
211 # SSLError of Python 2.7.9 contains a unicode
215 # SSLError of Python 2.7.9 contains a unicode
212 reason = reason.encode(encoding.encoding, 'replace')
216 reason = reason.encode(encoding.encoding, 'replace')
213 ui.warn(_("abort: error: %s\n") % reason)
217 ui.warn(_("abort: error: %s\n") % reason)
214 elif (util.safehasattr(inst, "args")
218 elif (util.safehasattr(inst, "args")
215 and inst.args and inst.args[0] == errno.EPIPE):
219 and inst.args and inst.args[0] == errno.EPIPE):
216 pass
220 pass
217 elif getattr(inst, "strerror", None):
221 elif getattr(inst, "strerror", None):
218 if getattr(inst, "filename", None):
222 if getattr(inst, "filename", None):
219 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
223 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
220 else:
224 else:
221 ui.warn(_("abort: %s\n") % inst.strerror)
225 ui.warn(_("abort: %s\n") % inst.strerror)
222 else:
226 else:
223 raise
227 raise
224 except OSError as inst:
228 except OSError as inst:
225 if getattr(inst, "filename", None) is not None:
229 if getattr(inst, "filename", None) is not None:
226 ui.warn(_("abort: %s: '%s'\n") % (inst.strerror, inst.filename))
230 ui.warn(_("abort: %s: '%s'\n") % (inst.strerror, inst.filename))
227 else:
231 else:
228 ui.warn(_("abort: %s\n") % inst.strerror)
232 ui.warn(_("abort: %s\n") % inst.strerror)
229 except MemoryError:
233 except MemoryError:
230 ui.warn(_("abort: out of memory\n"))
234 ui.warn(_("abort: out of memory\n"))
231 except SystemExit as inst:
235 except SystemExit as inst:
232 # Commands shouldn't sys.exit directly, but give a return code.
236 # Commands shouldn't sys.exit directly, but give a return code.
233 # Just in case catch this and and pass exit code to caller.
237 # Just in case catch this and and pass exit code to caller.
234 return inst.code
238 return inst.code
235 except socket.error as inst:
239 except socket.error as inst:
236 ui.warn(_("abort: %s\n") % inst.args[-1])
240 ui.warn(_("abort: %s\n") % inst.args[-1])
237
241
238 return -1
242 return -1
239
243
240 def checknewlabel(repo, lbl, kind):
244 def checknewlabel(repo, lbl, kind):
241 # Do not use the "kind" parameter in ui output.
245 # Do not use the "kind" parameter in ui output.
242 # It makes strings difficult to translate.
246 # It makes strings difficult to translate.
243 if lbl in ['tip', '.', 'null']:
247 if lbl in ['tip', '.', 'null']:
244 raise error.Abort(_("the name '%s' is reserved") % lbl)
248 raise error.Abort(_("the name '%s' is reserved") % lbl)
245 for c in (':', '\0', '\n', '\r'):
249 for c in (':', '\0', '\n', '\r'):
246 if c in lbl:
250 if c in lbl:
247 raise error.Abort(_("%r cannot be used in a name") % c)
251 raise error.Abort(_("%r cannot be used in a name") % c)
248 try:
252 try:
249 int(lbl)
253 int(lbl)
250 raise error.Abort(_("cannot use an integer as a name"))
254 raise error.Abort(_("cannot use an integer as a name"))
251 except ValueError:
255 except ValueError:
252 pass
256 pass
253
257
254 def checkfilename(f):
258 def checkfilename(f):
255 '''Check that the filename f is an acceptable filename for a tracked file'''
259 '''Check that the filename f is an acceptable filename for a tracked file'''
256 if '\r' in f or '\n' in f:
260 if '\r' in f or '\n' in f:
257 raise error.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
261 raise error.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
258
262
259 def checkportable(ui, f):
263 def checkportable(ui, f):
260 '''Check if filename f is portable and warn or abort depending on config'''
264 '''Check if filename f is portable and warn or abort depending on config'''
261 checkfilename(f)
265 checkfilename(f)
262 abort, warn = checkportabilityalert(ui)
266 abort, warn = checkportabilityalert(ui)
263 if abort or warn:
267 if abort or warn:
264 msg = util.checkwinfilename(f)
268 msg = util.checkwinfilename(f)
265 if msg:
269 if msg:
266 msg = "%s: %r" % (msg, f)
270 msg = "%s: %r" % (msg, f)
267 if abort:
271 if abort:
268 raise error.Abort(msg)
272 raise error.Abort(msg)
269 ui.warn(_("warning: %s\n") % msg)
273 ui.warn(_("warning: %s\n") % msg)
270
274
271 def checkportabilityalert(ui):
275 def checkportabilityalert(ui):
272 '''check if the user's config requests nothing, a warning, or abort for
276 '''check if the user's config requests nothing, a warning, or abort for
273 non-portable filenames'''
277 non-portable filenames'''
274 val = ui.config('ui', 'portablefilenames', 'warn')
278 val = ui.config('ui', 'portablefilenames', 'warn')
275 lval = val.lower()
279 lval = val.lower()
276 bval = util.parsebool(val)
280 bval = util.parsebool(val)
277 abort = pycompat.osname == 'nt' or lval == 'abort'
281 abort = pycompat.osname == 'nt' or lval == 'abort'
278 warn = bval or lval == 'warn'
282 warn = bval or lval == 'warn'
279 if bval is None and not (warn or abort or lval == 'ignore'):
283 if bval is None and not (warn or abort or lval == 'ignore'):
280 raise error.ConfigError(
284 raise error.ConfigError(
281 _("ui.portablefilenames value is invalid ('%s')") % val)
285 _("ui.portablefilenames value is invalid ('%s')") % val)
282 return abort, warn
286 return abort, warn
283
287
284 class casecollisionauditor(object):
288 class casecollisionauditor(object):
285 def __init__(self, ui, abort, dirstate):
289 def __init__(self, ui, abort, dirstate):
286 self._ui = ui
290 self._ui = ui
287 self._abort = abort
291 self._abort = abort
288 allfiles = '\0'.join(dirstate._map)
292 allfiles = '\0'.join(dirstate._map)
289 self._loweredfiles = set(encoding.lower(allfiles).split('\0'))
293 self._loweredfiles = set(encoding.lower(allfiles).split('\0'))
290 self._dirstate = dirstate
294 self._dirstate = dirstate
291 # The purpose of _newfiles is so that we don't complain about
295 # The purpose of _newfiles is so that we don't complain about
292 # case collisions if someone were to call this object with the
296 # case collisions if someone were to call this object with the
293 # same filename twice.
297 # same filename twice.
294 self._newfiles = set()
298 self._newfiles = set()
295
299
296 def __call__(self, f):
300 def __call__(self, f):
297 if f in self._newfiles:
301 if f in self._newfiles:
298 return
302 return
299 fl = encoding.lower(f)
303 fl = encoding.lower(f)
300 if fl in self._loweredfiles and f not in self._dirstate:
304 if fl in self._loweredfiles and f not in self._dirstate:
301 msg = _('possible case-folding collision for %s') % f
305 msg = _('possible case-folding collision for %s') % f
302 if self._abort:
306 if self._abort:
303 raise error.Abort(msg)
307 raise error.Abort(msg)
304 self._ui.warn(_("warning: %s\n") % msg)
308 self._ui.warn(_("warning: %s\n") % msg)
305 self._loweredfiles.add(fl)
309 self._loweredfiles.add(fl)
306 self._newfiles.add(f)
310 self._newfiles.add(f)
307
311
308 def filteredhash(repo, maxrev):
312 def filteredhash(repo, maxrev):
309 """build hash of filtered revisions in the current repoview.
313 """build hash of filtered revisions in the current repoview.
310
314
311 Multiple caches perform up-to-date validation by checking that the
315 Multiple caches perform up-to-date validation by checking that the
312 tiprev and tipnode stored in the cache file match the current repository.
316 tiprev and tipnode stored in the cache file match the current repository.
313 However, this is not sufficient for validating repoviews because the set
317 However, this is not sufficient for validating repoviews because the set
314 of revisions in the view may change without the repository tiprev and
318 of revisions in the view may change without the repository tiprev and
315 tipnode changing.
319 tipnode changing.
316
320
317 This function hashes all the revs filtered from the view and returns
321 This function hashes all the revs filtered from the view and returns
318 that SHA-1 digest.
322 that SHA-1 digest.
319 """
323 """
320 cl = repo.changelog
324 cl = repo.changelog
321 if not cl.filteredrevs:
325 if not cl.filteredrevs:
322 return None
326 return None
323 key = None
327 key = None
324 revs = sorted(r for r in cl.filteredrevs if r <= maxrev)
328 revs = sorted(r for r in cl.filteredrevs if r <= maxrev)
325 if revs:
329 if revs:
326 s = hashlib.sha1()
330 s = hashlib.sha1()
327 for rev in revs:
331 for rev in revs:
328 s.update('%d;' % rev)
332 s.update('%d;' % rev)
329 key = s.digest()
333 key = s.digest()
330 return key
334 return key
331
335
332 def _deprecated(old, new, func):
336 def _deprecated(old, new, func):
333 msg = ('class at mercurial.scmutil.%s moved to mercurial.vfs.%s'
337 msg = ('class at mercurial.scmutil.%s moved to mercurial.vfs.%s'
334 % (old, new))
338 % (old, new))
335 def wrapper(*args, **kwargs):
339 def wrapper(*args, **kwargs):
336 util.nouideprecwarn(msg, '4.2')
340 util.nouideprecwarn(msg, '4.2')
337 return func(*args, **kwargs)
341 return func(*args, **kwargs)
338 return wrapper
342 return wrapper
339
343
340 # compatibility layer since all 'vfs' code moved to 'mercurial.vfs'
344 # compatibility layer since all 'vfs' code moved to 'mercurial.vfs'
341 #
345 #
342 # This is hard to instal deprecation warning to this since we do not have
346 # This is hard to instal deprecation warning to this since we do not have
343 # access to a 'ui' object.
347 # access to a 'ui' object.
344 opener = _deprecated('opener', 'vfs', vfsmod.vfs)
348 opener = _deprecated('opener', 'vfs', vfsmod.vfs)
345 vfs = _deprecated('vfs', 'vfs', vfsmod.vfs)
349 vfs = _deprecated('vfs', 'vfs', vfsmod.vfs)
346 filteropener = _deprecated('filteropener', 'filtervfs', vfsmod.filtervfs)
350 filteropener = _deprecated('filteropener', 'filtervfs', vfsmod.filtervfs)
347 filtervfs = _deprecated('filtervfs', 'filtervfs', vfsmod.filtervfs)
351 filtervfs = _deprecated('filtervfs', 'filtervfs', vfsmod.filtervfs)
348 abstractvfs = _deprecated('abstractvfs', 'abstractvfs', vfsmod.abstractvfs)
352 abstractvfs = _deprecated('abstractvfs', 'abstractvfs', vfsmod.abstractvfs)
349 readonlyvfs = _deprecated('readonlyvfs', 'readonlyvfs', vfsmod.readonlyvfs)
353 readonlyvfs = _deprecated('readonlyvfs', 'readonlyvfs', vfsmod.readonlyvfs)
350 auditvfs = _deprecated('auditvfs', 'auditvfs', vfsmod.auditvfs)
354 auditvfs = _deprecated('auditvfs', 'auditvfs', vfsmod.auditvfs)
351 checkambigatclosing = vfsmod.checkambigatclosing
355 checkambigatclosing = vfsmod.checkambigatclosing
352
356
353 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
357 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
354 '''yield every hg repository under path, always recursively.
358 '''yield every hg repository under path, always recursively.
355 The recurse flag will only control recursion into repo working dirs'''
359 The recurse flag will only control recursion into repo working dirs'''
356 def errhandler(err):
360 def errhandler(err):
357 if err.filename == path:
361 if err.filename == path:
358 raise err
362 raise err
359 samestat = getattr(os.path, 'samestat', None)
363 samestat = getattr(os.path, 'samestat', None)
360 if followsym and samestat is not None:
364 if followsym and samestat is not None:
361 def adddir(dirlst, dirname):
365 def adddir(dirlst, dirname):
362 match = False
366 match = False
363 dirstat = os.stat(dirname)
367 dirstat = os.stat(dirname)
364 for lstdirstat in dirlst:
368 for lstdirstat in dirlst:
365 if samestat(dirstat, lstdirstat):
369 if samestat(dirstat, lstdirstat):
366 match = True
370 match = True
367 break
371 break
368 if not match:
372 if not match:
369 dirlst.append(dirstat)
373 dirlst.append(dirstat)
370 return not match
374 return not match
371 else:
375 else:
372 followsym = False
376 followsym = False
373
377
374 if (seen_dirs is None) and followsym:
378 if (seen_dirs is None) and followsym:
375 seen_dirs = []
379 seen_dirs = []
376 adddir(seen_dirs, path)
380 adddir(seen_dirs, path)
377 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
381 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
378 dirs.sort()
382 dirs.sort()
379 if '.hg' in dirs:
383 if '.hg' in dirs:
380 yield root # found a repository
384 yield root # found a repository
381 qroot = os.path.join(root, '.hg', 'patches')
385 qroot = os.path.join(root, '.hg', 'patches')
382 if os.path.isdir(os.path.join(qroot, '.hg')):
386 if os.path.isdir(os.path.join(qroot, '.hg')):
383 yield qroot # we have a patch queue repo here
387 yield qroot # we have a patch queue repo here
384 if recurse:
388 if recurse:
385 # avoid recursing inside the .hg directory
389 # avoid recursing inside the .hg directory
386 dirs.remove('.hg')
390 dirs.remove('.hg')
387 else:
391 else:
388 dirs[:] = [] # don't descend further
392 dirs[:] = [] # don't descend further
389 elif followsym:
393 elif followsym:
390 newdirs = []
394 newdirs = []
391 for d in dirs:
395 for d in dirs:
392 fname = os.path.join(root, d)
396 fname = os.path.join(root, d)
393 if adddir(seen_dirs, fname):
397 if adddir(seen_dirs, fname):
394 if os.path.islink(fname):
398 if os.path.islink(fname):
395 for hgname in walkrepos(fname, True, seen_dirs):
399 for hgname in walkrepos(fname, True, seen_dirs):
396 yield hgname
400 yield hgname
397 else:
401 else:
398 newdirs.append(d)
402 newdirs.append(d)
399 dirs[:] = newdirs
403 dirs[:] = newdirs
400
404
401 def intrev(rev):
405 def intrev(rev):
402 """Return integer for a given revision that can be used in comparison or
406 """Return integer for a given revision that can be used in comparison or
403 arithmetic operation"""
407 arithmetic operation"""
404 if rev is None:
408 if rev is None:
405 return wdirrev
409 return wdirrev
406 return rev
410 return rev
407
411
408 def revsingle(repo, revspec, default='.'):
412 def revsingle(repo, revspec, default='.'):
409 if not revspec and revspec != 0:
413 if not revspec and revspec != 0:
410 return repo[default]
414 return repo[default]
411
415
412 l = revrange(repo, [revspec])
416 l = revrange(repo, [revspec])
413 if not l:
417 if not l:
414 raise error.Abort(_('empty revision set'))
418 raise error.Abort(_('empty revision set'))
415 return repo[l.last()]
419 return repo[l.last()]
416
420
417 def _pairspec(revspec):
421 def _pairspec(revspec):
418 tree = revsetlang.parse(revspec)
422 tree = revsetlang.parse(revspec)
419 return tree and tree[0] in ('range', 'rangepre', 'rangepost', 'rangeall')
423 return tree and tree[0] in ('range', 'rangepre', 'rangepost', 'rangeall')
420
424
421 def revpair(repo, revs):
425 def revpair(repo, revs):
422 if not revs:
426 if not revs:
423 return repo.dirstate.p1(), None
427 return repo.dirstate.p1(), None
424
428
425 l = revrange(repo, revs)
429 l = revrange(repo, revs)
426
430
427 if not l:
431 if not l:
428 first = second = None
432 first = second = None
429 elif l.isascending():
433 elif l.isascending():
430 first = l.min()
434 first = l.min()
431 second = l.max()
435 second = l.max()
432 elif l.isdescending():
436 elif l.isdescending():
433 first = l.max()
437 first = l.max()
434 second = l.min()
438 second = l.min()
435 else:
439 else:
436 first = l.first()
440 first = l.first()
437 second = l.last()
441 second = l.last()
438
442
439 if first is None:
443 if first is None:
440 raise error.Abort(_('empty revision range'))
444 raise error.Abort(_('empty revision range'))
441 if (first == second and len(revs) >= 2
445 if (first == second and len(revs) >= 2
442 and not all(revrange(repo, [r]) for r in revs)):
446 and not all(revrange(repo, [r]) for r in revs)):
443 raise error.Abort(_('empty revision on one side of range'))
447 raise error.Abort(_('empty revision on one side of range'))
444
448
445 # if top-level is range expression, the result must always be a pair
449 # if top-level is range expression, the result must always be a pair
446 if first == second and len(revs) == 1 and not _pairspec(revs[0]):
450 if first == second and len(revs) == 1 and not _pairspec(revs[0]):
447 return repo.lookup(first), None
451 return repo.lookup(first), None
448
452
449 return repo.lookup(first), repo.lookup(second)
453 return repo.lookup(first), repo.lookup(second)
450
454
451 def revrange(repo, specs):
455 def revrange(repo, specs):
452 """Execute 1 to many revsets and return the union.
456 """Execute 1 to many revsets and return the union.
453
457
454 This is the preferred mechanism for executing revsets using user-specified
458 This is the preferred mechanism for executing revsets using user-specified
455 config options, such as revset aliases.
459 config options, such as revset aliases.
456
460
457 The revsets specified by ``specs`` will be executed via a chained ``OR``
461 The revsets specified by ``specs`` will be executed via a chained ``OR``
458 expression. If ``specs`` is empty, an empty result is returned.
462 expression. If ``specs`` is empty, an empty result is returned.
459
463
460 ``specs`` can contain integers, in which case they are assumed to be
464 ``specs`` can contain integers, in which case they are assumed to be
461 revision numbers.
465 revision numbers.
462
466
463 It is assumed the revsets are already formatted. If you have arguments
467 It is assumed the revsets are already formatted. If you have arguments
464 that need to be expanded in the revset, call ``revsetlang.formatspec()``
468 that need to be expanded in the revset, call ``revsetlang.formatspec()``
465 and pass the result as an element of ``specs``.
469 and pass the result as an element of ``specs``.
466
470
467 Specifying a single revset is allowed.
471 Specifying a single revset is allowed.
468
472
469 Returns a ``revset.abstractsmartset`` which is a list-like interface over
473 Returns a ``revset.abstractsmartset`` which is a list-like interface over
470 integer revisions.
474 integer revisions.
471 """
475 """
472 allspecs = []
476 allspecs = []
473 for spec in specs:
477 for spec in specs:
474 if isinstance(spec, int):
478 if isinstance(spec, int):
475 spec = revsetlang.formatspec('rev(%d)', spec)
479 spec = revsetlang.formatspec('rev(%d)', spec)
476 allspecs.append(spec)
480 allspecs.append(spec)
477 return repo.anyrevs(allspecs, user=True)
481 return repo.anyrevs(allspecs, user=True)
478
482
479 def meaningfulparents(repo, ctx):
483 def meaningfulparents(repo, ctx):
480 """Return list of meaningful (or all if debug) parentrevs for rev.
484 """Return list of meaningful (or all if debug) parentrevs for rev.
481
485
482 For merges (two non-nullrev revisions) both parents are meaningful.
486 For merges (two non-nullrev revisions) both parents are meaningful.
483 Otherwise the first parent revision is considered meaningful if it
487 Otherwise the first parent revision is considered meaningful if it
484 is not the preceding revision.
488 is not the preceding revision.
485 """
489 """
486 parents = ctx.parents()
490 parents = ctx.parents()
487 if len(parents) > 1:
491 if len(parents) > 1:
488 return parents
492 return parents
489 if repo.ui.debugflag:
493 if repo.ui.debugflag:
490 return [parents[0], repo['null']]
494 return [parents[0], repo['null']]
491 if parents[0].rev() >= intrev(ctx.rev()) - 1:
495 if parents[0].rev() >= intrev(ctx.rev()) - 1:
492 return []
496 return []
493 return parents
497 return parents
494
498
495 def expandpats(pats):
499 def expandpats(pats):
496 '''Expand bare globs when running on windows.
500 '''Expand bare globs when running on windows.
497 On posix we assume it already has already been done by sh.'''
501 On posix we assume it already has already been done by sh.'''
498 if not util.expandglobs:
502 if not util.expandglobs:
499 return list(pats)
503 return list(pats)
500 ret = []
504 ret = []
501 for kindpat in pats:
505 for kindpat in pats:
502 kind, pat = matchmod._patsplit(kindpat, None)
506 kind, pat = matchmod._patsplit(kindpat, None)
503 if kind is None:
507 if kind is None:
504 try:
508 try:
505 globbed = glob.glob(pat)
509 globbed = glob.glob(pat)
506 except re.error:
510 except re.error:
507 globbed = [pat]
511 globbed = [pat]
508 if globbed:
512 if globbed:
509 ret.extend(globbed)
513 ret.extend(globbed)
510 continue
514 continue
511 ret.append(kindpat)
515 ret.append(kindpat)
512 return ret
516 return ret
513
517
514 def matchandpats(ctx, pats=(), opts=None, globbed=False, default='relpath',
518 def matchandpats(ctx, pats=(), opts=None, globbed=False, default='relpath',
515 badfn=None):
519 badfn=None):
516 '''Return a matcher and the patterns that were used.
520 '''Return a matcher and the patterns that were used.
517 The matcher will warn about bad matches, unless an alternate badfn callback
521 The matcher will warn about bad matches, unless an alternate badfn callback
518 is provided.'''
522 is provided.'''
519 if pats == ("",):
523 if pats == ("",):
520 pats = []
524 pats = []
521 if opts is None:
525 if opts is None:
522 opts = {}
526 opts = {}
523 if not globbed and default == 'relpath':
527 if not globbed and default == 'relpath':
524 pats = expandpats(pats or [])
528 pats = expandpats(pats or [])
525
529
526 def bad(f, msg):
530 def bad(f, msg):
527 ctx.repo().ui.warn("%s: %s\n" % (m.rel(f), msg))
531 ctx.repo().ui.warn("%s: %s\n" % (m.rel(f), msg))
528
532
529 if badfn is None:
533 if badfn is None:
530 badfn = bad
534 badfn = bad
531
535
532 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
536 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
533 default, listsubrepos=opts.get('subrepos'), badfn=badfn)
537 default, listsubrepos=opts.get('subrepos'), badfn=badfn)
534
538
535 if m.always():
539 if m.always():
536 pats = []
540 pats = []
537 return m, pats
541 return m, pats
538
542
539 def match(ctx, pats=(), opts=None, globbed=False, default='relpath',
543 def match(ctx, pats=(), opts=None, globbed=False, default='relpath',
540 badfn=None):
544 badfn=None):
541 '''Return a matcher that will warn about bad matches.'''
545 '''Return a matcher that will warn about bad matches.'''
542 return matchandpats(ctx, pats, opts, globbed, default, badfn=badfn)[0]
546 return matchandpats(ctx, pats, opts, globbed, default, badfn=badfn)[0]
543
547
544 def matchall(repo):
548 def matchall(repo):
545 '''Return a matcher that will efficiently match everything.'''
549 '''Return a matcher that will efficiently match everything.'''
546 return matchmod.always(repo.root, repo.getcwd())
550 return matchmod.always(repo.root, repo.getcwd())
547
551
548 def matchfiles(repo, files, badfn=None):
552 def matchfiles(repo, files, badfn=None):
549 '''Return a matcher that will efficiently match exactly these files.'''
553 '''Return a matcher that will efficiently match exactly these files.'''
550 return matchmod.exact(repo.root, repo.getcwd(), files, badfn=badfn)
554 return matchmod.exact(repo.root, repo.getcwd(), files, badfn=badfn)
551
555
552 def origpath(ui, repo, filepath):
556 def origpath(ui, repo, filepath):
553 '''customize where .orig files are created
557 '''customize where .orig files are created
554
558
555 Fetch user defined path from config file: [ui] origbackuppath = <path>
559 Fetch user defined path from config file: [ui] origbackuppath = <path>
556 Fall back to default (filepath) if not specified
560 Fall back to default (filepath) if not specified
557 '''
561 '''
558 origbackuppath = ui.config('ui', 'origbackuppath', None)
562 origbackuppath = ui.config('ui', 'origbackuppath', None)
559 if origbackuppath is None:
563 if origbackuppath is None:
560 return filepath + ".orig"
564 return filepath + ".orig"
561
565
562 filepathfromroot = os.path.relpath(filepath, start=repo.root)
566 filepathfromroot = os.path.relpath(filepath, start=repo.root)
563 fullorigpath = repo.wjoin(origbackuppath, filepathfromroot)
567 fullorigpath = repo.wjoin(origbackuppath, filepathfromroot)
564
568
565 origbackupdir = repo.vfs.dirname(fullorigpath)
569 origbackupdir = repo.vfs.dirname(fullorigpath)
566 if not repo.vfs.exists(origbackupdir):
570 if not repo.vfs.exists(origbackupdir):
567 ui.note(_('creating directory: %s\n') % origbackupdir)
571 ui.note(_('creating directory: %s\n') % origbackupdir)
568 util.makedirs(origbackupdir)
572 util.makedirs(origbackupdir)
569
573
570 return fullorigpath + ".orig"
574 return fullorigpath + ".orig"
571
575
572 def addremove(repo, matcher, prefix, opts=None, dry_run=None, similarity=None):
576 def addremove(repo, matcher, prefix, opts=None, dry_run=None, similarity=None):
573 if opts is None:
577 if opts is None:
574 opts = {}
578 opts = {}
575 m = matcher
579 m = matcher
576 if dry_run is None:
580 if dry_run is None:
577 dry_run = opts.get('dry_run')
581 dry_run = opts.get('dry_run')
578 if similarity is None:
582 if similarity is None:
579 similarity = float(opts.get('similarity') or 0)
583 similarity = float(opts.get('similarity') or 0)
580
584
581 ret = 0
585 ret = 0
582 join = lambda f: os.path.join(prefix, f)
586 join = lambda f: os.path.join(prefix, f)
583
587
584 wctx = repo[None]
588 wctx = repo[None]
585 for subpath in sorted(wctx.substate):
589 for subpath in sorted(wctx.substate):
586 submatch = matchmod.subdirmatcher(subpath, m)
590 submatch = matchmod.subdirmatcher(subpath, m)
587 if opts.get('subrepos') or m.exact(subpath) or any(submatch.files()):
591 if opts.get('subrepos') or m.exact(subpath) or any(submatch.files()):
588 sub = wctx.sub(subpath)
592 sub = wctx.sub(subpath)
589 try:
593 try:
590 if sub.addremove(submatch, prefix, opts, dry_run, similarity):
594 if sub.addremove(submatch, prefix, opts, dry_run, similarity):
591 ret = 1
595 ret = 1
592 except error.LookupError:
596 except error.LookupError:
593 repo.ui.status(_("skipping missing subrepository: %s\n")
597 repo.ui.status(_("skipping missing subrepository: %s\n")
594 % join(subpath))
598 % join(subpath))
595
599
596 rejected = []
600 rejected = []
597 def badfn(f, msg):
601 def badfn(f, msg):
598 if f in m.files():
602 if f in m.files():
599 m.bad(f, msg)
603 m.bad(f, msg)
600 rejected.append(f)
604 rejected.append(f)
601
605
602 badmatch = matchmod.badmatch(m, badfn)
606 badmatch = matchmod.badmatch(m, badfn)
603 added, unknown, deleted, removed, forgotten = _interestingfiles(repo,
607 added, unknown, deleted, removed, forgotten = _interestingfiles(repo,
604 badmatch)
608 badmatch)
605
609
606 unknownset = set(unknown + forgotten)
610 unknownset = set(unknown + forgotten)
607 toprint = unknownset.copy()
611 toprint = unknownset.copy()
608 toprint.update(deleted)
612 toprint.update(deleted)
609 for abs in sorted(toprint):
613 for abs in sorted(toprint):
610 if repo.ui.verbose or not m.exact(abs):
614 if repo.ui.verbose or not m.exact(abs):
611 if abs in unknownset:
615 if abs in unknownset:
612 status = _('adding %s\n') % m.uipath(abs)
616 status = _('adding %s\n') % m.uipath(abs)
613 else:
617 else:
614 status = _('removing %s\n') % m.uipath(abs)
618 status = _('removing %s\n') % m.uipath(abs)
615 repo.ui.status(status)
619 repo.ui.status(status)
616
620
617 renames = _findrenames(repo, m, added + unknown, removed + deleted,
621 renames = _findrenames(repo, m, added + unknown, removed + deleted,
618 similarity)
622 similarity)
619
623
620 if not dry_run:
624 if not dry_run:
621 _markchanges(repo, unknown + forgotten, deleted, renames)
625 _markchanges(repo, unknown + forgotten, deleted, renames)
622
626
623 for f in rejected:
627 for f in rejected:
624 if f in m.files():
628 if f in m.files():
625 return 1
629 return 1
626 return ret
630 return ret
627
631
628 def marktouched(repo, files, similarity=0.0):
632 def marktouched(repo, files, similarity=0.0):
629 '''Assert that files have somehow been operated upon. files are relative to
633 '''Assert that files have somehow been operated upon. files are relative to
630 the repo root.'''
634 the repo root.'''
631 m = matchfiles(repo, files, badfn=lambda x, y: rejected.append(x))
635 m = matchfiles(repo, files, badfn=lambda x, y: rejected.append(x))
632 rejected = []
636 rejected = []
633
637
634 added, unknown, deleted, removed, forgotten = _interestingfiles(repo, m)
638 added, unknown, deleted, removed, forgotten = _interestingfiles(repo, m)
635
639
636 if repo.ui.verbose:
640 if repo.ui.verbose:
637 unknownset = set(unknown + forgotten)
641 unknownset = set(unknown + forgotten)
638 toprint = unknownset.copy()
642 toprint = unknownset.copy()
639 toprint.update(deleted)
643 toprint.update(deleted)
640 for abs in sorted(toprint):
644 for abs in sorted(toprint):
641 if abs in unknownset:
645 if abs in unknownset:
642 status = _('adding %s\n') % abs
646 status = _('adding %s\n') % abs
643 else:
647 else:
644 status = _('removing %s\n') % abs
648 status = _('removing %s\n') % abs
645 repo.ui.status(status)
649 repo.ui.status(status)
646
650
647 renames = _findrenames(repo, m, added + unknown, removed + deleted,
651 renames = _findrenames(repo, m, added + unknown, removed + deleted,
648 similarity)
652 similarity)
649
653
650 _markchanges(repo, unknown + forgotten, deleted, renames)
654 _markchanges(repo, unknown + forgotten, deleted, renames)
651
655
652 for f in rejected:
656 for f in rejected:
653 if f in m.files():
657 if f in m.files():
654 return 1
658 return 1
655 return 0
659 return 0
656
660
657 def _interestingfiles(repo, matcher):
661 def _interestingfiles(repo, matcher):
658 '''Walk dirstate with matcher, looking for files that addremove would care
662 '''Walk dirstate with matcher, looking for files that addremove would care
659 about.
663 about.
660
664
661 This is different from dirstate.status because it doesn't care about
665 This is different from dirstate.status because it doesn't care about
662 whether files are modified or clean.'''
666 whether files are modified or clean.'''
663 added, unknown, deleted, removed, forgotten = [], [], [], [], []
667 added, unknown, deleted, removed, forgotten = [], [], [], [], []
664 audit_path = pathutil.pathauditor(repo.root)
668 audit_path = pathutil.pathauditor(repo.root)
665
669
666 ctx = repo[None]
670 ctx = repo[None]
667 dirstate = repo.dirstate
671 dirstate = repo.dirstate
668 walkresults = dirstate.walk(matcher, sorted(ctx.substate), True, False,
672 walkresults = dirstate.walk(matcher, sorted(ctx.substate), True, False,
669 full=False)
673 full=False)
670 for abs, st in walkresults.iteritems():
674 for abs, st in walkresults.iteritems():
671 dstate = dirstate[abs]
675 dstate = dirstate[abs]
672 if dstate == '?' and audit_path.check(abs):
676 if dstate == '?' and audit_path.check(abs):
673 unknown.append(abs)
677 unknown.append(abs)
674 elif dstate != 'r' and not st:
678 elif dstate != 'r' and not st:
675 deleted.append(abs)
679 deleted.append(abs)
676 elif dstate == 'r' and st:
680 elif dstate == 'r' and st:
677 forgotten.append(abs)
681 forgotten.append(abs)
678 # for finding renames
682 # for finding renames
679 elif dstate == 'r' and not st:
683 elif dstate == 'r' and not st:
680 removed.append(abs)
684 removed.append(abs)
681 elif dstate == 'a':
685 elif dstate == 'a':
682 added.append(abs)
686 added.append(abs)
683
687
684 return added, unknown, deleted, removed, forgotten
688 return added, unknown, deleted, removed, forgotten
685
689
686 def _findrenames(repo, matcher, added, removed, similarity):
690 def _findrenames(repo, matcher, added, removed, similarity):
687 '''Find renames from removed files to added ones.'''
691 '''Find renames from removed files to added ones.'''
688 renames = {}
692 renames = {}
689 if similarity > 0:
693 if similarity > 0:
690 for old, new, score in similar.findrenames(repo, added, removed,
694 for old, new, score in similar.findrenames(repo, added, removed,
691 similarity):
695 similarity):
692 if (repo.ui.verbose or not matcher.exact(old)
696 if (repo.ui.verbose or not matcher.exact(old)
693 or not matcher.exact(new)):
697 or not matcher.exact(new)):
694 repo.ui.status(_('recording removal of %s as rename to %s '
698 repo.ui.status(_('recording removal of %s as rename to %s '
695 '(%d%% similar)\n') %
699 '(%d%% similar)\n') %
696 (matcher.rel(old), matcher.rel(new),
700 (matcher.rel(old), matcher.rel(new),
697 score * 100))
701 score * 100))
698 renames[new] = old
702 renames[new] = old
699 return renames
703 return renames
700
704
701 def _markchanges(repo, unknown, deleted, renames):
705 def _markchanges(repo, unknown, deleted, renames):
702 '''Marks the files in unknown as added, the files in deleted as removed,
706 '''Marks the files in unknown as added, the files in deleted as removed,
703 and the files in renames as copied.'''
707 and the files in renames as copied.'''
704 wctx = repo[None]
708 wctx = repo[None]
705 with repo.wlock():
709 with repo.wlock():
706 wctx.forget(deleted)
710 wctx.forget(deleted)
707 wctx.add(unknown)
711 wctx.add(unknown)
708 for new, old in renames.iteritems():
712 for new, old in renames.iteritems():
709 wctx.copy(old, new)
713 wctx.copy(old, new)
710
714
711 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
715 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
712 """Update the dirstate to reflect the intent of copying src to dst. For
716 """Update the dirstate to reflect the intent of copying src to dst. For
713 different reasons it might not end with dst being marked as copied from src.
717 different reasons it might not end with dst being marked as copied from src.
714 """
718 """
715 origsrc = repo.dirstate.copied(src) or src
719 origsrc = repo.dirstate.copied(src) or src
716 if dst == origsrc: # copying back a copy?
720 if dst == origsrc: # copying back a copy?
717 if repo.dirstate[dst] not in 'mn' and not dryrun:
721 if repo.dirstate[dst] not in 'mn' and not dryrun:
718 repo.dirstate.normallookup(dst)
722 repo.dirstate.normallookup(dst)
719 else:
723 else:
720 if repo.dirstate[origsrc] == 'a' and origsrc == src:
724 if repo.dirstate[origsrc] == 'a' and origsrc == src:
721 if not ui.quiet:
725 if not ui.quiet:
722 ui.warn(_("%s has not been committed yet, so no copy "
726 ui.warn(_("%s has not been committed yet, so no copy "
723 "data will be stored for %s.\n")
727 "data will be stored for %s.\n")
724 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
728 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
725 if repo.dirstate[dst] in '?r' and not dryrun:
729 if repo.dirstate[dst] in '?r' and not dryrun:
726 wctx.add([dst])
730 wctx.add([dst])
727 elif not dryrun:
731 elif not dryrun:
728 wctx.copy(origsrc, dst)
732 wctx.copy(origsrc, dst)
729
733
730 def readrequires(opener, supported):
734 def readrequires(opener, supported):
731 '''Reads and parses .hg/requires and checks if all entries found
735 '''Reads and parses .hg/requires and checks if all entries found
732 are in the list of supported features.'''
736 are in the list of supported features.'''
733 requirements = set(opener.read("requires").splitlines())
737 requirements = set(opener.read("requires").splitlines())
734 missings = []
738 missings = []
735 for r in requirements:
739 for r in requirements:
736 if r not in supported:
740 if r not in supported:
737 if not r or not r[0].isalnum():
741 if not r or not r[0].isalnum():
738 raise error.RequirementError(_(".hg/requires file is corrupt"))
742 raise error.RequirementError(_(".hg/requires file is corrupt"))
739 missings.append(r)
743 missings.append(r)
740 missings.sort()
744 missings.sort()
741 if missings:
745 if missings:
742 raise error.RequirementError(
746 raise error.RequirementError(
743 _("repository requires features unknown to this Mercurial: %s")
747 _("repository requires features unknown to this Mercurial: %s")
744 % " ".join(missings),
748 % " ".join(missings),
745 hint=_("see https://mercurial-scm.org/wiki/MissingRequirement"
749 hint=_("see https://mercurial-scm.org/wiki/MissingRequirement"
746 " for more information"))
750 " for more information"))
747 return requirements
751 return requirements
748
752
749 def writerequires(opener, requirements):
753 def writerequires(opener, requirements):
750 with opener('requires', 'w') as fp:
754 with opener('requires', 'w') as fp:
751 for r in sorted(requirements):
755 for r in sorted(requirements):
752 fp.write("%s\n" % r)
756 fp.write("%s\n" % r)
753
757
754 class filecachesubentry(object):
758 class filecachesubentry(object):
755 def __init__(self, path, stat):
759 def __init__(self, path, stat):
756 self.path = path
760 self.path = path
757 self.cachestat = None
761 self.cachestat = None
758 self._cacheable = None
762 self._cacheable = None
759
763
760 if stat:
764 if stat:
761 self.cachestat = filecachesubentry.stat(self.path)
765 self.cachestat = filecachesubentry.stat(self.path)
762
766
763 if self.cachestat:
767 if self.cachestat:
764 self._cacheable = self.cachestat.cacheable()
768 self._cacheable = self.cachestat.cacheable()
765 else:
769 else:
766 # None means we don't know yet
770 # None means we don't know yet
767 self._cacheable = None
771 self._cacheable = None
768
772
769 def refresh(self):
773 def refresh(self):
770 if self.cacheable():
774 if self.cacheable():
771 self.cachestat = filecachesubentry.stat(self.path)
775 self.cachestat = filecachesubentry.stat(self.path)
772
776
773 def cacheable(self):
777 def cacheable(self):
774 if self._cacheable is not None:
778 if self._cacheable is not None:
775 return self._cacheable
779 return self._cacheable
776
780
777 # we don't know yet, assume it is for now
781 # we don't know yet, assume it is for now
778 return True
782 return True
779
783
780 def changed(self):
784 def changed(self):
781 # no point in going further if we can't cache it
785 # no point in going further if we can't cache it
782 if not self.cacheable():
786 if not self.cacheable():
783 return True
787 return True
784
788
785 newstat = filecachesubentry.stat(self.path)
789 newstat = filecachesubentry.stat(self.path)
786
790
787 # we may not know if it's cacheable yet, check again now
791 # we may not know if it's cacheable yet, check again now
788 if newstat and self._cacheable is None:
792 if newstat and self._cacheable is None:
789 self._cacheable = newstat.cacheable()
793 self._cacheable = newstat.cacheable()
790
794
791 # check again
795 # check again
792 if not self._cacheable:
796 if not self._cacheable:
793 return True
797 return True
794
798
795 if self.cachestat != newstat:
799 if self.cachestat != newstat:
796 self.cachestat = newstat
800 self.cachestat = newstat
797 return True
801 return True
798 else:
802 else:
799 return False
803 return False
800
804
801 @staticmethod
805 @staticmethod
802 def stat(path):
806 def stat(path):
803 try:
807 try:
804 return util.cachestat(path)
808 return util.cachestat(path)
805 except OSError as e:
809 except OSError as e:
806 if e.errno != errno.ENOENT:
810 if e.errno != errno.ENOENT:
807 raise
811 raise
808
812
809 class filecacheentry(object):
813 class filecacheentry(object):
810 def __init__(self, paths, stat=True):
814 def __init__(self, paths, stat=True):
811 self._entries = []
815 self._entries = []
812 for path in paths:
816 for path in paths:
813 self._entries.append(filecachesubentry(path, stat))
817 self._entries.append(filecachesubentry(path, stat))
814
818
815 def changed(self):
819 def changed(self):
816 '''true if any entry has changed'''
820 '''true if any entry has changed'''
817 for entry in self._entries:
821 for entry in self._entries:
818 if entry.changed():
822 if entry.changed():
819 return True
823 return True
820 return False
824 return False
821
825
822 def refresh(self):
826 def refresh(self):
823 for entry in self._entries:
827 for entry in self._entries:
824 entry.refresh()
828 entry.refresh()
825
829
826 class filecache(object):
830 class filecache(object):
827 '''A property like decorator that tracks files under .hg/ for updates.
831 '''A property like decorator that tracks files under .hg/ for updates.
828
832
829 Records stat info when called in _filecache.
833 Records stat info when called in _filecache.
830
834
831 On subsequent calls, compares old stat info with new info, and recreates the
835 On subsequent calls, compares old stat info with new info, and recreates the
832 object when any of the files changes, updating the new stat info in
836 object when any of the files changes, updating the new stat info in
833 _filecache.
837 _filecache.
834
838
835 Mercurial either atomic renames or appends for files under .hg,
839 Mercurial either atomic renames or appends for files under .hg,
836 so to ensure the cache is reliable we need the filesystem to be able
840 so to ensure the cache is reliable we need the filesystem to be able
837 to tell us if a file has been replaced. If it can't, we fallback to
841 to tell us if a file has been replaced. If it can't, we fallback to
838 recreating the object on every call (essentially the same behavior as
842 recreating the object on every call (essentially the same behavior as
839 propertycache).
843 propertycache).
840
844
841 '''
845 '''
842 def __init__(self, *paths):
846 def __init__(self, *paths):
843 self.paths = paths
847 self.paths = paths
844
848
845 def join(self, obj, fname):
849 def join(self, obj, fname):
846 """Used to compute the runtime path of a cached file.
850 """Used to compute the runtime path of a cached file.
847
851
848 Users should subclass filecache and provide their own version of this
852 Users should subclass filecache and provide their own version of this
849 function to call the appropriate join function on 'obj' (an instance
853 function to call the appropriate join function on 'obj' (an instance
850 of the class that its member function was decorated).
854 of the class that its member function was decorated).
851 """
855 """
852 raise NotImplementedError
856 raise NotImplementedError
853
857
854 def __call__(self, func):
858 def __call__(self, func):
855 self.func = func
859 self.func = func
856 self.name = func.__name__.encode('ascii')
860 self.name = func.__name__.encode('ascii')
857 return self
861 return self
858
862
859 def __get__(self, obj, type=None):
863 def __get__(self, obj, type=None):
860 # if accessed on the class, return the descriptor itself.
864 # if accessed on the class, return the descriptor itself.
861 if obj is None:
865 if obj is None:
862 return self
866 return self
863 # do we need to check if the file changed?
867 # do we need to check if the file changed?
864 if self.name in obj.__dict__:
868 if self.name in obj.__dict__:
865 assert self.name in obj._filecache, self.name
869 assert self.name in obj._filecache, self.name
866 return obj.__dict__[self.name]
870 return obj.__dict__[self.name]
867
871
868 entry = obj._filecache.get(self.name)
872 entry = obj._filecache.get(self.name)
869
873
870 if entry:
874 if entry:
871 if entry.changed():
875 if entry.changed():
872 entry.obj = self.func(obj)
876 entry.obj = self.func(obj)
873 else:
877 else:
874 paths = [self.join(obj, path) for path in self.paths]
878 paths = [self.join(obj, path) for path in self.paths]
875
879
876 # We stat -before- creating the object so our cache doesn't lie if
880 # We stat -before- creating the object so our cache doesn't lie if
877 # a writer modified between the time we read and stat
881 # a writer modified between the time we read and stat
878 entry = filecacheentry(paths, True)
882 entry = filecacheentry(paths, True)
879 entry.obj = self.func(obj)
883 entry.obj = self.func(obj)
880
884
881 obj._filecache[self.name] = entry
885 obj._filecache[self.name] = entry
882
886
883 obj.__dict__[self.name] = entry.obj
887 obj.__dict__[self.name] = entry.obj
884 return entry.obj
888 return entry.obj
885
889
886 def __set__(self, obj, value):
890 def __set__(self, obj, value):
887 if self.name not in obj._filecache:
891 if self.name not in obj._filecache:
888 # we add an entry for the missing value because X in __dict__
892 # we add an entry for the missing value because X in __dict__
889 # implies X in _filecache
893 # implies X in _filecache
890 paths = [self.join(obj, path) for path in self.paths]
894 paths = [self.join(obj, path) for path in self.paths]
891 ce = filecacheentry(paths, False)
895 ce = filecacheentry(paths, False)
892 obj._filecache[self.name] = ce
896 obj._filecache[self.name] = ce
893 else:
897 else:
894 ce = obj._filecache[self.name]
898 ce = obj._filecache[self.name]
895
899
896 ce.obj = value # update cached copy
900 ce.obj = value # update cached copy
897 obj.__dict__[self.name] = value # update copy returned by obj.x
901 obj.__dict__[self.name] = value # update copy returned by obj.x
898
902
899 def __delete__(self, obj):
903 def __delete__(self, obj):
900 try:
904 try:
901 del obj.__dict__[self.name]
905 del obj.__dict__[self.name]
902 except KeyError:
906 except KeyError:
903 raise AttributeError(self.name)
907 raise AttributeError(self.name)
904
908
905 def _locksub(repo, lock, envvar, cmd, environ=None, *args, **kwargs):
909 def _locksub(repo, lock, envvar, cmd, environ=None, *args, **kwargs):
906 if lock is None:
910 if lock is None:
907 raise error.LockInheritanceContractViolation(
911 raise error.LockInheritanceContractViolation(
908 'lock can only be inherited while held')
912 'lock can only be inherited while held')
909 if environ is None:
913 if environ is None:
910 environ = {}
914 environ = {}
911 with lock.inherit() as locker:
915 with lock.inherit() as locker:
912 environ[envvar] = locker
916 environ[envvar] = locker
913 return repo.ui.system(cmd, environ=environ, *args, **kwargs)
917 return repo.ui.system(cmd, environ=environ, *args, **kwargs)
914
918
915 def wlocksub(repo, cmd, *args, **kwargs):
919 def wlocksub(repo, cmd, *args, **kwargs):
916 """run cmd as a subprocess that allows inheriting repo's wlock
920 """run cmd as a subprocess that allows inheriting repo's wlock
917
921
918 This can only be called while the wlock is held. This takes all the
922 This can only be called while the wlock is held. This takes all the
919 arguments that ui.system does, and returns the exit code of the
923 arguments that ui.system does, and returns the exit code of the
920 subprocess."""
924 subprocess."""
921 return _locksub(repo, repo.currentwlock(), 'HG_WLOCK_LOCKER', cmd, *args,
925 return _locksub(repo, repo.currentwlock(), 'HG_WLOCK_LOCKER', cmd, *args,
922 **kwargs)
926 **kwargs)
923
927
924 def gdinitconfig(ui):
928 def gdinitconfig(ui):
925 """helper function to know if a repo should be created as general delta
929 """helper function to know if a repo should be created as general delta
926 """
930 """
927 # experimental config: format.generaldelta
931 # experimental config: format.generaldelta
928 return (ui.configbool('format', 'generaldelta', False)
932 return (ui.configbool('format', 'generaldelta', False)
929 or ui.configbool('format', 'usegeneraldelta', True))
933 or ui.configbool('format', 'usegeneraldelta', True))
930
934
931 def gddeltaconfig(ui):
935 def gddeltaconfig(ui):
932 """helper function to know if incoming delta should be optimised
936 """helper function to know if incoming delta should be optimised
933 """
937 """
934 # experimental config: format.generaldelta
938 # experimental config: format.generaldelta
935 return ui.configbool('format', 'generaldelta', False)
939 return ui.configbool('format', 'generaldelta', False)
936
940
937 class simplekeyvaluefile(object):
941 class simplekeyvaluefile(object):
938 """A simple file with key=value lines
942 """A simple file with key=value lines
939
943
940 Keys must be alphanumerics and start with a letter, values must not
944 Keys must be alphanumerics and start with a letter, values must not
941 contain '\n' characters"""
945 contain '\n' characters"""
942
946
943 def __init__(self, vfs, path, keys=None):
947 def __init__(self, vfs, path, keys=None):
944 self.vfs = vfs
948 self.vfs = vfs
945 self.path = path
949 self.path = path
946
950
947 def read(self):
951 def read(self):
948 lines = self.vfs.readlines(self.path)
952 lines = self.vfs.readlines(self.path)
949 try:
953 try:
950 d = dict(line[:-1].split('=', 1) for line in lines if line)
954 d = dict(line[:-1].split('=', 1) for line in lines if line)
951 except ValueError as e:
955 except ValueError as e:
952 raise error.CorruptedState(str(e))
956 raise error.CorruptedState(str(e))
953 return d
957 return d
954
958
955 def write(self, data):
959 def write(self, data):
956 """Write key=>value mapping to a file
960 """Write key=>value mapping to a file
957 data is a dict. Keys must be alphanumerical and start with a letter.
961 data is a dict. Keys must be alphanumerical and start with a letter.
958 Values must not contain newline characters."""
962 Values must not contain newline characters."""
959 lines = []
963 lines = []
960 for k, v in data.items():
964 for k, v in data.items():
961 if not k[0].isalpha():
965 if not k[0].isalpha():
962 e = "keys must start with a letter in a key-value file"
966 e = "keys must start with a letter in a key-value file"
963 raise error.ProgrammingError(e)
967 raise error.ProgrammingError(e)
964 if not k.isalnum():
968 if not k.isalnum():
965 e = "invalid key name in a simple key-value file"
969 e = "invalid key name in a simple key-value file"
966 raise error.ProgrammingError(e)
970 raise error.ProgrammingError(e)
967 if '\n' in v:
971 if '\n' in v:
968 e = "invalid value in a simple key-value file"
972 e = "invalid value in a simple key-value file"
969 raise error.ProgrammingError(e)
973 raise error.ProgrammingError(e)
970 lines.append("%s=%s\n" % (k, v))
974 lines.append("%s=%s\n" % (k, v))
971 with self.vfs(self.path, mode='wb', atomictemp=True) as fp:
975 with self.vfs(self.path, mode='wb', atomictemp=True) as fp:
972 fp.write(''.join(lines))
976 fp.write(''.join(lines))
@@ -1,54 +1,77 b''
1 Test UI worker interaction
1 Test UI worker interaction
2
2
3 $ cat > t.py <<EOF
3 $ cat > t.py <<EOF
4 > from __future__ import absolute_import, print_function
4 > from __future__ import absolute_import, print_function
5 > from mercurial import (
5 > from mercurial import (
6 > cmdutil,
6 > cmdutil,
7 > error,
7 > ui as uimod,
8 > ui as uimod,
8 > worker,
9 > worker,
9 > )
10 > )
11 > def abort(ui, args):
12 > if args[0] == 0:
13 > # by first worker for test stability
14 > raise error.Abort('known exception')
15 > return runme(ui, [])
10 > def runme(ui, args):
16 > def runme(ui, args):
11 > for arg in args:
17 > for arg in args:
12 > ui.status('run\n')
18 > ui.status('run\n')
13 > yield 1, arg
19 > yield 1, arg
20 > functable = {
21 > 'abort': abort,
22 > 'runme': runme,
23 > }
14 > cmdtable = {}
24 > cmdtable = {}
15 > command = cmdutil.command(cmdtable)
25 > command = cmdutil.command(cmdtable)
16 > @command('test', [], 'hg test [COST]')
26 > @command('test', [], 'hg test [COST] [FUNC]')
17 > def t(ui, repo, cost=1.0):
27 > def t(ui, repo, cost=1.0, func='runme'):
18 > cost = float(cost)
28 > cost = float(cost)
29 > func = functable[func]
19 > ui.status('start\n')
30 > ui.status('start\n')
20 > runs = worker.worker(ui, cost, runme, (ui,), range(8))
31 > runs = worker.worker(ui, cost, func, (ui,), range(8))
21 > for n, i in runs:
32 > for n, i in runs:
22 > pass
33 > pass
23 > ui.status('done\n')
34 > ui.status('done\n')
24 > EOF
35 > EOF
25 $ abspath=`pwd`/t.py
36 $ abspath=`pwd`/t.py
26 $ hg init
37 $ hg init
27
38
28 Run tests with worker enable by forcing a heigh cost
39 Run tests with worker enable by forcing a heigh cost
29
40
30 $ hg --config "extensions.t=$abspath" test 100000.0
41 $ hg --config "extensions.t=$abspath" test 100000.0
31 start
42 start
32 run
43 run
33 run
44 run
34 run
45 run
35 run
46 run
36 run
47 run
37 run
48 run
38 run
49 run
39 run
50 run
40 done
51 done
41
52
42 Run tests without worker by forcing a low cost
53 Run tests without worker by forcing a low cost
43
54
44 $ hg --config "extensions.t=$abspath" test 0.0000001
55 $ hg --config "extensions.t=$abspath" test 0.0000001
45 start
56 start
46 run
57 run
47 run
58 run
48 run
59 run
49 run
60 run
50 run
61 run
51 run
62 run
52 run
63 run
53 run
64 run
54 done
65 done
66
67 Known exception should be caught, but printed if --traceback is enabled
68
69 $ hg --config "extensions.t=$abspath" --config 'worker.numcpus=2' \
70 > test 100000.0 abort
71 start
72 abort: known exception
73 done
74
75 $ hg --config "extensions.t=$abspath" --config 'worker.numcpus=2' \
76 > test 100000.0 abort --traceback 2>&1 | grep '^Traceback'
77 Traceback (most recent call last):
General Comments 0
You need to be logged in to leave comments. Login now