##// END OF EJS Templates
py3: make the regular expression bytes to prevent TypeError
Pulkit Goyal -
r31491:492c64af default
parent child Browse files
Show More
@@ -1,922 +1,922 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 atexit
10 import atexit
11 import difflib
11 import difflib
12 import errno
12 import errno
13 import getopt
13 import getopt
14 import os
14 import os
15 import pdb
15 import pdb
16 import re
16 import re
17 import signal
17 import signal
18 import sys
18 import sys
19 import time
19 import time
20 import traceback
20 import traceback
21
21
22
22
23 from .i18n import _
23 from .i18n import _
24
24
25 from . import (
25 from . import (
26 cmdutil,
26 cmdutil,
27 color,
27 color,
28 commands,
28 commands,
29 debugcommands,
29 debugcommands,
30 demandimport,
30 demandimport,
31 encoding,
31 encoding,
32 error,
32 error,
33 extensions,
33 extensions,
34 fancyopts,
34 fancyopts,
35 fileset,
35 fileset,
36 help,
36 help,
37 hg,
37 hg,
38 hook,
38 hook,
39 profiling,
39 profiling,
40 pycompat,
40 pycompat,
41 revset,
41 revset,
42 scmutil,
42 scmutil,
43 templatefilters,
43 templatefilters,
44 templatekw,
44 templatekw,
45 templater,
45 templater,
46 ui as uimod,
46 ui as uimod,
47 util,
47 util,
48 )
48 )
49
49
50 class request(object):
50 class request(object):
51 def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
51 def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
52 ferr=None):
52 ferr=None):
53 self.args = args
53 self.args = args
54 self.ui = ui
54 self.ui = ui
55 self.repo = repo
55 self.repo = repo
56
56
57 # input/output/error streams
57 # input/output/error streams
58 self.fin = fin
58 self.fin = fin
59 self.fout = fout
59 self.fout = fout
60 self.ferr = ferr
60 self.ferr = ferr
61
61
62 def run():
62 def run():
63 "run the command in sys.argv"
63 "run the command in sys.argv"
64 sys.exit((dispatch(request(pycompat.sysargv[1:])) or 0) & 255)
64 sys.exit((dispatch(request(pycompat.sysargv[1:])) or 0) & 255)
65
65
66 def _getsimilar(symbols, value):
66 def _getsimilar(symbols, value):
67 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
67 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
68 # The cutoff for similarity here is pretty arbitrary. It should
68 # The cutoff for similarity here is pretty arbitrary. It should
69 # probably be investigated and tweaked.
69 # probably be investigated and tweaked.
70 return [s for s in symbols if sim(s) > 0.6]
70 return [s for s in symbols if sim(s) > 0.6]
71
71
72 def _reportsimilar(write, similar):
72 def _reportsimilar(write, similar):
73 if len(similar) == 1:
73 if len(similar) == 1:
74 write(_("(did you mean %s?)\n") % similar[0])
74 write(_("(did you mean %s?)\n") % similar[0])
75 elif similar:
75 elif similar:
76 ss = ", ".join(sorted(similar))
76 ss = ", ".join(sorted(similar))
77 write(_("(did you mean one of %s?)\n") % ss)
77 write(_("(did you mean one of %s?)\n") % ss)
78
78
79 def _formatparse(write, inst):
79 def _formatparse(write, inst):
80 similar = []
80 similar = []
81 if isinstance(inst, error.UnknownIdentifier):
81 if isinstance(inst, error.UnknownIdentifier):
82 # make sure to check fileset first, as revset can invoke fileset
82 # make sure to check fileset first, as revset can invoke fileset
83 similar = _getsimilar(inst.symbols, inst.function)
83 similar = _getsimilar(inst.symbols, inst.function)
84 if len(inst.args) > 1:
84 if len(inst.args) > 1:
85 write(_("hg: parse error at %s: %s\n") %
85 write(_("hg: parse error at %s: %s\n") %
86 (inst.args[1], inst.args[0]))
86 (inst.args[1], inst.args[0]))
87 if (inst.args[0][0] == ' '):
87 if (inst.args[0][0] == ' '):
88 write(_("unexpected leading whitespace\n"))
88 write(_("unexpected leading whitespace\n"))
89 else:
89 else:
90 write(_("hg: parse error: %s\n") % inst.args[0])
90 write(_("hg: parse error: %s\n") % inst.args[0])
91 _reportsimilar(write, similar)
91 _reportsimilar(write, similar)
92 if inst.hint:
92 if inst.hint:
93 write(_("(%s)\n") % inst.hint)
93 write(_("(%s)\n") % inst.hint)
94
94
95 def dispatch(req):
95 def dispatch(req):
96 "run the command specified in req.args"
96 "run the command specified in req.args"
97 if req.ferr:
97 if req.ferr:
98 ferr = req.ferr
98 ferr = req.ferr
99 elif req.ui:
99 elif req.ui:
100 ferr = req.ui.ferr
100 ferr = req.ui.ferr
101 else:
101 else:
102 ferr = util.stderr
102 ferr = util.stderr
103
103
104 try:
104 try:
105 if not req.ui:
105 if not req.ui:
106 req.ui = uimod.ui.load()
106 req.ui = uimod.ui.load()
107 if '--traceback' in req.args:
107 if '--traceback' in req.args:
108 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
108 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
109
109
110 # set ui streams from the request
110 # set ui streams from the request
111 if req.fin:
111 if req.fin:
112 req.ui.fin = req.fin
112 req.ui.fin = req.fin
113 if req.fout:
113 if req.fout:
114 req.ui.fout = req.fout
114 req.ui.fout = req.fout
115 if req.ferr:
115 if req.ferr:
116 req.ui.ferr = req.ferr
116 req.ui.ferr = req.ferr
117 except error.Abort as inst:
117 except error.Abort as inst:
118 ferr.write(_("abort: %s\n") % inst)
118 ferr.write(_("abort: %s\n") % inst)
119 if inst.hint:
119 if inst.hint:
120 ferr.write(_("(%s)\n") % inst.hint)
120 ferr.write(_("(%s)\n") % inst.hint)
121 return -1
121 return -1
122 except error.ParseError as inst:
122 except error.ParseError as inst:
123 _formatparse(ferr.write, inst)
123 _formatparse(ferr.write, inst)
124 return -1
124 return -1
125
125
126 msg = ' '.join(' ' in a and repr(a) or a for a in req.args)
126 msg = ' '.join(' ' in a and repr(a) or a for a in req.args)
127 starttime = util.timer()
127 starttime = util.timer()
128 ret = None
128 ret = None
129 try:
129 try:
130 ret = _runcatch(req)
130 ret = _runcatch(req)
131 except KeyboardInterrupt:
131 except KeyboardInterrupt:
132 try:
132 try:
133 req.ui.warn(_("interrupted!\n"))
133 req.ui.warn(_("interrupted!\n"))
134 except IOError as inst:
134 except IOError as inst:
135 if inst.errno != errno.EPIPE:
135 if inst.errno != errno.EPIPE:
136 raise
136 raise
137 ret = -1
137 ret = -1
138 finally:
138 finally:
139 duration = util.timer() - starttime
139 duration = util.timer() - starttime
140 req.ui.flush()
140 req.ui.flush()
141 if req.ui.logblockedtimes:
141 if req.ui.logblockedtimes:
142 req.ui._blockedtimes['command_duration'] = duration * 1000
142 req.ui._blockedtimes['command_duration'] = duration * 1000
143 req.ui.log('uiblocked', 'ui blocked ms', **req.ui._blockedtimes)
143 req.ui.log('uiblocked', 'ui blocked ms', **req.ui._blockedtimes)
144 req.ui.log("commandfinish", "%s exited %s after %0.2f seconds\n",
144 req.ui.log("commandfinish", "%s exited %s after %0.2f seconds\n",
145 msg, ret or 0, duration)
145 msg, ret or 0, duration)
146 return ret
146 return ret
147
147
148 def _runcatch(req):
148 def _runcatch(req):
149 def catchterm(*args):
149 def catchterm(*args):
150 raise error.SignalInterrupt
150 raise error.SignalInterrupt
151
151
152 ui = req.ui
152 ui = req.ui
153 try:
153 try:
154 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
154 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
155 num = getattr(signal, name, None)
155 num = getattr(signal, name, None)
156 if num:
156 if num:
157 signal.signal(num, catchterm)
157 signal.signal(num, catchterm)
158 except ValueError:
158 except ValueError:
159 pass # happens if called in a thread
159 pass # happens if called in a thread
160
160
161 def _runcatchfunc():
161 def _runcatchfunc():
162 try:
162 try:
163 debugger = 'pdb'
163 debugger = 'pdb'
164 debugtrace = {
164 debugtrace = {
165 'pdb' : pdb.set_trace
165 'pdb' : pdb.set_trace
166 }
166 }
167 debugmortem = {
167 debugmortem = {
168 'pdb' : pdb.post_mortem
168 'pdb' : pdb.post_mortem
169 }
169 }
170
170
171 # read --config before doing anything else
171 # read --config before doing anything else
172 # (e.g. to change trust settings for reading .hg/hgrc)
172 # (e.g. to change trust settings for reading .hg/hgrc)
173 cfgs = _parseconfig(req.ui, _earlygetopt(['--config'], req.args))
173 cfgs = _parseconfig(req.ui, _earlygetopt(['--config'], req.args))
174
174
175 if req.repo:
175 if req.repo:
176 # copy configs that were passed on the cmdline (--config) to
176 # copy configs that were passed on the cmdline (--config) to
177 # the repo ui
177 # the repo ui
178 for sec, name, val in cfgs:
178 for sec, name, val in cfgs:
179 req.repo.ui.setconfig(sec, name, val, source='--config')
179 req.repo.ui.setconfig(sec, name, val, source='--config')
180
180
181 # developer config: ui.debugger
181 # developer config: ui.debugger
182 debugger = ui.config("ui", "debugger")
182 debugger = ui.config("ui", "debugger")
183 debugmod = pdb
183 debugmod = pdb
184 if not debugger or ui.plain():
184 if not debugger or ui.plain():
185 # if we are in HGPLAIN mode, then disable custom debugging
185 # if we are in HGPLAIN mode, then disable custom debugging
186 debugger = 'pdb'
186 debugger = 'pdb'
187 elif '--debugger' in req.args:
187 elif '--debugger' in req.args:
188 # This import can be slow for fancy debuggers, so only
188 # This import can be slow for fancy debuggers, so only
189 # do it when absolutely necessary, i.e. when actual
189 # do it when absolutely necessary, i.e. when actual
190 # debugging has been requested
190 # debugging has been requested
191 with demandimport.deactivated():
191 with demandimport.deactivated():
192 try:
192 try:
193 debugmod = __import__(debugger)
193 debugmod = __import__(debugger)
194 except ImportError:
194 except ImportError:
195 pass # Leave debugmod = pdb
195 pass # Leave debugmod = pdb
196
196
197 debugtrace[debugger] = debugmod.set_trace
197 debugtrace[debugger] = debugmod.set_trace
198 debugmortem[debugger] = debugmod.post_mortem
198 debugmortem[debugger] = debugmod.post_mortem
199
199
200 # enter the debugger before command execution
200 # enter the debugger before command execution
201 if '--debugger' in req.args:
201 if '--debugger' in req.args:
202 ui.warn(_("entering debugger - "
202 ui.warn(_("entering debugger - "
203 "type c to continue starting hg or h for help\n"))
203 "type c to continue starting hg or h for help\n"))
204
204
205 if (debugger != 'pdb' and
205 if (debugger != 'pdb' and
206 debugtrace[debugger] == debugtrace['pdb']):
206 debugtrace[debugger] == debugtrace['pdb']):
207 ui.warn(_("%s debugger specified "
207 ui.warn(_("%s debugger specified "
208 "but its module was not found\n") % debugger)
208 "but its module was not found\n") % debugger)
209 with demandimport.deactivated():
209 with demandimport.deactivated():
210 debugtrace[debugger]()
210 debugtrace[debugger]()
211 try:
211 try:
212 return _dispatch(req)
212 return _dispatch(req)
213 finally:
213 finally:
214 ui.flush()
214 ui.flush()
215 except: # re-raises
215 except: # re-raises
216 # enter the debugger when we hit an exception
216 # enter the debugger when we hit an exception
217 if '--debugger' in req.args:
217 if '--debugger' in req.args:
218 traceback.print_exc()
218 traceback.print_exc()
219 debugmortem[debugger](sys.exc_info()[2])
219 debugmortem[debugger](sys.exc_info()[2])
220 ui.traceback()
220 ui.traceback()
221 raise
221 raise
222
222
223 return callcatch(ui, _runcatchfunc)
223 return callcatch(ui, _runcatchfunc)
224
224
225 def callcatch(ui, func):
225 def callcatch(ui, func):
226 """like scmutil.callcatch but handles more high-level exceptions about
226 """like scmutil.callcatch but handles more high-level exceptions about
227 config parsing and commands. besides, use handlecommandexception to handle
227 config parsing and commands. besides, use handlecommandexception to handle
228 uncaught exceptions.
228 uncaught exceptions.
229 """
229 """
230 try:
230 try:
231 return scmutil.callcatch(ui, func)
231 return scmutil.callcatch(ui, func)
232 except error.AmbiguousCommand as inst:
232 except error.AmbiguousCommand as inst:
233 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
233 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
234 (inst.args[0], " ".join(inst.args[1])))
234 (inst.args[0], " ".join(inst.args[1])))
235 except error.CommandError as inst:
235 except error.CommandError as inst:
236 if inst.args[0]:
236 if inst.args[0]:
237 ui.pager('help')
237 ui.pager('help')
238 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
238 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
239 commands.help_(ui, inst.args[0], full=False, command=True)
239 commands.help_(ui, inst.args[0], full=False, command=True)
240 else:
240 else:
241 ui.pager('help')
241 ui.pager('help')
242 ui.warn(_("hg: %s\n") % inst.args[1])
242 ui.warn(_("hg: %s\n") % inst.args[1])
243 commands.help_(ui, 'shortlist')
243 commands.help_(ui, 'shortlist')
244 except error.ParseError as inst:
244 except error.ParseError as inst:
245 _formatparse(ui.warn, inst)
245 _formatparse(ui.warn, inst)
246 return -1
246 return -1
247 except error.UnknownCommand as inst:
247 except error.UnknownCommand as inst:
248 nocmdmsg = _("hg: unknown command '%s'\n") % inst.args[0]
248 nocmdmsg = _("hg: unknown command '%s'\n") % inst.args[0]
249 try:
249 try:
250 # check if the command is in a disabled extension
250 # check if the command is in a disabled extension
251 # (but don't check for extensions themselves)
251 # (but don't check for extensions themselves)
252 formatted = help.formattedhelp(ui, inst.args[0], unknowncmd=True)
252 formatted = help.formattedhelp(ui, inst.args[0], unknowncmd=True)
253 ui.warn(nocmdmsg)
253 ui.warn(nocmdmsg)
254 ui.write(formatted)
254 ui.write(formatted)
255 except (error.UnknownCommand, error.Abort):
255 except (error.UnknownCommand, error.Abort):
256 suggested = False
256 suggested = False
257 if len(inst.args) == 2:
257 if len(inst.args) == 2:
258 sim = _getsimilar(inst.args[1], inst.args[0])
258 sim = _getsimilar(inst.args[1], inst.args[0])
259 if sim:
259 if sim:
260 ui.warn(nocmdmsg)
260 ui.warn(nocmdmsg)
261 _reportsimilar(ui.warn, sim)
261 _reportsimilar(ui.warn, sim)
262 suggested = True
262 suggested = True
263 if not suggested:
263 if not suggested:
264 ui.pager('help')
264 ui.pager('help')
265 ui.warn(nocmdmsg)
265 ui.warn(nocmdmsg)
266 commands.help_(ui, 'shortlist')
266 commands.help_(ui, 'shortlist')
267 except IOError:
267 except IOError:
268 raise
268 raise
269 except KeyboardInterrupt:
269 except KeyboardInterrupt:
270 raise
270 raise
271 except: # probably re-raises
271 except: # probably re-raises
272 if not handlecommandexception(ui):
272 if not handlecommandexception(ui):
273 raise
273 raise
274
274
275 return -1
275 return -1
276
276
277 def aliasargs(fn, givenargs):
277 def aliasargs(fn, givenargs):
278 args = getattr(fn, 'args', [])
278 args = getattr(fn, 'args', [])
279 if args:
279 if args:
280 cmd = ' '.join(map(util.shellquote, args))
280 cmd = ' '.join(map(util.shellquote, args))
281
281
282 nums = []
282 nums = []
283 def replacer(m):
283 def replacer(m):
284 num = int(m.group(1)) - 1
284 num = int(m.group(1)) - 1
285 nums.append(num)
285 nums.append(num)
286 if num < len(givenargs):
286 if num < len(givenargs):
287 return givenargs[num]
287 return givenargs[num]
288 raise error.Abort(_('too few arguments for command alias'))
288 raise error.Abort(_('too few arguments for command alias'))
289 cmd = re.sub(r'\$(\d+|\$)', replacer, cmd)
289 cmd = re.sub(br'\$(\d+|\$)', replacer, cmd)
290 givenargs = [x for i, x in enumerate(givenargs)
290 givenargs = [x for i, x in enumerate(givenargs)
291 if i not in nums]
291 if i not in nums]
292 args = pycompat.shlexsplit(cmd)
292 args = pycompat.shlexsplit(cmd)
293 return args + givenargs
293 return args + givenargs
294
294
295 def aliasinterpolate(name, args, cmd):
295 def aliasinterpolate(name, args, cmd):
296 '''interpolate args into cmd for shell aliases
296 '''interpolate args into cmd for shell aliases
297
297
298 This also handles $0, $@ and "$@".
298 This also handles $0, $@ and "$@".
299 '''
299 '''
300 # util.interpolate can't deal with "$@" (with quotes) because it's only
300 # util.interpolate can't deal with "$@" (with quotes) because it's only
301 # built to match prefix + patterns.
301 # built to match prefix + patterns.
302 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
302 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
303 replacemap['$0'] = name
303 replacemap['$0'] = name
304 replacemap['$$'] = '$'
304 replacemap['$$'] = '$'
305 replacemap['$@'] = ' '.join(args)
305 replacemap['$@'] = ' '.join(args)
306 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
306 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
307 # parameters, separated out into words. Emulate the same behavior here by
307 # parameters, separated out into words. Emulate the same behavior here by
308 # quoting the arguments individually. POSIX shells will then typically
308 # quoting the arguments individually. POSIX shells will then typically
309 # tokenize each argument into exactly one word.
309 # tokenize each argument into exactly one word.
310 replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
310 replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
311 # escape '\$' for regex
311 # escape '\$' for regex
312 regex = '|'.join(replacemap.keys()).replace('$', r'\$')
312 regex = '|'.join(replacemap.keys()).replace('$', r'\$')
313 r = re.compile(regex)
313 r = re.compile(regex)
314 return r.sub(lambda x: replacemap[x.group()], cmd)
314 return r.sub(lambda x: replacemap[x.group()], cmd)
315
315
316 class cmdalias(object):
316 class cmdalias(object):
317 def __init__(self, name, definition, cmdtable, source):
317 def __init__(self, name, definition, cmdtable, source):
318 self.name = self.cmd = name
318 self.name = self.cmd = name
319 self.cmdname = ''
319 self.cmdname = ''
320 self.definition = definition
320 self.definition = definition
321 self.fn = None
321 self.fn = None
322 self.givenargs = []
322 self.givenargs = []
323 self.opts = []
323 self.opts = []
324 self.help = ''
324 self.help = ''
325 self.badalias = None
325 self.badalias = None
326 self.unknowncmd = False
326 self.unknowncmd = False
327 self.source = source
327 self.source = source
328
328
329 try:
329 try:
330 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
330 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
331 for alias, e in cmdtable.iteritems():
331 for alias, e in cmdtable.iteritems():
332 if e is entry:
332 if e is entry:
333 self.cmd = alias
333 self.cmd = alias
334 break
334 break
335 self.shadows = True
335 self.shadows = True
336 except error.UnknownCommand:
336 except error.UnknownCommand:
337 self.shadows = False
337 self.shadows = False
338
338
339 if not self.definition:
339 if not self.definition:
340 self.badalias = _("no definition for alias '%s'") % self.name
340 self.badalias = _("no definition for alias '%s'") % self.name
341 return
341 return
342
342
343 if self.definition.startswith('!'):
343 if self.definition.startswith('!'):
344 self.shell = True
344 self.shell = True
345 def fn(ui, *args):
345 def fn(ui, *args):
346 env = {'HG_ARGS': ' '.join((self.name,) + args)}
346 env = {'HG_ARGS': ' '.join((self.name,) + args)}
347 def _checkvar(m):
347 def _checkvar(m):
348 if m.groups()[0] == '$':
348 if m.groups()[0] == '$':
349 return m.group()
349 return m.group()
350 elif int(m.groups()[0]) <= len(args):
350 elif int(m.groups()[0]) <= len(args):
351 return m.group()
351 return m.group()
352 else:
352 else:
353 ui.debug("No argument found for substitution "
353 ui.debug("No argument found for substitution "
354 "of %i variable in alias '%s' definition."
354 "of %i variable in alias '%s' definition."
355 % (int(m.groups()[0]), self.name))
355 % (int(m.groups()[0]), self.name))
356 return ''
356 return ''
357 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
357 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
358 cmd = aliasinterpolate(self.name, args, cmd)
358 cmd = aliasinterpolate(self.name, args, cmd)
359 return ui.system(cmd, environ=env,
359 return ui.system(cmd, environ=env,
360 blockedtag='alias_%s' % self.name)
360 blockedtag='alias_%s' % self.name)
361 self.fn = fn
361 self.fn = fn
362 return
362 return
363
363
364 try:
364 try:
365 args = pycompat.shlexsplit(self.definition)
365 args = pycompat.shlexsplit(self.definition)
366 except ValueError as inst:
366 except ValueError as inst:
367 self.badalias = (_("error in definition for alias '%s': %s")
367 self.badalias = (_("error in definition for alias '%s': %s")
368 % (self.name, inst))
368 % (self.name, inst))
369 return
369 return
370 self.cmdname = cmd = args.pop(0)
370 self.cmdname = cmd = args.pop(0)
371 self.givenargs = args
371 self.givenargs = args
372
372
373 for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"):
373 for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"):
374 if _earlygetopt([invalidarg], args):
374 if _earlygetopt([invalidarg], args):
375 self.badalias = (_("error in definition for alias '%s': %s may "
375 self.badalias = (_("error in definition for alias '%s': %s may "
376 "only be given on the command line")
376 "only be given on the command line")
377 % (self.name, invalidarg))
377 % (self.name, invalidarg))
378 return
378 return
379
379
380 try:
380 try:
381 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
381 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
382 if len(tableentry) > 2:
382 if len(tableentry) > 2:
383 self.fn, self.opts, self.help = tableentry
383 self.fn, self.opts, self.help = tableentry
384 else:
384 else:
385 self.fn, self.opts = tableentry
385 self.fn, self.opts = tableentry
386
386
387 if self.help.startswith("hg " + cmd):
387 if self.help.startswith("hg " + cmd):
388 # drop prefix in old-style help lines so hg shows the alias
388 # drop prefix in old-style help lines so hg shows the alias
389 self.help = self.help[4 + len(cmd):]
389 self.help = self.help[4 + len(cmd):]
390 self.__doc__ = self.fn.__doc__
390 self.__doc__ = self.fn.__doc__
391
391
392 except error.UnknownCommand:
392 except error.UnknownCommand:
393 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
393 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
394 % (self.name, cmd))
394 % (self.name, cmd))
395 self.unknowncmd = True
395 self.unknowncmd = True
396 except error.AmbiguousCommand:
396 except error.AmbiguousCommand:
397 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
397 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
398 % (self.name, cmd))
398 % (self.name, cmd))
399
399
400 @property
400 @property
401 def args(self):
401 def args(self):
402 args = map(util.expandpath, self.givenargs)
402 args = map(util.expandpath, self.givenargs)
403 return aliasargs(self.fn, args)
403 return aliasargs(self.fn, args)
404
404
405 def __getattr__(self, name):
405 def __getattr__(self, name):
406 adefaults = {'norepo': True, 'optionalrepo': False, 'inferrepo': False}
406 adefaults = {'norepo': True, 'optionalrepo': False, 'inferrepo': False}
407 if name not in adefaults:
407 if name not in adefaults:
408 raise AttributeError(name)
408 raise AttributeError(name)
409 if self.badalias or util.safehasattr(self, 'shell'):
409 if self.badalias or util.safehasattr(self, 'shell'):
410 return adefaults[name]
410 return adefaults[name]
411 return getattr(self.fn, name)
411 return getattr(self.fn, name)
412
412
413 def __call__(self, ui, *args, **opts):
413 def __call__(self, ui, *args, **opts):
414 if self.badalias:
414 if self.badalias:
415 hint = None
415 hint = None
416 if self.unknowncmd:
416 if self.unknowncmd:
417 try:
417 try:
418 # check if the command is in a disabled extension
418 # check if the command is in a disabled extension
419 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
419 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
420 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
420 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
421 except error.UnknownCommand:
421 except error.UnknownCommand:
422 pass
422 pass
423 raise error.Abort(self.badalias, hint=hint)
423 raise error.Abort(self.badalias, hint=hint)
424 if self.shadows:
424 if self.shadows:
425 ui.debug("alias '%s' shadows command '%s'\n" %
425 ui.debug("alias '%s' shadows command '%s'\n" %
426 (self.name, self.cmdname))
426 (self.name, self.cmdname))
427
427
428 ui.log('commandalias', "alias '%s' expands to '%s'\n",
428 ui.log('commandalias', "alias '%s' expands to '%s'\n",
429 self.name, self.definition)
429 self.name, self.definition)
430 if util.safehasattr(self, 'shell'):
430 if util.safehasattr(self, 'shell'):
431 return self.fn(ui, *args, **opts)
431 return self.fn(ui, *args, **opts)
432 else:
432 else:
433 try:
433 try:
434 return util.checksignature(self.fn)(ui, *args, **opts)
434 return util.checksignature(self.fn)(ui, *args, **opts)
435 except error.SignatureError:
435 except error.SignatureError:
436 args = ' '.join([self.cmdname] + self.args)
436 args = ' '.join([self.cmdname] + self.args)
437 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
437 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
438 raise
438 raise
439
439
440 def addaliases(ui, cmdtable):
440 def addaliases(ui, cmdtable):
441 # aliases are processed after extensions have been loaded, so they
441 # aliases are processed after extensions have been loaded, so they
442 # may use extension commands. Aliases can also use other alias definitions,
442 # may use extension commands. Aliases can also use other alias definitions,
443 # but only if they have been defined prior to the current definition.
443 # but only if they have been defined prior to the current definition.
444 for alias, definition in ui.configitems('alias'):
444 for alias, definition in ui.configitems('alias'):
445 source = ui.configsource('alias', alias)
445 source = ui.configsource('alias', alias)
446 aliasdef = cmdalias(alias, definition, cmdtable, source)
446 aliasdef = cmdalias(alias, definition, cmdtable, source)
447
447
448 try:
448 try:
449 olddef = cmdtable[aliasdef.cmd][0]
449 olddef = cmdtable[aliasdef.cmd][0]
450 if olddef.definition == aliasdef.definition:
450 if olddef.definition == aliasdef.definition:
451 continue
451 continue
452 except (KeyError, AttributeError):
452 except (KeyError, AttributeError):
453 # definition might not exist or it might not be a cmdalias
453 # definition might not exist or it might not be a cmdalias
454 pass
454 pass
455
455
456 cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help)
456 cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help)
457
457
458 def _parse(ui, args):
458 def _parse(ui, args):
459 options = {}
459 options = {}
460 cmdoptions = {}
460 cmdoptions = {}
461
461
462 try:
462 try:
463 args = fancyopts.fancyopts(args, commands.globalopts, options)
463 args = fancyopts.fancyopts(args, commands.globalopts, options)
464 except getopt.GetoptError as inst:
464 except getopt.GetoptError as inst:
465 raise error.CommandError(None, inst)
465 raise error.CommandError(None, inst)
466
466
467 if args:
467 if args:
468 cmd, args = args[0], args[1:]
468 cmd, args = args[0], args[1:]
469 aliases, entry = cmdutil.findcmd(cmd, commands.table,
469 aliases, entry = cmdutil.findcmd(cmd, commands.table,
470 ui.configbool("ui", "strict"))
470 ui.configbool("ui", "strict"))
471 cmd = aliases[0]
471 cmd = aliases[0]
472 args = aliasargs(entry[0], args)
472 args = aliasargs(entry[0], args)
473 defaults = ui.config("defaults", cmd)
473 defaults = ui.config("defaults", cmd)
474 if defaults:
474 if defaults:
475 args = map(util.expandpath, pycompat.shlexsplit(defaults)) + args
475 args = map(util.expandpath, pycompat.shlexsplit(defaults)) + args
476 c = list(entry[1])
476 c = list(entry[1])
477 else:
477 else:
478 cmd = None
478 cmd = None
479 c = []
479 c = []
480
480
481 # combine global options into local
481 # combine global options into local
482 for o in commands.globalopts:
482 for o in commands.globalopts:
483 c.append((o[0], o[1], options[o[1]], o[3]))
483 c.append((o[0], o[1], options[o[1]], o[3]))
484
484
485 try:
485 try:
486 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
486 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
487 except getopt.GetoptError as inst:
487 except getopt.GetoptError as inst:
488 raise error.CommandError(cmd, inst)
488 raise error.CommandError(cmd, inst)
489
489
490 # separate global options back out
490 # separate global options back out
491 for o in commands.globalopts:
491 for o in commands.globalopts:
492 n = o[1]
492 n = o[1]
493 options[n] = cmdoptions[n]
493 options[n] = cmdoptions[n]
494 del cmdoptions[n]
494 del cmdoptions[n]
495
495
496 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
496 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
497
497
498 def _parseconfig(ui, config):
498 def _parseconfig(ui, config):
499 """parse the --config options from the command line"""
499 """parse the --config options from the command line"""
500 configs = []
500 configs = []
501
501
502 for cfg in config:
502 for cfg in config:
503 try:
503 try:
504 name, value = [cfgelem.strip()
504 name, value = [cfgelem.strip()
505 for cfgelem in cfg.split('=', 1)]
505 for cfgelem in cfg.split('=', 1)]
506 section, name = name.split('.', 1)
506 section, name = name.split('.', 1)
507 if not section or not name:
507 if not section or not name:
508 raise IndexError
508 raise IndexError
509 ui.setconfig(section, name, value, '--config')
509 ui.setconfig(section, name, value, '--config')
510 configs.append((section, name, value))
510 configs.append((section, name, value))
511 except (IndexError, ValueError):
511 except (IndexError, ValueError):
512 raise error.Abort(_('malformed --config option: %r '
512 raise error.Abort(_('malformed --config option: %r '
513 '(use --config section.name=value)') % cfg)
513 '(use --config section.name=value)') % cfg)
514
514
515 return configs
515 return configs
516
516
517 def _earlygetopt(aliases, args):
517 def _earlygetopt(aliases, args):
518 """Return list of values for an option (or aliases).
518 """Return list of values for an option (or aliases).
519
519
520 The values are listed in the order they appear in args.
520 The values are listed in the order they appear in args.
521 The options and values are removed from args.
521 The options and values are removed from args.
522
522
523 >>> args = ['x', '--cwd', 'foo', 'y']
523 >>> args = ['x', '--cwd', 'foo', 'y']
524 >>> _earlygetopt(['--cwd'], args), args
524 >>> _earlygetopt(['--cwd'], args), args
525 (['foo'], ['x', 'y'])
525 (['foo'], ['x', 'y'])
526
526
527 >>> args = ['x', '--cwd=bar', 'y']
527 >>> args = ['x', '--cwd=bar', 'y']
528 >>> _earlygetopt(['--cwd'], args), args
528 >>> _earlygetopt(['--cwd'], args), args
529 (['bar'], ['x', 'y'])
529 (['bar'], ['x', 'y'])
530
530
531 >>> args = ['x', '-R', 'foo', 'y']
531 >>> args = ['x', '-R', 'foo', 'y']
532 >>> _earlygetopt(['-R'], args), args
532 >>> _earlygetopt(['-R'], args), args
533 (['foo'], ['x', 'y'])
533 (['foo'], ['x', 'y'])
534
534
535 >>> args = ['x', '-Rbar', 'y']
535 >>> args = ['x', '-Rbar', 'y']
536 >>> _earlygetopt(['-R'], args), args
536 >>> _earlygetopt(['-R'], args), args
537 (['bar'], ['x', 'y'])
537 (['bar'], ['x', 'y'])
538 """
538 """
539 try:
539 try:
540 argcount = args.index("--")
540 argcount = args.index("--")
541 except ValueError:
541 except ValueError:
542 argcount = len(args)
542 argcount = len(args)
543 shortopts = [opt for opt in aliases if len(opt) == 2]
543 shortopts = [opt for opt in aliases if len(opt) == 2]
544 values = []
544 values = []
545 pos = 0
545 pos = 0
546 while pos < argcount:
546 while pos < argcount:
547 fullarg = arg = args[pos]
547 fullarg = arg = args[pos]
548 equals = arg.find('=')
548 equals = arg.find('=')
549 if equals > -1:
549 if equals > -1:
550 arg = arg[:equals]
550 arg = arg[:equals]
551 if arg in aliases:
551 if arg in aliases:
552 del args[pos]
552 del args[pos]
553 if equals > -1:
553 if equals > -1:
554 values.append(fullarg[equals + 1:])
554 values.append(fullarg[equals + 1:])
555 argcount -= 1
555 argcount -= 1
556 else:
556 else:
557 if pos + 1 >= argcount:
557 if pos + 1 >= argcount:
558 # ignore and let getopt report an error if there is no value
558 # ignore and let getopt report an error if there is no value
559 break
559 break
560 values.append(args.pop(pos))
560 values.append(args.pop(pos))
561 argcount -= 2
561 argcount -= 2
562 elif arg[:2] in shortopts:
562 elif arg[:2] in shortopts:
563 # short option can have no following space, e.g. hg log -Rfoo
563 # short option can have no following space, e.g. hg log -Rfoo
564 values.append(args.pop(pos)[2:])
564 values.append(args.pop(pos)[2:])
565 argcount -= 1
565 argcount -= 1
566 else:
566 else:
567 pos += 1
567 pos += 1
568 return values
568 return values
569
569
570 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
570 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
571 # run pre-hook, and abort if it fails
571 # run pre-hook, and abort if it fails
572 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
572 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
573 pats=cmdpats, opts=cmdoptions)
573 pats=cmdpats, opts=cmdoptions)
574 try:
574 try:
575 ret = _runcommand(ui, options, cmd, d)
575 ret = _runcommand(ui, options, cmd, d)
576 # run post-hook, passing command result
576 # run post-hook, passing command result
577 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
577 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
578 result=ret, pats=cmdpats, opts=cmdoptions)
578 result=ret, pats=cmdpats, opts=cmdoptions)
579 except Exception:
579 except Exception:
580 # run failure hook and re-raise
580 # run failure hook and re-raise
581 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
581 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
582 pats=cmdpats, opts=cmdoptions)
582 pats=cmdpats, opts=cmdoptions)
583 raise
583 raise
584 return ret
584 return ret
585
585
586 def _getlocal(ui, rpath, wd=None):
586 def _getlocal(ui, rpath, wd=None):
587 """Return (path, local ui object) for the given target path.
587 """Return (path, local ui object) for the given target path.
588
588
589 Takes paths in [cwd]/.hg/hgrc into account."
589 Takes paths in [cwd]/.hg/hgrc into account."
590 """
590 """
591 if wd is None:
591 if wd is None:
592 try:
592 try:
593 wd = pycompat.getcwd()
593 wd = pycompat.getcwd()
594 except OSError as e:
594 except OSError as e:
595 raise error.Abort(_("error getting current working directory: %s") %
595 raise error.Abort(_("error getting current working directory: %s") %
596 e.strerror)
596 e.strerror)
597 path = cmdutil.findrepo(wd) or ""
597 path = cmdutil.findrepo(wd) or ""
598 if not path:
598 if not path:
599 lui = ui
599 lui = ui
600 else:
600 else:
601 lui = ui.copy()
601 lui = ui.copy()
602 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
602 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
603
603
604 if rpath and rpath[-1]:
604 if rpath and rpath[-1]:
605 path = lui.expandpath(rpath[-1])
605 path = lui.expandpath(rpath[-1])
606 lui = ui.copy()
606 lui = ui.copy()
607 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
607 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
608
608
609 return path, lui
609 return path, lui
610
610
611 def _checkshellalias(lui, ui, args):
611 def _checkshellalias(lui, ui, args):
612 """Return the function to run the shell alias, if it is required"""
612 """Return the function to run the shell alias, if it is required"""
613 options = {}
613 options = {}
614
614
615 try:
615 try:
616 args = fancyopts.fancyopts(args, commands.globalopts, options)
616 args = fancyopts.fancyopts(args, commands.globalopts, options)
617 except getopt.GetoptError:
617 except getopt.GetoptError:
618 return
618 return
619
619
620 if not args:
620 if not args:
621 return
621 return
622
622
623 cmdtable = commands.table
623 cmdtable = commands.table
624
624
625 cmd = args[0]
625 cmd = args[0]
626 try:
626 try:
627 strict = ui.configbool("ui", "strict")
627 strict = ui.configbool("ui", "strict")
628 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
628 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
629 except (error.AmbiguousCommand, error.UnknownCommand):
629 except (error.AmbiguousCommand, error.UnknownCommand):
630 return
630 return
631
631
632 cmd = aliases[0]
632 cmd = aliases[0]
633 fn = entry[0]
633 fn = entry[0]
634
634
635 if cmd and util.safehasattr(fn, 'shell'):
635 if cmd and util.safehasattr(fn, 'shell'):
636 d = lambda: fn(ui, *args[1:])
636 d = lambda: fn(ui, *args[1:])
637 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
637 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
638 [], {})
638 [], {})
639
639
640 _loaded = set()
640 _loaded = set()
641
641
642 # list of (objname, loadermod, loadername) tuple:
642 # list of (objname, loadermod, loadername) tuple:
643 # - objname is the name of an object in extension module, from which
643 # - objname is the name of an object in extension module, from which
644 # extra information is loaded
644 # extra information is loaded
645 # - loadermod is the module where loader is placed
645 # - loadermod is the module where loader is placed
646 # - loadername is the name of the function, which takes (ui, extensionname,
646 # - loadername is the name of the function, which takes (ui, extensionname,
647 # extraobj) arguments
647 # extraobj) arguments
648 extraloaders = [
648 extraloaders = [
649 ('cmdtable', commands, 'loadcmdtable'),
649 ('cmdtable', commands, 'loadcmdtable'),
650 ('colortable', color, 'loadcolortable'),
650 ('colortable', color, 'loadcolortable'),
651 ('filesetpredicate', fileset, 'loadpredicate'),
651 ('filesetpredicate', fileset, 'loadpredicate'),
652 ('revsetpredicate', revset, 'loadpredicate'),
652 ('revsetpredicate', revset, 'loadpredicate'),
653 ('templatefilter', templatefilters, 'loadfilter'),
653 ('templatefilter', templatefilters, 'loadfilter'),
654 ('templatefunc', templater, 'loadfunction'),
654 ('templatefunc', templater, 'loadfunction'),
655 ('templatekeyword', templatekw, 'loadkeyword'),
655 ('templatekeyword', templatekw, 'loadkeyword'),
656 ]
656 ]
657
657
658 def _dispatch(req):
658 def _dispatch(req):
659 args = req.args
659 args = req.args
660 ui = req.ui
660 ui = req.ui
661
661
662 # check for cwd
662 # check for cwd
663 cwd = _earlygetopt(['--cwd'], args)
663 cwd = _earlygetopt(['--cwd'], args)
664 if cwd:
664 if cwd:
665 os.chdir(cwd[-1])
665 os.chdir(cwd[-1])
666
666
667 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
667 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
668 path, lui = _getlocal(ui, rpath)
668 path, lui = _getlocal(ui, rpath)
669
669
670 # Side-effect of accessing is debugcommands module is guaranteed to be
670 # Side-effect of accessing is debugcommands module is guaranteed to be
671 # imported and commands.table is populated.
671 # imported and commands.table is populated.
672 debugcommands.command
672 debugcommands.command
673
673
674 uis = set([ui, lui])
674 uis = set([ui, lui])
675
675
676 if req.repo:
676 if req.repo:
677 uis.add(req.repo.ui)
677 uis.add(req.repo.ui)
678
678
679 if '--profile' in args:
679 if '--profile' in args:
680 for ui_ in uis:
680 for ui_ in uis:
681 ui_.setconfig('profiling', 'enabled', 'true', '--profile')
681 ui_.setconfig('profiling', 'enabled', 'true', '--profile')
682
682
683 with profiling.maybeprofile(lui):
683 with profiling.maybeprofile(lui):
684 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
684 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
685 # reposetup. Programs like TortoiseHg will call _dispatch several
685 # reposetup. Programs like TortoiseHg will call _dispatch several
686 # times so we keep track of configured extensions in _loaded.
686 # times so we keep track of configured extensions in _loaded.
687 extensions.loadall(lui)
687 extensions.loadall(lui)
688 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
688 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
689 # Propagate any changes to lui.__class__ by extensions
689 # Propagate any changes to lui.__class__ by extensions
690 ui.__class__ = lui.__class__
690 ui.__class__ = lui.__class__
691
691
692 # (uisetup and extsetup are handled in extensions.loadall)
692 # (uisetup and extsetup are handled in extensions.loadall)
693
693
694 for name, module in exts:
694 for name, module in exts:
695 for objname, loadermod, loadername in extraloaders:
695 for objname, loadermod, loadername in extraloaders:
696 extraobj = getattr(module, objname, None)
696 extraobj = getattr(module, objname, None)
697 if extraobj is not None:
697 if extraobj is not None:
698 getattr(loadermod, loadername)(ui, name, extraobj)
698 getattr(loadermod, loadername)(ui, name, extraobj)
699 _loaded.add(name)
699 _loaded.add(name)
700
700
701 # (reposetup is handled in hg.repository)
701 # (reposetup is handled in hg.repository)
702
702
703 addaliases(lui, commands.table)
703 addaliases(lui, commands.table)
704
704
705 # All aliases and commands are completely defined, now.
705 # All aliases and commands are completely defined, now.
706 # Check abbreviation/ambiguity of shell alias.
706 # Check abbreviation/ambiguity of shell alias.
707 shellaliasfn = _checkshellalias(lui, ui, args)
707 shellaliasfn = _checkshellalias(lui, ui, args)
708 if shellaliasfn:
708 if shellaliasfn:
709 return shellaliasfn()
709 return shellaliasfn()
710
710
711 # check for fallback encoding
711 # check for fallback encoding
712 fallback = lui.config('ui', 'fallbackencoding')
712 fallback = lui.config('ui', 'fallbackencoding')
713 if fallback:
713 if fallback:
714 encoding.fallbackencoding = fallback
714 encoding.fallbackencoding = fallback
715
715
716 fullargs = args
716 fullargs = args
717 cmd, func, args, options, cmdoptions = _parse(lui, args)
717 cmd, func, args, options, cmdoptions = _parse(lui, args)
718
718
719 if options["config"]:
719 if options["config"]:
720 raise error.Abort(_("option --config may not be abbreviated!"))
720 raise error.Abort(_("option --config may not be abbreviated!"))
721 if options["cwd"]:
721 if options["cwd"]:
722 raise error.Abort(_("option --cwd may not be abbreviated!"))
722 raise error.Abort(_("option --cwd may not be abbreviated!"))
723 if options["repository"]:
723 if options["repository"]:
724 raise error.Abort(_(
724 raise error.Abort(_(
725 "option -R has to be separated from other options (e.g. not "
725 "option -R has to be separated from other options (e.g. not "
726 "-qR) and --repository may only be abbreviated as --repo!"))
726 "-qR) and --repository may only be abbreviated as --repo!"))
727
727
728 if options["encoding"]:
728 if options["encoding"]:
729 encoding.encoding = options["encoding"]
729 encoding.encoding = options["encoding"]
730 if options["encodingmode"]:
730 if options["encodingmode"]:
731 encoding.encodingmode = options["encodingmode"]
731 encoding.encodingmode = options["encodingmode"]
732 if options["time"]:
732 if options["time"]:
733 def get_times():
733 def get_times():
734 t = os.times()
734 t = os.times()
735 if t[4] == 0.0:
735 if t[4] == 0.0:
736 # Windows leaves this as zero, so use time.clock()
736 # Windows leaves this as zero, so use time.clock()
737 t = (t[0], t[1], t[2], t[3], time.clock())
737 t = (t[0], t[1], t[2], t[3], time.clock())
738 return t
738 return t
739 s = get_times()
739 s = get_times()
740 def print_time():
740 def print_time():
741 t = get_times()
741 t = get_times()
742 ui.warn(
742 ui.warn(
743 _("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
743 _("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
744 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
744 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
745 atexit.register(print_time)
745 atexit.register(print_time)
746
746
747 if options['verbose'] or options['debug'] or options['quiet']:
747 if options['verbose'] or options['debug'] or options['quiet']:
748 for opt in ('verbose', 'debug', 'quiet'):
748 for opt in ('verbose', 'debug', 'quiet'):
749 val = str(bool(options[opt]))
749 val = str(bool(options[opt]))
750 if pycompat.ispy3:
750 if pycompat.ispy3:
751 val = val.encode('ascii')
751 val = val.encode('ascii')
752 for ui_ in uis:
752 for ui_ in uis:
753 ui_.setconfig('ui', opt, val, '--' + opt)
753 ui_.setconfig('ui', opt, val, '--' + opt)
754
754
755 if options['traceback']:
755 if options['traceback']:
756 for ui_ in uis:
756 for ui_ in uis:
757 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
757 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
758
758
759 if options['noninteractive']:
759 if options['noninteractive']:
760 for ui_ in uis:
760 for ui_ in uis:
761 ui_.setconfig('ui', 'interactive', 'off', '-y')
761 ui_.setconfig('ui', 'interactive', 'off', '-y')
762
762
763 if util.parsebool(options['pager']):
763 if util.parsebool(options['pager']):
764 ui.pager('internal-always-' + cmd)
764 ui.pager('internal-always-' + cmd)
765 elif options['pager'] != 'auto':
765 elif options['pager'] != 'auto':
766 ui.disablepager()
766 ui.disablepager()
767
767
768 if cmdoptions.get('insecure', False):
768 if cmdoptions.get('insecure', False):
769 for ui_ in uis:
769 for ui_ in uis:
770 ui_.insecureconnections = True
770 ui_.insecureconnections = True
771
771
772 # setup color handling
772 # setup color handling
773 coloropt = options['color']
773 coloropt = options['color']
774 for ui_ in uis:
774 for ui_ in uis:
775 if coloropt:
775 if coloropt:
776 ui_.setconfig('ui', 'color', coloropt, '--color')
776 ui_.setconfig('ui', 'color', coloropt, '--color')
777 color.setup(ui_)
777 color.setup(ui_)
778
778
779 if options['version']:
779 if options['version']:
780 return commands.version_(ui)
780 return commands.version_(ui)
781 if options['help']:
781 if options['help']:
782 return commands.help_(ui, cmd, command=cmd is not None)
782 return commands.help_(ui, cmd, command=cmd is not None)
783 elif not cmd:
783 elif not cmd:
784 return commands.help_(ui, 'shortlist')
784 return commands.help_(ui, 'shortlist')
785
785
786 repo = None
786 repo = None
787 cmdpats = args[:]
787 cmdpats = args[:]
788 if not func.norepo:
788 if not func.norepo:
789 # use the repo from the request only if we don't have -R
789 # use the repo from the request only if we don't have -R
790 if not rpath and not cwd:
790 if not rpath and not cwd:
791 repo = req.repo
791 repo = req.repo
792
792
793 if repo:
793 if repo:
794 # set the descriptors of the repo ui to those of ui
794 # set the descriptors of the repo ui to those of ui
795 repo.ui.fin = ui.fin
795 repo.ui.fin = ui.fin
796 repo.ui.fout = ui.fout
796 repo.ui.fout = ui.fout
797 repo.ui.ferr = ui.ferr
797 repo.ui.ferr = ui.ferr
798 else:
798 else:
799 try:
799 try:
800 repo = hg.repository(ui, path=path)
800 repo = hg.repository(ui, path=path)
801 if not repo.local():
801 if not repo.local():
802 raise error.Abort(_("repository '%s' is not local")
802 raise error.Abort(_("repository '%s' is not local")
803 % path)
803 % path)
804 repo.ui.setconfig("bundle", "mainreporoot", repo.root,
804 repo.ui.setconfig("bundle", "mainreporoot", repo.root,
805 'repo')
805 'repo')
806 except error.RequirementError:
806 except error.RequirementError:
807 raise
807 raise
808 except error.RepoError:
808 except error.RepoError:
809 if rpath and rpath[-1]: # invalid -R path
809 if rpath and rpath[-1]: # invalid -R path
810 raise
810 raise
811 if not func.optionalrepo:
811 if not func.optionalrepo:
812 if func.inferrepo and args and not path:
812 if func.inferrepo and args and not path:
813 # try to infer -R from command args
813 # try to infer -R from command args
814 repos = map(cmdutil.findrepo, args)
814 repos = map(cmdutil.findrepo, args)
815 guess = repos[0]
815 guess = repos[0]
816 if guess and repos.count(guess) == len(repos):
816 if guess and repos.count(guess) == len(repos):
817 req.args = ['--repository', guess] + fullargs
817 req.args = ['--repository', guess] + fullargs
818 return _dispatch(req)
818 return _dispatch(req)
819 if not path:
819 if not path:
820 raise error.RepoError(_("no repository found in"
820 raise error.RepoError(_("no repository found in"
821 " '%s' (.hg not found)")
821 " '%s' (.hg not found)")
822 % pycompat.getcwd())
822 % pycompat.getcwd())
823 raise
823 raise
824 if repo:
824 if repo:
825 ui = repo.ui
825 ui = repo.ui
826 if options['hidden']:
826 if options['hidden']:
827 repo = repo.unfiltered()
827 repo = repo.unfiltered()
828 args.insert(0, repo)
828 args.insert(0, repo)
829 elif rpath:
829 elif rpath:
830 ui.warn(_("warning: --repository ignored\n"))
830 ui.warn(_("warning: --repository ignored\n"))
831
831
832 msg = ' '.join(' ' in a and repr(a) or a for a in fullargs)
832 msg = ' '.join(' ' in a and repr(a) or a for a in fullargs)
833 ui.log("command", '%s\n', msg)
833 ui.log("command", '%s\n', msg)
834 strcmdopt = pycompat.strkwargs(cmdoptions)
834 strcmdopt = pycompat.strkwargs(cmdoptions)
835 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
835 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
836 try:
836 try:
837 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
837 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
838 cmdpats, cmdoptions)
838 cmdpats, cmdoptions)
839 finally:
839 finally:
840 if repo and repo != req.repo:
840 if repo and repo != req.repo:
841 repo.close()
841 repo.close()
842
842
843 def _runcommand(ui, options, cmd, cmdfunc):
843 def _runcommand(ui, options, cmd, cmdfunc):
844 """Run a command function, possibly with profiling enabled."""
844 """Run a command function, possibly with profiling enabled."""
845 try:
845 try:
846 return cmdfunc()
846 return cmdfunc()
847 except error.SignatureError:
847 except error.SignatureError:
848 raise error.CommandError(cmd, _('invalid arguments'))
848 raise error.CommandError(cmd, _('invalid arguments'))
849
849
850 def _exceptionwarning(ui):
850 def _exceptionwarning(ui):
851 """Produce a warning message for the current active exception"""
851 """Produce a warning message for the current active exception"""
852
852
853 # For compatibility checking, we discard the portion of the hg
853 # For compatibility checking, we discard the portion of the hg
854 # version after the + on the assumption that if a "normal
854 # version after the + on the assumption that if a "normal
855 # user" is running a build with a + in it the packager
855 # user" is running a build with a + in it the packager
856 # probably built from fairly close to a tag and anyone with a
856 # probably built from fairly close to a tag and anyone with a
857 # 'make local' copy of hg (where the version number can be out
857 # 'make local' copy of hg (where the version number can be out
858 # of date) will be clueful enough to notice the implausible
858 # of date) will be clueful enough to notice the implausible
859 # version number and try updating.
859 # version number and try updating.
860 ct = util.versiontuple(n=2)
860 ct = util.versiontuple(n=2)
861 worst = None, ct, ''
861 worst = None, ct, ''
862 if ui.config('ui', 'supportcontact', None) is None:
862 if ui.config('ui', 'supportcontact', None) is None:
863 for name, mod in extensions.extensions():
863 for name, mod in extensions.extensions():
864 testedwith = getattr(mod, 'testedwith', '')
864 testedwith = getattr(mod, 'testedwith', '')
865 if pycompat.ispy3 and isinstance(testedwith, str):
865 if pycompat.ispy3 and isinstance(testedwith, str):
866 testedwith = testedwith.encode(u'utf-8')
866 testedwith = testedwith.encode(u'utf-8')
867 report = getattr(mod, 'buglink', _('the extension author.'))
867 report = getattr(mod, 'buglink', _('the extension author.'))
868 if not testedwith.strip():
868 if not testedwith.strip():
869 # We found an untested extension. It's likely the culprit.
869 # We found an untested extension. It's likely the culprit.
870 worst = name, 'unknown', report
870 worst = name, 'unknown', report
871 break
871 break
872
872
873 # Never blame on extensions bundled with Mercurial.
873 # Never blame on extensions bundled with Mercurial.
874 if extensions.ismoduleinternal(mod):
874 if extensions.ismoduleinternal(mod):
875 continue
875 continue
876
876
877 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
877 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
878 if ct in tested:
878 if ct in tested:
879 continue
879 continue
880
880
881 lower = [t for t in tested if t < ct]
881 lower = [t for t in tested if t < ct]
882 nearest = max(lower or tested)
882 nearest = max(lower or tested)
883 if worst[0] is None or nearest < worst[1]:
883 if worst[0] is None or nearest < worst[1]:
884 worst = name, nearest, report
884 worst = name, nearest, report
885 if worst[0] is not None:
885 if worst[0] is not None:
886 name, testedwith, report = worst
886 name, testedwith, report = worst
887 if not isinstance(testedwith, (bytes, str)):
887 if not isinstance(testedwith, (bytes, str)):
888 testedwith = '.'.join([str(c) for c in testedwith])
888 testedwith = '.'.join([str(c) for c in testedwith])
889 warning = (_('** Unknown exception encountered with '
889 warning = (_('** Unknown exception encountered with '
890 'possibly-broken third-party extension %s\n'
890 'possibly-broken third-party extension %s\n'
891 '** which supports versions %s of Mercurial.\n'
891 '** which supports versions %s of Mercurial.\n'
892 '** Please disable %s and try your action again.\n'
892 '** Please disable %s and try your action again.\n'
893 '** If that fixes the bug please report it to %s\n')
893 '** If that fixes the bug please report it to %s\n')
894 % (name, testedwith, name, report))
894 % (name, testedwith, name, report))
895 else:
895 else:
896 bugtracker = ui.config('ui', 'supportcontact', None)
896 bugtracker = ui.config('ui', 'supportcontact', None)
897 if bugtracker is None:
897 if bugtracker is None:
898 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
898 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
899 warning = (_("** unknown exception encountered, "
899 warning = (_("** unknown exception encountered, "
900 "please report by visiting\n** ") + bugtracker + '\n')
900 "please report by visiting\n** ") + bugtracker + '\n')
901 if pycompat.ispy3:
901 if pycompat.ispy3:
902 sysversion = sys.version.encode(u'utf-8')
902 sysversion = sys.version.encode(u'utf-8')
903 else:
903 else:
904 sysversion = sys.version
904 sysversion = sys.version
905 sysversion = sysversion.replace('\n', '')
905 sysversion = sysversion.replace('\n', '')
906 warning += ((_("** Python %s\n") % sysversion) +
906 warning += ((_("** Python %s\n") % sysversion) +
907 (_("** Mercurial Distributed SCM (version %s)\n") %
907 (_("** Mercurial Distributed SCM (version %s)\n") %
908 util.version()) +
908 util.version()) +
909 (_("** Extensions loaded: %s\n") %
909 (_("** Extensions loaded: %s\n") %
910 ", ".join([x[0] for x in extensions.extensions()])))
910 ", ".join([x[0] for x in extensions.extensions()])))
911 return warning
911 return warning
912
912
913 def handlecommandexception(ui):
913 def handlecommandexception(ui):
914 """Produce a warning message for broken commands
914 """Produce a warning message for broken commands
915
915
916 Called when handling an exception; the exception is reraised if
916 Called when handling an exception; the exception is reraised if
917 this function returns False, ignored otherwise.
917 this function returns False, ignored otherwise.
918 """
918 """
919 warning = _exceptionwarning(ui)
919 warning = _exceptionwarning(ui)
920 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
920 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
921 ui.warn(warning)
921 ui.warn(warning)
922 return False # re-raise the exception
922 return False # re-raise the exception
@@ -1,659 +1,659 b''
1 # posix.py - Posix utility function implementations for Mercurial
1 # posix.py - Posix utility function implementations for Mercurial
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
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 fcntl
11 import fcntl
12 import getpass
12 import getpass
13 import grp
13 import grp
14 import os
14 import os
15 import pwd
15 import pwd
16 import re
16 import re
17 import select
17 import select
18 import stat
18 import stat
19 import sys
19 import sys
20 import tempfile
20 import tempfile
21 import unicodedata
21 import unicodedata
22
22
23 from .i18n import _
23 from .i18n import _
24 from . import (
24 from . import (
25 encoding,
25 encoding,
26 pycompat,
26 pycompat,
27 )
27 )
28
28
29 posixfile = open
29 posixfile = open
30 normpath = os.path.normpath
30 normpath = os.path.normpath
31 samestat = os.path.samestat
31 samestat = os.path.samestat
32 try:
32 try:
33 oslink = os.link
33 oslink = os.link
34 except AttributeError:
34 except AttributeError:
35 # Some platforms build Python without os.link on systems that are
35 # Some platforms build Python without os.link on systems that are
36 # vaguely unix-like but don't have hardlink support. For those
36 # vaguely unix-like but don't have hardlink support. For those
37 # poor souls, just say we tried and that it failed so we fall back
37 # poor souls, just say we tried and that it failed so we fall back
38 # to copies.
38 # to copies.
39 def oslink(src, dst):
39 def oslink(src, dst):
40 raise OSError(errno.EINVAL,
40 raise OSError(errno.EINVAL,
41 'hardlinks not supported: %s to %s' % (src, dst))
41 'hardlinks not supported: %s to %s' % (src, dst))
42 unlink = os.unlink
42 unlink = os.unlink
43 rename = os.rename
43 rename = os.rename
44 removedirs = os.removedirs
44 removedirs = os.removedirs
45 expandglobs = False
45 expandglobs = False
46
46
47 umask = os.umask(0)
47 umask = os.umask(0)
48 os.umask(umask)
48 os.umask(umask)
49
49
50 def split(p):
50 def split(p):
51 '''Same as posixpath.split, but faster
51 '''Same as posixpath.split, but faster
52
52
53 >>> import posixpath
53 >>> import posixpath
54 >>> for f in ['/absolute/path/to/file',
54 >>> for f in ['/absolute/path/to/file',
55 ... 'relative/path/to/file',
55 ... 'relative/path/to/file',
56 ... 'file_alone',
56 ... 'file_alone',
57 ... 'path/to/directory/',
57 ... 'path/to/directory/',
58 ... '/multiple/path//separators',
58 ... '/multiple/path//separators',
59 ... '/file_at_root',
59 ... '/file_at_root',
60 ... '///multiple_leading_separators_at_root',
60 ... '///multiple_leading_separators_at_root',
61 ... '']:
61 ... '']:
62 ... assert split(f) == posixpath.split(f), f
62 ... assert split(f) == posixpath.split(f), f
63 '''
63 '''
64 ht = p.rsplit('/', 1)
64 ht = p.rsplit('/', 1)
65 if len(ht) == 1:
65 if len(ht) == 1:
66 return '', p
66 return '', p
67 nh = ht[0].rstrip('/')
67 nh = ht[0].rstrip('/')
68 if nh:
68 if nh:
69 return nh, ht[1]
69 return nh, ht[1]
70 return ht[0] + '/', ht[1]
70 return ht[0] + '/', ht[1]
71
71
72 def openhardlinks():
72 def openhardlinks():
73 '''return true if it is safe to hold open file handles to hardlinks'''
73 '''return true if it is safe to hold open file handles to hardlinks'''
74 return True
74 return True
75
75
76 def nlinks(name):
76 def nlinks(name):
77 '''return number of hardlinks for the given file'''
77 '''return number of hardlinks for the given file'''
78 return os.lstat(name).st_nlink
78 return os.lstat(name).st_nlink
79
79
80 def parsepatchoutput(output_line):
80 def parsepatchoutput(output_line):
81 """parses the output produced by patch and returns the filename"""
81 """parses the output produced by patch and returns the filename"""
82 pf = output_line[14:]
82 pf = output_line[14:]
83 if pycompat.sysplatform == 'OpenVMS':
83 if pycompat.sysplatform == 'OpenVMS':
84 if pf[0] == '`':
84 if pf[0] == '`':
85 pf = pf[1:-1] # Remove the quotes
85 pf = pf[1:-1] # Remove the quotes
86 else:
86 else:
87 if pf.startswith("'") and pf.endswith("'") and " " in pf:
87 if pf.startswith("'") and pf.endswith("'") and " " in pf:
88 pf = pf[1:-1] # Remove the quotes
88 pf = pf[1:-1] # Remove the quotes
89 return pf
89 return pf
90
90
91 def sshargs(sshcmd, host, user, port):
91 def sshargs(sshcmd, host, user, port):
92 '''Build argument list for ssh'''
92 '''Build argument list for ssh'''
93 args = user and ("%s@%s" % (user, host)) or host
93 args = user and ("%s@%s" % (user, host)) or host
94 return port and ("%s -p %s" % (args, port)) or args
94 return port and ("%s -p %s" % (args, port)) or args
95
95
96 def isexec(f):
96 def isexec(f):
97 """check whether a file is executable"""
97 """check whether a file is executable"""
98 return (os.lstat(f).st_mode & 0o100 != 0)
98 return (os.lstat(f).st_mode & 0o100 != 0)
99
99
100 def setflags(f, l, x):
100 def setflags(f, l, x):
101 s = os.lstat(f).st_mode
101 s = os.lstat(f).st_mode
102 if l:
102 if l:
103 if not stat.S_ISLNK(s):
103 if not stat.S_ISLNK(s):
104 # switch file to link
104 # switch file to link
105 fp = open(f)
105 fp = open(f)
106 data = fp.read()
106 data = fp.read()
107 fp.close()
107 fp.close()
108 os.unlink(f)
108 os.unlink(f)
109 try:
109 try:
110 os.symlink(data, f)
110 os.symlink(data, f)
111 except OSError:
111 except OSError:
112 # failed to make a link, rewrite file
112 # failed to make a link, rewrite file
113 fp = open(f, "w")
113 fp = open(f, "w")
114 fp.write(data)
114 fp.write(data)
115 fp.close()
115 fp.close()
116 # no chmod needed at this point
116 # no chmod needed at this point
117 return
117 return
118 if stat.S_ISLNK(s):
118 if stat.S_ISLNK(s):
119 # switch link to file
119 # switch link to file
120 data = os.readlink(f)
120 data = os.readlink(f)
121 os.unlink(f)
121 os.unlink(f)
122 fp = open(f, "w")
122 fp = open(f, "w")
123 fp.write(data)
123 fp.write(data)
124 fp.close()
124 fp.close()
125 s = 0o666 & ~umask # avoid restatting for chmod
125 s = 0o666 & ~umask # avoid restatting for chmod
126
126
127 sx = s & 0o100
127 sx = s & 0o100
128 if x and not sx:
128 if x and not sx:
129 # Turn on +x for every +r bit when making a file executable
129 # Turn on +x for every +r bit when making a file executable
130 # and obey umask.
130 # and obey umask.
131 os.chmod(f, s | (s & 0o444) >> 2 & ~umask)
131 os.chmod(f, s | (s & 0o444) >> 2 & ~umask)
132 elif not x and sx:
132 elif not x and sx:
133 # Turn off all +x bits
133 # Turn off all +x bits
134 os.chmod(f, s & 0o666)
134 os.chmod(f, s & 0o666)
135
135
136 def copymode(src, dst, mode=None):
136 def copymode(src, dst, mode=None):
137 '''Copy the file mode from the file at path src to dst.
137 '''Copy the file mode from the file at path src to dst.
138 If src doesn't exist, we're using mode instead. If mode is None, we're
138 If src doesn't exist, we're using mode instead. If mode is None, we're
139 using umask.'''
139 using umask.'''
140 try:
140 try:
141 st_mode = os.lstat(src).st_mode & 0o777
141 st_mode = os.lstat(src).st_mode & 0o777
142 except OSError as inst:
142 except OSError as inst:
143 if inst.errno != errno.ENOENT:
143 if inst.errno != errno.ENOENT:
144 raise
144 raise
145 st_mode = mode
145 st_mode = mode
146 if st_mode is None:
146 if st_mode is None:
147 st_mode = ~umask
147 st_mode = ~umask
148 st_mode &= 0o666
148 st_mode &= 0o666
149 os.chmod(dst, st_mode)
149 os.chmod(dst, st_mode)
150
150
151 def checkexec(path):
151 def checkexec(path):
152 """
152 """
153 Check whether the given path is on a filesystem with UNIX-like exec flags
153 Check whether the given path is on a filesystem with UNIX-like exec flags
154
154
155 Requires a directory (like /foo/.hg)
155 Requires a directory (like /foo/.hg)
156 """
156 """
157
157
158 # VFAT on some Linux versions can flip mode but it doesn't persist
158 # VFAT on some Linux versions can flip mode but it doesn't persist
159 # a FS remount. Frequently we can detect it if files are created
159 # a FS remount. Frequently we can detect it if files are created
160 # with exec bit on.
160 # with exec bit on.
161
161
162 try:
162 try:
163 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
163 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
164 cachedir = os.path.join(path, '.hg', 'cache')
164 cachedir = os.path.join(path, '.hg', 'cache')
165 if os.path.isdir(cachedir):
165 if os.path.isdir(cachedir):
166 checkisexec = os.path.join(cachedir, 'checkisexec')
166 checkisexec = os.path.join(cachedir, 'checkisexec')
167 checknoexec = os.path.join(cachedir, 'checknoexec')
167 checknoexec = os.path.join(cachedir, 'checknoexec')
168
168
169 try:
169 try:
170 m = os.stat(checkisexec).st_mode
170 m = os.stat(checkisexec).st_mode
171 except OSError as e:
171 except OSError as e:
172 if e.errno != errno.ENOENT:
172 if e.errno != errno.ENOENT:
173 raise
173 raise
174 # checkisexec does not exist - fall through ...
174 # checkisexec does not exist - fall through ...
175 else:
175 else:
176 # checkisexec exists, check if it actually is exec
176 # checkisexec exists, check if it actually is exec
177 if m & EXECFLAGS != 0:
177 if m & EXECFLAGS != 0:
178 # ensure checkisexec exists, check it isn't exec
178 # ensure checkisexec exists, check it isn't exec
179 try:
179 try:
180 m = os.stat(checknoexec).st_mode
180 m = os.stat(checknoexec).st_mode
181 except OSError as e:
181 except OSError as e:
182 if e.errno != errno.ENOENT:
182 if e.errno != errno.ENOENT:
183 raise
183 raise
184 file(checknoexec, 'w').close() # might fail
184 file(checknoexec, 'w').close() # might fail
185 m = os.stat(checknoexec).st_mode
185 m = os.stat(checknoexec).st_mode
186 if m & EXECFLAGS == 0:
186 if m & EXECFLAGS == 0:
187 # check-exec is exec and check-no-exec is not exec
187 # check-exec is exec and check-no-exec is not exec
188 return True
188 return True
189 # checknoexec exists but is exec - delete it
189 # checknoexec exists but is exec - delete it
190 os.unlink(checknoexec)
190 os.unlink(checknoexec)
191 # checkisexec exists but is not exec - delete it
191 # checkisexec exists but is not exec - delete it
192 os.unlink(checkisexec)
192 os.unlink(checkisexec)
193
193
194 # check using one file, leave it as checkisexec
194 # check using one file, leave it as checkisexec
195 checkdir = cachedir
195 checkdir = cachedir
196 else:
196 else:
197 # check directly in path and don't leave checkisexec behind
197 # check directly in path and don't leave checkisexec behind
198 checkdir = path
198 checkdir = path
199 checkisexec = None
199 checkisexec = None
200 fh, fn = tempfile.mkstemp(dir=checkdir, prefix='hg-checkexec-')
200 fh, fn = tempfile.mkstemp(dir=checkdir, prefix='hg-checkexec-')
201 try:
201 try:
202 os.close(fh)
202 os.close(fh)
203 m = os.stat(fn).st_mode
203 m = os.stat(fn).st_mode
204 if m & EXECFLAGS == 0:
204 if m & EXECFLAGS == 0:
205 os.chmod(fn, m & 0o777 | EXECFLAGS)
205 os.chmod(fn, m & 0o777 | EXECFLAGS)
206 if os.stat(fn).st_mode & EXECFLAGS != 0:
206 if os.stat(fn).st_mode & EXECFLAGS != 0:
207 if checkisexec is not None:
207 if checkisexec is not None:
208 os.rename(fn, checkisexec)
208 os.rename(fn, checkisexec)
209 fn = None
209 fn = None
210 return True
210 return True
211 finally:
211 finally:
212 if fn is not None:
212 if fn is not None:
213 os.unlink(fn)
213 os.unlink(fn)
214 except (IOError, OSError):
214 except (IOError, OSError):
215 # we don't care, the user probably won't be able to commit anyway
215 # we don't care, the user probably won't be able to commit anyway
216 return False
216 return False
217
217
218 def checklink(path):
218 def checklink(path):
219 """check whether the given path is on a symlink-capable filesystem"""
219 """check whether the given path is on a symlink-capable filesystem"""
220 # mktemp is not racy because symlink creation will fail if the
220 # mktemp is not racy because symlink creation will fail if the
221 # file already exists
221 # file already exists
222 while True:
222 while True:
223 cachedir = os.path.join(path, '.hg', 'cache')
223 cachedir = os.path.join(path, '.hg', 'cache')
224 checklink = os.path.join(cachedir, 'checklink')
224 checklink = os.path.join(cachedir, 'checklink')
225 # try fast path, read only
225 # try fast path, read only
226 if os.path.islink(checklink):
226 if os.path.islink(checklink):
227 return True
227 return True
228 if os.path.isdir(cachedir):
228 if os.path.isdir(cachedir):
229 checkdir = cachedir
229 checkdir = cachedir
230 else:
230 else:
231 checkdir = path
231 checkdir = path
232 cachedir = None
232 cachedir = None
233 name = tempfile.mktemp(dir=checkdir, prefix='checklink-')
233 name = tempfile.mktemp(dir=checkdir, prefix='checklink-')
234 try:
234 try:
235 fd = None
235 fd = None
236 if cachedir is None:
236 if cachedir is None:
237 fd = tempfile.NamedTemporaryFile(dir=checkdir,
237 fd = tempfile.NamedTemporaryFile(dir=checkdir,
238 prefix='hg-checklink-')
238 prefix='hg-checklink-')
239 target = os.path.basename(fd.name)
239 target = os.path.basename(fd.name)
240 else:
240 else:
241 # create a fixed file to link to; doesn't matter if it
241 # create a fixed file to link to; doesn't matter if it
242 # already exists.
242 # already exists.
243 target = 'checklink-target'
243 target = 'checklink-target'
244 open(os.path.join(cachedir, target), 'w').close()
244 open(os.path.join(cachedir, target), 'w').close()
245 try:
245 try:
246 os.symlink(target, name)
246 os.symlink(target, name)
247 if cachedir is None:
247 if cachedir is None:
248 os.unlink(name)
248 os.unlink(name)
249 else:
249 else:
250 try:
250 try:
251 os.rename(name, checklink)
251 os.rename(name, checklink)
252 except OSError:
252 except OSError:
253 os.unlink(name)
253 os.unlink(name)
254 return True
254 return True
255 except OSError as inst:
255 except OSError as inst:
256 # link creation might race, try again
256 # link creation might race, try again
257 if inst[0] == errno.EEXIST:
257 if inst[0] == errno.EEXIST:
258 continue
258 continue
259 raise
259 raise
260 finally:
260 finally:
261 if fd is not None:
261 if fd is not None:
262 fd.close()
262 fd.close()
263 except AttributeError:
263 except AttributeError:
264 return False
264 return False
265 except OSError as inst:
265 except OSError as inst:
266 # sshfs might report failure while successfully creating the link
266 # sshfs might report failure while successfully creating the link
267 if inst[0] == errno.EIO and os.path.exists(name):
267 if inst[0] == errno.EIO and os.path.exists(name):
268 os.unlink(name)
268 os.unlink(name)
269 return False
269 return False
270
270
271 def checkosfilename(path):
271 def checkosfilename(path):
272 '''Check that the base-relative path is a valid filename on this platform.
272 '''Check that the base-relative path is a valid filename on this platform.
273 Returns None if the path is ok, or a UI string describing the problem.'''
273 Returns None if the path is ok, or a UI string describing the problem.'''
274 pass # on posix platforms, every path is ok
274 pass # on posix platforms, every path is ok
275
275
276 def setbinary(fd):
276 def setbinary(fd):
277 pass
277 pass
278
278
279 def pconvert(path):
279 def pconvert(path):
280 return path
280 return path
281
281
282 def localpath(path):
282 def localpath(path):
283 return path
283 return path
284
284
285 def samefile(fpath1, fpath2):
285 def samefile(fpath1, fpath2):
286 """Returns whether path1 and path2 refer to the same file. This is only
286 """Returns whether path1 and path2 refer to the same file. This is only
287 guaranteed to work for files, not directories."""
287 guaranteed to work for files, not directories."""
288 return os.path.samefile(fpath1, fpath2)
288 return os.path.samefile(fpath1, fpath2)
289
289
290 def samedevice(fpath1, fpath2):
290 def samedevice(fpath1, fpath2):
291 """Returns whether fpath1 and fpath2 are on the same device. This is only
291 """Returns whether fpath1 and fpath2 are on the same device. This is only
292 guaranteed to work for files, not directories."""
292 guaranteed to work for files, not directories."""
293 st1 = os.lstat(fpath1)
293 st1 = os.lstat(fpath1)
294 st2 = os.lstat(fpath2)
294 st2 = os.lstat(fpath2)
295 return st1.st_dev == st2.st_dev
295 return st1.st_dev == st2.st_dev
296
296
297 # os.path.normcase is a no-op, which doesn't help us on non-native filesystems
297 # os.path.normcase is a no-op, which doesn't help us on non-native filesystems
298 def normcase(path):
298 def normcase(path):
299 return path.lower()
299 return path.lower()
300
300
301 # what normcase does to ASCII strings
301 # what normcase does to ASCII strings
302 normcasespec = encoding.normcasespecs.lower
302 normcasespec = encoding.normcasespecs.lower
303 # fallback normcase function for non-ASCII strings
303 # fallback normcase function for non-ASCII strings
304 normcasefallback = normcase
304 normcasefallback = normcase
305
305
306 if pycompat.sysplatform == 'darwin':
306 if pycompat.sysplatform == 'darwin':
307
307
308 def normcase(path):
308 def normcase(path):
309 '''
309 '''
310 Normalize a filename for OS X-compatible comparison:
310 Normalize a filename for OS X-compatible comparison:
311 - escape-encode invalid characters
311 - escape-encode invalid characters
312 - decompose to NFD
312 - decompose to NFD
313 - lowercase
313 - lowercase
314 - omit ignored characters [200c-200f, 202a-202e, 206a-206f,feff]
314 - omit ignored characters [200c-200f, 202a-202e, 206a-206f,feff]
315
315
316 >>> normcase('UPPER')
316 >>> normcase('UPPER')
317 'upper'
317 'upper'
318 >>> normcase('Caf\xc3\xa9')
318 >>> normcase('Caf\xc3\xa9')
319 'cafe\\xcc\\x81'
319 'cafe\\xcc\\x81'
320 >>> normcase('\xc3\x89')
320 >>> normcase('\xc3\x89')
321 'e\\xcc\\x81'
321 'e\\xcc\\x81'
322 >>> normcase('\xb8\xca\xc3\xca\xbe\xc8.JPG') # issue3918
322 >>> normcase('\xb8\xca\xc3\xca\xbe\xc8.JPG') # issue3918
323 '%b8%ca%c3\\xca\\xbe%c8.jpg'
323 '%b8%ca%c3\\xca\\xbe%c8.jpg'
324 '''
324 '''
325
325
326 try:
326 try:
327 return encoding.asciilower(path) # exception for non-ASCII
327 return encoding.asciilower(path) # exception for non-ASCII
328 except UnicodeDecodeError:
328 except UnicodeDecodeError:
329 return normcasefallback(path)
329 return normcasefallback(path)
330
330
331 normcasespec = encoding.normcasespecs.lower
331 normcasespec = encoding.normcasespecs.lower
332
332
333 def normcasefallback(path):
333 def normcasefallback(path):
334 try:
334 try:
335 u = path.decode('utf-8')
335 u = path.decode('utf-8')
336 except UnicodeDecodeError:
336 except UnicodeDecodeError:
337 # OS X percent-encodes any bytes that aren't valid utf-8
337 # OS X percent-encodes any bytes that aren't valid utf-8
338 s = ''
338 s = ''
339 pos = 0
339 pos = 0
340 l = len(path)
340 l = len(path)
341 while pos < l:
341 while pos < l:
342 try:
342 try:
343 c = encoding.getutf8char(path, pos)
343 c = encoding.getutf8char(path, pos)
344 pos += len(c)
344 pos += len(c)
345 except ValueError:
345 except ValueError:
346 c = '%%%02X' % ord(path[pos])
346 c = '%%%02X' % ord(path[pos])
347 pos += 1
347 pos += 1
348 s += c
348 s += c
349
349
350 u = s.decode('utf-8')
350 u = s.decode('utf-8')
351
351
352 # Decompose then lowercase (HFS+ technote specifies lower)
352 # Decompose then lowercase (HFS+ technote specifies lower)
353 enc = unicodedata.normalize('NFD', u).lower().encode('utf-8')
353 enc = unicodedata.normalize('NFD', u).lower().encode('utf-8')
354 # drop HFS+ ignored characters
354 # drop HFS+ ignored characters
355 return encoding.hfsignoreclean(enc)
355 return encoding.hfsignoreclean(enc)
356
356
357 if pycompat.sysplatform == 'cygwin':
357 if pycompat.sysplatform == 'cygwin':
358 # workaround for cygwin, in which mount point part of path is
358 # workaround for cygwin, in which mount point part of path is
359 # treated as case sensitive, even though underlying NTFS is case
359 # treated as case sensitive, even though underlying NTFS is case
360 # insensitive.
360 # insensitive.
361
361
362 # default mount points
362 # default mount points
363 cygwinmountpoints = sorted([
363 cygwinmountpoints = sorted([
364 "/usr/bin",
364 "/usr/bin",
365 "/usr/lib",
365 "/usr/lib",
366 "/cygdrive",
366 "/cygdrive",
367 ], reverse=True)
367 ], reverse=True)
368
368
369 # use upper-ing as normcase as same as NTFS workaround
369 # use upper-ing as normcase as same as NTFS workaround
370 def normcase(path):
370 def normcase(path):
371 pathlen = len(path)
371 pathlen = len(path)
372 if (pathlen == 0) or (path[0] != pycompat.ossep):
372 if (pathlen == 0) or (path[0] != pycompat.ossep):
373 # treat as relative
373 # treat as relative
374 return encoding.upper(path)
374 return encoding.upper(path)
375
375
376 # to preserve case of mountpoint part
376 # to preserve case of mountpoint part
377 for mp in cygwinmountpoints:
377 for mp in cygwinmountpoints:
378 if not path.startswith(mp):
378 if not path.startswith(mp):
379 continue
379 continue
380
380
381 mplen = len(mp)
381 mplen = len(mp)
382 if mplen == pathlen: # mount point itself
382 if mplen == pathlen: # mount point itself
383 return mp
383 return mp
384 if path[mplen] == pycompat.ossep:
384 if path[mplen] == pycompat.ossep:
385 return mp + encoding.upper(path[mplen:])
385 return mp + encoding.upper(path[mplen:])
386
386
387 return encoding.upper(path)
387 return encoding.upper(path)
388
388
389 normcasespec = encoding.normcasespecs.other
389 normcasespec = encoding.normcasespecs.other
390 normcasefallback = normcase
390 normcasefallback = normcase
391
391
392 # Cygwin translates native ACLs to POSIX permissions,
392 # Cygwin translates native ACLs to POSIX permissions,
393 # but these translations are not supported by native
393 # but these translations are not supported by native
394 # tools, so the exec bit tends to be set erroneously.
394 # tools, so the exec bit tends to be set erroneously.
395 # Therefore, disable executable bit access on Cygwin.
395 # Therefore, disable executable bit access on Cygwin.
396 def checkexec(path):
396 def checkexec(path):
397 return False
397 return False
398
398
399 # Similarly, Cygwin's symlink emulation is likely to create
399 # Similarly, Cygwin's symlink emulation is likely to create
400 # problems when Mercurial is used from both Cygwin and native
400 # problems when Mercurial is used from both Cygwin and native
401 # Windows, with other native tools, or on shared volumes
401 # Windows, with other native tools, or on shared volumes
402 def checklink(path):
402 def checklink(path):
403 return False
403 return False
404
404
405 _needsshellquote = None
405 _needsshellquote = None
406 def shellquote(s):
406 def shellquote(s):
407 if pycompat.sysplatform == 'OpenVMS':
407 if pycompat.sysplatform == 'OpenVMS':
408 return '"%s"' % s
408 return '"%s"' % s
409 global _needsshellquote
409 global _needsshellquote
410 if _needsshellquote is None:
410 if _needsshellquote is None:
411 _needsshellquote = re.compile(r'[^a-zA-Z0-9._/+-]').search
411 _needsshellquote = re.compile(br'[^a-zA-Z0-9._/+-]').search
412 if s and not _needsshellquote(s):
412 if s and not _needsshellquote(s):
413 # "s" shouldn't have to be quoted
413 # "s" shouldn't have to be quoted
414 return s
414 return s
415 else:
415 else:
416 return "'%s'" % s.replace("'", "'\\''")
416 return "'%s'" % s.replace("'", "'\\''")
417
417
418 def quotecommand(cmd):
418 def quotecommand(cmd):
419 return cmd
419 return cmd
420
420
421 def popen(command, mode='r'):
421 def popen(command, mode='r'):
422 return os.popen(command, mode)
422 return os.popen(command, mode)
423
423
424 def testpid(pid):
424 def testpid(pid):
425 '''return False if pid dead, True if running or not sure'''
425 '''return False if pid dead, True if running or not sure'''
426 if pycompat.sysplatform == 'OpenVMS':
426 if pycompat.sysplatform == 'OpenVMS':
427 return True
427 return True
428 try:
428 try:
429 os.kill(pid, 0)
429 os.kill(pid, 0)
430 return True
430 return True
431 except OSError as inst:
431 except OSError as inst:
432 return inst.errno != errno.ESRCH
432 return inst.errno != errno.ESRCH
433
433
434 def explainexit(code):
434 def explainexit(code):
435 """return a 2-tuple (desc, code) describing a subprocess status
435 """return a 2-tuple (desc, code) describing a subprocess status
436 (codes from kill are negative - not os.system/wait encoding)"""
436 (codes from kill are negative - not os.system/wait encoding)"""
437 if code >= 0:
437 if code >= 0:
438 return _("exited with status %d") % code, code
438 return _("exited with status %d") % code, code
439 return _("killed by signal %d") % -code, -code
439 return _("killed by signal %d") % -code, -code
440
440
441 def isowner(st):
441 def isowner(st):
442 """Return True if the stat object st is from the current user."""
442 """Return True if the stat object st is from the current user."""
443 return st.st_uid == os.getuid()
443 return st.st_uid == os.getuid()
444
444
445 def findexe(command):
445 def findexe(command):
446 '''Find executable for command searching like which does.
446 '''Find executable for command searching like which does.
447 If command is a basename then PATH is searched for command.
447 If command is a basename then PATH is searched for command.
448 PATH isn't searched if command is an absolute or relative path.
448 PATH isn't searched if command is an absolute or relative path.
449 If command isn't found None is returned.'''
449 If command isn't found None is returned.'''
450 if pycompat.sysplatform == 'OpenVMS':
450 if pycompat.sysplatform == 'OpenVMS':
451 return command
451 return command
452
452
453 def findexisting(executable):
453 def findexisting(executable):
454 'Will return executable if existing file'
454 'Will return executable if existing file'
455 if os.path.isfile(executable) and os.access(executable, os.X_OK):
455 if os.path.isfile(executable) and os.access(executable, os.X_OK):
456 return executable
456 return executable
457 return None
457 return None
458
458
459 if pycompat.ossep in command:
459 if pycompat.ossep in command:
460 return findexisting(command)
460 return findexisting(command)
461
461
462 if pycompat.sysplatform == 'plan9':
462 if pycompat.sysplatform == 'plan9':
463 return findexisting(os.path.join('/bin', command))
463 return findexisting(os.path.join('/bin', command))
464
464
465 for path in encoding.environ.get('PATH', '').split(pycompat.ospathsep):
465 for path in encoding.environ.get('PATH', '').split(pycompat.ospathsep):
466 executable = findexisting(os.path.join(path, command))
466 executable = findexisting(os.path.join(path, command))
467 if executable is not None:
467 if executable is not None:
468 return executable
468 return executable
469 return None
469 return None
470
470
471 def setsignalhandler():
471 def setsignalhandler():
472 pass
472 pass
473
473
474 _wantedkinds = set([stat.S_IFREG, stat.S_IFLNK])
474 _wantedkinds = set([stat.S_IFREG, stat.S_IFLNK])
475
475
476 def statfiles(files):
476 def statfiles(files):
477 '''Stat each file in files. Yield each stat, or None if a file does not
477 '''Stat each file in files. Yield each stat, or None if a file does not
478 exist or has a type we don't care about.'''
478 exist or has a type we don't care about.'''
479 lstat = os.lstat
479 lstat = os.lstat
480 getkind = stat.S_IFMT
480 getkind = stat.S_IFMT
481 for nf in files:
481 for nf in files:
482 try:
482 try:
483 st = lstat(nf)
483 st = lstat(nf)
484 if getkind(st.st_mode) not in _wantedkinds:
484 if getkind(st.st_mode) not in _wantedkinds:
485 st = None
485 st = None
486 except OSError as err:
486 except OSError as err:
487 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
487 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
488 raise
488 raise
489 st = None
489 st = None
490 yield st
490 yield st
491
491
492 def getuser():
492 def getuser():
493 '''return name of current user'''
493 '''return name of current user'''
494 return getpass.getuser()
494 return getpass.getuser()
495
495
496 def username(uid=None):
496 def username(uid=None):
497 """Return the name of the user with the given uid.
497 """Return the name of the user with the given uid.
498
498
499 If uid is None, return the name of the current user."""
499 If uid is None, return the name of the current user."""
500
500
501 if uid is None:
501 if uid is None:
502 uid = os.getuid()
502 uid = os.getuid()
503 try:
503 try:
504 return pwd.getpwuid(uid)[0]
504 return pwd.getpwuid(uid)[0]
505 except KeyError:
505 except KeyError:
506 return str(uid)
506 return str(uid)
507
507
508 def groupname(gid=None):
508 def groupname(gid=None):
509 """Return the name of the group with the given gid.
509 """Return the name of the group with the given gid.
510
510
511 If gid is None, return the name of the current group."""
511 If gid is None, return the name of the current group."""
512
512
513 if gid is None:
513 if gid is None:
514 gid = os.getgid()
514 gid = os.getgid()
515 try:
515 try:
516 return grp.getgrgid(gid)[0]
516 return grp.getgrgid(gid)[0]
517 except KeyError:
517 except KeyError:
518 return str(gid)
518 return str(gid)
519
519
520 def groupmembers(name):
520 def groupmembers(name):
521 """Return the list of members of the group with the given
521 """Return the list of members of the group with the given
522 name, KeyError if the group does not exist.
522 name, KeyError if the group does not exist.
523 """
523 """
524 return list(grp.getgrnam(name).gr_mem)
524 return list(grp.getgrnam(name).gr_mem)
525
525
526 def spawndetached(args):
526 def spawndetached(args):
527 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
527 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
528 args[0], args)
528 args[0], args)
529
529
530 def gethgcmd():
530 def gethgcmd():
531 return sys.argv[:1]
531 return sys.argv[:1]
532
532
533 def makedir(path, notindexed):
533 def makedir(path, notindexed):
534 os.mkdir(path)
534 os.mkdir(path)
535
535
536 def unlinkpath(f, ignoremissing=False):
536 def unlinkpath(f, ignoremissing=False):
537 """unlink and remove the directory if it is empty"""
537 """unlink and remove the directory if it is empty"""
538 try:
538 try:
539 os.unlink(f)
539 os.unlink(f)
540 except OSError as e:
540 except OSError as e:
541 if not (ignoremissing and e.errno == errno.ENOENT):
541 if not (ignoremissing and e.errno == errno.ENOENT):
542 raise
542 raise
543 # try removing directories that might now be empty
543 # try removing directories that might now be empty
544 try:
544 try:
545 os.removedirs(os.path.dirname(f))
545 os.removedirs(os.path.dirname(f))
546 except OSError:
546 except OSError:
547 pass
547 pass
548
548
549 def lookupreg(key, name=None, scope=None):
549 def lookupreg(key, name=None, scope=None):
550 return None
550 return None
551
551
552 def hidewindow():
552 def hidewindow():
553 """Hide current shell window.
553 """Hide current shell window.
554
554
555 Used to hide the window opened when starting asynchronous
555 Used to hide the window opened when starting asynchronous
556 child process under Windows, unneeded on other systems.
556 child process under Windows, unneeded on other systems.
557 """
557 """
558 pass
558 pass
559
559
560 class cachestat(object):
560 class cachestat(object):
561 def __init__(self, path):
561 def __init__(self, path):
562 self.stat = os.stat(path)
562 self.stat = os.stat(path)
563
563
564 def cacheable(self):
564 def cacheable(self):
565 return bool(self.stat.st_ino)
565 return bool(self.stat.st_ino)
566
566
567 __hash__ = object.__hash__
567 __hash__ = object.__hash__
568
568
569 def __eq__(self, other):
569 def __eq__(self, other):
570 try:
570 try:
571 # Only dev, ino, size, mtime and atime are likely to change. Out
571 # Only dev, ino, size, mtime and atime are likely to change. Out
572 # of these, we shouldn't compare atime but should compare the
572 # of these, we shouldn't compare atime but should compare the
573 # rest. However, one of the other fields changing indicates
573 # rest. However, one of the other fields changing indicates
574 # something fishy going on, so return False if anything but atime
574 # something fishy going on, so return False if anything but atime
575 # changes.
575 # changes.
576 return (self.stat.st_mode == other.stat.st_mode and
576 return (self.stat.st_mode == other.stat.st_mode and
577 self.stat.st_ino == other.stat.st_ino and
577 self.stat.st_ino == other.stat.st_ino and
578 self.stat.st_dev == other.stat.st_dev and
578 self.stat.st_dev == other.stat.st_dev and
579 self.stat.st_nlink == other.stat.st_nlink and
579 self.stat.st_nlink == other.stat.st_nlink and
580 self.stat.st_uid == other.stat.st_uid and
580 self.stat.st_uid == other.stat.st_uid and
581 self.stat.st_gid == other.stat.st_gid and
581 self.stat.st_gid == other.stat.st_gid and
582 self.stat.st_size == other.stat.st_size and
582 self.stat.st_size == other.stat.st_size and
583 self.stat.st_mtime == other.stat.st_mtime and
583 self.stat.st_mtime == other.stat.st_mtime and
584 self.stat.st_ctime == other.stat.st_ctime)
584 self.stat.st_ctime == other.stat.st_ctime)
585 except AttributeError:
585 except AttributeError:
586 return False
586 return False
587
587
588 def __ne__(self, other):
588 def __ne__(self, other):
589 return not self == other
589 return not self == other
590
590
591 def executablepath():
591 def executablepath():
592 return None # available on Windows only
592 return None # available on Windows only
593
593
594 def statislink(st):
594 def statislink(st):
595 '''check whether a stat result is a symlink'''
595 '''check whether a stat result is a symlink'''
596 return st and stat.S_ISLNK(st.st_mode)
596 return st and stat.S_ISLNK(st.st_mode)
597
597
598 def statisexec(st):
598 def statisexec(st):
599 '''check whether a stat result is an executable file'''
599 '''check whether a stat result is an executable file'''
600 return st and (st.st_mode & 0o100 != 0)
600 return st and (st.st_mode & 0o100 != 0)
601
601
602 def poll(fds):
602 def poll(fds):
603 """block until something happens on any file descriptor
603 """block until something happens on any file descriptor
604
604
605 This is a generic helper that will check for any activity
605 This is a generic helper that will check for any activity
606 (read, write. exception) and return the list of touched files.
606 (read, write. exception) and return the list of touched files.
607
607
608 In unsupported cases, it will raise a NotImplementedError"""
608 In unsupported cases, it will raise a NotImplementedError"""
609 try:
609 try:
610 while True:
610 while True:
611 try:
611 try:
612 res = select.select(fds, fds, fds)
612 res = select.select(fds, fds, fds)
613 break
613 break
614 except select.error as inst:
614 except select.error as inst:
615 if inst.args[0] == errno.EINTR:
615 if inst.args[0] == errno.EINTR:
616 continue
616 continue
617 raise
617 raise
618 except ValueError: # out of range file descriptor
618 except ValueError: # out of range file descriptor
619 raise NotImplementedError()
619 raise NotImplementedError()
620 return sorted(list(set(sum(res, []))))
620 return sorted(list(set(sum(res, []))))
621
621
622 def readpipe(pipe):
622 def readpipe(pipe):
623 """Read all available data from a pipe."""
623 """Read all available data from a pipe."""
624 # We can't fstat() a pipe because Linux will always report 0.
624 # We can't fstat() a pipe because Linux will always report 0.
625 # So, we set the pipe to non-blocking mode and read everything
625 # So, we set the pipe to non-blocking mode and read everything
626 # that's available.
626 # that's available.
627 flags = fcntl.fcntl(pipe, fcntl.F_GETFL)
627 flags = fcntl.fcntl(pipe, fcntl.F_GETFL)
628 flags |= os.O_NONBLOCK
628 flags |= os.O_NONBLOCK
629 oldflags = fcntl.fcntl(pipe, fcntl.F_SETFL, flags)
629 oldflags = fcntl.fcntl(pipe, fcntl.F_SETFL, flags)
630
630
631 try:
631 try:
632 chunks = []
632 chunks = []
633 while True:
633 while True:
634 try:
634 try:
635 s = pipe.read()
635 s = pipe.read()
636 if not s:
636 if not s:
637 break
637 break
638 chunks.append(s)
638 chunks.append(s)
639 except IOError:
639 except IOError:
640 break
640 break
641
641
642 return ''.join(chunks)
642 return ''.join(chunks)
643 finally:
643 finally:
644 fcntl.fcntl(pipe, fcntl.F_SETFL, oldflags)
644 fcntl.fcntl(pipe, fcntl.F_SETFL, oldflags)
645
645
646 def bindunixsocket(sock, path):
646 def bindunixsocket(sock, path):
647 """Bind the UNIX domain socket to the specified path"""
647 """Bind the UNIX domain socket to the specified path"""
648 # use relative path instead of full path at bind() if possible, since
648 # use relative path instead of full path at bind() if possible, since
649 # AF_UNIX path has very small length limit (107 chars) on common
649 # AF_UNIX path has very small length limit (107 chars) on common
650 # platforms (see sys/un.h)
650 # platforms (see sys/un.h)
651 dirname, basename = os.path.split(path)
651 dirname, basename = os.path.split(path)
652 bakwdfd = None
652 bakwdfd = None
653 if dirname:
653 if dirname:
654 bakwdfd = os.open('.', os.O_DIRECTORY)
654 bakwdfd = os.open('.', os.O_DIRECTORY)
655 os.chdir(dirname)
655 os.chdir(dirname)
656 sock.bind(basename)
656 sock.bind(basename)
657 if bakwdfd:
657 if bakwdfd:
658 os.fchdir(bakwdfd)
658 os.fchdir(bakwdfd)
659 os.close(bakwdfd)
659 os.close(bakwdfd)
General Comments 0
You need to be logged in to leave comments. Login now