##// END OF EJS Templates
dispatch: support for $ escaping in shell-alias definition...
Roman Sokolov -
r13392:777cef34 default
parent child Browse files
Show More
@@ -1,648 +1,650
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 of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from i18n import _
9 9 import os, sys, atexit, signal, pdb, socket, errno, shlex, time, traceback, re
10 10 import util, commands, hg, fancyopts, extensions, hook, error
11 11 import cmdutil, encoding
12 12 import ui as uimod
13 13
14 14 def run():
15 15 "run the command in sys.argv"
16 16 sys.exit(dispatch(sys.argv[1:]))
17 17
18 18 def dispatch(args):
19 19 "run the command specified in args"
20 20 try:
21 21 u = uimod.ui()
22 22 if '--traceback' in args:
23 23 u.setconfig('ui', 'traceback', 'on')
24 24 except util.Abort, inst:
25 25 sys.stderr.write(_("abort: %s\n") % inst)
26 26 if inst.hint:
27 27 sys.stderr.write(_("(%s)\n") % inst.hint)
28 28 return -1
29 29 except error.ParseError, inst:
30 30 if len(inst.args) > 1:
31 31 sys.stderr.write(_("hg: parse error at %s: %s\n") %
32 32 (inst.args[1], inst.args[0]))
33 33 else:
34 34 sys.stderr.write(_("hg: parse error: %s\n") % inst.args[0])
35 35 return -1
36 36 return _runcatch(u, args)
37 37
38 38 def _runcatch(ui, args):
39 39 def catchterm(*args):
40 40 raise error.SignalInterrupt
41 41
42 42 try:
43 43 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
44 44 num = getattr(signal, name, None)
45 45 if num:
46 46 signal.signal(num, catchterm)
47 47 except ValueError:
48 48 pass # happens if called in a thread
49 49
50 50 try:
51 51 try:
52 52 # enter the debugger before command execution
53 53 if '--debugger' in args:
54 54 ui.warn(_("entering debugger - "
55 55 "type c to continue starting hg or h for help\n"))
56 56 pdb.set_trace()
57 57 try:
58 58 return _dispatch(ui, args)
59 59 finally:
60 60 ui.flush()
61 61 except:
62 62 # enter the debugger when we hit an exception
63 63 if '--debugger' in args:
64 64 traceback.print_exc()
65 65 pdb.post_mortem(sys.exc_info()[2])
66 66 ui.traceback()
67 67 raise
68 68
69 69 # Global exception handling, alphabetically
70 70 # Mercurial-specific first, followed by built-in and library exceptions
71 71 except error.AmbiguousCommand, inst:
72 72 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
73 73 (inst.args[0], " ".join(inst.args[1])))
74 74 except error.ParseError, inst:
75 75 if len(inst.args) > 1:
76 76 ui.warn(_("hg: parse error at %s: %s\n") %
77 77 (inst.args[1], inst.args[0]))
78 78 else:
79 79 ui.warn(_("hg: parse error: %s\n") % inst.args[0])
80 80 return -1
81 81 except error.LockHeld, inst:
82 82 if inst.errno == errno.ETIMEDOUT:
83 83 reason = _('timed out waiting for lock held by %s') % inst.locker
84 84 else:
85 85 reason = _('lock held by %s') % inst.locker
86 86 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
87 87 except error.LockUnavailable, inst:
88 88 ui.warn(_("abort: could not lock %s: %s\n") %
89 89 (inst.desc or inst.filename, inst.strerror))
90 90 except error.CommandError, inst:
91 91 if inst.args[0]:
92 92 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
93 93 commands.help_(ui, inst.args[0])
94 94 else:
95 95 ui.warn(_("hg: %s\n") % inst.args[1])
96 96 commands.help_(ui, 'shortlist')
97 97 except error.RepoError, inst:
98 98 ui.warn(_("abort: %s!\n") % inst)
99 99 except error.ResponseError, inst:
100 100 ui.warn(_("abort: %s") % inst.args[0])
101 101 if not isinstance(inst.args[1], basestring):
102 102 ui.warn(" %r\n" % (inst.args[1],))
103 103 elif not inst.args[1]:
104 104 ui.warn(_(" empty string\n"))
105 105 else:
106 106 ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
107 107 except error.RevlogError, inst:
108 108 ui.warn(_("abort: %s!\n") % inst)
109 109 except error.SignalInterrupt:
110 110 ui.warn(_("killed!\n"))
111 111 except error.UnknownCommand, inst:
112 112 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
113 113 try:
114 114 # check if the command is in a disabled extension
115 115 # (but don't check for extensions themselves)
116 116 commands.help_(ui, inst.args[0], unknowncmd=True)
117 117 except error.UnknownCommand:
118 118 commands.help_(ui, 'shortlist')
119 119 except util.Abort, inst:
120 120 ui.warn(_("abort: %s\n") % inst)
121 121 if inst.hint:
122 122 ui.warn(_("(%s)\n") % inst.hint)
123 123 except ImportError, inst:
124 124 ui.warn(_("abort: %s!\n") % inst)
125 125 m = str(inst).split()[-1]
126 126 if m in "mpatch bdiff".split():
127 127 ui.warn(_("(did you forget to compile extensions?)\n"))
128 128 elif m in "zlib".split():
129 129 ui.warn(_("(is your Python install correct?)\n"))
130 130 except IOError, inst:
131 131 if hasattr(inst, "code"):
132 132 ui.warn(_("abort: %s\n") % inst)
133 133 elif hasattr(inst, "reason"):
134 134 try: # usually it is in the form (errno, strerror)
135 135 reason = inst.reason.args[1]
136 136 except: # it might be anything, for example a string
137 137 reason = inst.reason
138 138 ui.warn(_("abort: error: %s\n") % reason)
139 139 elif hasattr(inst, "args") and inst.args[0] == errno.EPIPE:
140 140 if ui.debugflag:
141 141 ui.warn(_("broken pipe\n"))
142 142 elif getattr(inst, "strerror", None):
143 143 if getattr(inst, "filename", None):
144 144 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
145 145 else:
146 146 ui.warn(_("abort: %s\n") % inst.strerror)
147 147 else:
148 148 raise
149 149 except OSError, inst:
150 150 if getattr(inst, "filename", None):
151 151 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
152 152 else:
153 153 ui.warn(_("abort: %s\n") % inst.strerror)
154 154 except KeyboardInterrupt:
155 155 try:
156 156 ui.warn(_("interrupted!\n"))
157 157 except IOError, inst:
158 158 if inst.errno == errno.EPIPE:
159 159 if ui.debugflag:
160 160 ui.warn(_("\nbroken pipe\n"))
161 161 else:
162 162 raise
163 163 except MemoryError:
164 164 ui.warn(_("abort: out of memory\n"))
165 165 except SystemExit, inst:
166 166 # Commands shouldn't sys.exit directly, but give a return code.
167 167 # Just in case catch this and and pass exit code to caller.
168 168 return inst.code
169 169 except socket.error, inst:
170 170 ui.warn(_("abort: %s\n") % inst.args[-1])
171 171 except:
172 172 ui.warn(_("** unknown exception encountered,"
173 173 " please report by visiting\n"))
174 174 ui.warn(_("** http://mercurial.selenic.com/wiki/BugTracker\n"))
175 175 ui.warn(_("** Python %s\n") % sys.version.replace('\n', ''))
176 176 ui.warn(_("** Mercurial Distributed SCM (version %s)\n")
177 177 % util.version())
178 178 ui.warn(_("** Extensions loaded: %s\n")
179 179 % ", ".join([x[0] for x in extensions.extensions()]))
180 180 raise
181 181
182 182 return -1
183 183
184 184 def aliasargs(fn):
185 185 if hasattr(fn, 'args'):
186 186 return fn.args
187 187 return []
188 188
189 189 class cmdalias(object):
190 190 def __init__(self, name, definition, cmdtable):
191 191 self.name = self.cmd = name
192 192 self.cmdname = ''
193 193 self.definition = definition
194 194 self.args = []
195 195 self.opts = []
196 196 self.help = ''
197 197 self.norepo = True
198 198 self.badalias = False
199 199
200 200 try:
201 201 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
202 202 for alias, e in cmdtable.iteritems():
203 203 if e is entry:
204 204 self.cmd = alias
205 205 break
206 206 self.shadows = True
207 207 except error.UnknownCommand:
208 208 self.shadows = False
209 209
210 210 if not self.definition:
211 211 def fn(ui, *args):
212 212 ui.warn(_("no definition for alias '%s'\n") % self.name)
213 213 return 1
214 214 self.fn = fn
215 215 self.badalias = True
216 216
217 217 return
218 218
219 219 if self.definition.startswith('!'):
220 220 self.shell = True
221 221 def fn(ui, *args):
222 222 env = {'HG_ARGS': ' '.join((self.name,) + args)}
223 223 def _checkvar(m):
224 if int(m.groups()[0]) <= len(args):
224 if m.groups()[0] == '$':
225 return m.group()
226 elif int(m.groups()[0]) <= len(args):
225 227 return m.group()
226 228 else:
227 229 return ''
228 cmd = re.sub(r'\$(\d+)', _checkvar, self.definition[1:])
230 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
229 231 replace = dict((str(i + 1), arg) for i, arg in enumerate(args))
230 232 replace['0'] = self.name
231 233 replace['@'] = ' '.join(args)
232 cmd = util.interpolate(r'\$', replace, cmd)
234 cmd = util.interpolate(r'\$', replace, cmd, escape_prefix=True)
233 235 return util.system(cmd, environ=env)
234 236 self.fn = fn
235 237 return
236 238
237 239 args = shlex.split(self.definition)
238 240 self.cmdname = cmd = args.pop(0)
239 241 args = map(util.expandpath, args)
240 242
241 243 for invalidarg in ("--cwd", "-R", "--repository", "--repo"):
242 244 if _earlygetopt([invalidarg], args):
243 245 def fn(ui, *args):
244 246 ui.warn(_("error in definition for alias '%s': %s may only "
245 247 "be given on the command line\n")
246 248 % (self.name, invalidarg))
247 249 return 1
248 250
249 251 self.fn = fn
250 252 self.badalias = True
251 253 return
252 254
253 255 try:
254 256 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
255 257 if len(tableentry) > 2:
256 258 self.fn, self.opts, self.help = tableentry
257 259 else:
258 260 self.fn, self.opts = tableentry
259 261
260 262 self.args = aliasargs(self.fn) + args
261 263 if cmd not in commands.norepo.split(' '):
262 264 self.norepo = False
263 265 if self.help.startswith("hg " + cmd):
264 266 # drop prefix in old-style help lines so hg shows the alias
265 267 self.help = self.help[4 + len(cmd):]
266 268 self.__doc__ = self.fn.__doc__
267 269
268 270 except error.UnknownCommand:
269 271 def fn(ui, *args):
270 272 ui.warn(_("alias '%s' resolves to unknown command '%s'\n") \
271 273 % (self.name, cmd))
272 274 try:
273 275 # check if the command is in a disabled extension
274 276 commands.help_(ui, cmd, unknowncmd=True)
275 277 except error.UnknownCommand:
276 278 pass
277 279 return 1
278 280 self.fn = fn
279 281 self.badalias = True
280 282 except error.AmbiguousCommand:
281 283 def fn(ui, *args):
282 284 ui.warn(_("alias '%s' resolves to ambiguous command '%s'\n") \
283 285 % (self.name, cmd))
284 286 return 1
285 287 self.fn = fn
286 288 self.badalias = True
287 289
288 290 def __call__(self, ui, *args, **opts):
289 291 if self.shadows:
290 292 ui.debug("alias '%s' shadows command '%s'\n" %
291 293 (self.name, self.cmdname))
292 294
293 295 if self.definition.startswith('!'):
294 296 return self.fn(ui, *args, **opts)
295 297 else:
296 298 try:
297 299 util.checksignature(self.fn)(ui, *args, **opts)
298 300 except error.SignatureError:
299 301 args = ' '.join([self.cmdname] + self.args)
300 302 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
301 303 raise
302 304
303 305 def addaliases(ui, cmdtable):
304 306 # aliases are processed after extensions have been loaded, so they
305 307 # may use extension commands. Aliases can also use other alias definitions,
306 308 # but only if they have been defined prior to the current definition.
307 309 for alias, definition in ui.configitems('alias'):
308 310 aliasdef = cmdalias(alias, definition, cmdtable)
309 311 cmdtable[aliasdef.cmd] = (aliasdef, aliasdef.opts, aliasdef.help)
310 312 if aliasdef.norepo:
311 313 commands.norepo += ' %s' % alias
312 314
313 315 def _parse(ui, args):
314 316 options = {}
315 317 cmdoptions = {}
316 318
317 319 try:
318 320 args = fancyopts.fancyopts(args, commands.globalopts, options)
319 321 except fancyopts.getopt.GetoptError, inst:
320 322 raise error.CommandError(None, inst)
321 323
322 324 if args:
323 325 cmd, args = args[0], args[1:]
324 326 aliases, entry = cmdutil.findcmd(cmd, commands.table,
325 327 ui.config("ui", "strict"))
326 328 cmd = aliases[0]
327 329 args = aliasargs(entry[0]) + args
328 330 defaults = ui.config("defaults", cmd)
329 331 if defaults:
330 332 args = map(util.expandpath, shlex.split(defaults)) + args
331 333 c = list(entry[1])
332 334 else:
333 335 cmd = None
334 336 c = []
335 337
336 338 # combine global options into local
337 339 for o in commands.globalopts:
338 340 c.append((o[0], o[1], options[o[1]], o[3]))
339 341
340 342 try:
341 343 args = fancyopts.fancyopts(args, c, cmdoptions, True)
342 344 except fancyopts.getopt.GetoptError, inst:
343 345 raise error.CommandError(cmd, inst)
344 346
345 347 # separate global options back out
346 348 for o in commands.globalopts:
347 349 n = o[1]
348 350 options[n] = cmdoptions[n]
349 351 del cmdoptions[n]
350 352
351 353 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
352 354
353 355 def _parseconfig(ui, config):
354 356 """parse the --config options from the command line"""
355 357 for cfg in config:
356 358 try:
357 359 name, value = cfg.split('=', 1)
358 360 section, name = name.split('.', 1)
359 361 if not section or not name:
360 362 raise IndexError
361 363 ui.setconfig(section, name, value)
362 364 except (IndexError, ValueError):
363 365 raise util.Abort(_('malformed --config option: %r '
364 366 '(use --config section.name=value)') % cfg)
365 367
366 368 def _earlygetopt(aliases, args):
367 369 """Return list of values for an option (or aliases).
368 370
369 371 The values are listed in the order they appear in args.
370 372 The options and values are removed from args.
371 373 """
372 374 try:
373 375 argcount = args.index("--")
374 376 except ValueError:
375 377 argcount = len(args)
376 378 shortopts = [opt for opt in aliases if len(opt) == 2]
377 379 values = []
378 380 pos = 0
379 381 while pos < argcount:
380 382 if args[pos] in aliases:
381 383 if pos + 1 >= argcount:
382 384 # ignore and let getopt report an error if there is no value
383 385 break
384 386 del args[pos]
385 387 values.append(args.pop(pos))
386 388 argcount -= 2
387 389 elif args[pos][:2] in shortopts:
388 390 # short option can have no following space, e.g. hg log -Rfoo
389 391 values.append(args.pop(pos)[2:])
390 392 argcount -= 1
391 393 else:
392 394 pos += 1
393 395 return values
394 396
395 397 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
396 398 # run pre-hook, and abort if it fails
397 399 ret = hook.hook(lui, repo, "pre-%s" % cmd, False, args=" ".join(fullargs),
398 400 pats=cmdpats, opts=cmdoptions)
399 401 if ret:
400 402 return ret
401 403 ret = _runcommand(ui, options, cmd, d)
402 404 # run post-hook, passing command result
403 405 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
404 406 result=ret, pats=cmdpats, opts=cmdoptions)
405 407 return ret
406 408
407 409 def _getlocal(ui, rpath):
408 410 """Return (path, local ui object) for the given target path.
409 411
410 412 Takes paths in [cwd]/.hg/hgrc into account."
411 413 """
412 414 try:
413 415 wd = os.getcwd()
414 416 except OSError, e:
415 417 raise util.Abort(_("error getting current working directory: %s") %
416 418 e.strerror)
417 419 path = cmdutil.findrepo(wd) or ""
418 420 if not path:
419 421 lui = ui
420 422 else:
421 423 lui = ui.copy()
422 424 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
423 425
424 426 if rpath:
425 427 path = lui.expandpath(rpath[-1])
426 428 lui = ui.copy()
427 429 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
428 430
429 431 return path, lui
430 432
431 433 def _checkshellalias(ui, args):
432 434 cwd = os.getcwd()
433 435 norepo = commands.norepo
434 436 options = {}
435 437
436 438 try:
437 439 args = fancyopts.fancyopts(args, commands.globalopts, options)
438 440 except fancyopts.getopt.GetoptError:
439 441 return
440 442
441 443 if not args:
442 444 return
443 445
444 446 _parseconfig(ui, options['config'])
445 447 if options['cwd']:
446 448 os.chdir(options['cwd'])
447 449
448 450 path, lui = _getlocal(ui, [options['repository']])
449 451
450 452 cmdtable = commands.table.copy()
451 453 addaliases(lui, cmdtable)
452 454
453 455 cmd = args[0]
454 456 try:
455 457 aliases, entry = cmdutil.findcmd(cmd, cmdtable, lui.config("ui", "strict"))
456 458 except (error.AmbiguousCommand, error.UnknownCommand):
457 459 commands.norepo = norepo
458 460 os.chdir(cwd)
459 461 return
460 462
461 463 cmd = aliases[0]
462 464 fn = entry[0]
463 465
464 466 if cmd and hasattr(fn, 'shell'):
465 467 d = lambda: fn(ui, *args[1:])
466 468 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d, [], {})
467 469
468 470 commands.norepo = norepo
469 471 os.chdir(cwd)
470 472
471 473 _loaded = set()
472 474 def _dispatch(ui, args):
473 475 shellaliasfn = _checkshellalias(ui, args)
474 476 if shellaliasfn:
475 477 return shellaliasfn()
476 478
477 479 # read --config before doing anything else
478 480 # (e.g. to change trust settings for reading .hg/hgrc)
479 481 _parseconfig(ui, _earlygetopt(['--config'], args))
480 482
481 483 # check for cwd
482 484 cwd = _earlygetopt(['--cwd'], args)
483 485 if cwd:
484 486 os.chdir(cwd[-1])
485 487
486 488 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
487 489 path, lui = _getlocal(ui, rpath)
488 490
489 491 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
490 492 # reposetup. Programs like TortoiseHg will call _dispatch several
491 493 # times so we keep track of configured extensions in _loaded.
492 494 extensions.loadall(lui)
493 495 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
494 496 # Propagate any changes to lui.__class__ by extensions
495 497 ui.__class__ = lui.__class__
496 498
497 499 # (uisetup and extsetup are handled in extensions.loadall)
498 500
499 501 for name, module in exts:
500 502 cmdtable = getattr(module, 'cmdtable', {})
501 503 overrides = [cmd for cmd in cmdtable if cmd in commands.table]
502 504 if overrides:
503 505 ui.warn(_("extension '%s' overrides commands: %s\n")
504 506 % (name, " ".join(overrides)))
505 507 commands.table.update(cmdtable)
506 508 _loaded.add(name)
507 509
508 510 # (reposetup is handled in hg.repository)
509 511
510 512 addaliases(lui, commands.table)
511 513
512 514 # check for fallback encoding
513 515 fallback = lui.config('ui', 'fallbackencoding')
514 516 if fallback:
515 517 encoding.fallbackencoding = fallback
516 518
517 519 fullargs = args
518 520 cmd, func, args, options, cmdoptions = _parse(lui, args)
519 521
520 522 if options["config"]:
521 523 raise util.Abort(_("option --config may not be abbreviated!"))
522 524 if options["cwd"]:
523 525 raise util.Abort(_("option --cwd may not be abbreviated!"))
524 526 if options["repository"]:
525 527 raise util.Abort(_(
526 528 "Option -R has to be separated from other options (e.g. not -qR) "
527 529 "and --repository may only be abbreviated as --repo!"))
528 530
529 531 if options["encoding"]:
530 532 encoding.encoding = options["encoding"]
531 533 if options["encodingmode"]:
532 534 encoding.encodingmode = options["encodingmode"]
533 535 if options["time"]:
534 536 def get_times():
535 537 t = os.times()
536 538 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
537 539 t = (t[0], t[1], t[2], t[3], time.clock())
538 540 return t
539 541 s = get_times()
540 542 def print_time():
541 543 t = get_times()
542 544 ui.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
543 545 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
544 546 atexit.register(print_time)
545 547
546 548 if options['verbose'] or options['debug'] or options['quiet']:
547 549 ui.setconfig('ui', 'verbose', str(bool(options['verbose'])))
548 550 ui.setconfig('ui', 'debug', str(bool(options['debug'])))
549 551 ui.setconfig('ui', 'quiet', str(bool(options['quiet'])))
550 552 if options['traceback']:
551 553 ui.setconfig('ui', 'traceback', 'on')
552 554 if options['noninteractive']:
553 555 ui.setconfig('ui', 'interactive', 'off')
554 556
555 557 if cmdoptions.get('insecure', False):
556 558 ui.setconfig('web', 'cacerts', '')
557 559
558 560 if options['help']:
559 561 return commands.help_(ui, cmd, options['version'])
560 562 elif options['version']:
561 563 return commands.version_(ui)
562 564 elif not cmd:
563 565 return commands.help_(ui, 'shortlist')
564 566
565 567 repo = None
566 568 cmdpats = args[:]
567 569 if cmd not in commands.norepo.split():
568 570 try:
569 571 repo = hg.repository(ui, path=path)
570 572 ui = repo.ui
571 573 if not repo.local():
572 574 raise util.Abort(_("repository '%s' is not local") % path)
573 575 ui.setconfig("bundle", "mainreporoot", repo.root)
574 576 except error.RepoError:
575 577 if cmd not in commands.optionalrepo.split():
576 578 if args and not path: # try to infer -R from command args
577 579 repos = map(cmdutil.findrepo, args)
578 580 guess = repos[0]
579 581 if guess and repos.count(guess) == len(repos):
580 582 return _dispatch(ui, ['--repository', guess] + fullargs)
581 583 if not path:
582 584 raise error.RepoError(_("There is no Mercurial repository"
583 585 " here (.hg not found)"))
584 586 raise
585 587 args.insert(0, repo)
586 588 elif rpath:
587 589 ui.warn(_("warning: --repository ignored\n"))
588 590
589 591 msg = ' '.join(' ' in a and repr(a) or a for a in fullargs)
590 592 ui.log("command", msg + "\n")
591 593 d = lambda: util.checksignature(func)(ui, *args, **cmdoptions)
592 594 try:
593 595 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
594 596 cmdpats, cmdoptions)
595 597 finally:
596 598 if repo:
597 599 repo.close()
598 600
599 601 def _runcommand(ui, options, cmd, cmdfunc):
600 602 def checkargs():
601 603 try:
602 604 return cmdfunc()
603 605 except error.SignatureError:
604 606 raise error.CommandError(cmd, _("invalid arguments"))
605 607
606 608 if options['profile']:
607 609 format = ui.config('profiling', 'format', default='text')
608 610
609 611 if not format in ['text', 'kcachegrind']:
610 612 ui.warn(_("unrecognized profiling format '%s'"
611 613 " - Ignored\n") % format)
612 614 format = 'text'
613 615
614 616 output = ui.config('profiling', 'output')
615 617
616 618 if output:
617 619 path = ui.expandpath(output)
618 620 ostream = open(path, 'wb')
619 621 else:
620 622 ostream = sys.stderr
621 623
622 624 try:
623 625 from mercurial import lsprof
624 626 except ImportError:
625 627 raise util.Abort(_(
626 628 'lsprof not available - install from '
627 629 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
628 630 p = lsprof.Profiler()
629 631 p.enable(subcalls=True)
630 632 try:
631 633 return checkargs()
632 634 finally:
633 635 p.disable()
634 636
635 637 if format == 'kcachegrind':
636 638 import lsprofcalltree
637 639 calltree = lsprofcalltree.KCacheGrind(p)
638 640 calltree.output(ostream)
639 641 else:
640 642 # format == 'text'
641 643 stats = lsprof.Stats(p.getstats())
642 644 stats.sort()
643 645 stats.pprint(top=10, file=ostream, climit=5)
644 646
645 647 if output:
646 648 ostream.close()
647 649 else:
648 650 return checkargs()
@@ -1,1545 +1,1556
1 1 # util.py - Mercurial utility functions and platform specfic implementations
2 2 #
3 3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10 """Mercurial utility functions and platform specfic implementations.
11 11
12 12 This contains helper routines that are independent of the SCM core and
13 13 hide platform-specific details from the core.
14 14 """
15 15
16 16 from i18n import _
17 17 import error, osutil, encoding
18 18 import errno, re, shutil, sys, tempfile, traceback
19 19 import os, stat, time, calendar, textwrap, unicodedata, signal
20 20 import imp, socket
21 21
22 22 # Python compatibility
23 23
24 24 def sha1(s):
25 25 return _fastsha1(s)
26 26
27 27 def _fastsha1(s):
28 28 # This function will import sha1 from hashlib or sha (whichever is
29 29 # available) and overwrite itself with it on the first call.
30 30 # Subsequent calls will go directly to the imported function.
31 31 if sys.version_info >= (2, 5):
32 32 from hashlib import sha1 as _sha1
33 33 else:
34 34 from sha import sha as _sha1
35 35 global _fastsha1, sha1
36 36 _fastsha1 = sha1 = _sha1
37 37 return _sha1(s)
38 38
39 39 import __builtin__
40 40
41 41 if sys.version_info[0] < 3:
42 42 def fakebuffer(sliceable, offset=0):
43 43 return sliceable[offset:]
44 44 else:
45 45 def fakebuffer(sliceable, offset=0):
46 46 return memoryview(sliceable)[offset:]
47 47 try:
48 48 buffer
49 49 except NameError:
50 50 __builtin__.buffer = fakebuffer
51 51
52 52 import subprocess
53 53 closefds = os.name == 'posix'
54 54
55 55 def popen2(cmd, env=None, newlines=False):
56 56 # Setting bufsize to -1 lets the system decide the buffer size.
57 57 # The default for bufsize is 0, meaning unbuffered. This leads to
58 58 # poor performance on Mac OS X: http://bugs.python.org/issue4194
59 59 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
60 60 close_fds=closefds,
61 61 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
62 62 universal_newlines=newlines,
63 63 env=env)
64 64 return p.stdin, p.stdout
65 65
66 66 def popen3(cmd, env=None, newlines=False):
67 67 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
68 68 close_fds=closefds,
69 69 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
70 70 stderr=subprocess.PIPE,
71 71 universal_newlines=newlines,
72 72 env=env)
73 73 return p.stdin, p.stdout, p.stderr
74 74
75 75 def version():
76 76 """Return version information if available."""
77 77 try:
78 78 import __version__
79 79 return __version__.version
80 80 except ImportError:
81 81 return 'unknown'
82 82
83 83 # used by parsedate
84 84 defaultdateformats = (
85 85 '%Y-%m-%d %H:%M:%S',
86 86 '%Y-%m-%d %I:%M:%S%p',
87 87 '%Y-%m-%d %H:%M',
88 88 '%Y-%m-%d %I:%M%p',
89 89 '%Y-%m-%d',
90 90 '%m-%d',
91 91 '%m/%d',
92 92 '%m/%d/%y',
93 93 '%m/%d/%Y',
94 94 '%a %b %d %H:%M:%S %Y',
95 95 '%a %b %d %I:%M:%S%p %Y',
96 96 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
97 97 '%b %d %H:%M:%S %Y',
98 98 '%b %d %I:%M:%S%p %Y',
99 99 '%b %d %H:%M:%S',
100 100 '%b %d %I:%M:%S%p',
101 101 '%b %d %H:%M',
102 102 '%b %d %I:%M%p',
103 103 '%b %d %Y',
104 104 '%b %d',
105 105 '%H:%M:%S',
106 106 '%I:%M:%S%p',
107 107 '%H:%M',
108 108 '%I:%M%p',
109 109 )
110 110
111 111 extendeddateformats = defaultdateformats + (
112 112 "%Y",
113 113 "%Y-%m",
114 114 "%b",
115 115 "%b %Y",
116 116 )
117 117
118 118 def cachefunc(func):
119 119 '''cache the result of function calls'''
120 120 # XXX doesn't handle keywords args
121 121 cache = {}
122 122 if func.func_code.co_argcount == 1:
123 123 # we gain a small amount of time because
124 124 # we don't need to pack/unpack the list
125 125 def f(arg):
126 126 if arg not in cache:
127 127 cache[arg] = func(arg)
128 128 return cache[arg]
129 129 else:
130 130 def f(*args):
131 131 if args not in cache:
132 132 cache[args] = func(*args)
133 133 return cache[args]
134 134
135 135 return f
136 136
137 137 def lrucachefunc(func):
138 138 '''cache most recent results of function calls'''
139 139 cache = {}
140 140 order = []
141 141 if func.func_code.co_argcount == 1:
142 142 def f(arg):
143 143 if arg not in cache:
144 144 if len(cache) > 20:
145 145 del cache[order.pop(0)]
146 146 cache[arg] = func(arg)
147 147 else:
148 148 order.remove(arg)
149 149 order.append(arg)
150 150 return cache[arg]
151 151 else:
152 152 def f(*args):
153 153 if args not in cache:
154 154 if len(cache) > 20:
155 155 del cache[order.pop(0)]
156 156 cache[args] = func(*args)
157 157 else:
158 158 order.remove(args)
159 159 order.append(args)
160 160 return cache[args]
161 161
162 162 return f
163 163
164 164 class propertycache(object):
165 165 def __init__(self, func):
166 166 self.func = func
167 167 self.name = func.__name__
168 168 def __get__(self, obj, type=None):
169 169 result = self.func(obj)
170 170 setattr(obj, self.name, result)
171 171 return result
172 172
173 173 def pipefilter(s, cmd):
174 174 '''filter string S through command CMD, returning its output'''
175 175 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
176 176 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
177 177 pout, perr = p.communicate(s)
178 178 return pout
179 179
180 180 def tempfilter(s, cmd):
181 181 '''filter string S through a pair of temporary files with CMD.
182 182 CMD is used as a template to create the real command to be run,
183 183 with the strings INFILE and OUTFILE replaced by the real names of
184 184 the temporary files generated.'''
185 185 inname, outname = None, None
186 186 try:
187 187 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
188 188 fp = os.fdopen(infd, 'wb')
189 189 fp.write(s)
190 190 fp.close()
191 191 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
192 192 os.close(outfd)
193 193 cmd = cmd.replace('INFILE', inname)
194 194 cmd = cmd.replace('OUTFILE', outname)
195 195 code = os.system(cmd)
196 196 if sys.platform == 'OpenVMS' and code & 1:
197 197 code = 0
198 198 if code:
199 199 raise Abort(_("command '%s' failed: %s") %
200 200 (cmd, explain_exit(code)))
201 201 return open(outname, 'rb').read()
202 202 finally:
203 203 try:
204 204 if inname:
205 205 os.unlink(inname)
206 206 except:
207 207 pass
208 208 try:
209 209 if outname:
210 210 os.unlink(outname)
211 211 except:
212 212 pass
213 213
214 214 filtertable = {
215 215 'tempfile:': tempfilter,
216 216 'pipe:': pipefilter,
217 217 }
218 218
219 219 def filter(s, cmd):
220 220 "filter a string through a command that transforms its input to its output"
221 221 for name, fn in filtertable.iteritems():
222 222 if cmd.startswith(name):
223 223 return fn(s, cmd[len(name):].lstrip())
224 224 return pipefilter(s, cmd)
225 225
226 226 def binary(s):
227 227 """return true if a string is binary data"""
228 228 return bool(s and '\0' in s)
229 229
230 230 def increasingchunks(source, min=1024, max=65536):
231 231 '''return no less than min bytes per chunk while data remains,
232 232 doubling min after each chunk until it reaches max'''
233 233 def log2(x):
234 234 if not x:
235 235 return 0
236 236 i = 0
237 237 while x:
238 238 x >>= 1
239 239 i += 1
240 240 return i - 1
241 241
242 242 buf = []
243 243 blen = 0
244 244 for chunk in source:
245 245 buf.append(chunk)
246 246 blen += len(chunk)
247 247 if blen >= min:
248 248 if min < max:
249 249 min = min << 1
250 250 nmin = 1 << log2(blen)
251 251 if nmin > min:
252 252 min = nmin
253 253 if min > max:
254 254 min = max
255 255 yield ''.join(buf)
256 256 blen = 0
257 257 buf = []
258 258 if buf:
259 259 yield ''.join(buf)
260 260
261 261 Abort = error.Abort
262 262
263 263 def always(fn):
264 264 return True
265 265
266 266 def never(fn):
267 267 return False
268 268
269 269 def pathto(root, n1, n2):
270 270 '''return the relative path from one place to another.
271 271 root should use os.sep to separate directories
272 272 n1 should use os.sep to separate directories
273 273 n2 should use "/" to separate directories
274 274 returns an os.sep-separated path.
275 275
276 276 If n1 is a relative path, it's assumed it's
277 277 relative to root.
278 278 n2 should always be relative to root.
279 279 '''
280 280 if not n1:
281 281 return localpath(n2)
282 282 if os.path.isabs(n1):
283 283 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
284 284 return os.path.join(root, localpath(n2))
285 285 n2 = '/'.join((pconvert(root), n2))
286 286 a, b = splitpath(n1), n2.split('/')
287 287 a.reverse()
288 288 b.reverse()
289 289 while a and b and a[-1] == b[-1]:
290 290 a.pop()
291 291 b.pop()
292 292 b.reverse()
293 293 return os.sep.join((['..'] * len(a)) + b) or '.'
294 294
295 295 def canonpath(root, cwd, myname, auditor=None):
296 296 """return the canonical path of myname, given cwd and root"""
297 297 if endswithsep(root):
298 298 rootsep = root
299 299 else:
300 300 rootsep = root + os.sep
301 301 name = myname
302 302 if not os.path.isabs(name):
303 303 name = os.path.join(root, cwd, name)
304 304 name = os.path.normpath(name)
305 305 if auditor is None:
306 306 auditor = path_auditor(root)
307 307 if name != rootsep and name.startswith(rootsep):
308 308 name = name[len(rootsep):]
309 309 auditor(name)
310 310 return pconvert(name)
311 311 elif name == root:
312 312 return ''
313 313 else:
314 314 # Determine whether `name' is in the hierarchy at or beneath `root',
315 315 # by iterating name=dirname(name) until that causes no change (can't
316 316 # check name == '/', because that doesn't work on windows). For each
317 317 # `name', compare dev/inode numbers. If they match, the list `rel'
318 318 # holds the reversed list of components making up the relative file
319 319 # name we want.
320 320 root_st = os.stat(root)
321 321 rel = []
322 322 while True:
323 323 try:
324 324 name_st = os.stat(name)
325 325 except OSError:
326 326 break
327 327 if samestat(name_st, root_st):
328 328 if not rel:
329 329 # name was actually the same as root (maybe a symlink)
330 330 return ''
331 331 rel.reverse()
332 332 name = os.path.join(*rel)
333 333 auditor(name)
334 334 return pconvert(name)
335 335 dirname, basename = os.path.split(name)
336 336 rel.append(basename)
337 337 if dirname == name:
338 338 break
339 339 name = dirname
340 340
341 341 raise Abort('%s not under root' % myname)
342 342
343 343 _hgexecutable = None
344 344
345 345 def main_is_frozen():
346 346 """return True if we are a frozen executable.
347 347
348 348 The code supports py2exe (most common, Windows only) and tools/freeze
349 349 (portable, not much used).
350 350 """
351 351 return (hasattr(sys, "frozen") or # new py2exe
352 352 hasattr(sys, "importers") or # old py2exe
353 353 imp.is_frozen("__main__")) # tools/freeze
354 354
355 355 def hgexecutable():
356 356 """return location of the 'hg' executable.
357 357
358 358 Defaults to $HG or 'hg' in the search path.
359 359 """
360 360 if _hgexecutable is None:
361 361 hg = os.environ.get('HG')
362 362 if hg:
363 363 set_hgexecutable(hg)
364 364 elif main_is_frozen():
365 365 set_hgexecutable(sys.executable)
366 366 else:
367 367 exe = find_exe('hg') or os.path.basename(sys.argv[0])
368 368 set_hgexecutable(exe)
369 369 return _hgexecutable
370 370
371 371 def set_hgexecutable(path):
372 372 """set location of the 'hg' executable"""
373 373 global _hgexecutable
374 374 _hgexecutable = path
375 375
376 376 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
377 377 '''enhanced shell command execution.
378 378 run with environment maybe modified, maybe in different dir.
379 379
380 380 if command fails and onerr is None, return status. if ui object,
381 381 print error message and return status, else raise onerr object as
382 382 exception.
383 383
384 384 if out is specified, it is assumed to be a file-like object that has a
385 385 write() method. stdout and stderr will be redirected to out.'''
386 386 def py2shell(val):
387 387 'convert python object into string that is useful to shell'
388 388 if val is None or val is False:
389 389 return '0'
390 390 if val is True:
391 391 return '1'
392 392 return str(val)
393 393 origcmd = cmd
394 394 cmd = quotecommand(cmd)
395 395 env = dict(os.environ)
396 396 env.update((k, py2shell(v)) for k, v in environ.iteritems())
397 397 env['HG'] = hgexecutable()
398 398 if out is None:
399 399 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
400 400 env=env, cwd=cwd)
401 401 else:
402 402 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
403 403 env=env, cwd=cwd, stdout=subprocess.PIPE,
404 404 stderr=subprocess.STDOUT)
405 405 for line in proc.stdout:
406 406 out.write(line)
407 407 proc.wait()
408 408 rc = proc.returncode
409 409 if sys.platform == 'OpenVMS' and rc & 1:
410 410 rc = 0
411 411 if rc and onerr:
412 412 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
413 413 explain_exit(rc)[0])
414 414 if errprefix:
415 415 errmsg = '%s: %s' % (errprefix, errmsg)
416 416 try:
417 417 onerr.warn(errmsg + '\n')
418 418 except AttributeError:
419 419 raise onerr(errmsg)
420 420 return rc
421 421
422 422 def checksignature(func):
423 423 '''wrap a function with code to check for calling errors'''
424 424 def check(*args, **kwargs):
425 425 try:
426 426 return func(*args, **kwargs)
427 427 except TypeError:
428 428 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
429 429 raise error.SignatureError
430 430 raise
431 431
432 432 return check
433 433
434 434 def unlinkpath(f):
435 435 """unlink and remove the directory if it is empty"""
436 436 os.unlink(f)
437 437 # try removing directories that might now be empty
438 438 try:
439 439 os.removedirs(os.path.dirname(f))
440 440 except OSError:
441 441 pass
442 442
443 443 def copyfile(src, dest):
444 444 "copy a file, preserving mode and atime/mtime"
445 445 if os.path.islink(src):
446 446 try:
447 447 os.unlink(dest)
448 448 except:
449 449 pass
450 450 os.symlink(os.readlink(src), dest)
451 451 else:
452 452 try:
453 453 shutil.copyfile(src, dest)
454 454 shutil.copymode(src, dest)
455 455 except shutil.Error, inst:
456 456 raise Abort(str(inst))
457 457
458 458 def copyfiles(src, dst, hardlink=None):
459 459 """Copy a directory tree using hardlinks if possible"""
460 460
461 461 if hardlink is None:
462 462 hardlink = (os.stat(src).st_dev ==
463 463 os.stat(os.path.dirname(dst)).st_dev)
464 464
465 465 num = 0
466 466 if os.path.isdir(src):
467 467 os.mkdir(dst)
468 468 for name, kind in osutil.listdir(src):
469 469 srcname = os.path.join(src, name)
470 470 dstname = os.path.join(dst, name)
471 471 hardlink, n = copyfiles(srcname, dstname, hardlink)
472 472 num += n
473 473 else:
474 474 if hardlink:
475 475 try:
476 476 os_link(src, dst)
477 477 except (IOError, OSError):
478 478 hardlink = False
479 479 shutil.copy(src, dst)
480 480 else:
481 481 shutil.copy(src, dst)
482 482 num += 1
483 483
484 484 return hardlink, num
485 485
486 486 class path_auditor(object):
487 487 '''ensure that a filesystem path contains no banned components.
488 488 the following properties of a path are checked:
489 489
490 490 - ends with a directory separator
491 491 - under top-level .hg
492 492 - starts at the root of a windows drive
493 493 - contains ".."
494 494 - traverses a symlink (e.g. a/symlink_here/b)
495 495 - inside a nested repository (a callback can be used to approve
496 496 some nested repositories, e.g., subrepositories)
497 497 '''
498 498
499 499 def __init__(self, root, callback=None):
500 500 self.audited = set()
501 501 self.auditeddir = set()
502 502 self.root = root
503 503 self.callback = callback
504 504
505 505 def __call__(self, path):
506 506 if path in self.audited:
507 507 return
508 508 # AIX ignores "/" at end of path, others raise EISDIR.
509 509 if endswithsep(path):
510 510 raise Abort(_("path ends in directory separator: %s") % path)
511 511 normpath = os.path.normcase(path)
512 512 parts = splitpath(normpath)
513 513 if (os.path.splitdrive(path)[0]
514 514 or parts[0].lower() in ('.hg', '.hg.', '')
515 515 or os.pardir in parts):
516 516 raise Abort(_("path contains illegal component: %s") % path)
517 517 if '.hg' in path.lower():
518 518 lparts = [p.lower() for p in parts]
519 519 for p in '.hg', '.hg.':
520 520 if p in lparts[1:]:
521 521 pos = lparts.index(p)
522 522 base = os.path.join(*parts[:pos])
523 523 raise Abort(_('path %r is inside repo %r') % (path, base))
524 524 def check(prefix):
525 525 curpath = os.path.join(self.root, prefix)
526 526 try:
527 527 st = os.lstat(curpath)
528 528 except OSError, err:
529 529 # EINVAL can be raised as invalid path syntax under win32.
530 530 # They must be ignored for patterns can be checked too.
531 531 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
532 532 raise
533 533 else:
534 534 if stat.S_ISLNK(st.st_mode):
535 535 raise Abort(_('path %r traverses symbolic link %r') %
536 536 (path, prefix))
537 537 elif (stat.S_ISDIR(st.st_mode) and
538 538 os.path.isdir(os.path.join(curpath, '.hg'))):
539 539 if not self.callback or not self.callback(curpath):
540 540 raise Abort(_('path %r is inside repo %r') %
541 541 (path, prefix))
542 542 parts.pop()
543 543 prefixes = []
544 544 while parts:
545 545 prefix = os.sep.join(parts)
546 546 if prefix in self.auditeddir:
547 547 break
548 548 check(prefix)
549 549 prefixes.append(prefix)
550 550 parts.pop()
551 551
552 552 self.audited.add(path)
553 553 # only add prefixes to the cache after checking everything: we don't
554 554 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
555 555 self.auditeddir.update(prefixes)
556 556
557 557 def lookup_reg(key, name=None, scope=None):
558 558 return None
559 559
560 560 def hidewindow():
561 561 """Hide current shell window.
562 562
563 563 Used to hide the window opened when starting asynchronous
564 564 child process under Windows, unneeded on other systems.
565 565 """
566 566 pass
567 567
568 568 if os.name == 'nt':
569 569 from windows import *
570 570 else:
571 571 from posix import *
572 572
573 573 def makelock(info, pathname):
574 574 try:
575 575 return os.symlink(info, pathname)
576 576 except OSError, why:
577 577 if why.errno == errno.EEXIST:
578 578 raise
579 579 except AttributeError: # no symlink in os
580 580 pass
581 581
582 582 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
583 583 os.write(ld, info)
584 584 os.close(ld)
585 585
586 586 def readlock(pathname):
587 587 try:
588 588 return os.readlink(pathname)
589 589 except OSError, why:
590 590 if why.errno not in (errno.EINVAL, errno.ENOSYS):
591 591 raise
592 592 except AttributeError: # no symlink in os
593 593 pass
594 594 return posixfile(pathname).read()
595 595
596 596 def fstat(fp):
597 597 '''stat file object that may not have fileno method.'''
598 598 try:
599 599 return os.fstat(fp.fileno())
600 600 except AttributeError:
601 601 return os.stat(fp.name)
602 602
603 603 # File system features
604 604
605 605 def checkcase(path):
606 606 """
607 607 Check whether the given path is on a case-sensitive filesystem
608 608
609 609 Requires a path (like /foo/.hg) ending with a foldable final
610 610 directory component.
611 611 """
612 612 s1 = os.stat(path)
613 613 d, b = os.path.split(path)
614 614 p2 = os.path.join(d, b.upper())
615 615 if path == p2:
616 616 p2 = os.path.join(d, b.lower())
617 617 try:
618 618 s2 = os.stat(p2)
619 619 if s2 == s1:
620 620 return False
621 621 return True
622 622 except:
623 623 return True
624 624
625 625 _fspathcache = {}
626 626 def fspath(name, root):
627 627 '''Get name in the case stored in the filesystem
628 628
629 629 The name is either relative to root, or it is an absolute path starting
630 630 with root. Note that this function is unnecessary, and should not be
631 631 called, for case-sensitive filesystems (simply because it's expensive).
632 632 '''
633 633 # If name is absolute, make it relative
634 634 if name.lower().startswith(root.lower()):
635 635 l = len(root)
636 636 if name[l] == os.sep or name[l] == os.altsep:
637 637 l = l + 1
638 638 name = name[l:]
639 639
640 640 if not os.path.lexists(os.path.join(root, name)):
641 641 return None
642 642
643 643 seps = os.sep
644 644 if os.altsep:
645 645 seps = seps + os.altsep
646 646 # Protect backslashes. This gets silly very quickly.
647 647 seps.replace('\\','\\\\')
648 648 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
649 649 dir = os.path.normcase(os.path.normpath(root))
650 650 result = []
651 651 for part, sep in pattern.findall(name):
652 652 if sep:
653 653 result.append(sep)
654 654 continue
655 655
656 656 if dir not in _fspathcache:
657 657 _fspathcache[dir] = os.listdir(dir)
658 658 contents = _fspathcache[dir]
659 659
660 660 lpart = part.lower()
661 661 lenp = len(part)
662 662 for n in contents:
663 663 if lenp == len(n) and n.lower() == lpart:
664 664 result.append(n)
665 665 break
666 666 else:
667 667 # Cannot happen, as the file exists!
668 668 result.append(part)
669 669 dir = os.path.join(dir, lpart)
670 670
671 671 return ''.join(result)
672 672
673 673 def checkexec(path):
674 674 """
675 675 Check whether the given path is on a filesystem with UNIX-like exec flags
676 676
677 677 Requires a directory (like /foo/.hg)
678 678 """
679 679
680 680 # VFAT on some Linux versions can flip mode but it doesn't persist
681 681 # a FS remount. Frequently we can detect it if files are created
682 682 # with exec bit on.
683 683
684 684 try:
685 685 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
686 686 fh, fn = tempfile.mkstemp(dir=path, prefix='hg-checkexec-')
687 687 try:
688 688 os.close(fh)
689 689 m = os.stat(fn).st_mode & 0777
690 690 new_file_has_exec = m & EXECFLAGS
691 691 os.chmod(fn, m ^ EXECFLAGS)
692 692 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
693 693 finally:
694 694 os.unlink(fn)
695 695 except (IOError, OSError):
696 696 # we don't care, the user probably won't be able to commit anyway
697 697 return False
698 698 return not (new_file_has_exec or exec_flags_cannot_flip)
699 699
700 700 def checklink(path):
701 701 """check whether the given path is on a symlink-capable filesystem"""
702 702 # mktemp is not racy because symlink creation will fail if the
703 703 # file already exists
704 704 name = tempfile.mktemp(dir=path, prefix='hg-checklink-')
705 705 try:
706 706 os.symlink(".", name)
707 707 os.unlink(name)
708 708 return True
709 709 except (OSError, AttributeError):
710 710 return False
711 711
712 712 def checknlink(testfile):
713 713 '''check whether hardlink count reporting works properly'''
714 714
715 715 # testfile may be open, so we need a separate file for checking to
716 716 # work around issue2543 (or testfile may get lost on Samba shares)
717 717 f1 = testfile + ".hgtmp1"
718 718 if os.path.lexists(f1):
719 719 return False
720 720 try:
721 721 posixfile(f1, 'w').close()
722 722 except IOError:
723 723 return False
724 724
725 725 f2 = testfile + ".hgtmp2"
726 726 fd = None
727 727 try:
728 728 try:
729 729 os_link(f1, f2)
730 730 except OSError:
731 731 return False
732 732
733 733 # nlinks() may behave differently for files on Windows shares if
734 734 # the file is open.
735 735 fd = posixfile(f2)
736 736 return nlinks(f2) > 1
737 737 finally:
738 738 if fd is not None:
739 739 fd.close()
740 740 for f in (f1, f2):
741 741 try:
742 742 os.unlink(f)
743 743 except OSError:
744 744 pass
745 745
746 746 return False
747 747
748 748 def endswithsep(path):
749 749 '''Check path ends with os.sep or os.altsep.'''
750 750 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
751 751
752 752 def splitpath(path):
753 753 '''Split path by os.sep.
754 754 Note that this function does not use os.altsep because this is
755 755 an alternative of simple "xxx.split(os.sep)".
756 756 It is recommended to use os.path.normpath() before using this
757 757 function if need.'''
758 758 return path.split(os.sep)
759 759
760 760 def gui():
761 761 '''Are we running in a GUI?'''
762 762 return os.name == "nt" or os.name == "mac" or os.environ.get("DISPLAY")
763 763
764 764 def mktempcopy(name, emptyok=False, createmode=None):
765 765 """Create a temporary file with the same contents from name
766 766
767 767 The permission bits are copied from the original file.
768 768
769 769 If the temporary file is going to be truncated immediately, you
770 770 can use emptyok=True as an optimization.
771 771
772 772 Returns the name of the temporary file.
773 773 """
774 774 d, fn = os.path.split(name)
775 775 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
776 776 os.close(fd)
777 777 # Temporary files are created with mode 0600, which is usually not
778 778 # what we want. If the original file already exists, just copy
779 779 # its mode. Otherwise, manually obey umask.
780 780 try:
781 781 st_mode = os.lstat(name).st_mode & 0777
782 782 except OSError, inst:
783 783 if inst.errno != errno.ENOENT:
784 784 raise
785 785 st_mode = createmode
786 786 if st_mode is None:
787 787 st_mode = ~umask
788 788 st_mode &= 0666
789 789 os.chmod(temp, st_mode)
790 790 if emptyok:
791 791 return temp
792 792 try:
793 793 try:
794 794 ifp = posixfile(name, "rb")
795 795 except IOError, inst:
796 796 if inst.errno == errno.ENOENT:
797 797 return temp
798 798 if not getattr(inst, 'filename', None):
799 799 inst.filename = name
800 800 raise
801 801 ofp = posixfile(temp, "wb")
802 802 for chunk in filechunkiter(ifp):
803 803 ofp.write(chunk)
804 804 ifp.close()
805 805 ofp.close()
806 806 except:
807 807 try: os.unlink(temp)
808 808 except: pass
809 809 raise
810 810 return temp
811 811
812 812 class atomictempfile(object):
813 813 """file-like object that atomically updates a file
814 814
815 815 All writes will be redirected to a temporary copy of the original
816 816 file. When rename is called, the copy is renamed to the original
817 817 name, making the changes visible.
818 818 """
819 819 def __init__(self, name, mode='w+b', createmode=None):
820 820 self.__name = name
821 821 self._fp = None
822 822 self.temp = mktempcopy(name, emptyok=('w' in mode),
823 823 createmode=createmode)
824 824 self._fp = posixfile(self.temp, mode)
825 825
826 826 def __getattr__(self, name):
827 827 return getattr(self._fp, name)
828 828
829 829 def rename(self):
830 830 if not self._fp.closed:
831 831 self._fp.close()
832 832 rename(self.temp, localpath(self.__name))
833 833
834 834 def close(self):
835 835 if not self._fp:
836 836 return
837 837 if not self._fp.closed:
838 838 try:
839 839 os.unlink(self.temp)
840 840 except: pass
841 841 self._fp.close()
842 842
843 843 def __del__(self):
844 844 self.close()
845 845
846 846 def makedirs(name, mode=None):
847 847 """recursive directory creation with parent mode inheritance"""
848 848 parent = os.path.abspath(os.path.dirname(name))
849 849 try:
850 850 os.mkdir(name)
851 851 if mode is not None:
852 852 os.chmod(name, mode)
853 853 return
854 854 except OSError, err:
855 855 if err.errno == errno.EEXIST:
856 856 return
857 857 if not name or parent == name or err.errno != errno.ENOENT:
858 858 raise
859 859 makedirs(parent, mode)
860 860 makedirs(name, mode)
861 861
862 862 class opener(object):
863 863 """Open files relative to a base directory
864 864
865 865 This class is used to hide the details of COW semantics and
866 866 remote file access from higher level code.
867 867 """
868 868 def __init__(self, base, audit=True):
869 869 self.base = base
870 870 if audit:
871 871 self.auditor = path_auditor(base)
872 872 else:
873 873 self.auditor = always
874 874 self.createmode = None
875 875 self._trustnlink = None
876 876
877 877 @propertycache
878 878 def _can_symlink(self):
879 879 return checklink(self.base)
880 880
881 881 def _fixfilemode(self, name):
882 882 if self.createmode is None:
883 883 return
884 884 os.chmod(name, self.createmode & 0666)
885 885
886 886 def __call__(self, path, mode="r", text=False, atomictemp=False):
887 887 self.auditor(path)
888 888 f = os.path.join(self.base, path)
889 889
890 890 if not text and "b" not in mode:
891 891 mode += "b" # for that other OS
892 892
893 893 nlink = -1
894 894 dirname, basename = os.path.split(f)
895 895 # If basename is empty, then the path is malformed because it points
896 896 # to a directory. Let the posixfile() call below raise IOError.
897 897 if basename and mode not in ('r', 'rb'):
898 898 if atomictemp:
899 899 if not os.path.isdir(dirname):
900 900 makedirs(dirname, self.createmode)
901 901 return atomictempfile(f, mode, self.createmode)
902 902 try:
903 903 if 'w' in mode:
904 904 unlink(f)
905 905 nlink = 0
906 906 else:
907 907 # nlinks() may behave differently for files on Windows
908 908 # shares if the file is open.
909 909 fd = posixfile(f)
910 910 nlink = nlinks(f)
911 911 if nlink < 1:
912 912 nlink = 2 # force mktempcopy (issue1922)
913 913 fd.close()
914 914 except (OSError, IOError), e:
915 915 if e.errno != errno.ENOENT:
916 916 raise
917 917 nlink = 0
918 918 if not os.path.isdir(dirname):
919 919 makedirs(dirname, self.createmode)
920 920 if nlink > 0:
921 921 if self._trustnlink is None:
922 922 self._trustnlink = nlink > 1 or checknlink(f)
923 923 if nlink > 1 or not self._trustnlink:
924 924 rename(mktempcopy(f), f)
925 925 fp = posixfile(f, mode)
926 926 if nlink == 0:
927 927 self._fixfilemode(f)
928 928 return fp
929 929
930 930 def symlink(self, src, dst):
931 931 self.auditor(dst)
932 932 linkname = os.path.join(self.base, dst)
933 933 try:
934 934 os.unlink(linkname)
935 935 except OSError:
936 936 pass
937 937
938 938 dirname = os.path.dirname(linkname)
939 939 if not os.path.exists(dirname):
940 940 makedirs(dirname, self.createmode)
941 941
942 942 if self._can_symlink:
943 943 try:
944 944 os.symlink(src, linkname)
945 945 except OSError, err:
946 946 raise OSError(err.errno, _('could not symlink to %r: %s') %
947 947 (src, err.strerror), linkname)
948 948 else:
949 949 f = self(dst, "w")
950 950 f.write(src)
951 951 f.close()
952 952 self._fixfilemode(dst)
953 953
954 954 class chunkbuffer(object):
955 955 """Allow arbitrary sized chunks of data to be efficiently read from an
956 956 iterator over chunks of arbitrary size."""
957 957
958 958 def __init__(self, in_iter):
959 959 """in_iter is the iterator that's iterating over the input chunks.
960 960 targetsize is how big a buffer to try to maintain."""
961 961 def splitbig(chunks):
962 962 for chunk in chunks:
963 963 if len(chunk) > 2**20:
964 964 pos = 0
965 965 while pos < len(chunk):
966 966 end = pos + 2 ** 18
967 967 yield chunk[pos:end]
968 968 pos = end
969 969 else:
970 970 yield chunk
971 971 self.iter = splitbig(in_iter)
972 972 self._queue = []
973 973
974 974 def read(self, l):
975 975 """Read L bytes of data from the iterator of chunks of data.
976 976 Returns less than L bytes if the iterator runs dry."""
977 977 left = l
978 978 buf = ''
979 979 queue = self._queue
980 980 while left > 0:
981 981 # refill the queue
982 982 if not queue:
983 983 target = 2**18
984 984 for chunk in self.iter:
985 985 queue.append(chunk)
986 986 target -= len(chunk)
987 987 if target <= 0:
988 988 break
989 989 if not queue:
990 990 break
991 991
992 992 chunk = queue.pop(0)
993 993 left -= len(chunk)
994 994 if left < 0:
995 995 queue.insert(0, chunk[left:])
996 996 buf += chunk[:left]
997 997 else:
998 998 buf += chunk
999 999
1000 1000 return buf
1001 1001
1002 1002 def filechunkiter(f, size=65536, limit=None):
1003 1003 """Create a generator that produces the data in the file size
1004 1004 (default 65536) bytes at a time, up to optional limit (default is
1005 1005 to read all data). Chunks may be less than size bytes if the
1006 1006 chunk is the last chunk in the file, or the file is a socket or
1007 1007 some other type of file that sometimes reads less data than is
1008 1008 requested."""
1009 1009 assert size >= 0
1010 1010 assert limit is None or limit >= 0
1011 1011 while True:
1012 1012 if limit is None:
1013 1013 nbytes = size
1014 1014 else:
1015 1015 nbytes = min(limit, size)
1016 1016 s = nbytes and f.read(nbytes)
1017 1017 if not s:
1018 1018 break
1019 1019 if limit:
1020 1020 limit -= len(s)
1021 1021 yield s
1022 1022
1023 1023 def makedate():
1024 1024 lt = time.localtime()
1025 1025 if lt[8] == 1 and time.daylight:
1026 1026 tz = time.altzone
1027 1027 else:
1028 1028 tz = time.timezone
1029 1029 t = time.mktime(lt)
1030 1030 if t < 0:
1031 1031 hint = _("check your clock")
1032 1032 raise Abort(_("negative timestamp: %d") % t, hint=hint)
1033 1033 return t, tz
1034 1034
1035 1035 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1036 1036 """represent a (unixtime, offset) tuple as a localized time.
1037 1037 unixtime is seconds since the epoch, and offset is the time zone's
1038 1038 number of seconds away from UTC. if timezone is false, do not
1039 1039 append time zone to string."""
1040 1040 t, tz = date or makedate()
1041 1041 if t < 0:
1042 1042 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
1043 1043 tz = 0
1044 1044 if "%1" in format or "%2" in format:
1045 1045 sign = (tz > 0) and "-" or "+"
1046 1046 minutes = abs(tz) // 60
1047 1047 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
1048 1048 format = format.replace("%2", "%02d" % (minutes % 60))
1049 1049 s = time.strftime(format, time.gmtime(float(t) - tz))
1050 1050 return s
1051 1051
1052 1052 def shortdate(date=None):
1053 1053 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1054 1054 return datestr(date, format='%Y-%m-%d')
1055 1055
1056 1056 def strdate(string, format, defaults=[]):
1057 1057 """parse a localized time string and return a (unixtime, offset) tuple.
1058 1058 if the string cannot be parsed, ValueError is raised."""
1059 1059 def timezone(string):
1060 1060 tz = string.split()[-1]
1061 1061 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1062 1062 sign = (tz[0] == "+") and 1 or -1
1063 1063 hours = int(tz[1:3])
1064 1064 minutes = int(tz[3:5])
1065 1065 return -sign * (hours * 60 + minutes) * 60
1066 1066 if tz == "GMT" or tz == "UTC":
1067 1067 return 0
1068 1068 return None
1069 1069
1070 1070 # NOTE: unixtime = localunixtime + offset
1071 1071 offset, date = timezone(string), string
1072 1072 if offset is not None:
1073 1073 date = " ".join(string.split()[:-1])
1074 1074
1075 1075 # add missing elements from defaults
1076 1076 usenow = False # default to using biased defaults
1077 1077 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
1078 1078 found = [True for p in part if ("%"+p) in format]
1079 1079 if not found:
1080 1080 date += "@" + defaults[part][usenow]
1081 1081 format += "@%" + part[0]
1082 1082 else:
1083 1083 # We've found a specific time element, less specific time
1084 1084 # elements are relative to today
1085 1085 usenow = True
1086 1086
1087 1087 timetuple = time.strptime(date, format)
1088 1088 localunixtime = int(calendar.timegm(timetuple))
1089 1089 if offset is None:
1090 1090 # local timezone
1091 1091 unixtime = int(time.mktime(timetuple))
1092 1092 offset = unixtime - localunixtime
1093 1093 else:
1094 1094 unixtime = localunixtime + offset
1095 1095 return unixtime, offset
1096 1096
1097 1097 def parsedate(date, formats=None, bias={}):
1098 1098 """parse a localized date/time and return a (unixtime, offset) tuple.
1099 1099
1100 1100 The date may be a "unixtime offset" string or in one of the specified
1101 1101 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1102 1102 """
1103 1103 if not date:
1104 1104 return 0, 0
1105 1105 if isinstance(date, tuple) and len(date) == 2:
1106 1106 return date
1107 1107 if not formats:
1108 1108 formats = defaultdateformats
1109 1109 date = date.strip()
1110 1110 try:
1111 1111 when, offset = map(int, date.split(' '))
1112 1112 except ValueError:
1113 1113 # fill out defaults
1114 1114 now = makedate()
1115 1115 defaults = {}
1116 1116 nowmap = {}
1117 1117 for part in ("d", "mb", "yY", "HI", "M", "S"):
1118 1118 # this piece is for rounding the specific end of unknowns
1119 1119 b = bias.get(part)
1120 1120 if b is None:
1121 1121 if part[0] in "HMS":
1122 1122 b = "00"
1123 1123 else:
1124 1124 b = "0"
1125 1125
1126 1126 # this piece is for matching the generic end to today's date
1127 1127 n = datestr(now, "%" + part[0])
1128 1128
1129 1129 defaults[part] = (b, n)
1130 1130
1131 1131 for format in formats:
1132 1132 try:
1133 1133 when, offset = strdate(date, format, defaults)
1134 1134 except (ValueError, OverflowError):
1135 1135 pass
1136 1136 else:
1137 1137 break
1138 1138 else:
1139 1139 raise Abort(_('invalid date: %r') % date)
1140 1140 # validate explicit (probably user-specified) date and
1141 1141 # time zone offset. values must fit in signed 32 bits for
1142 1142 # current 32-bit linux runtimes. timezones go from UTC-12
1143 1143 # to UTC+14
1144 1144 if abs(when) > 0x7fffffff:
1145 1145 raise Abort(_('date exceeds 32 bits: %d') % when)
1146 1146 if when < 0:
1147 1147 raise Abort(_('negative date value: %d') % when)
1148 1148 if offset < -50400 or offset > 43200:
1149 1149 raise Abort(_('impossible time zone offset: %d') % offset)
1150 1150 return when, offset
1151 1151
1152 1152 def matchdate(date):
1153 1153 """Return a function that matches a given date match specifier
1154 1154
1155 1155 Formats include:
1156 1156
1157 1157 '{date}' match a given date to the accuracy provided
1158 1158
1159 1159 '<{date}' on or before a given date
1160 1160
1161 1161 '>{date}' on or after a given date
1162 1162
1163 1163 >>> p1 = parsedate("10:29:59")
1164 1164 >>> p2 = parsedate("10:30:00")
1165 1165 >>> p3 = parsedate("10:30:59")
1166 1166 >>> p4 = parsedate("10:31:00")
1167 1167 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1168 1168 >>> f = matchdate("10:30")
1169 1169 >>> f(p1[0])
1170 1170 False
1171 1171 >>> f(p2[0])
1172 1172 True
1173 1173 >>> f(p3[0])
1174 1174 True
1175 1175 >>> f(p4[0])
1176 1176 False
1177 1177 >>> f(p5[0])
1178 1178 False
1179 1179 """
1180 1180
1181 1181 def lower(date):
1182 1182 d = dict(mb="1", d="1")
1183 1183 return parsedate(date, extendeddateformats, d)[0]
1184 1184
1185 1185 def upper(date):
1186 1186 d = dict(mb="12", HI="23", M="59", S="59")
1187 1187 for days in ("31", "30", "29"):
1188 1188 try:
1189 1189 d["d"] = days
1190 1190 return parsedate(date, extendeddateformats, d)[0]
1191 1191 except:
1192 1192 pass
1193 1193 d["d"] = "28"
1194 1194 return parsedate(date, extendeddateformats, d)[0]
1195 1195
1196 1196 date = date.strip()
1197 1197 if date[0] == "<":
1198 1198 when = upper(date[1:])
1199 1199 return lambda x: x <= when
1200 1200 elif date[0] == ">":
1201 1201 when = lower(date[1:])
1202 1202 return lambda x: x >= when
1203 1203 elif date[0] == "-":
1204 1204 try:
1205 1205 days = int(date[1:])
1206 1206 except ValueError:
1207 1207 raise Abort(_("invalid day spec: %s") % date[1:])
1208 1208 when = makedate()[0] - days * 3600 * 24
1209 1209 return lambda x: x >= when
1210 1210 elif " to " in date:
1211 1211 a, b = date.split(" to ")
1212 1212 start, stop = lower(a), upper(b)
1213 1213 return lambda x: x >= start and x <= stop
1214 1214 else:
1215 1215 start, stop = lower(date), upper(date)
1216 1216 return lambda x: x >= start and x <= stop
1217 1217
1218 1218 def shortuser(user):
1219 1219 """Return a short representation of a user name or email address."""
1220 1220 f = user.find('@')
1221 1221 if f >= 0:
1222 1222 user = user[:f]
1223 1223 f = user.find('<')
1224 1224 if f >= 0:
1225 1225 user = user[f + 1:]
1226 1226 f = user.find(' ')
1227 1227 if f >= 0:
1228 1228 user = user[:f]
1229 1229 f = user.find('.')
1230 1230 if f >= 0:
1231 1231 user = user[:f]
1232 1232 return user
1233 1233
1234 1234 def email(author):
1235 1235 '''get email of author.'''
1236 1236 r = author.find('>')
1237 1237 if r == -1:
1238 1238 r = None
1239 1239 return author[author.find('<') + 1:r]
1240 1240
1241 1241 def _ellipsis(text, maxlength):
1242 1242 if len(text) <= maxlength:
1243 1243 return text, False
1244 1244 else:
1245 1245 return "%s..." % (text[:maxlength - 3]), True
1246 1246
1247 1247 def ellipsis(text, maxlength=400):
1248 1248 """Trim string to at most maxlength (default: 400) characters."""
1249 1249 try:
1250 1250 # use unicode not to split at intermediate multi-byte sequence
1251 1251 utext, truncated = _ellipsis(text.decode(encoding.encoding),
1252 1252 maxlength)
1253 1253 if not truncated:
1254 1254 return text
1255 1255 return utext.encode(encoding.encoding)
1256 1256 except (UnicodeDecodeError, UnicodeEncodeError):
1257 1257 return _ellipsis(text, maxlength)[0]
1258 1258
1259 1259 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
1260 1260 '''yield every hg repository under path, recursively.'''
1261 1261 def errhandler(err):
1262 1262 if err.filename == path:
1263 1263 raise err
1264 1264 if followsym and hasattr(os.path, 'samestat'):
1265 1265 def _add_dir_if_not_there(dirlst, dirname):
1266 1266 match = False
1267 1267 samestat = os.path.samestat
1268 1268 dirstat = os.stat(dirname)
1269 1269 for lstdirstat in dirlst:
1270 1270 if samestat(dirstat, lstdirstat):
1271 1271 match = True
1272 1272 break
1273 1273 if not match:
1274 1274 dirlst.append(dirstat)
1275 1275 return not match
1276 1276 else:
1277 1277 followsym = False
1278 1278
1279 1279 if (seen_dirs is None) and followsym:
1280 1280 seen_dirs = []
1281 1281 _add_dir_if_not_there(seen_dirs, path)
1282 1282 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
1283 1283 dirs.sort()
1284 1284 if '.hg' in dirs:
1285 1285 yield root # found a repository
1286 1286 qroot = os.path.join(root, '.hg', 'patches')
1287 1287 if os.path.isdir(os.path.join(qroot, '.hg')):
1288 1288 yield qroot # we have a patch queue repo here
1289 1289 if recurse:
1290 1290 # avoid recursing inside the .hg directory
1291 1291 dirs.remove('.hg')
1292 1292 else:
1293 1293 dirs[:] = [] # don't descend further
1294 1294 elif followsym:
1295 1295 newdirs = []
1296 1296 for d in dirs:
1297 1297 fname = os.path.join(root, d)
1298 1298 if _add_dir_if_not_there(seen_dirs, fname):
1299 1299 if os.path.islink(fname):
1300 1300 for hgname in walkrepos(fname, True, seen_dirs):
1301 1301 yield hgname
1302 1302 else:
1303 1303 newdirs.append(d)
1304 1304 dirs[:] = newdirs
1305 1305
1306 1306 _rcpath = None
1307 1307
1308 1308 def os_rcpath():
1309 1309 '''return default os-specific hgrc search path'''
1310 1310 path = system_rcpath()
1311 1311 path.extend(user_rcpath())
1312 1312 path = [os.path.normpath(f) for f in path]
1313 1313 return path
1314 1314
1315 1315 def rcpath():
1316 1316 '''return hgrc search path. if env var HGRCPATH is set, use it.
1317 1317 for each item in path, if directory, use files ending in .rc,
1318 1318 else use item.
1319 1319 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1320 1320 if no HGRCPATH, use default os-specific path.'''
1321 1321 global _rcpath
1322 1322 if _rcpath is None:
1323 1323 if 'HGRCPATH' in os.environ:
1324 1324 _rcpath = []
1325 1325 for p in os.environ['HGRCPATH'].split(os.pathsep):
1326 1326 if not p:
1327 1327 continue
1328 1328 p = expandpath(p)
1329 1329 if os.path.isdir(p):
1330 1330 for f, kind in osutil.listdir(p):
1331 1331 if f.endswith('.rc'):
1332 1332 _rcpath.append(os.path.join(p, f))
1333 1333 else:
1334 1334 _rcpath.append(p)
1335 1335 else:
1336 1336 _rcpath = os_rcpath()
1337 1337 return _rcpath
1338 1338
1339 1339 def bytecount(nbytes):
1340 1340 '''return byte count formatted as readable string, with units'''
1341 1341
1342 1342 units = (
1343 1343 (100, 1 << 30, _('%.0f GB')),
1344 1344 (10, 1 << 30, _('%.1f GB')),
1345 1345 (1, 1 << 30, _('%.2f GB')),
1346 1346 (100, 1 << 20, _('%.0f MB')),
1347 1347 (10, 1 << 20, _('%.1f MB')),
1348 1348 (1, 1 << 20, _('%.2f MB')),
1349 1349 (100, 1 << 10, _('%.0f KB')),
1350 1350 (10, 1 << 10, _('%.1f KB')),
1351 1351 (1, 1 << 10, _('%.2f KB')),
1352 1352 (1, 1, _('%.0f bytes')),
1353 1353 )
1354 1354
1355 1355 for multiplier, divisor, format in units:
1356 1356 if nbytes >= divisor * multiplier:
1357 1357 return format % (nbytes / float(divisor))
1358 1358 return units[-1][2] % nbytes
1359 1359
1360 1360 def drop_scheme(scheme, path):
1361 1361 sc = scheme + ':'
1362 1362 if path.startswith(sc):
1363 1363 path = path[len(sc):]
1364 1364 if path.startswith('//'):
1365 1365 if scheme == 'file':
1366 1366 i = path.find('/', 2)
1367 1367 if i == -1:
1368 1368 return ''
1369 1369 # On Windows, absolute paths are rooted at the current drive
1370 1370 # root. On POSIX they are rooted at the file system root.
1371 1371 if os.name == 'nt':
1372 1372 droot = os.path.splitdrive(os.getcwd())[0] + '/'
1373 1373 path = os.path.join(droot, path[i + 1:])
1374 1374 else:
1375 1375 path = path[i:]
1376 1376 else:
1377 1377 path = path[2:]
1378 1378 return path
1379 1379
1380 1380 def uirepr(s):
1381 1381 # Avoid double backslash in Windows path repr()
1382 1382 return repr(s).replace('\\\\', '\\')
1383 1383
1384 1384 # delay import of textwrap
1385 1385 def MBTextWrapper(**kwargs):
1386 1386 class tw(textwrap.TextWrapper):
1387 1387 """
1388 1388 Extend TextWrapper for double-width characters.
1389 1389
1390 1390 Some Asian characters use two terminal columns instead of one.
1391 1391 A good example of this behavior can be seen with u'\u65e5\u672c',
1392 1392 the two Japanese characters for "Japan":
1393 1393 len() returns 2, but when printed to a terminal, they eat 4 columns.
1394 1394
1395 1395 (Note that this has nothing to do whatsoever with unicode
1396 1396 representation, or encoding of the underlying string)
1397 1397 """
1398 1398 def __init__(self, **kwargs):
1399 1399 textwrap.TextWrapper.__init__(self, **kwargs)
1400 1400
1401 1401 def _cutdown(self, str, space_left):
1402 1402 l = 0
1403 1403 ucstr = unicode(str, encoding.encoding)
1404 1404 colwidth = unicodedata.east_asian_width
1405 1405 for i in xrange(len(ucstr)):
1406 1406 l += colwidth(ucstr[i]) in 'WFA' and 2 or 1
1407 1407 if space_left < l:
1408 1408 return (ucstr[:i].encode(encoding.encoding),
1409 1409 ucstr[i:].encode(encoding.encoding))
1410 1410 return str, ''
1411 1411
1412 1412 # overriding of base class
1413 1413 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1414 1414 space_left = max(width - cur_len, 1)
1415 1415
1416 1416 if self.break_long_words:
1417 1417 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1418 1418 cur_line.append(cut)
1419 1419 reversed_chunks[-1] = res
1420 1420 elif not cur_line:
1421 1421 cur_line.append(reversed_chunks.pop())
1422 1422
1423 1423 global MBTextWrapper
1424 1424 MBTextWrapper = tw
1425 1425 return tw(**kwargs)
1426 1426
1427 1427 def wrap(line, width, initindent='', hangindent=''):
1428 1428 maxindent = max(len(hangindent), len(initindent))
1429 1429 if width <= maxindent:
1430 1430 # adjust for weird terminal size
1431 1431 width = max(78, maxindent + 1)
1432 1432 wrapper = MBTextWrapper(width=width,
1433 1433 initial_indent=initindent,
1434 1434 subsequent_indent=hangindent)
1435 1435 return wrapper.fill(line)
1436 1436
1437 1437 def iterlines(iterator):
1438 1438 for chunk in iterator:
1439 1439 for line in chunk.splitlines():
1440 1440 yield line
1441 1441
1442 1442 def expandpath(path):
1443 1443 return os.path.expanduser(os.path.expandvars(path))
1444 1444
1445 1445 def hgcmd():
1446 1446 """Return the command used to execute current hg
1447 1447
1448 1448 This is different from hgexecutable() because on Windows we want
1449 1449 to avoid things opening new shell windows like batch files, so we
1450 1450 get either the python call or current executable.
1451 1451 """
1452 1452 if main_is_frozen():
1453 1453 return [sys.executable]
1454 1454 return gethgcmd()
1455 1455
1456 1456 def rundetached(args, condfn):
1457 1457 """Execute the argument list in a detached process.
1458 1458
1459 1459 condfn is a callable which is called repeatedly and should return
1460 1460 True once the child process is known to have started successfully.
1461 1461 At this point, the child process PID is returned. If the child
1462 1462 process fails to start or finishes before condfn() evaluates to
1463 1463 True, return -1.
1464 1464 """
1465 1465 # Windows case is easier because the child process is either
1466 1466 # successfully starting and validating the condition or exiting
1467 1467 # on failure. We just poll on its PID. On Unix, if the child
1468 1468 # process fails to start, it will be left in a zombie state until
1469 1469 # the parent wait on it, which we cannot do since we expect a long
1470 1470 # running process on success. Instead we listen for SIGCHLD telling
1471 1471 # us our child process terminated.
1472 1472 terminated = set()
1473 1473 def handler(signum, frame):
1474 1474 terminated.add(os.wait())
1475 1475 prevhandler = None
1476 1476 if hasattr(signal, 'SIGCHLD'):
1477 1477 prevhandler = signal.signal(signal.SIGCHLD, handler)
1478 1478 try:
1479 1479 pid = spawndetached(args)
1480 1480 while not condfn():
1481 1481 if ((pid in terminated or not testpid(pid))
1482 1482 and not condfn()):
1483 1483 return -1
1484 1484 time.sleep(0.1)
1485 1485 return pid
1486 1486 finally:
1487 1487 if prevhandler is not None:
1488 1488 signal.signal(signal.SIGCHLD, prevhandler)
1489 1489
1490 1490 try:
1491 1491 any, all = any, all
1492 1492 except NameError:
1493 1493 def any(iterable):
1494 1494 for i in iterable:
1495 1495 if i:
1496 1496 return True
1497 1497 return False
1498 1498
1499 1499 def all(iterable):
1500 1500 for i in iterable:
1501 1501 if not i:
1502 1502 return False
1503 1503 return True
1504 1504
1505 def interpolate(prefix, mapping, s, fn=None):
1505 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1506 1506 """Return the result of interpolating items in the mapping into string s.
1507 1507
1508 1508 prefix is a single character string, or a two character string with
1509 1509 a backslash as the first character if the prefix needs to be escaped in
1510 1510 a regular expression.
1511 1511
1512 1512 fn is an optional function that will be applied to the replacement text
1513 1513 just before replacement.
1514
1515 escape_prefix is an optional flag that allows using doubled prefix for
1516 its escaping.
1514 1517 """
1515 1518 fn = fn or (lambda s: s)
1516 r = re.compile(r'%s(%s)' % (prefix, '|'.join(mapping.keys())))
1519 patterns = '|'.join(mapping.keys())
1520 if escape_prefix:
1521 patterns += '|' + prefix
1522 if len(prefix) > 1:
1523 prefix_char = prefix[1:]
1524 else:
1525 prefix_char = prefix
1526 mapping[prefix_char] = prefix_char
1527 r = re.compile(r'%s(%s)' % (prefix, patterns))
1517 1528 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1518 1529
1519 1530 def getport(port):
1520 1531 """Return the port for a given network service.
1521 1532
1522 1533 If port is an integer, it's returned as is. If it's a string, it's
1523 1534 looked up using socket.getservbyname(). If there's no matching
1524 1535 service, util.Abort is raised.
1525 1536 """
1526 1537 try:
1527 1538 return int(port)
1528 1539 except ValueError:
1529 1540 pass
1530 1541
1531 1542 try:
1532 1543 return socket.getservbyname(port)
1533 1544 except socket.error:
1534 1545 raise Abort(_("no port number associated with service '%s'") % port)
1535 1546
1536 1547 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1537 1548 '0': False, 'no': False, 'false': False, 'off': False,
1538 1549 'never': False}
1539 1550
1540 1551 def parsebool(s):
1541 1552 """Parse s into a boolean.
1542 1553
1543 1554 If s is not a valid boolean, returns None.
1544 1555 """
1545 1556 return _booleans.get(s.lower(), None)
General Comments 0
You need to be logged in to leave comments. Login now