##// END OF EJS Templates
dispatch: generalize signature checking for extension command wrapping
Matt Mackall -
r7388:57516312 default
parent child Browse files
Show More
@@ -1,420 +1,416
1 1 # dispatch.py - command dispatching for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from i18n import _
9 9 from repo import RepoError
10 import os, sys, atexit, signal, pdb, traceback, socket, errno, shlex, time
10 import os, sys, atexit, signal, pdb, socket, errno, shlex, time
11 11 import util, commands, hg, lock, fancyopts, revlog, version, extensions, hook
12 12 import cmdutil
13 13 import ui as _ui
14 14
15 15 class ParseError(Exception):
16 16 """Exception raised on errors in parsing the command line."""
17 17
18 18 def run():
19 19 "run the command in sys.argv"
20 20 sys.exit(dispatch(sys.argv[1:]))
21 21
22 22 def dispatch(args):
23 23 "run the command specified in args"
24 24 try:
25 25 u = _ui.ui(traceback='--traceback' in args)
26 26 except util.Abort, inst:
27 27 sys.stderr.write(_("abort: %s\n") % inst)
28 28 return -1
29 29 return _runcatch(u, args)
30 30
31 31 def _runcatch(ui, args):
32 32 def catchterm(*args):
33 33 raise util.SignalInterrupt
34 34
35 35 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
36 36 num = getattr(signal, name, None)
37 37 if num: signal.signal(num, catchterm)
38 38
39 39 try:
40 40 try:
41 41 # enter the debugger before command execution
42 42 if '--debugger' in args:
43 43 pdb.set_trace()
44 44 try:
45 45 return _dispatch(ui, args)
46 46 finally:
47 47 ui.flush()
48 48 except:
49 49 # enter the debugger when we hit an exception
50 50 if '--debugger' in args:
51 51 pdb.post_mortem(sys.exc_info()[2])
52 52 ui.print_exc()
53 53 raise
54 54
55 55 except ParseError, inst:
56 56 if inst.args[0]:
57 57 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
58 58 commands.help_(ui, inst.args[0])
59 59 else:
60 60 ui.warn(_("hg: %s\n") % inst.args[1])
61 61 commands.help_(ui, 'shortlist')
62 62 except cmdutil.AmbiguousCommand, inst:
63 63 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
64 64 (inst.args[0], " ".join(inst.args[1])))
65 65 except cmdutil.UnknownCommand, inst:
66 66 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
67 67 commands.help_(ui, 'shortlist')
68 68 except RepoError, inst:
69 69 ui.warn(_("abort: %s!\n") % inst)
70 70 except lock.LockHeld, inst:
71 71 if inst.errno == errno.ETIMEDOUT:
72 72 reason = _('timed out waiting for lock held by %s') % inst.locker
73 73 else:
74 74 reason = _('lock held by %s') % inst.locker
75 75 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
76 76 except lock.LockUnavailable, inst:
77 77 ui.warn(_("abort: could not lock %s: %s\n") %
78 78 (inst.desc or inst.filename, inst.strerror))
79 79 except revlog.RevlogError, inst:
80 80 ui.warn(_("abort: %s!\n") % inst)
81 81 except util.SignalInterrupt:
82 82 ui.warn(_("killed!\n"))
83 83 except KeyboardInterrupt:
84 84 try:
85 85 ui.warn(_("interrupted!\n"))
86 86 except IOError, inst:
87 87 if inst.errno == errno.EPIPE:
88 88 if ui.debugflag:
89 89 ui.warn(_("\nbroken pipe\n"))
90 90 else:
91 91 raise
92 92 except socket.error, inst:
93 93 ui.warn(_("abort: %s\n") % inst[-1])
94 94 except IOError, inst:
95 95 if hasattr(inst, "code"):
96 96 ui.warn(_("abort: %s\n") % inst)
97 97 elif hasattr(inst, "reason"):
98 98 try: # usually it is in the form (errno, strerror)
99 99 reason = inst.reason.args[1]
100 100 except: # it might be anything, for example a string
101 101 reason = inst.reason
102 102 ui.warn(_("abort: error: %s\n") % reason)
103 103 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
104 104 if ui.debugflag:
105 105 ui.warn(_("broken pipe\n"))
106 106 elif getattr(inst, "strerror", None):
107 107 if getattr(inst, "filename", None):
108 108 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
109 109 else:
110 110 ui.warn(_("abort: %s\n") % inst.strerror)
111 111 else:
112 112 raise
113 113 except OSError, inst:
114 114 if getattr(inst, "filename", None):
115 115 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
116 116 else:
117 117 ui.warn(_("abort: %s\n") % inst.strerror)
118 118 except util.UnexpectedOutput, inst:
119 119 ui.warn(_("abort: %s") % inst[0])
120 120 if not isinstance(inst[1], basestring):
121 121 ui.warn(" %r\n" % (inst[1],))
122 122 elif not inst[1]:
123 123 ui.warn(_(" empty string\n"))
124 124 else:
125 125 ui.warn("\n%r\n" % util.ellipsis(inst[1]))
126 126 except ImportError, inst:
127 127 m = str(inst).split()[-1]
128 128 ui.warn(_("abort: could not import module %s!\n") % m)
129 129 if m in "mpatch bdiff".split():
130 130 ui.warn(_("(did you forget to compile extensions?)\n"))
131 131 elif m in "zlib".split():
132 132 ui.warn(_("(is your Python install correct?)\n"))
133 133
134 134 except util.Abort, inst:
135 135 ui.warn(_("abort: %s\n") % inst)
136 136 except MemoryError:
137 137 ui.warn(_("abort: out of memory\n"))
138 138 except SystemExit, inst:
139 139 # Commands shouldn't sys.exit directly, but give a return code.
140 140 # Just in case catch this and and pass exit code to caller.
141 141 return inst.code
142 142 except:
143 143 ui.warn(_("** unknown exception encountered, details follow\n"))
144 144 ui.warn(_("** report bug details to "
145 145 "http://www.selenic.com/mercurial/bts\n"))
146 146 ui.warn(_("** or mercurial@selenic.com\n"))
147 147 ui.warn(_("** Mercurial Distributed SCM (version %s)\n")
148 148 % version.get_version())
149 149 ui.warn(_("** Extensions loaded: %s\n")
150 150 % ", ".join([x[0] for x in extensions.extensions()]))
151 151 raise
152 152
153 153 return -1
154 154
155 155 def _findrepo(p):
156 156 while not os.path.isdir(os.path.join(p, ".hg")):
157 157 oldp, p = p, os.path.dirname(p)
158 158 if p == oldp:
159 159 return None
160 160
161 161 return p
162 162
163 163 def _parse(ui, args):
164 164 options = {}
165 165 cmdoptions = {}
166 166
167 167 try:
168 168 args = fancyopts.fancyopts(args, commands.globalopts, options)
169 169 except fancyopts.getopt.GetoptError, inst:
170 170 raise ParseError(None, inst)
171 171
172 172 if args:
173 173 cmd, args = args[0], args[1:]
174 174 aliases, i = cmdutil.findcmd(cmd, commands.table,
175 175 ui.config("ui", "strict"))
176 176 cmd = aliases[0]
177 177 defaults = ui.config("defaults", cmd)
178 178 if defaults:
179 179 args = shlex.split(defaults) + args
180 180 c = list(i[1])
181 181 else:
182 182 cmd = None
183 183 c = []
184 184
185 185 # combine global options into local
186 186 for o in commands.globalopts:
187 187 c.append((o[0], o[1], options[o[1]], o[3]))
188 188
189 189 try:
190 190 args = fancyopts.fancyopts(args, c, cmdoptions)
191 191 except fancyopts.getopt.GetoptError, inst:
192 192 raise ParseError(cmd, inst)
193 193
194 194 # separate global options back out
195 195 for o in commands.globalopts:
196 196 n = o[1]
197 197 options[n] = cmdoptions[n]
198 198 del cmdoptions[n]
199 199
200 200 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
201 201
202 202 def _parseconfig(config):
203 203 """parse the --config options from the command line"""
204 204 parsed = []
205 205 for cfg in config:
206 206 try:
207 207 name, value = cfg.split('=', 1)
208 208 section, name = name.split('.', 1)
209 209 if not section or not name:
210 210 raise IndexError
211 211 parsed.append((section, name, value))
212 212 except (IndexError, ValueError):
213 213 raise util.Abort(_('malformed --config option: %s') % cfg)
214 214 return parsed
215 215
216 216 def _earlygetopt(aliases, args):
217 217 """Return list of values for an option (or aliases).
218 218
219 219 The values are listed in the order they appear in args.
220 220 The options and values are removed from args.
221 221 """
222 222 try:
223 223 argcount = args.index("--")
224 224 except ValueError:
225 225 argcount = len(args)
226 226 shortopts = [opt for opt in aliases if len(opt) == 2]
227 227 values = []
228 228 pos = 0
229 229 while pos < argcount:
230 230 if args[pos] in aliases:
231 231 if pos + 1 >= argcount:
232 232 # ignore and let getopt report an error if there is no value
233 233 break
234 234 del args[pos]
235 235 values.append(args.pop(pos))
236 236 argcount -= 2
237 237 elif args[pos][:2] in shortopts:
238 238 # short option can have no following space, e.g. hg log -Rfoo
239 239 values.append(args.pop(pos)[2:])
240 240 argcount -= 1
241 241 else:
242 242 pos += 1
243 243 return values
244 244
245 245 _loaded = {}
246 246 def _dispatch(ui, args):
247 247 # read --config before doing anything else
248 248 # (e.g. to change trust settings for reading .hg/hgrc)
249 249 config = _earlygetopt(['--config'], args)
250 250 if config:
251 251 ui.updateopts(config=_parseconfig(config))
252 252
253 253 # check for cwd
254 254 cwd = _earlygetopt(['--cwd'], args)
255 255 if cwd:
256 256 os.chdir(cwd[-1])
257 257
258 258 # read the local repository .hgrc into a local ui object
259 259 path = _findrepo(os.getcwd()) or ""
260 260 if not path:
261 261 lui = ui
262 262 if path:
263 263 try:
264 264 lui = _ui.ui(parentui=ui)
265 265 lui.readconfig(os.path.join(path, ".hg", "hgrc"))
266 266 except IOError:
267 267 pass
268 268
269 269 # now we can expand paths, even ones in .hg/hgrc
270 270 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
271 271 if rpath:
272 272 path = lui.expandpath(rpath[-1])
273 273 lui = _ui.ui(parentui=ui)
274 274 lui.readconfig(os.path.join(path, ".hg", "hgrc"))
275 275
276 276 extensions.loadall(lui)
277 277 for name, module in extensions.extensions():
278 278 if name in _loaded:
279 279 continue
280 280
281 281 # setup extensions
282 282 # TODO this should be generalized to scheme, where extensions can
283 283 # redepend on other extensions. then we should toposort them, and
284 284 # do initialization in correct order
285 285 extsetup = getattr(module, 'extsetup', None)
286 286 if extsetup:
287 287 extsetup()
288 288
289 289 cmdtable = getattr(module, 'cmdtable', {})
290 290 overrides = [cmd for cmd in cmdtable if cmd in commands.table]
291 291 if overrides:
292 292 ui.warn(_("extension '%s' overrides commands: %s\n")
293 293 % (name, " ".join(overrides)))
294 294 commands.table.update(cmdtable)
295 295 _loaded[name] = 1
296 296 # check for fallback encoding
297 297 fallback = lui.config('ui', 'fallbackencoding')
298 298 if fallback:
299 299 util._fallbackencoding = fallback
300 300
301 301 fullargs = args
302 302 cmd, func, args, options, cmdoptions = _parse(lui, args)
303 303
304 304 if options["config"]:
305 305 raise util.Abort(_("Option --config may not be abbreviated!"))
306 306 if options["cwd"]:
307 307 raise util.Abort(_("Option --cwd may not be abbreviated!"))
308 308 if options["repository"]:
309 309 raise util.Abort(_(
310 310 "Option -R has to be separated from other options (i.e. not -qR) "
311 311 "and --repository may only be abbreviated as --repo!"))
312 312
313 313 if options["encoding"]:
314 314 util._encoding = options["encoding"]
315 315 if options["encodingmode"]:
316 316 util._encodingmode = options["encodingmode"]
317 317 if options["time"]:
318 318 def get_times():
319 319 t = os.times()
320 320 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
321 321 t = (t[0], t[1], t[2], t[3], time.clock())
322 322 return t
323 323 s = get_times()
324 324 def print_time():
325 325 t = get_times()
326 326 ui.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
327 327 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
328 328 atexit.register(print_time)
329 329
330 330 ui.updateopts(options["verbose"], options["debug"], options["quiet"],
331 331 not options["noninteractive"], options["traceback"])
332 332
333 333 if options['help']:
334 334 return commands.help_(ui, cmd, options['version'])
335 335 elif options['version']:
336 336 return commands.version_(ui)
337 337 elif not cmd:
338 338 return commands.help_(ui, 'shortlist')
339 339
340 340 repo = None
341 341 if cmd not in commands.norepo.split():
342 342 try:
343 343 repo = hg.repository(ui, path=path)
344 344 ui = repo.ui
345 345 if not repo.local():
346 346 raise util.Abort(_("repository '%s' is not local") % path)
347 347 ui.setconfig("bundle", "mainreporoot", repo.root)
348 348 except RepoError:
349 349 if cmd not in commands.optionalrepo.split():
350 350 if args and not path: # try to infer -R from command args
351 351 repos = map(_findrepo, args)
352 352 guess = repos[0]
353 353 if guess and repos.count(guess) == len(repos):
354 354 return _dispatch(ui, ['--repository', guess] + fullargs)
355 355 if not path:
356 356 raise RepoError(_("There is no Mercurial repository here"
357 357 " (.hg not found)"))
358 358 raise
359 d = lambda: func(ui, repo, *args, **cmdoptions)
360 else:
361 d = lambda: func(ui, *args, **cmdoptions)
359 args.insert(0, repo)
360
361 d = lambda: util.checksignature(func)(ui, *args, **cmdoptions)
362 362
363 363 # run pre-hook, and abort if it fails
364 364 ret = hook.hook(lui, repo, "pre-%s" % cmd, False, args=" ".join(fullargs))
365 365 if ret:
366 366 return ret
367 367 ret = _runcommand(ui, options, cmd, d)
368 368 # run post-hook, passing command result
369 369 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
370 370 result = ret)
371 371 return ret
372 372
373 373 def _runcommand(ui, options, cmd, cmdfunc):
374 374 def checkargs():
375 375 try:
376 376 return cmdfunc()
377 except TypeError:
378 # was this an argument error?
379 tb = traceback.extract_tb(sys.exc_info()[2])
380 if len(tb) != 2: # no
381 raise
377 except util.SignatureError:
382 378 raise ParseError(cmd, _("invalid arguments"))
383 379
384 380 if options['profile']:
385 381 import hotshot, hotshot.stats
386 382 prof = hotshot.Profile("hg.prof")
387 383 try:
388 384 try:
389 385 return prof.runcall(checkargs)
390 386 except:
391 387 try:
392 388 ui.warn(_('exception raised - generating '
393 389 'profile anyway\n'))
394 390 except:
395 391 pass
396 392 raise
397 393 finally:
398 394 prof.close()
399 395 stats = hotshot.stats.load("hg.prof")
400 396 stats.strip_dirs()
401 397 stats.sort_stats('time', 'calls')
402 398 stats.print_stats(40)
403 399 elif options['lsprof']:
404 400 try:
405 401 from mercurial import lsprof
406 402 except ImportError:
407 403 raise util.Abort(_(
408 404 'lsprof not available - install from '
409 405 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
410 406 p = lsprof.Profiler()
411 407 p.enable(subcalls=True)
412 408 try:
413 409 return checkargs()
414 410 finally:
415 411 p.disable()
416 412 stats = lsprof.Stats(p.getstats())
417 413 stats.sort()
418 414 stats.pprint(top=10, file=sys.stderr, climit=5)
419 415 else:
420 416 return checkargs()
@@ -1,115 +1,116
1 1 # extensions.py - extension handling for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 import imp, os
9 9 import util, cmdutil
10 10 from i18n import _
11 11
12 12 _extensions = {}
13 13 _order = []
14 14
15 15 def extensions():
16 16 for name in _order:
17 17 module = _extensions[name]
18 18 if module:
19 19 yield name, module
20 20
21 21 def find(name):
22 22 '''return module with given extension name'''
23 23 try:
24 24 return _extensions[name]
25 25 except KeyError:
26 26 for k, v in _extensions.iteritems():
27 27 if k.endswith('.' + name) or k.endswith('/' + name):
28 28 return v
29 29 raise KeyError(name)
30 30
31 31 def load(ui, name, path):
32 32 if name.startswith('hgext.') or name.startswith('hgext/'):
33 33 shortname = name[6:]
34 34 else:
35 35 shortname = name
36 36 if shortname in _extensions:
37 37 return
38 38 _extensions[shortname] = None
39 39 if path:
40 40 # the module will be loaded in sys.modules
41 41 # choose an unique name so that it doesn't
42 42 # conflicts with other modules
43 43 module_name = "hgext_%s" % name.replace('.', '_')
44 44 if os.path.isdir(path):
45 45 # module/__init__.py style
46 46 d, f = os.path.split(path)
47 47 fd, fpath, desc = imp.find_module(f, [d])
48 48 mod = imp.load_module(module_name, fd, fpath, desc)
49 49 else:
50 50 mod = imp.load_source(module_name, path)
51 51 else:
52 52 def importh(name):
53 53 mod = __import__(name)
54 54 components = name.split('.')
55 55 for comp in components[1:]:
56 56 mod = getattr(mod, comp)
57 57 return mod
58 58 try:
59 59 mod = importh("hgext.%s" % name)
60 60 except ImportError:
61 61 mod = importh(name)
62 62 _extensions[shortname] = mod
63 63 _order.append(shortname)
64 64
65 65 uisetup = getattr(mod, 'uisetup', None)
66 66 if uisetup:
67 67 uisetup(ui)
68 68
69 69 def loadall(ui):
70 70 result = ui.configitems("extensions")
71 71 for i, (name, path) in enumerate(result):
72 72 if path:
73 73 if path[0] == '!':
74 74 continue
75 75 path = os.path.expanduser(path)
76 76 try:
77 77 load(ui, name, path)
78 78 except (util.SignalInterrupt, KeyboardInterrupt):
79 79 raise
80 80 except Exception, inst:
81 81 if path:
82 82 ui.warn(_("*** failed to import extension %s from %s: %s\n")
83 83 % (name, path, inst))
84 84 else:
85 85 ui.warn(_("*** failed to import extension %s: %s\n")
86 86 % (name, inst))
87 87 if ui.print_exc():
88 88 return 1
89 89
90 90 def wrapcommand(table, command, wrapper):
91 91 aliases, entry = cmdutil.findcmd(command, table)
92 92 for alias, e in table.iteritems():
93 93 if e is entry:
94 94 key = alias
95 95 break
96 96
97 97 origfn = entry[0]
98 98 def wrap(*args, **kwargs):
99 return wrapper(origfn, *args, **kwargs)
99 return util.checksignature(wrapper)(
100 util.checksignature(origfn), *args, **kwargs)
100 101
101 102 wrap.__doc__ = getattr(origfn, '__doc__')
102 103 wrap.__module__ = getattr(origfn, '__module__')
103 104
104 105 newentry = list(entry)
105 106 newentry[0] = wrap
106 107 table[key] = tuple(newentry)
107 108 return entry
108 109
109 110 def wrapfunction(container, funcname, wrapper):
110 111 def wrap(*args, **kwargs):
111 112 return wrapper(origfn, *args, **kwargs)
112 113
113 114 origfn = getattr(container, funcname)
114 115 setattr(container, funcname, wrap)
115 116 return origfn
@@ -1,1934 +1,1949
1 1 """
2 2 util.py - Mercurial utility functions and platform specfic implementations
3 3
4 4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
5 5 Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
6 6 Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
7 7
8 8 This software may be used and distributed according to the terms
9 9 of the GNU General Public License, incorporated herein by reference.
10 10
11 11 This contains helper routines that are independent of the SCM core and hide
12 12 platform-specific details from the core.
13 13 """
14 14
15 15 from i18n import _
16 import cStringIO, errno, getpass, re, shutil, sys, tempfile
16 import cStringIO, errno, getpass, re, shutil, sys, tempfile, traceback
17 17 import os, stat, threading, time, calendar, ConfigParser, locale, glob, osutil
18 18 import imp
19 19
20 20 # Python compatibility
21 21
22 22 try:
23 23 set = set
24 24 frozenset = frozenset
25 25 except NameError:
26 26 from sets import Set as set, ImmutableSet as frozenset
27 27
28 28 _md5 = None
29 29 def md5(s):
30 30 global _md5
31 31 if _md5 is None:
32 32 try:
33 33 import hashlib
34 34 _md5 = hashlib.md5
35 35 except ImportError:
36 36 import md5
37 37 _md5 = md5.md5
38 38 return _md5(s)
39 39
40 40 _sha1 = None
41 41 def sha1(s):
42 42 global _sha1
43 43 if _sha1 is None:
44 44 try:
45 45 import hashlib
46 46 _sha1 = hashlib.sha1
47 47 except ImportError:
48 48 import sha
49 49 _sha1 = sha.sha
50 50 return _sha1(s)
51 51
52 52 try:
53 53 import subprocess
54 54 subprocess.Popen # trigger ImportError early
55 55 closefds = os.name == 'posix'
56 56 def popen2(cmd, mode='t', bufsize=-1):
57 57 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
58 58 close_fds=closefds,
59 59 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
60 60 return p.stdin, p.stdout
61 61 def popen3(cmd, mode='t', bufsize=-1):
62 62 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
63 63 close_fds=closefds,
64 64 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
65 65 stderr=subprocess.PIPE)
66 66 return p.stdin, p.stdout, p.stderr
67 67 def Popen3(cmd, capturestderr=False, bufsize=-1):
68 68 stderr = capturestderr and subprocess.PIPE or None
69 69 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
70 70 close_fds=closefds,
71 71 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
72 72 stderr=stderr)
73 73 p.fromchild = p.stdout
74 74 p.tochild = p.stdin
75 75 p.childerr = p.stderr
76 76 return p
77 77 except ImportError:
78 78 subprocess = None
79 79 from popen2 import Popen3
80 80 popen2 = os.popen2
81 81 popen3 = os.popen3
82 82
83 83
84 84 try:
85 85 _encoding = os.environ.get("HGENCODING")
86 86 if sys.platform == 'darwin' and not _encoding:
87 87 # On darwin, getpreferredencoding ignores the locale environment and
88 88 # always returns mac-roman. We override this if the environment is
89 89 # not C (has been customized by the user).
90 90 locale.setlocale(locale.LC_CTYPE, '')
91 91 _encoding = locale.getlocale()[1]
92 92 if not _encoding:
93 93 _encoding = locale.getpreferredencoding() or 'ascii'
94 94 except locale.Error:
95 95 _encoding = 'ascii'
96 96 _encodingmode = os.environ.get("HGENCODINGMODE", "strict")
97 97 _fallbackencoding = 'ISO-8859-1'
98 98
99 99 def tolocal(s):
100 100 """
101 101 Convert a string from internal UTF-8 to local encoding
102 102
103 103 All internal strings should be UTF-8 but some repos before the
104 104 implementation of locale support may contain latin1 or possibly
105 105 other character sets. We attempt to decode everything strictly
106 106 using UTF-8, then Latin-1, and failing that, we use UTF-8 and
107 107 replace unknown characters.
108 108 """
109 109 for e in ('UTF-8', _fallbackencoding):
110 110 try:
111 111 u = s.decode(e) # attempt strict decoding
112 112 return u.encode(_encoding, "replace")
113 113 except LookupError, k:
114 114 raise Abort(_("%s, please check your locale settings") % k)
115 115 except UnicodeDecodeError:
116 116 pass
117 117 u = s.decode("utf-8", "replace") # last ditch
118 118 return u.encode(_encoding, "replace")
119 119
120 120 def fromlocal(s):
121 121 """
122 122 Convert a string from the local character encoding to UTF-8
123 123
124 124 We attempt to decode strings using the encoding mode set by
125 125 HGENCODINGMODE, which defaults to 'strict'. In this mode, unknown
126 126 characters will cause an error message. Other modes include
127 127 'replace', which replaces unknown characters with a special
128 128 Unicode character, and 'ignore', which drops the character.
129 129 """
130 130 try:
131 131 return s.decode(_encoding, _encodingmode).encode("utf-8")
132 132 except UnicodeDecodeError, inst:
133 133 sub = s[max(0, inst.start-10):inst.start+10]
134 134 raise Abort("decoding near '%s': %s!" % (sub, inst))
135 135 except LookupError, k:
136 136 raise Abort(_("%s, please check your locale settings") % k)
137 137
138 138 def locallen(s):
139 139 """Find the length in characters of a local string"""
140 140 return len(s.decode(_encoding, "replace"))
141 141
142 142 # used by parsedate
143 143 defaultdateformats = (
144 144 '%Y-%m-%d %H:%M:%S',
145 145 '%Y-%m-%d %I:%M:%S%p',
146 146 '%Y-%m-%d %H:%M',
147 147 '%Y-%m-%d %I:%M%p',
148 148 '%Y-%m-%d',
149 149 '%m-%d',
150 150 '%m/%d',
151 151 '%m/%d/%y',
152 152 '%m/%d/%Y',
153 153 '%a %b %d %H:%M:%S %Y',
154 154 '%a %b %d %I:%M:%S%p %Y',
155 155 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
156 156 '%b %d %H:%M:%S %Y',
157 157 '%b %d %I:%M:%S%p %Y',
158 158 '%b %d %H:%M:%S',
159 159 '%b %d %I:%M:%S%p',
160 160 '%b %d %H:%M',
161 161 '%b %d %I:%M%p',
162 162 '%b %d %Y',
163 163 '%b %d',
164 164 '%H:%M:%S',
165 165 '%I:%M:%SP',
166 166 '%H:%M',
167 167 '%I:%M%p',
168 168 )
169 169
170 170 extendeddateformats = defaultdateformats + (
171 171 "%Y",
172 172 "%Y-%m",
173 173 "%b",
174 174 "%b %Y",
175 175 )
176 176
177 177 class SignalInterrupt(Exception):
178 178 """Exception raised on SIGTERM and SIGHUP."""
179 179
180 180 # differences from SafeConfigParser:
181 181 # - case-sensitive keys
182 182 # - allows values that are not strings (this means that you may not
183 183 # be able to save the configuration to a file)
184 184 class configparser(ConfigParser.SafeConfigParser):
185 185 def optionxform(self, optionstr):
186 186 return optionstr
187 187
188 188 def set(self, section, option, value):
189 189 return ConfigParser.ConfigParser.set(self, section, option, value)
190 190
191 191 def _interpolate(self, section, option, rawval, vars):
192 192 if not isinstance(rawval, basestring):
193 193 return rawval
194 194 return ConfigParser.SafeConfigParser._interpolate(self, section,
195 195 option, rawval, vars)
196 196
197 197 def cachefunc(func):
198 198 '''cache the result of function calls'''
199 199 # XXX doesn't handle keywords args
200 200 cache = {}
201 201 if func.func_code.co_argcount == 1:
202 202 # we gain a small amount of time because
203 203 # we don't need to pack/unpack the list
204 204 def f(arg):
205 205 if arg not in cache:
206 206 cache[arg] = func(arg)
207 207 return cache[arg]
208 208 else:
209 209 def f(*args):
210 210 if args not in cache:
211 211 cache[args] = func(*args)
212 212 return cache[args]
213 213
214 214 return f
215 215
216 216 def pipefilter(s, cmd):
217 217 '''filter string S through command CMD, returning its output'''
218 218 (pin, pout) = popen2(cmd, 'b')
219 219 def writer():
220 220 try:
221 221 pin.write(s)
222 222 pin.close()
223 223 except IOError, inst:
224 224 if inst.errno != errno.EPIPE:
225 225 raise
226 226
227 227 # we should use select instead on UNIX, but this will work on most
228 228 # systems, including Windows
229 229 w = threading.Thread(target=writer)
230 230 w.start()
231 231 f = pout.read()
232 232 pout.close()
233 233 w.join()
234 234 return f
235 235
236 236 def tempfilter(s, cmd):
237 237 '''filter string S through a pair of temporary files with CMD.
238 238 CMD is used as a template to create the real command to be run,
239 239 with the strings INFILE and OUTFILE replaced by the real names of
240 240 the temporary files generated.'''
241 241 inname, outname = None, None
242 242 try:
243 243 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
244 244 fp = os.fdopen(infd, 'wb')
245 245 fp.write(s)
246 246 fp.close()
247 247 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
248 248 os.close(outfd)
249 249 cmd = cmd.replace('INFILE', inname)
250 250 cmd = cmd.replace('OUTFILE', outname)
251 251 code = os.system(cmd)
252 252 if sys.platform == 'OpenVMS' and code & 1:
253 253 code = 0
254 254 if code: raise Abort(_("command '%s' failed: %s") %
255 255 (cmd, explain_exit(code)))
256 256 return open(outname, 'rb').read()
257 257 finally:
258 258 try:
259 259 if inname: os.unlink(inname)
260 260 except: pass
261 261 try:
262 262 if outname: os.unlink(outname)
263 263 except: pass
264 264
265 265 filtertable = {
266 266 'tempfile:': tempfilter,
267 267 'pipe:': pipefilter,
268 268 }
269 269
270 270 def filter(s, cmd):
271 271 "filter a string through a command that transforms its input to its output"
272 272 for name, fn in filtertable.iteritems():
273 273 if cmd.startswith(name):
274 274 return fn(s, cmd[len(name):].lstrip())
275 275 return pipefilter(s, cmd)
276 276
277 277 def binary(s):
278 278 """return true if a string is binary data"""
279 279 if s and '\0' in s:
280 280 return True
281 281 return False
282 282
283 283 def unique(g):
284 284 """return the uniq elements of iterable g"""
285 285 return dict.fromkeys(g).keys()
286 286
287 287 def sort(l):
288 288 if not isinstance(l, list):
289 289 l = list(l)
290 290 l.sort()
291 291 return l
292 292
293 293 class Abort(Exception):
294 294 """Raised if a command needs to print an error and exit."""
295 295
296 296 class UnexpectedOutput(Abort):
297 297 """Raised to print an error with part of output and exit."""
298 298
299 299 def always(fn): return True
300 300 def never(fn): return False
301 301
302 302 def expand_glob(pats):
303 303 '''On Windows, expand the implicit globs in a list of patterns'''
304 304 if os.name != 'nt':
305 305 return list(pats)
306 306 ret = []
307 307 for p in pats:
308 308 kind, name = patkind(p, None)
309 309 if kind is None:
310 310 globbed = glob.glob(name)
311 311 if globbed:
312 312 ret.extend(globbed)
313 313 continue
314 314 # if we couldn't expand the glob, just keep it around
315 315 ret.append(p)
316 316 return ret
317 317
318 318 def patkind(name, default):
319 319 """Split a string into an optional pattern kind prefix and the
320 320 actual pattern."""
321 321 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
322 322 if name.startswith(prefix + ':'): return name.split(':', 1)
323 323 return default, name
324 324
325 325 def globre(pat, head='^', tail='$'):
326 326 "convert a glob pattern into a regexp"
327 327 i, n = 0, len(pat)
328 328 res = ''
329 329 group = 0
330 330 def peek(): return i < n and pat[i]
331 331 while i < n:
332 332 c = pat[i]
333 333 i = i+1
334 334 if c == '*':
335 335 if peek() == '*':
336 336 i += 1
337 337 res += '.*'
338 338 else:
339 339 res += '[^/]*'
340 340 elif c == '?':
341 341 res += '.'
342 342 elif c == '[':
343 343 j = i
344 344 if j < n and pat[j] in '!]':
345 345 j += 1
346 346 while j < n and pat[j] != ']':
347 347 j += 1
348 348 if j >= n:
349 349 res += '\\['
350 350 else:
351 351 stuff = pat[i:j].replace('\\','\\\\')
352 352 i = j + 1
353 353 if stuff[0] == '!':
354 354 stuff = '^' + stuff[1:]
355 355 elif stuff[0] == '^':
356 356 stuff = '\\' + stuff
357 357 res = '%s[%s]' % (res, stuff)
358 358 elif c == '{':
359 359 group += 1
360 360 res += '(?:'
361 361 elif c == '}' and group:
362 362 res += ')'
363 363 group -= 1
364 364 elif c == ',' and group:
365 365 res += '|'
366 366 elif c == '\\':
367 367 p = peek()
368 368 if p:
369 369 i += 1
370 370 res += re.escape(p)
371 371 else:
372 372 res += re.escape(c)
373 373 else:
374 374 res += re.escape(c)
375 375 return head + res + tail
376 376
377 377 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
378 378
379 379 def pathto(root, n1, n2):
380 380 '''return the relative path from one place to another.
381 381 root should use os.sep to separate directories
382 382 n1 should use os.sep to separate directories
383 383 n2 should use "/" to separate directories
384 384 returns an os.sep-separated path.
385 385
386 386 If n1 is a relative path, it's assumed it's
387 387 relative to root.
388 388 n2 should always be relative to root.
389 389 '''
390 390 if not n1: return localpath(n2)
391 391 if os.path.isabs(n1):
392 392 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
393 393 return os.path.join(root, localpath(n2))
394 394 n2 = '/'.join((pconvert(root), n2))
395 395 a, b = splitpath(n1), n2.split('/')
396 396 a.reverse()
397 397 b.reverse()
398 398 while a and b and a[-1] == b[-1]:
399 399 a.pop()
400 400 b.pop()
401 401 b.reverse()
402 402 return os.sep.join((['..'] * len(a)) + b) or '.'
403 403
404 404 def canonpath(root, cwd, myname):
405 405 """return the canonical path of myname, given cwd and root"""
406 406 if root == os.sep:
407 407 rootsep = os.sep
408 408 elif endswithsep(root):
409 409 rootsep = root
410 410 else:
411 411 rootsep = root + os.sep
412 412 name = myname
413 413 if not os.path.isabs(name):
414 414 name = os.path.join(root, cwd, name)
415 415 name = os.path.normpath(name)
416 416 audit_path = path_auditor(root)
417 417 if name != rootsep and name.startswith(rootsep):
418 418 name = name[len(rootsep):]
419 419 audit_path(name)
420 420 return pconvert(name)
421 421 elif name == root:
422 422 return ''
423 423 else:
424 424 # Determine whether `name' is in the hierarchy at or beneath `root',
425 425 # by iterating name=dirname(name) until that causes no change (can't
426 426 # check name == '/', because that doesn't work on windows). For each
427 427 # `name', compare dev/inode numbers. If they match, the list `rel'
428 428 # holds the reversed list of components making up the relative file
429 429 # name we want.
430 430 root_st = os.stat(root)
431 431 rel = []
432 432 while True:
433 433 try:
434 434 name_st = os.stat(name)
435 435 except OSError:
436 436 break
437 437 if samestat(name_st, root_st):
438 438 if not rel:
439 439 # name was actually the same as root (maybe a symlink)
440 440 return ''
441 441 rel.reverse()
442 442 name = os.path.join(*rel)
443 443 audit_path(name)
444 444 return pconvert(name)
445 445 dirname, basename = os.path.split(name)
446 446 rel.append(basename)
447 447 if dirname == name:
448 448 break
449 449 name = dirname
450 450
451 451 raise Abort('%s not under root' % myname)
452 452
453 453 def matcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None, dflt_pat='glob'):
454 454 """build a function to match a set of file patterns
455 455
456 456 arguments:
457 457 canonroot - the canonical root of the tree you're matching against
458 458 cwd - the current working directory, if relevant
459 459 names - patterns to find
460 460 inc - patterns to include
461 461 exc - patterns to exclude
462 462 dflt_pat - if a pattern in names has no explicit type, assume this one
463 463 src - where these patterns came from (e.g. .hgignore)
464 464
465 465 a pattern is one of:
466 466 'glob:<glob>' - a glob relative to cwd
467 467 're:<regexp>' - a regular expression
468 468 'path:<path>' - a path relative to canonroot
469 469 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
470 470 'relpath:<path>' - a path relative to cwd
471 471 'relre:<regexp>' - a regexp that doesn't have to match the start of a name
472 472 '<something>' - one of the cases above, selected by the dflt_pat argument
473 473
474 474 returns:
475 475 a 3-tuple containing
476 476 - list of roots (places where one should start a recursive walk of the fs);
477 477 this often matches the explicit non-pattern names passed in, but also
478 478 includes the initial part of glob: patterns that has no glob characters
479 479 - a bool match(filename) function
480 480 - a bool indicating if any patterns were passed in
481 481 """
482 482
483 483 # a common case: no patterns at all
484 484 if not names and not inc and not exc:
485 485 return [], always, False
486 486
487 487 def contains_glob(name):
488 488 for c in name:
489 489 if c in _globchars: return True
490 490 return False
491 491
492 492 def regex(kind, name, tail):
493 493 '''convert a pattern into a regular expression'''
494 494 if not name:
495 495 return ''
496 496 if kind == 're':
497 497 return name
498 498 elif kind == 'path':
499 499 return '^' + re.escape(name) + '(?:/|$)'
500 500 elif kind == 'relglob':
501 501 return globre(name, '(?:|.*/)', tail)
502 502 elif kind == 'relpath':
503 503 return re.escape(name) + '(?:/|$)'
504 504 elif kind == 'relre':
505 505 if name.startswith('^'):
506 506 return name
507 507 return '.*' + name
508 508 return globre(name, '', tail)
509 509
510 510 def matchfn(pats, tail):
511 511 """build a matching function from a set of patterns"""
512 512 if not pats:
513 513 return
514 514 try:
515 515 pat = '(?:%s)' % '|'.join([regex(k, p, tail) for (k, p) in pats])
516 516 if len(pat) > 20000:
517 517 raise OverflowError()
518 518 return re.compile(pat).match
519 519 except OverflowError:
520 520 # We're using a Python with a tiny regex engine and we
521 521 # made it explode, so we'll divide the pattern list in two
522 522 # until it works
523 523 l = len(pats)
524 524 if l < 2:
525 525 raise
526 526 a, b = matchfn(pats[:l//2], tail), matchfn(pats[l//2:], tail)
527 527 return lambda s: a(s) or b(s)
528 528 except re.error:
529 529 for k, p in pats:
530 530 try:
531 531 re.compile('(?:%s)' % regex(k, p, tail))
532 532 except re.error:
533 533 if src:
534 534 raise Abort("%s: invalid pattern (%s): %s" %
535 535 (src, k, p))
536 536 else:
537 537 raise Abort("invalid pattern (%s): %s" % (k, p))
538 538 raise Abort("invalid pattern")
539 539
540 540 def globprefix(pat):
541 541 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
542 542 root = []
543 543 for p in pat.split('/'):
544 544 if contains_glob(p): break
545 545 root.append(p)
546 546 return '/'.join(root) or '.'
547 547
548 548 def normalizepats(names, default):
549 549 pats = []
550 550 roots = []
551 551 anypats = False
552 552 for kind, name in [patkind(p, default) for p in names]:
553 553 if kind in ('glob', 'relpath'):
554 554 name = canonpath(canonroot, cwd, name)
555 555 elif kind in ('relglob', 'path'):
556 556 name = normpath(name)
557 557
558 558 pats.append((kind, name))
559 559
560 560 if kind in ('glob', 're', 'relglob', 'relre'):
561 561 anypats = True
562 562
563 563 if kind == 'glob':
564 564 root = globprefix(name)
565 565 roots.append(root)
566 566 elif kind in ('relpath', 'path'):
567 567 roots.append(name or '.')
568 568 elif kind == 'relglob':
569 569 roots.append('.')
570 570 return roots, pats, anypats
571 571
572 572 roots, pats, anypats = normalizepats(names, dflt_pat)
573 573
574 574 patmatch = matchfn(pats, '$') or always
575 575 incmatch = always
576 576 if inc:
577 577 dummy, inckinds, dummy = normalizepats(inc, 'glob')
578 578 incmatch = matchfn(inckinds, '(?:/|$)')
579 579 excmatch = lambda fn: False
580 580 if exc:
581 581 dummy, exckinds, dummy = normalizepats(exc, 'glob')
582 582 excmatch = matchfn(exckinds, '(?:/|$)')
583 583
584 584 if not names and inc and not exc:
585 585 # common case: hgignore patterns
586 586 match = incmatch
587 587 else:
588 588 match = lambda fn: incmatch(fn) and not excmatch(fn) and patmatch(fn)
589 589
590 590 return (roots, match, (inc or exc or anypats) and True)
591 591
592 592 _hgexecutable = None
593 593
594 594 def main_is_frozen():
595 595 """return True if we are a frozen executable.
596 596
597 597 The code supports py2exe (most common, Windows only) and tools/freeze
598 598 (portable, not much used).
599 599 """
600 600 return (hasattr(sys, "frozen") or # new py2exe
601 601 hasattr(sys, "importers") or # old py2exe
602 602 imp.is_frozen("__main__")) # tools/freeze
603 603
604 604 def hgexecutable():
605 605 """return location of the 'hg' executable.
606 606
607 607 Defaults to $HG or 'hg' in the search path.
608 608 """
609 609 if _hgexecutable is None:
610 610 hg = os.environ.get('HG')
611 611 if hg:
612 612 set_hgexecutable(hg)
613 613 elif main_is_frozen():
614 614 set_hgexecutable(sys.executable)
615 615 else:
616 616 set_hgexecutable(find_exe('hg', 'hg'))
617 617 return _hgexecutable
618 618
619 619 def set_hgexecutable(path):
620 620 """set location of the 'hg' executable"""
621 621 global _hgexecutable
622 622 _hgexecutable = path
623 623
624 624 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
625 625 '''enhanced shell command execution.
626 626 run with environment maybe modified, maybe in different dir.
627 627
628 628 if command fails and onerr is None, return status. if ui object,
629 629 print error message and return status, else raise onerr object as
630 630 exception.'''
631 631 def py2shell(val):
632 632 'convert python object into string that is useful to shell'
633 633 if val in (None, False):
634 634 return '0'
635 635 if val == True:
636 636 return '1'
637 637 return str(val)
638 638 oldenv = {}
639 639 for k in environ:
640 640 oldenv[k] = os.environ.get(k)
641 641 if cwd is not None:
642 642 oldcwd = os.getcwd()
643 643 origcmd = cmd
644 644 if os.name == 'nt':
645 645 cmd = '"%s"' % cmd
646 646 try:
647 647 for k, v in environ.iteritems():
648 648 os.environ[k] = py2shell(v)
649 649 os.environ['HG'] = hgexecutable()
650 650 if cwd is not None and oldcwd != cwd:
651 651 os.chdir(cwd)
652 652 rc = os.system(cmd)
653 653 if sys.platform == 'OpenVMS' and rc & 1:
654 654 rc = 0
655 655 if rc and onerr:
656 656 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
657 657 explain_exit(rc)[0])
658 658 if errprefix:
659 659 errmsg = '%s: %s' % (errprefix, errmsg)
660 660 try:
661 661 onerr.warn(errmsg + '\n')
662 662 except AttributeError:
663 663 raise onerr(errmsg)
664 664 return rc
665 665 finally:
666 666 for k, v in oldenv.iteritems():
667 667 if v is None:
668 668 del os.environ[k]
669 669 else:
670 670 os.environ[k] = v
671 671 if cwd is not None and oldcwd != cwd:
672 672 os.chdir(oldcwd)
673 673
674 class SignatureError:
675 pass
676
677 def checksignature(func):
678 '''wrap a function with code to check for calling errors'''
679 def check(*args, **kwargs):
680 try:
681 return func(*args, **kwargs)
682 except TypeError:
683 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
684 raise SignatureError
685 raise
686
687 return check
688
674 689 # os.path.lexists is not available on python2.3
675 690 def lexists(filename):
676 691 "test whether a file with this name exists. does not follow symlinks"
677 692 try:
678 693 os.lstat(filename)
679 694 except:
680 695 return False
681 696 return True
682 697
683 698 def rename(src, dst):
684 699 """forcibly rename a file"""
685 700 try:
686 701 os.rename(src, dst)
687 702 except OSError, err: # FIXME: check err (EEXIST ?)
688 703 # on windows, rename to existing file is not allowed, so we
689 704 # must delete destination first. but if file is open, unlink
690 705 # schedules it for delete but does not delete it. rename
691 706 # happens immediately even for open files, so we create
692 707 # temporary file, delete it, rename destination to that name,
693 708 # then delete that. then rename is safe to do.
694 709 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
695 710 os.close(fd)
696 711 os.unlink(temp)
697 712 os.rename(dst, temp)
698 713 os.unlink(temp)
699 714 os.rename(src, dst)
700 715
701 716 def unlink(f):
702 717 """unlink and remove the directory if it is empty"""
703 718 os.unlink(f)
704 719 # try removing directories that might now be empty
705 720 try:
706 721 os.removedirs(os.path.dirname(f))
707 722 except OSError:
708 723 pass
709 724
710 725 def copyfile(src, dest):
711 726 "copy a file, preserving mode"
712 727 if os.path.islink(src):
713 728 try:
714 729 os.unlink(dest)
715 730 except:
716 731 pass
717 732 os.symlink(os.readlink(src), dest)
718 733 else:
719 734 try:
720 735 shutil.copyfile(src, dest)
721 736 shutil.copymode(src, dest)
722 737 except shutil.Error, inst:
723 738 raise Abort(str(inst))
724 739
725 740 def copyfiles(src, dst, hardlink=None):
726 741 """Copy a directory tree using hardlinks if possible"""
727 742
728 743 if hardlink is None:
729 744 hardlink = (os.stat(src).st_dev ==
730 745 os.stat(os.path.dirname(dst)).st_dev)
731 746
732 747 if os.path.isdir(src):
733 748 os.mkdir(dst)
734 749 for name, kind in osutil.listdir(src):
735 750 srcname = os.path.join(src, name)
736 751 dstname = os.path.join(dst, name)
737 752 copyfiles(srcname, dstname, hardlink)
738 753 else:
739 754 if hardlink:
740 755 try:
741 756 os_link(src, dst)
742 757 except (IOError, OSError):
743 758 hardlink = False
744 759 shutil.copy(src, dst)
745 760 else:
746 761 shutil.copy(src, dst)
747 762
748 763 class path_auditor(object):
749 764 '''ensure that a filesystem path contains no banned components.
750 765 the following properties of a path are checked:
751 766
752 767 - under top-level .hg
753 768 - starts at the root of a windows drive
754 769 - contains ".."
755 770 - traverses a symlink (e.g. a/symlink_here/b)
756 771 - inside a nested repository'''
757 772
758 773 def __init__(self, root):
759 774 self.audited = set()
760 775 self.auditeddir = set()
761 776 self.root = root
762 777
763 778 def __call__(self, path):
764 779 if path in self.audited:
765 780 return
766 781 normpath = os.path.normcase(path)
767 782 parts = splitpath(normpath)
768 783 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
769 784 or os.pardir in parts):
770 785 raise Abort(_("path contains illegal component: %s") % path)
771 786 def check(prefix):
772 787 curpath = os.path.join(self.root, prefix)
773 788 try:
774 789 st = os.lstat(curpath)
775 790 except OSError, err:
776 791 # EINVAL can be raised as invalid path syntax under win32.
777 792 # They must be ignored for patterns can be checked too.
778 793 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
779 794 raise
780 795 else:
781 796 if stat.S_ISLNK(st.st_mode):
782 797 raise Abort(_('path %r traverses symbolic link %r') %
783 798 (path, prefix))
784 799 elif (stat.S_ISDIR(st.st_mode) and
785 800 os.path.isdir(os.path.join(curpath, '.hg'))):
786 801 raise Abort(_('path %r is inside repo %r') %
787 802 (path, prefix))
788 803 parts.pop()
789 804 prefixes = []
790 805 for n in range(len(parts)):
791 806 prefix = os.sep.join(parts)
792 807 if prefix in self.auditeddir:
793 808 break
794 809 check(prefix)
795 810 prefixes.append(prefix)
796 811 parts.pop()
797 812
798 813 self.audited.add(path)
799 814 # only add prefixes to the cache after checking everything: we don't
800 815 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
801 816 self.auditeddir.update(prefixes)
802 817
803 818 def _makelock_file(info, pathname):
804 819 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
805 820 os.write(ld, info)
806 821 os.close(ld)
807 822
808 823 def _readlock_file(pathname):
809 824 return posixfile(pathname).read()
810 825
811 826 def nlinks(pathname):
812 827 """Return number of hardlinks for the given file."""
813 828 return os.lstat(pathname).st_nlink
814 829
815 830 if hasattr(os, 'link'):
816 831 os_link = os.link
817 832 else:
818 833 def os_link(src, dst):
819 834 raise OSError(0, _("Hardlinks not supported"))
820 835
821 836 def fstat(fp):
822 837 '''stat file object that may not have fileno method.'''
823 838 try:
824 839 return os.fstat(fp.fileno())
825 840 except AttributeError:
826 841 return os.stat(fp.name)
827 842
828 843 posixfile = file
829 844
830 845 def openhardlinks():
831 846 '''return true if it is safe to hold open file handles to hardlinks'''
832 847 return True
833 848
834 849 def _statfiles(files):
835 850 'Stat each file in files and yield stat or None if file does not exist.'
836 851 lstat = os.lstat
837 852 for nf in files:
838 853 try:
839 854 st = lstat(nf)
840 855 except OSError, err:
841 856 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
842 857 raise
843 858 st = None
844 859 yield st
845 860
846 861 def _statfiles_clustered(files):
847 862 '''Stat each file in files and yield stat or None if file does not exist.
848 863 Cluster and cache stat per directory to minimize number of OS stat calls.'''
849 864 lstat = os.lstat
850 865 ncase = os.path.normcase
851 866 sep = os.sep
852 867 dircache = {} # dirname -> filename -> status | None if file does not exist
853 868 for nf in files:
854 869 nf = ncase(nf)
855 870 pos = nf.rfind(sep)
856 871 if pos == -1:
857 872 dir, base = '.', nf
858 873 else:
859 874 dir, base = nf[:pos+1], nf[pos+1:]
860 875 cache = dircache.get(dir, None)
861 876 if cache is None:
862 877 try:
863 878 dmap = dict([(ncase(n), s)
864 879 for n, k, s in osutil.listdir(dir, True)])
865 880 except OSError, err:
866 881 # handle directory not found in Python version prior to 2.5
867 882 # Python <= 2.4 returns native Windows code 3 in errno
868 883 # Python >= 2.5 returns ENOENT and adds winerror field
869 884 # EINVAL is raised if dir is not a directory.
870 885 if err.errno not in (3, errno.ENOENT, errno.EINVAL,
871 886 errno.ENOTDIR):
872 887 raise
873 888 dmap = {}
874 889 cache = dircache.setdefault(dir, dmap)
875 890 yield cache.get(base, None)
876 891
877 892 if sys.platform == 'win32':
878 893 statfiles = _statfiles_clustered
879 894 else:
880 895 statfiles = _statfiles
881 896
882 897 getuser_fallback = None
883 898
884 899 def getuser():
885 900 '''return name of current user'''
886 901 try:
887 902 return getpass.getuser()
888 903 except ImportError:
889 904 # import of pwd will fail on windows - try fallback
890 905 if getuser_fallback:
891 906 return getuser_fallback()
892 907 # raised if win32api not available
893 908 raise Abort(_('user name not available - set USERNAME '
894 909 'environment variable'))
895 910
896 911 def username(uid=None):
897 912 """Return the name of the user with the given uid.
898 913
899 914 If uid is None, return the name of the current user."""
900 915 try:
901 916 import pwd
902 917 if uid is None:
903 918 uid = os.getuid()
904 919 try:
905 920 return pwd.getpwuid(uid)[0]
906 921 except KeyError:
907 922 return str(uid)
908 923 except ImportError:
909 924 return None
910 925
911 926 def groupname(gid=None):
912 927 """Return the name of the group with the given gid.
913 928
914 929 If gid is None, return the name of the current group."""
915 930 try:
916 931 import grp
917 932 if gid is None:
918 933 gid = os.getgid()
919 934 try:
920 935 return grp.getgrgid(gid)[0]
921 936 except KeyError:
922 937 return str(gid)
923 938 except ImportError:
924 939 return None
925 940
926 941 # File system features
927 942
928 943 def checkcase(path):
929 944 """
930 945 Check whether the given path is on a case-sensitive filesystem
931 946
932 947 Requires a path (like /foo/.hg) ending with a foldable final
933 948 directory component.
934 949 """
935 950 s1 = os.stat(path)
936 951 d, b = os.path.split(path)
937 952 p2 = os.path.join(d, b.upper())
938 953 if path == p2:
939 954 p2 = os.path.join(d, b.lower())
940 955 try:
941 956 s2 = os.stat(p2)
942 957 if s2 == s1:
943 958 return False
944 959 return True
945 960 except:
946 961 return True
947 962
948 963 _fspathcache = {}
949 964 def fspath(name, root):
950 965 '''Get name in the case stored in the filesystem
951 966
952 967 The name is either relative to root, or it is an absolute path starting
953 968 with root. Note that this function is unnecessary, and should not be
954 969 called, for case-sensitive filesystems (simply because it's expensive).
955 970 '''
956 971 # If name is absolute, make it relative
957 972 if name.lower().startswith(root.lower()):
958 973 l = len(root)
959 974 if name[l] == os.sep or name[l] == os.altsep:
960 975 l = l + 1
961 976 name = name[l:]
962 977
963 978 if not os.path.exists(os.path.join(root, name)):
964 979 return None
965 980
966 981 seps = os.sep
967 982 if os.altsep:
968 983 seps = seps + os.altsep
969 984 # Protect backslashes. This gets silly very quickly.
970 985 seps.replace('\\','\\\\')
971 986 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
972 987 dir = os.path.normcase(os.path.normpath(root))
973 988 result = []
974 989 for part, sep in pattern.findall(name):
975 990 if sep:
976 991 result.append(sep)
977 992 continue
978 993
979 994 if dir not in _fspathcache:
980 995 _fspathcache[dir] = os.listdir(dir)
981 996 contents = _fspathcache[dir]
982 997
983 998 lpart = part.lower()
984 999 for n in contents:
985 1000 if n.lower() == lpart:
986 1001 result.append(n)
987 1002 break
988 1003 else:
989 1004 # Cannot happen, as the file exists!
990 1005 result.append(part)
991 1006 dir = os.path.join(dir, lpart)
992 1007
993 1008 return ''.join(result)
994 1009
995 1010 def checkexec(path):
996 1011 """
997 1012 Check whether the given path is on a filesystem with UNIX-like exec flags
998 1013
999 1014 Requires a directory (like /foo/.hg)
1000 1015 """
1001 1016
1002 1017 # VFAT on some Linux versions can flip mode but it doesn't persist
1003 1018 # a FS remount. Frequently we can detect it if files are created
1004 1019 # with exec bit on.
1005 1020
1006 1021 try:
1007 1022 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
1008 1023 fh, fn = tempfile.mkstemp("", "", path)
1009 1024 try:
1010 1025 os.close(fh)
1011 1026 m = os.stat(fn).st_mode & 0777
1012 1027 new_file_has_exec = m & EXECFLAGS
1013 1028 os.chmod(fn, m ^ EXECFLAGS)
1014 1029 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
1015 1030 finally:
1016 1031 os.unlink(fn)
1017 1032 except (IOError, OSError):
1018 1033 # we don't care, the user probably won't be able to commit anyway
1019 1034 return False
1020 1035 return not (new_file_has_exec or exec_flags_cannot_flip)
1021 1036
1022 1037 def checklink(path):
1023 1038 """check whether the given path is on a symlink-capable filesystem"""
1024 1039 # mktemp is not racy because symlink creation will fail if the
1025 1040 # file already exists
1026 1041 name = tempfile.mktemp(dir=path)
1027 1042 try:
1028 1043 os.symlink(".", name)
1029 1044 os.unlink(name)
1030 1045 return True
1031 1046 except (OSError, AttributeError):
1032 1047 return False
1033 1048
1034 1049 _umask = os.umask(0)
1035 1050 os.umask(_umask)
1036 1051
1037 1052 def needbinarypatch():
1038 1053 """return True if patches should be applied in binary mode by default."""
1039 1054 return os.name == 'nt'
1040 1055
1041 1056 def endswithsep(path):
1042 1057 '''Check path ends with os.sep or os.altsep.'''
1043 1058 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
1044 1059
1045 1060 def splitpath(path):
1046 1061 '''Split path by os.sep.
1047 1062 Note that this function does not use os.altsep because this is
1048 1063 an alternative of simple "xxx.split(os.sep)".
1049 1064 It is recommended to use os.path.normpath() before using this
1050 1065 function if need.'''
1051 1066 return path.split(os.sep)
1052 1067
1053 1068 def gui():
1054 1069 '''Are we running in a GUI?'''
1055 1070 return os.name == "nt" or os.name == "mac" or os.environ.get("DISPLAY")
1056 1071
1057 1072 def lookup_reg(key, name=None, scope=None):
1058 1073 return None
1059 1074
1060 1075 # Platform specific variants
1061 1076 if os.name == 'nt':
1062 1077 import msvcrt
1063 1078 nulldev = 'NUL:'
1064 1079
1065 1080 class winstdout:
1066 1081 '''stdout on windows misbehaves if sent through a pipe'''
1067 1082
1068 1083 def __init__(self, fp):
1069 1084 self.fp = fp
1070 1085
1071 1086 def __getattr__(self, key):
1072 1087 return getattr(self.fp, key)
1073 1088
1074 1089 def close(self):
1075 1090 try:
1076 1091 self.fp.close()
1077 1092 except: pass
1078 1093
1079 1094 def write(self, s):
1080 1095 try:
1081 1096 # This is workaround for "Not enough space" error on
1082 1097 # writing large size of data to console.
1083 1098 limit = 16000
1084 1099 l = len(s)
1085 1100 start = 0
1086 1101 while start < l:
1087 1102 end = start + limit
1088 1103 self.fp.write(s[start:end])
1089 1104 start = end
1090 1105 except IOError, inst:
1091 1106 if inst.errno != 0: raise
1092 1107 self.close()
1093 1108 raise IOError(errno.EPIPE, 'Broken pipe')
1094 1109
1095 1110 def flush(self):
1096 1111 try:
1097 1112 return self.fp.flush()
1098 1113 except IOError, inst:
1099 1114 if inst.errno != errno.EINVAL: raise
1100 1115 self.close()
1101 1116 raise IOError(errno.EPIPE, 'Broken pipe')
1102 1117
1103 1118 sys.stdout = winstdout(sys.stdout)
1104 1119
1105 1120 def _is_win_9x():
1106 1121 '''return true if run on windows 95, 98 or me.'''
1107 1122 try:
1108 1123 return sys.getwindowsversion()[3] == 1
1109 1124 except AttributeError:
1110 1125 return 'command' in os.environ.get('comspec', '')
1111 1126
1112 1127 def openhardlinks():
1113 1128 return not _is_win_9x and "win32api" in locals()
1114 1129
1115 1130 def system_rcpath():
1116 1131 try:
1117 1132 return system_rcpath_win32()
1118 1133 except:
1119 1134 return [r'c:\mercurial\mercurial.ini']
1120 1135
1121 1136 def user_rcpath():
1122 1137 '''return os-specific hgrc search path to the user dir'''
1123 1138 try:
1124 1139 path = user_rcpath_win32()
1125 1140 except:
1126 1141 home = os.path.expanduser('~')
1127 1142 path = [os.path.join(home, 'mercurial.ini'),
1128 1143 os.path.join(home, '.hgrc')]
1129 1144 userprofile = os.environ.get('USERPROFILE')
1130 1145 if userprofile:
1131 1146 path.append(os.path.join(userprofile, 'mercurial.ini'))
1132 1147 path.append(os.path.join(userprofile, '.hgrc'))
1133 1148 return path
1134 1149
1135 1150 def parse_patch_output(output_line):
1136 1151 """parses the output produced by patch and returns the file name"""
1137 1152 pf = output_line[14:]
1138 1153 if pf[0] == '`':
1139 1154 pf = pf[1:-1] # Remove the quotes
1140 1155 return pf
1141 1156
1142 1157 def sshargs(sshcmd, host, user, port):
1143 1158 '''Build argument list for ssh or Plink'''
1144 1159 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
1145 1160 args = user and ("%s@%s" % (user, host)) or host
1146 1161 return port and ("%s %s %s" % (args, pflag, port)) or args
1147 1162
1148 1163 def testpid(pid):
1149 1164 '''return False if pid dead, True if running or not known'''
1150 1165 return True
1151 1166
1152 1167 def set_flags(f, l, x):
1153 1168 pass
1154 1169
1155 1170 def set_binary(fd):
1156 1171 # When run without console, pipes may expose invalid
1157 1172 # fileno(), usually set to -1.
1158 1173 if hasattr(fd, 'fileno') and fd.fileno() >= 0:
1159 1174 msvcrt.setmode(fd.fileno(), os.O_BINARY)
1160 1175
1161 1176 def pconvert(path):
1162 1177 return '/'.join(splitpath(path))
1163 1178
1164 1179 def localpath(path):
1165 1180 return path.replace('/', '\\')
1166 1181
1167 1182 def normpath(path):
1168 1183 return pconvert(os.path.normpath(path))
1169 1184
1170 1185 makelock = _makelock_file
1171 1186 readlock = _readlock_file
1172 1187
1173 1188 def samestat(s1, s2):
1174 1189 return False
1175 1190
1176 1191 # A sequence of backslashes is special iff it precedes a double quote:
1177 1192 # - if there's an even number of backslashes, the double quote is not
1178 1193 # quoted (i.e. it ends the quoted region)
1179 1194 # - if there's an odd number of backslashes, the double quote is quoted
1180 1195 # - in both cases, every pair of backslashes is unquoted into a single
1181 1196 # backslash
1182 1197 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
1183 1198 # So, to quote a string, we must surround it in double quotes, double
1184 1199 # the number of backslashes that preceed double quotes and add another
1185 1200 # backslash before every double quote (being careful with the double
1186 1201 # quote we've appended to the end)
1187 1202 _quotere = None
1188 1203 def shellquote(s):
1189 1204 global _quotere
1190 1205 if _quotere is None:
1191 1206 _quotere = re.compile(r'(\\*)("|\\$)')
1192 1207 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
1193 1208
1194 1209 def quotecommand(cmd):
1195 1210 """Build a command string suitable for os.popen* calls."""
1196 1211 # The extra quotes are needed because popen* runs the command
1197 1212 # through the current COMSPEC. cmd.exe suppress enclosing quotes.
1198 1213 return '"' + cmd + '"'
1199 1214
1200 1215 def popen(command, mode='r'):
1201 1216 # Work around "popen spawned process may not write to stdout
1202 1217 # under windows"
1203 1218 # http://bugs.python.org/issue1366
1204 1219 command += " 2> %s" % nulldev
1205 1220 return os.popen(quotecommand(command), mode)
1206 1221
1207 1222 def explain_exit(code):
1208 1223 return _("exited with status %d") % code, code
1209 1224
1210 1225 # if you change this stub into a real check, please try to implement the
1211 1226 # username and groupname functions above, too.
1212 1227 def isowner(fp, st=None):
1213 1228 return True
1214 1229
1215 1230 def find_in_path(name, path, default=None):
1216 1231 '''find name in search path. path can be string (will be split
1217 1232 with os.pathsep), or iterable thing that returns strings. if name
1218 1233 found, return path to name. else return default. name is looked up
1219 1234 using cmd.exe rules, using PATHEXT.'''
1220 1235 if isinstance(path, str):
1221 1236 path = path.split(os.pathsep)
1222 1237
1223 1238 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
1224 1239 pathext = pathext.lower().split(os.pathsep)
1225 1240 isexec = os.path.splitext(name)[1].lower() in pathext
1226 1241
1227 1242 for p in path:
1228 1243 p_name = os.path.join(p, name)
1229 1244
1230 1245 if isexec and os.path.exists(p_name):
1231 1246 return p_name
1232 1247
1233 1248 for ext in pathext:
1234 1249 p_name_ext = p_name + ext
1235 1250 if os.path.exists(p_name_ext):
1236 1251 return p_name_ext
1237 1252 return default
1238 1253
1239 1254 def set_signal_handler():
1240 1255 try:
1241 1256 set_signal_handler_win32()
1242 1257 except NameError:
1243 1258 pass
1244 1259
1245 1260 try:
1246 1261 # override functions with win32 versions if possible
1247 1262 from util_win32 import *
1248 1263 if not _is_win_9x():
1249 1264 posixfile = posixfile_nt
1250 1265 except ImportError:
1251 1266 pass
1252 1267
1253 1268 else:
1254 1269 nulldev = '/dev/null'
1255 1270
1256 1271 def rcfiles(path):
1257 1272 rcs = [os.path.join(path, 'hgrc')]
1258 1273 rcdir = os.path.join(path, 'hgrc.d')
1259 1274 try:
1260 1275 rcs.extend([os.path.join(rcdir, f)
1261 1276 for f, kind in osutil.listdir(rcdir)
1262 1277 if f.endswith(".rc")])
1263 1278 except OSError:
1264 1279 pass
1265 1280 return rcs
1266 1281
1267 1282 def system_rcpath():
1268 1283 path = []
1269 1284 # old mod_python does not set sys.argv
1270 1285 if len(getattr(sys, 'argv', [])) > 0:
1271 1286 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
1272 1287 '/../etc/mercurial'))
1273 1288 path.extend(rcfiles('/etc/mercurial'))
1274 1289 return path
1275 1290
1276 1291 def user_rcpath():
1277 1292 return [os.path.expanduser('~/.hgrc')]
1278 1293
1279 1294 def parse_patch_output(output_line):
1280 1295 """parses the output produced by patch and returns the file name"""
1281 1296 pf = output_line[14:]
1282 1297 if os.sys.platform == 'OpenVMS':
1283 1298 if pf[0] == '`':
1284 1299 pf = pf[1:-1] # Remove the quotes
1285 1300 else:
1286 1301 if pf.startswith("'") and pf.endswith("'") and " " in pf:
1287 1302 pf = pf[1:-1] # Remove the quotes
1288 1303 return pf
1289 1304
1290 1305 def sshargs(sshcmd, host, user, port):
1291 1306 '''Build argument list for ssh'''
1292 1307 args = user and ("%s@%s" % (user, host)) or host
1293 1308 return port and ("%s -p %s" % (args, port)) or args
1294 1309
1295 1310 def is_exec(f):
1296 1311 """check whether a file is executable"""
1297 1312 return (os.lstat(f).st_mode & 0100 != 0)
1298 1313
1299 1314 def set_flags(f, l, x):
1300 1315 s = os.lstat(f).st_mode
1301 1316 if l:
1302 1317 if not stat.S_ISLNK(s):
1303 1318 # switch file to link
1304 1319 data = file(f).read()
1305 1320 os.unlink(f)
1306 1321 try:
1307 1322 os.symlink(data, f)
1308 1323 except:
1309 1324 # failed to make a link, rewrite file
1310 1325 file(f, "w").write(data)
1311 1326 # no chmod needed at this point
1312 1327 return
1313 1328 if stat.S_ISLNK(s):
1314 1329 # switch link to file
1315 1330 data = os.readlink(f)
1316 1331 os.unlink(f)
1317 1332 file(f, "w").write(data)
1318 1333 s = 0666 & ~_umask # avoid restatting for chmod
1319 1334
1320 1335 sx = s & 0100
1321 1336 if x and not sx:
1322 1337 # Turn on +x for every +r bit when making a file executable
1323 1338 # and obey umask.
1324 1339 os.chmod(f, s | (s & 0444) >> 2 & ~_umask)
1325 1340 elif not x and sx:
1326 1341 # Turn off all +x bits
1327 1342 os.chmod(f, s & 0666)
1328 1343
1329 1344 def set_binary(fd):
1330 1345 pass
1331 1346
1332 1347 def pconvert(path):
1333 1348 return path
1334 1349
1335 1350 def localpath(path):
1336 1351 return path
1337 1352
1338 1353 normpath = os.path.normpath
1339 1354 samestat = os.path.samestat
1340 1355
1341 1356 def makelock(info, pathname):
1342 1357 try:
1343 1358 os.symlink(info, pathname)
1344 1359 except OSError, why:
1345 1360 if why.errno == errno.EEXIST:
1346 1361 raise
1347 1362 else:
1348 1363 _makelock_file(info, pathname)
1349 1364
1350 1365 def readlock(pathname):
1351 1366 try:
1352 1367 return os.readlink(pathname)
1353 1368 except OSError, why:
1354 1369 if why.errno in (errno.EINVAL, errno.ENOSYS):
1355 1370 return _readlock_file(pathname)
1356 1371 else:
1357 1372 raise
1358 1373
1359 1374 def shellquote(s):
1360 1375 if os.sys.platform == 'OpenVMS':
1361 1376 return '"%s"' % s
1362 1377 else:
1363 1378 return "'%s'" % s.replace("'", "'\\''")
1364 1379
1365 1380 def quotecommand(cmd):
1366 1381 return cmd
1367 1382
1368 1383 def popen(command, mode='r'):
1369 1384 return os.popen(command, mode)
1370 1385
1371 1386 def testpid(pid):
1372 1387 '''return False if pid dead, True if running or not sure'''
1373 1388 if os.sys.platform == 'OpenVMS':
1374 1389 return True
1375 1390 try:
1376 1391 os.kill(pid, 0)
1377 1392 return True
1378 1393 except OSError, inst:
1379 1394 return inst.errno != errno.ESRCH
1380 1395
1381 1396 def explain_exit(code):
1382 1397 """return a 2-tuple (desc, code) describing a process's status"""
1383 1398 if os.WIFEXITED(code):
1384 1399 val = os.WEXITSTATUS(code)
1385 1400 return _("exited with status %d") % val, val
1386 1401 elif os.WIFSIGNALED(code):
1387 1402 val = os.WTERMSIG(code)
1388 1403 return _("killed by signal %d") % val, val
1389 1404 elif os.WIFSTOPPED(code):
1390 1405 val = os.WSTOPSIG(code)
1391 1406 return _("stopped by signal %d") % val, val
1392 1407 raise ValueError(_("invalid exit code"))
1393 1408
1394 1409 def isowner(fp, st=None):
1395 1410 """Return True if the file object f belongs to the current user.
1396 1411
1397 1412 The return value of a util.fstat(f) may be passed as the st argument.
1398 1413 """
1399 1414 if st is None:
1400 1415 st = fstat(fp)
1401 1416 return st.st_uid == os.getuid()
1402 1417
1403 1418 def find_in_path(name, path, default=None):
1404 1419 '''find name in search path. path can be string (will be split
1405 1420 with os.pathsep), or iterable thing that returns strings. if name
1406 1421 found, return path to name. else return default.'''
1407 1422 if isinstance(path, str):
1408 1423 path = path.split(os.pathsep)
1409 1424 for p in path:
1410 1425 p_name = os.path.join(p, name)
1411 1426 if os.path.exists(p_name):
1412 1427 return p_name
1413 1428 return default
1414 1429
1415 1430 def set_signal_handler():
1416 1431 pass
1417 1432
1418 1433 def find_exe(name, default=None):
1419 1434 '''find path of an executable.
1420 1435 if name contains a path component, return it as is. otherwise,
1421 1436 use normal executable search path.'''
1422 1437
1423 1438 if os.sep in name or sys.platform == 'OpenVMS':
1424 1439 # don't check the executable bit. if the file isn't
1425 1440 # executable, whoever tries to actually run it will give a
1426 1441 # much more useful error message.
1427 1442 return name
1428 1443 return find_in_path(name, os.environ.get('PATH', ''), default=default)
1429 1444
1430 1445 def mktempcopy(name, emptyok=False, createmode=None):
1431 1446 """Create a temporary file with the same contents from name
1432 1447
1433 1448 The permission bits are copied from the original file.
1434 1449
1435 1450 If the temporary file is going to be truncated immediately, you
1436 1451 can use emptyok=True as an optimization.
1437 1452
1438 1453 Returns the name of the temporary file.
1439 1454 """
1440 1455 d, fn = os.path.split(name)
1441 1456 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1442 1457 os.close(fd)
1443 1458 # Temporary files are created with mode 0600, which is usually not
1444 1459 # what we want. If the original file already exists, just copy
1445 1460 # its mode. Otherwise, manually obey umask.
1446 1461 try:
1447 1462 st_mode = os.lstat(name).st_mode & 0777
1448 1463 except OSError, inst:
1449 1464 if inst.errno != errno.ENOENT:
1450 1465 raise
1451 1466 st_mode = createmode
1452 1467 if st_mode is None:
1453 1468 st_mode = ~_umask
1454 1469 st_mode &= 0666
1455 1470 os.chmod(temp, st_mode)
1456 1471 if emptyok:
1457 1472 return temp
1458 1473 try:
1459 1474 try:
1460 1475 ifp = posixfile(name, "rb")
1461 1476 except IOError, inst:
1462 1477 if inst.errno == errno.ENOENT:
1463 1478 return temp
1464 1479 if not getattr(inst, 'filename', None):
1465 1480 inst.filename = name
1466 1481 raise
1467 1482 ofp = posixfile(temp, "wb")
1468 1483 for chunk in filechunkiter(ifp):
1469 1484 ofp.write(chunk)
1470 1485 ifp.close()
1471 1486 ofp.close()
1472 1487 except:
1473 1488 try: os.unlink(temp)
1474 1489 except: pass
1475 1490 raise
1476 1491 return temp
1477 1492
1478 1493 class atomictempfile(posixfile):
1479 1494 """file-like object that atomically updates a file
1480 1495
1481 1496 All writes will be redirected to a temporary copy of the original
1482 1497 file. When rename is called, the copy is renamed to the original
1483 1498 name, making the changes visible.
1484 1499 """
1485 1500 def __init__(self, name, mode, createmode):
1486 1501 self.__name = name
1487 1502 self.temp = mktempcopy(name, emptyok=('w' in mode),
1488 1503 createmode=createmode)
1489 1504 posixfile.__init__(self, self.temp, mode)
1490 1505
1491 1506 def rename(self):
1492 1507 if not self.closed:
1493 1508 posixfile.close(self)
1494 1509 rename(self.temp, localpath(self.__name))
1495 1510
1496 1511 def __del__(self):
1497 1512 if not self.closed:
1498 1513 try:
1499 1514 os.unlink(self.temp)
1500 1515 except: pass
1501 1516 posixfile.close(self)
1502 1517
1503 1518 def makedirs(name, mode=None):
1504 1519 """recursive directory creation with parent mode inheritance"""
1505 1520 try:
1506 1521 os.mkdir(name)
1507 1522 if mode is not None:
1508 1523 os.chmod(name, mode)
1509 1524 return
1510 1525 except OSError, err:
1511 1526 if err.errno == errno.EEXIST:
1512 1527 return
1513 1528 if err.errno != errno.ENOENT:
1514 1529 raise
1515 1530 parent = os.path.abspath(os.path.dirname(name))
1516 1531 makedirs(parent, mode)
1517 1532 makedirs(name, mode)
1518 1533
1519 1534 class opener(object):
1520 1535 """Open files relative to a base directory
1521 1536
1522 1537 This class is used to hide the details of COW semantics and
1523 1538 remote file access from higher level code.
1524 1539 """
1525 1540 def __init__(self, base, audit=True):
1526 1541 self.base = base
1527 1542 if audit:
1528 1543 self.audit_path = path_auditor(base)
1529 1544 else:
1530 1545 self.audit_path = always
1531 1546 self.createmode = None
1532 1547
1533 1548 def __getattr__(self, name):
1534 1549 if name == '_can_symlink':
1535 1550 self._can_symlink = checklink(self.base)
1536 1551 return self._can_symlink
1537 1552 raise AttributeError(name)
1538 1553
1539 1554 def _fixfilemode(self, name):
1540 1555 if self.createmode is None:
1541 1556 return
1542 1557 os.chmod(name, self.createmode & 0666)
1543 1558
1544 1559 def __call__(self, path, mode="r", text=False, atomictemp=False):
1545 1560 self.audit_path(path)
1546 1561 f = os.path.join(self.base, path)
1547 1562
1548 1563 if not text and "b" not in mode:
1549 1564 mode += "b" # for that other OS
1550 1565
1551 1566 nlink = -1
1552 1567 if mode not in ("r", "rb"):
1553 1568 try:
1554 1569 nlink = nlinks(f)
1555 1570 except OSError:
1556 1571 nlink = 0
1557 1572 d = os.path.dirname(f)
1558 1573 if not os.path.isdir(d):
1559 1574 makedirs(d, self.createmode)
1560 1575 if atomictemp:
1561 1576 return atomictempfile(f, mode, self.createmode)
1562 1577 if nlink > 1:
1563 1578 rename(mktempcopy(f), f)
1564 1579 fp = posixfile(f, mode)
1565 1580 if nlink == 0:
1566 1581 self._fixfilemode(f)
1567 1582 return fp
1568 1583
1569 1584 def symlink(self, src, dst):
1570 1585 self.audit_path(dst)
1571 1586 linkname = os.path.join(self.base, dst)
1572 1587 try:
1573 1588 os.unlink(linkname)
1574 1589 except OSError:
1575 1590 pass
1576 1591
1577 1592 dirname = os.path.dirname(linkname)
1578 1593 if not os.path.exists(dirname):
1579 1594 makedirs(dirname, self.createmode)
1580 1595
1581 1596 if self._can_symlink:
1582 1597 try:
1583 1598 os.symlink(src, linkname)
1584 1599 except OSError, err:
1585 1600 raise OSError(err.errno, _('could not symlink to %r: %s') %
1586 1601 (src, err.strerror), linkname)
1587 1602 else:
1588 1603 f = self(dst, "w")
1589 1604 f.write(src)
1590 1605 f.close()
1591 1606 self._fixfilemode(dst)
1592 1607
1593 1608 class chunkbuffer(object):
1594 1609 """Allow arbitrary sized chunks of data to be efficiently read from an
1595 1610 iterator over chunks of arbitrary size."""
1596 1611
1597 1612 def __init__(self, in_iter):
1598 1613 """in_iter is the iterator that's iterating over the input chunks.
1599 1614 targetsize is how big a buffer to try to maintain."""
1600 1615 self.iter = iter(in_iter)
1601 1616 self.buf = ''
1602 1617 self.targetsize = 2**16
1603 1618
1604 1619 def read(self, l):
1605 1620 """Read L bytes of data from the iterator of chunks of data.
1606 1621 Returns less than L bytes if the iterator runs dry."""
1607 1622 if l > len(self.buf) and self.iter:
1608 1623 # Clamp to a multiple of self.targetsize
1609 1624 targetsize = max(l, self.targetsize)
1610 1625 collector = cStringIO.StringIO()
1611 1626 collector.write(self.buf)
1612 1627 collected = len(self.buf)
1613 1628 for chunk in self.iter:
1614 1629 collector.write(chunk)
1615 1630 collected += len(chunk)
1616 1631 if collected >= targetsize:
1617 1632 break
1618 1633 if collected < targetsize:
1619 1634 self.iter = False
1620 1635 self.buf = collector.getvalue()
1621 1636 if len(self.buf) == l:
1622 1637 s, self.buf = str(self.buf), ''
1623 1638 else:
1624 1639 s, self.buf = self.buf[:l], buffer(self.buf, l)
1625 1640 return s
1626 1641
1627 1642 def filechunkiter(f, size=65536, limit=None):
1628 1643 """Create a generator that produces the data in the file size
1629 1644 (default 65536) bytes at a time, up to optional limit (default is
1630 1645 to read all data). Chunks may be less than size bytes if the
1631 1646 chunk is the last chunk in the file, or the file is a socket or
1632 1647 some other type of file that sometimes reads less data than is
1633 1648 requested."""
1634 1649 assert size >= 0
1635 1650 assert limit is None or limit >= 0
1636 1651 while True:
1637 1652 if limit is None: nbytes = size
1638 1653 else: nbytes = min(limit, size)
1639 1654 s = nbytes and f.read(nbytes)
1640 1655 if not s: break
1641 1656 if limit: limit -= len(s)
1642 1657 yield s
1643 1658
1644 1659 def makedate():
1645 1660 lt = time.localtime()
1646 1661 if lt[8] == 1 and time.daylight:
1647 1662 tz = time.altzone
1648 1663 else:
1649 1664 tz = time.timezone
1650 1665 return time.mktime(lt), tz
1651 1666
1652 1667 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1653 1668 """represent a (unixtime, offset) tuple as a localized time.
1654 1669 unixtime is seconds since the epoch, and offset is the time zone's
1655 1670 number of seconds away from UTC. if timezone is false, do not
1656 1671 append time zone to string."""
1657 1672 t, tz = date or makedate()
1658 1673 if "%1" in format or "%2" in format:
1659 1674 sign = (tz > 0) and "-" or "+"
1660 1675 minutes = abs(tz) / 60
1661 1676 format = format.replace("%1", "%c%02d" % (sign, minutes / 60))
1662 1677 format = format.replace("%2", "%02d" % (minutes % 60))
1663 1678 s = time.strftime(format, time.gmtime(float(t) - tz))
1664 1679 return s
1665 1680
1666 1681 def shortdate(date=None):
1667 1682 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1668 1683 return datestr(date, format='%Y-%m-%d')
1669 1684
1670 1685 def strdate(string, format, defaults=[]):
1671 1686 """parse a localized time string and return a (unixtime, offset) tuple.
1672 1687 if the string cannot be parsed, ValueError is raised."""
1673 1688 def timezone(string):
1674 1689 tz = string.split()[-1]
1675 1690 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1676 1691 sign = (tz[0] == "+") and 1 or -1
1677 1692 hours = int(tz[1:3])
1678 1693 minutes = int(tz[3:5])
1679 1694 return -sign * (hours * 60 + minutes) * 60
1680 1695 if tz == "GMT" or tz == "UTC":
1681 1696 return 0
1682 1697 return None
1683 1698
1684 1699 # NOTE: unixtime = localunixtime + offset
1685 1700 offset, date = timezone(string), string
1686 1701 if offset != None:
1687 1702 date = " ".join(string.split()[:-1])
1688 1703
1689 1704 # add missing elements from defaults
1690 1705 for part in defaults:
1691 1706 found = [True for p in part if ("%"+p) in format]
1692 1707 if not found:
1693 1708 date += "@" + defaults[part]
1694 1709 format += "@%" + part[0]
1695 1710
1696 1711 timetuple = time.strptime(date, format)
1697 1712 localunixtime = int(calendar.timegm(timetuple))
1698 1713 if offset is None:
1699 1714 # local timezone
1700 1715 unixtime = int(time.mktime(timetuple))
1701 1716 offset = unixtime - localunixtime
1702 1717 else:
1703 1718 unixtime = localunixtime + offset
1704 1719 return unixtime, offset
1705 1720
1706 1721 def parsedate(date, formats=None, defaults=None):
1707 1722 """parse a localized date/time string and return a (unixtime, offset) tuple.
1708 1723
1709 1724 The date may be a "unixtime offset" string or in one of the specified
1710 1725 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1711 1726 """
1712 1727 if not date:
1713 1728 return 0, 0
1714 1729 if isinstance(date, tuple) and len(date) == 2:
1715 1730 return date
1716 1731 if not formats:
1717 1732 formats = defaultdateformats
1718 1733 date = date.strip()
1719 1734 try:
1720 1735 when, offset = map(int, date.split(' '))
1721 1736 except ValueError:
1722 1737 # fill out defaults
1723 1738 if not defaults:
1724 1739 defaults = {}
1725 1740 now = makedate()
1726 1741 for part in "d mb yY HI M S".split():
1727 1742 if part not in defaults:
1728 1743 if part[0] in "HMS":
1729 1744 defaults[part] = "00"
1730 1745 else:
1731 1746 defaults[part] = datestr(now, "%" + part[0])
1732 1747
1733 1748 for format in formats:
1734 1749 try:
1735 1750 when, offset = strdate(date, format, defaults)
1736 1751 except (ValueError, OverflowError):
1737 1752 pass
1738 1753 else:
1739 1754 break
1740 1755 else:
1741 1756 raise Abort(_('invalid date: %r ') % date)
1742 1757 # validate explicit (probably user-specified) date and
1743 1758 # time zone offset. values must fit in signed 32 bits for
1744 1759 # current 32-bit linux runtimes. timezones go from UTC-12
1745 1760 # to UTC+14
1746 1761 if abs(when) > 0x7fffffff:
1747 1762 raise Abort(_('date exceeds 32 bits: %d') % when)
1748 1763 if offset < -50400 or offset > 43200:
1749 1764 raise Abort(_('impossible time zone offset: %d') % offset)
1750 1765 return when, offset
1751 1766
1752 1767 def matchdate(date):
1753 1768 """Return a function that matches a given date match specifier
1754 1769
1755 1770 Formats include:
1756 1771
1757 1772 '{date}' match a given date to the accuracy provided
1758 1773
1759 1774 '<{date}' on or before a given date
1760 1775
1761 1776 '>{date}' on or after a given date
1762 1777
1763 1778 """
1764 1779
1765 1780 def lower(date):
1766 1781 d = dict(mb="1", d="1")
1767 1782 return parsedate(date, extendeddateformats, d)[0]
1768 1783
1769 1784 def upper(date):
1770 1785 d = dict(mb="12", HI="23", M="59", S="59")
1771 1786 for days in "31 30 29".split():
1772 1787 try:
1773 1788 d["d"] = days
1774 1789 return parsedate(date, extendeddateformats, d)[0]
1775 1790 except:
1776 1791 pass
1777 1792 d["d"] = "28"
1778 1793 return parsedate(date, extendeddateformats, d)[0]
1779 1794
1780 1795 if date[0] == "<":
1781 1796 when = upper(date[1:])
1782 1797 return lambda x: x <= when
1783 1798 elif date[0] == ">":
1784 1799 when = lower(date[1:])
1785 1800 return lambda x: x >= when
1786 1801 elif date[0] == "-":
1787 1802 try:
1788 1803 days = int(date[1:])
1789 1804 except ValueError:
1790 1805 raise Abort(_("invalid day spec: %s") % date[1:])
1791 1806 when = makedate()[0] - days * 3600 * 24
1792 1807 return lambda x: x >= when
1793 1808 elif " to " in date:
1794 1809 a, b = date.split(" to ")
1795 1810 start, stop = lower(a), upper(b)
1796 1811 return lambda x: x >= start and x <= stop
1797 1812 else:
1798 1813 start, stop = lower(date), upper(date)
1799 1814 return lambda x: x >= start and x <= stop
1800 1815
1801 1816 def shortuser(user):
1802 1817 """Return a short representation of a user name or email address."""
1803 1818 f = user.find('@')
1804 1819 if f >= 0:
1805 1820 user = user[:f]
1806 1821 f = user.find('<')
1807 1822 if f >= 0:
1808 1823 user = user[f+1:]
1809 1824 f = user.find(' ')
1810 1825 if f >= 0:
1811 1826 user = user[:f]
1812 1827 f = user.find('.')
1813 1828 if f >= 0:
1814 1829 user = user[:f]
1815 1830 return user
1816 1831
1817 1832 def email(author):
1818 1833 '''get email of author.'''
1819 1834 r = author.find('>')
1820 1835 if r == -1: r = None
1821 1836 return author[author.find('<')+1:r]
1822 1837
1823 1838 def ellipsis(text, maxlength=400):
1824 1839 """Trim string to at most maxlength (default: 400) characters."""
1825 1840 if len(text) <= maxlength:
1826 1841 return text
1827 1842 else:
1828 1843 return "%s..." % (text[:maxlength-3])
1829 1844
1830 1845 def walkrepos(path, followsym=False, seen_dirs=None):
1831 1846 '''yield every hg repository under path, recursively.'''
1832 1847 def errhandler(err):
1833 1848 if err.filename == path:
1834 1849 raise err
1835 1850 if followsym and hasattr(os.path, 'samestat'):
1836 1851 def _add_dir_if_not_there(dirlst, dirname):
1837 1852 match = False
1838 1853 samestat = os.path.samestat
1839 1854 dirstat = os.stat(dirname)
1840 1855 for lstdirstat in dirlst:
1841 1856 if samestat(dirstat, lstdirstat):
1842 1857 match = True
1843 1858 break
1844 1859 if not match:
1845 1860 dirlst.append(dirstat)
1846 1861 return not match
1847 1862 else:
1848 1863 followsym = False
1849 1864
1850 1865 if (seen_dirs is None) and followsym:
1851 1866 seen_dirs = []
1852 1867 _add_dir_if_not_there(seen_dirs, path)
1853 1868 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
1854 1869 if '.hg' in dirs:
1855 1870 dirs.remove('.hg') # don't recurse inside the .hg directory
1856 1871 yield root # found a repository
1857 1872 qroot = os.path.join(root, '.hg', 'patches')
1858 1873 if os.path.isdir(os.path.join(qroot, '.hg')):
1859 1874 yield qroot # we have a patch queue repo here
1860 1875 elif followsym:
1861 1876 newdirs = []
1862 1877 for d in dirs:
1863 1878 fname = os.path.join(root, d)
1864 1879 if _add_dir_if_not_there(seen_dirs, fname):
1865 1880 if os.path.islink(fname):
1866 1881 for hgname in walkrepos(fname, True, seen_dirs):
1867 1882 yield hgname
1868 1883 else:
1869 1884 newdirs.append(d)
1870 1885 dirs[:] = newdirs
1871 1886
1872 1887 _rcpath = None
1873 1888
1874 1889 def os_rcpath():
1875 1890 '''return default os-specific hgrc search path'''
1876 1891 path = system_rcpath()
1877 1892 path.extend(user_rcpath())
1878 1893 path = [os.path.normpath(f) for f in path]
1879 1894 return path
1880 1895
1881 1896 def rcpath():
1882 1897 '''return hgrc search path. if env var HGRCPATH is set, use it.
1883 1898 for each item in path, if directory, use files ending in .rc,
1884 1899 else use item.
1885 1900 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1886 1901 if no HGRCPATH, use default os-specific path.'''
1887 1902 global _rcpath
1888 1903 if _rcpath is None:
1889 1904 if 'HGRCPATH' in os.environ:
1890 1905 _rcpath = []
1891 1906 for p in os.environ['HGRCPATH'].split(os.pathsep):
1892 1907 if not p: continue
1893 1908 if os.path.isdir(p):
1894 1909 for f, kind in osutil.listdir(p):
1895 1910 if f.endswith('.rc'):
1896 1911 _rcpath.append(os.path.join(p, f))
1897 1912 else:
1898 1913 _rcpath.append(p)
1899 1914 else:
1900 1915 _rcpath = os_rcpath()
1901 1916 return _rcpath
1902 1917
1903 1918 def bytecount(nbytes):
1904 1919 '''return byte count formatted as readable string, with units'''
1905 1920
1906 1921 units = (
1907 1922 (100, 1<<30, _('%.0f GB')),
1908 1923 (10, 1<<30, _('%.1f GB')),
1909 1924 (1, 1<<30, _('%.2f GB')),
1910 1925 (100, 1<<20, _('%.0f MB')),
1911 1926 (10, 1<<20, _('%.1f MB')),
1912 1927 (1, 1<<20, _('%.2f MB')),
1913 1928 (100, 1<<10, _('%.0f KB')),
1914 1929 (10, 1<<10, _('%.1f KB')),
1915 1930 (1, 1<<10, _('%.2f KB')),
1916 1931 (1, 1, _('%.0f bytes')),
1917 1932 )
1918 1933
1919 1934 for multiplier, divisor, format in units:
1920 1935 if nbytes >= divisor * multiplier:
1921 1936 return format % (nbytes / float(divisor))
1922 1937 return units[-1][2] % nbytes
1923 1938
1924 1939 def drop_scheme(scheme, path):
1925 1940 sc = scheme + ':'
1926 1941 if path.startswith(sc):
1927 1942 path = path[len(sc):]
1928 1943 if path.startswith('//'):
1929 1944 path = path[2:]
1930 1945 return path
1931 1946
1932 1947 def uirepr(s):
1933 1948 # Avoid double backslash in Windows path repr()
1934 1949 return repr(s).replace('\\\\', '\\')
General Comments 0
You need to be logged in to leave comments. Login now