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