##// END OF EJS Templates
Add cachefunc to abstract function call cache
Brendan Cully -
r3145:e4ea47c2 default
parent child Browse files
Show More
@@ -1,998 +1,1014
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, 2006 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 gettext as _
16 16 from demandload import *
17 17 demandload(globals(), "cStringIO errno getpass popen2 re shutil sys tempfile")
18 18 demandload(globals(), "os threading time")
19 19
20 20 # used by parsedate
21 21 defaultdateformats = ('%Y-%m-%d %H:%M:%S', '%Y-%m-%d %H:%M',
22 22 '%a %b %d %H:%M:%S %Y')
23 23
24 24 class SignalInterrupt(Exception):
25 25 """Exception raised on SIGTERM and SIGHUP."""
26 26
27 def cachefunc(func):
28 '''cache the result of function calls'''
29 cache = {}
30 if func.func_code.co_argcount == 1:
31 def f(arg):
32 if arg not in cache:
33 cache[arg] = func(arg)
34 return cache[arg]
35 else:
36 def f(*args):
37 if args not in cache:
38 cache[args] = func(*args)
39 return cache[args]
40
41 return f
42
27 43 def pipefilter(s, cmd):
28 44 '''filter string S through command CMD, returning its output'''
29 45 (pout, pin) = popen2.popen2(cmd, -1, 'b')
30 46 def writer():
31 47 try:
32 48 pin.write(s)
33 49 pin.close()
34 50 except IOError, inst:
35 51 if inst.errno != errno.EPIPE:
36 52 raise
37 53
38 54 # we should use select instead on UNIX, but this will work on most
39 55 # systems, including Windows
40 56 w = threading.Thread(target=writer)
41 57 w.start()
42 58 f = pout.read()
43 59 pout.close()
44 60 w.join()
45 61 return f
46 62
47 63 def tempfilter(s, cmd):
48 64 '''filter string S through a pair of temporary files with CMD.
49 65 CMD is used as a template to create the real command to be run,
50 66 with the strings INFILE and OUTFILE replaced by the real names of
51 67 the temporary files generated.'''
52 68 inname, outname = None, None
53 69 try:
54 70 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
55 71 fp = os.fdopen(infd, 'wb')
56 72 fp.write(s)
57 73 fp.close()
58 74 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
59 75 os.close(outfd)
60 76 cmd = cmd.replace('INFILE', inname)
61 77 cmd = cmd.replace('OUTFILE', outname)
62 78 code = os.system(cmd)
63 79 if code: raise Abort(_("command '%s' failed: %s") %
64 80 (cmd, explain_exit(code)))
65 81 return open(outname, 'rb').read()
66 82 finally:
67 83 try:
68 84 if inname: os.unlink(inname)
69 85 except: pass
70 86 try:
71 87 if outname: os.unlink(outname)
72 88 except: pass
73 89
74 90 filtertable = {
75 91 'tempfile:': tempfilter,
76 92 'pipe:': pipefilter,
77 93 }
78 94
79 95 def filter(s, cmd):
80 96 "filter a string through a command that transforms its input to its output"
81 97 for name, fn in filtertable.iteritems():
82 98 if cmd.startswith(name):
83 99 return fn(s, cmd[len(name):].lstrip())
84 100 return pipefilter(s, cmd)
85 101
86 102 def find_in_path(name, path, default=None):
87 103 '''find name in search path. path can be string (will be split
88 104 with os.pathsep), or iterable thing that returns strings. if name
89 105 found, return path to name. else return default.'''
90 106 if isinstance(path, str):
91 107 path = path.split(os.pathsep)
92 108 for p in path:
93 109 p_name = os.path.join(p, name)
94 110 if os.path.exists(p_name):
95 111 return p_name
96 112 return default
97 113
98 114 def binary(s):
99 115 """return true if a string is binary data using diff's heuristic"""
100 116 if s and '\0' in s[:4096]:
101 117 return True
102 118 return False
103 119
104 120 def unique(g):
105 121 """return the uniq elements of iterable g"""
106 122 seen = {}
107 123 for f in g:
108 124 if f not in seen:
109 125 seen[f] = 1
110 126 yield f
111 127
112 128 class Abort(Exception):
113 129 """Raised if a command needs to print an error and exit."""
114 130
115 131 def always(fn): return True
116 132 def never(fn): return False
117 133
118 134 def patkind(name, dflt_pat='glob'):
119 135 """Split a string into an optional pattern kind prefix and the
120 136 actual pattern."""
121 137 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
122 138 if name.startswith(prefix + ':'): return name.split(':', 1)
123 139 return dflt_pat, name
124 140
125 141 def globre(pat, head='^', tail='$'):
126 142 "convert a glob pattern into a regexp"
127 143 i, n = 0, len(pat)
128 144 res = ''
129 145 group = False
130 146 def peek(): return i < n and pat[i]
131 147 while i < n:
132 148 c = pat[i]
133 149 i = i+1
134 150 if c == '*':
135 151 if peek() == '*':
136 152 i += 1
137 153 res += '.*'
138 154 else:
139 155 res += '[^/]*'
140 156 elif c == '?':
141 157 res += '.'
142 158 elif c == '[':
143 159 j = i
144 160 if j < n and pat[j] in '!]':
145 161 j += 1
146 162 while j < n and pat[j] != ']':
147 163 j += 1
148 164 if j >= n:
149 165 res += '\\['
150 166 else:
151 167 stuff = pat[i:j].replace('\\','\\\\')
152 168 i = j + 1
153 169 if stuff[0] == '!':
154 170 stuff = '^' + stuff[1:]
155 171 elif stuff[0] == '^':
156 172 stuff = '\\' + stuff
157 173 res = '%s[%s]' % (res, stuff)
158 174 elif c == '{':
159 175 group = True
160 176 res += '(?:'
161 177 elif c == '}' and group:
162 178 res += ')'
163 179 group = False
164 180 elif c == ',' and group:
165 181 res += '|'
166 182 elif c == '\\':
167 183 p = peek()
168 184 if p:
169 185 i += 1
170 186 res += re.escape(p)
171 187 else:
172 188 res += re.escape(c)
173 189 else:
174 190 res += re.escape(c)
175 191 return head + res + tail
176 192
177 193 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
178 194
179 195 def pathto(n1, n2):
180 196 '''return the relative path from one place to another.
181 197 this returns a path in the form used by the local filesystem, not hg.'''
182 198 if not n1: return localpath(n2)
183 199 a, b = n1.split('/'), n2.split('/')
184 200 a.reverse()
185 201 b.reverse()
186 202 while a and b and a[-1] == b[-1]:
187 203 a.pop()
188 204 b.pop()
189 205 b.reverse()
190 206 return os.sep.join((['..'] * len(a)) + b)
191 207
192 208 def canonpath(root, cwd, myname):
193 209 """return the canonical path of myname, given cwd and root"""
194 210 if root == os.sep:
195 211 rootsep = os.sep
196 212 elif root.endswith(os.sep):
197 213 rootsep = root
198 214 else:
199 215 rootsep = root + os.sep
200 216 name = myname
201 217 if not os.path.isabs(name):
202 218 name = os.path.join(root, cwd, name)
203 219 name = os.path.normpath(name)
204 220 if name != rootsep and name.startswith(rootsep):
205 221 name = name[len(rootsep):]
206 222 audit_path(name)
207 223 return pconvert(name)
208 224 elif name == root:
209 225 return ''
210 226 else:
211 227 # Determine whether `name' is in the hierarchy at or beneath `root',
212 228 # by iterating name=dirname(name) until that causes no change (can't
213 229 # check name == '/', because that doesn't work on windows). For each
214 230 # `name', compare dev/inode numbers. If they match, the list `rel'
215 231 # holds the reversed list of components making up the relative file
216 232 # name we want.
217 233 root_st = os.stat(root)
218 234 rel = []
219 235 while True:
220 236 try:
221 237 name_st = os.stat(name)
222 238 except OSError:
223 239 break
224 240 if samestat(name_st, root_st):
225 241 rel.reverse()
226 242 name = os.path.join(*rel)
227 243 audit_path(name)
228 244 return pconvert(name)
229 245 dirname, basename = os.path.split(name)
230 246 rel.append(basename)
231 247 if dirname == name:
232 248 break
233 249 name = dirname
234 250
235 251 raise Abort('%s not under root' % myname)
236 252
237 253 def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
238 254 return _matcher(canonroot, cwd, names, inc, exc, head, 'glob', src)
239 255
240 256 def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
241 257 if os.name == 'nt':
242 258 dflt_pat = 'glob'
243 259 else:
244 260 dflt_pat = 'relpath'
245 261 return _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src)
246 262
247 263 def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src):
248 264 """build a function to match a set of file patterns
249 265
250 266 arguments:
251 267 canonroot - the canonical root of the tree you're matching against
252 268 cwd - the current working directory, if relevant
253 269 names - patterns to find
254 270 inc - patterns to include
255 271 exc - patterns to exclude
256 272 head - a regex to prepend to patterns to control whether a match is rooted
257 273
258 274 a pattern is one of:
259 275 'glob:<rooted glob>'
260 276 're:<rooted regexp>'
261 277 'path:<rooted path>'
262 278 'relglob:<relative glob>'
263 279 'relpath:<relative path>'
264 280 'relre:<relative regexp>'
265 281 '<rooted path or regexp>'
266 282
267 283 returns:
268 284 a 3-tuple containing
269 285 - list of explicit non-pattern names passed in
270 286 - a bool match(filename) function
271 287 - a bool indicating if any patterns were passed in
272 288
273 289 todo:
274 290 make head regex a rooted bool
275 291 """
276 292
277 293 def contains_glob(name):
278 294 for c in name:
279 295 if c in _globchars: return True
280 296 return False
281 297
282 298 def regex(kind, name, tail):
283 299 '''convert a pattern into a regular expression'''
284 300 if kind == 're':
285 301 return name
286 302 elif kind == 'path':
287 303 return '^' + re.escape(name) + '(?:/|$)'
288 304 elif kind == 'relglob':
289 305 return head + globre(name, '(?:|.*/)', tail)
290 306 elif kind == 'relpath':
291 307 return head + re.escape(name) + tail
292 308 elif kind == 'relre':
293 309 if name.startswith('^'):
294 310 return name
295 311 return '.*' + name
296 312 return head + globre(name, '', tail)
297 313
298 314 def matchfn(pats, tail):
299 315 """build a matching function from a set of patterns"""
300 316 if not pats:
301 317 return
302 318 matches = []
303 319 for k, p in pats:
304 320 try:
305 321 pat = '(?:%s)' % regex(k, p, tail)
306 322 matches.append(re.compile(pat).match)
307 323 except re.error:
308 324 if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p))
309 325 else: raise Abort("invalid pattern (%s): %s" % (k, p))
310 326
311 327 def buildfn(text):
312 328 for m in matches:
313 329 r = m(text)
314 330 if r:
315 331 return r
316 332
317 333 return buildfn
318 334
319 335 def globprefix(pat):
320 336 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
321 337 root = []
322 338 for p in pat.split(os.sep):
323 339 if contains_glob(p): break
324 340 root.append(p)
325 341 return '/'.join(root)
326 342
327 343 pats = []
328 344 files = []
329 345 roots = []
330 346 for kind, name in [patkind(p, dflt_pat) for p in names]:
331 347 if kind in ('glob', 'relpath'):
332 348 name = canonpath(canonroot, cwd, name)
333 349 if name == '':
334 350 kind, name = 'glob', '**'
335 351 if kind in ('glob', 'path', 're'):
336 352 pats.append((kind, name))
337 353 if kind == 'glob':
338 354 root = globprefix(name)
339 355 if root: roots.append(root)
340 356 elif kind == 'relpath':
341 357 files.append((kind, name))
342 358 roots.append(name)
343 359
344 360 patmatch = matchfn(pats, '$') or always
345 361 filematch = matchfn(files, '(?:/|$)') or always
346 362 incmatch = always
347 363 if inc:
348 364 inckinds = [patkind(canonpath(canonroot, cwd, i)) for i in inc]
349 365 incmatch = matchfn(inckinds, '(?:/|$)')
350 366 excmatch = lambda fn: False
351 367 if exc:
352 368 exckinds = [patkind(canonpath(canonroot, cwd, x)) for x in exc]
353 369 excmatch = matchfn(exckinds, '(?:/|$)')
354 370
355 371 return (roots,
356 372 lambda fn: (incmatch(fn) and not excmatch(fn) and
357 373 (fn.endswith('/') or
358 374 (not pats and not files) or
359 375 (pats and patmatch(fn)) or
360 376 (files and filematch(fn)))),
361 377 (inc or exc or (pats and pats != [('glob', '**')])) and True)
362 378
363 379 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
364 380 '''enhanced shell command execution.
365 381 run with environment maybe modified, maybe in different dir.
366 382
367 383 if command fails and onerr is None, return status. if ui object,
368 384 print error message and return status, else raise onerr object as
369 385 exception.'''
370 386 def py2shell(val):
371 387 'convert python object into string that is useful to shell'
372 388 if val in (None, False):
373 389 return '0'
374 390 if val == True:
375 391 return '1'
376 392 return str(val)
377 393 oldenv = {}
378 394 for k in environ:
379 395 oldenv[k] = os.environ.get(k)
380 396 if cwd is not None:
381 397 oldcwd = os.getcwd()
382 398 try:
383 399 for k, v in environ.iteritems():
384 400 os.environ[k] = py2shell(v)
385 401 if cwd is not None and oldcwd != cwd:
386 402 os.chdir(cwd)
387 403 rc = os.system(cmd)
388 404 if rc and onerr:
389 405 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
390 406 explain_exit(rc)[0])
391 407 if errprefix:
392 408 errmsg = '%s: %s' % (errprefix, errmsg)
393 409 try:
394 410 onerr.warn(errmsg + '\n')
395 411 except AttributeError:
396 412 raise onerr(errmsg)
397 413 return rc
398 414 finally:
399 415 for k, v in oldenv.iteritems():
400 416 if v is None:
401 417 del os.environ[k]
402 418 else:
403 419 os.environ[k] = v
404 420 if cwd is not None and oldcwd != cwd:
405 421 os.chdir(oldcwd)
406 422
407 423 def rename(src, dst):
408 424 """forcibly rename a file"""
409 425 try:
410 426 os.rename(src, dst)
411 427 except OSError, err:
412 428 # on windows, rename to existing file is not allowed, so we
413 429 # must delete destination first. but if file is open, unlink
414 430 # schedules it for delete but does not delete it. rename
415 431 # happens immediately even for open files, so we create
416 432 # temporary file, delete it, rename destination to that name,
417 433 # then delete that. then rename is safe to do.
418 434 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
419 435 os.close(fd)
420 436 os.unlink(temp)
421 437 os.rename(dst, temp)
422 438 os.unlink(temp)
423 439 os.rename(src, dst)
424 440
425 441 def unlink(f):
426 442 """unlink and remove the directory if it is empty"""
427 443 os.unlink(f)
428 444 # try removing directories that might now be empty
429 445 try:
430 446 os.removedirs(os.path.dirname(f))
431 447 except OSError:
432 448 pass
433 449
434 450 def copyfiles(src, dst, hardlink=None):
435 451 """Copy a directory tree using hardlinks if possible"""
436 452
437 453 if hardlink is None:
438 454 hardlink = (os.stat(src).st_dev ==
439 455 os.stat(os.path.dirname(dst)).st_dev)
440 456
441 457 if os.path.isdir(src):
442 458 os.mkdir(dst)
443 459 for name in os.listdir(src):
444 460 srcname = os.path.join(src, name)
445 461 dstname = os.path.join(dst, name)
446 462 copyfiles(srcname, dstname, hardlink)
447 463 else:
448 464 if hardlink:
449 465 try:
450 466 os_link(src, dst)
451 467 except (IOError, OSError):
452 468 hardlink = False
453 469 shutil.copy(src, dst)
454 470 else:
455 471 shutil.copy(src, dst)
456 472
457 473 def audit_path(path):
458 474 """Abort if path contains dangerous components"""
459 475 parts = os.path.normcase(path).split(os.sep)
460 476 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
461 477 or os.pardir in parts):
462 478 raise Abort(_("path contains illegal component: %s\n") % path)
463 479
464 480 def _makelock_file(info, pathname):
465 481 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
466 482 os.write(ld, info)
467 483 os.close(ld)
468 484
469 485 def _readlock_file(pathname):
470 486 return posixfile(pathname).read()
471 487
472 488 def nlinks(pathname):
473 489 """Return number of hardlinks for the given file."""
474 490 return os.lstat(pathname).st_nlink
475 491
476 492 if hasattr(os, 'link'):
477 493 os_link = os.link
478 494 else:
479 495 def os_link(src, dst):
480 496 raise OSError(0, _("Hardlinks not supported"))
481 497
482 498 def fstat(fp):
483 499 '''stat file object that may not have fileno method.'''
484 500 try:
485 501 return os.fstat(fp.fileno())
486 502 except AttributeError:
487 503 return os.stat(fp.name)
488 504
489 505 posixfile = file
490 506
491 507 def is_win_9x():
492 508 '''return true if run on windows 95, 98 or me.'''
493 509 try:
494 510 return sys.getwindowsversion()[3] == 1
495 511 except AttributeError:
496 512 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
497 513
498 514 getuser_fallback = None
499 515
500 516 def getuser():
501 517 '''return name of current user'''
502 518 try:
503 519 return getpass.getuser()
504 520 except ImportError:
505 521 # import of pwd will fail on windows - try fallback
506 522 if getuser_fallback:
507 523 return getuser_fallback()
508 524 # raised if win32api not available
509 525 raise Abort(_('user name not available - set USERNAME '
510 526 'environment variable'))
511 527
512 528 # Platform specific variants
513 529 if os.name == 'nt':
514 530 demandload(globals(), "msvcrt")
515 531 nulldev = 'NUL:'
516 532
517 533 class winstdout:
518 534 '''stdout on windows misbehaves if sent through a pipe'''
519 535
520 536 def __init__(self, fp):
521 537 self.fp = fp
522 538
523 539 def __getattr__(self, key):
524 540 return getattr(self.fp, key)
525 541
526 542 def close(self):
527 543 try:
528 544 self.fp.close()
529 545 except: pass
530 546
531 547 def write(self, s):
532 548 try:
533 549 return self.fp.write(s)
534 550 except IOError, inst:
535 551 if inst.errno != 0: raise
536 552 self.close()
537 553 raise IOError(errno.EPIPE, 'Broken pipe')
538 554
539 555 sys.stdout = winstdout(sys.stdout)
540 556
541 557 def system_rcpath():
542 558 try:
543 559 return system_rcpath_win32()
544 560 except:
545 561 return [r'c:\mercurial\mercurial.ini']
546 562
547 563 def os_rcpath():
548 564 '''return default os-specific hgrc search path'''
549 565 path = system_rcpath()
550 566 path.append(user_rcpath())
551 567 userprofile = os.environ.get('USERPROFILE')
552 568 if userprofile:
553 569 path.append(os.path.join(userprofile, 'mercurial.ini'))
554 570 return path
555 571
556 572 def user_rcpath():
557 573 '''return os-specific hgrc search path to the user dir'''
558 574 return os.path.join(os.path.expanduser('~'), 'mercurial.ini')
559 575
560 576 def parse_patch_output(output_line):
561 577 """parses the output produced by patch and returns the file name"""
562 578 pf = output_line[14:]
563 579 if pf[0] == '`':
564 580 pf = pf[1:-1] # Remove the quotes
565 581 return pf
566 582
567 583 def testpid(pid):
568 584 '''return False if pid dead, True if running or not known'''
569 585 return True
570 586
571 587 def is_exec(f, last):
572 588 return last
573 589
574 590 def set_exec(f, mode):
575 591 pass
576 592
577 593 def set_binary(fd):
578 594 msvcrt.setmode(fd.fileno(), os.O_BINARY)
579 595
580 596 def pconvert(path):
581 597 return path.replace("\\", "/")
582 598
583 599 def localpath(path):
584 600 return path.replace('/', '\\')
585 601
586 602 def normpath(path):
587 603 return pconvert(os.path.normpath(path))
588 604
589 605 makelock = _makelock_file
590 606 readlock = _readlock_file
591 607
592 608 def samestat(s1, s2):
593 609 return False
594 610
595 611 def shellquote(s):
596 612 return '"%s"' % s.replace('"', '\\"')
597 613
598 614 def explain_exit(code):
599 615 return _("exited with status %d") % code, code
600 616
601 617 try:
602 618 # override functions with win32 versions if possible
603 619 from util_win32 import *
604 620 if not is_win_9x():
605 621 posixfile = posixfile_nt
606 622 except ImportError:
607 623 pass
608 624
609 625 else:
610 626 nulldev = '/dev/null'
611 627
612 628 def rcfiles(path):
613 629 rcs = [os.path.join(path, 'hgrc')]
614 630 rcdir = os.path.join(path, 'hgrc.d')
615 631 try:
616 632 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
617 633 if f.endswith(".rc")])
618 634 except OSError:
619 635 pass
620 636 return rcs
621 637
622 638 def os_rcpath():
623 639 '''return default os-specific hgrc search path'''
624 640 path = []
625 641 # old mod_python does not set sys.argv
626 642 if len(getattr(sys, 'argv', [])) > 0:
627 643 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
628 644 '/../etc/mercurial'))
629 645 path.extend(rcfiles('/etc/mercurial'))
630 646 path.append(os.path.expanduser('~/.hgrc'))
631 647 path = [os.path.normpath(f) for f in path]
632 648 return path
633 649
634 650 def parse_patch_output(output_line):
635 651 """parses the output produced by patch and returns the file name"""
636 652 pf = output_line[14:]
637 653 if pf.startswith("'") and pf.endswith("'") and " " in pf:
638 654 pf = pf[1:-1] # Remove the quotes
639 655 return pf
640 656
641 657 def is_exec(f, last):
642 658 """check whether a file is executable"""
643 659 return (os.lstat(f).st_mode & 0100 != 0)
644 660
645 661 def set_exec(f, mode):
646 662 s = os.lstat(f).st_mode
647 663 if (s & 0100 != 0) == mode:
648 664 return
649 665 if mode:
650 666 # Turn on +x for every +r bit when making a file executable
651 667 # and obey umask.
652 668 umask = os.umask(0)
653 669 os.umask(umask)
654 670 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
655 671 else:
656 672 os.chmod(f, s & 0666)
657 673
658 674 def set_binary(fd):
659 675 pass
660 676
661 677 def pconvert(path):
662 678 return path
663 679
664 680 def localpath(path):
665 681 return path
666 682
667 683 normpath = os.path.normpath
668 684 samestat = os.path.samestat
669 685
670 686 def makelock(info, pathname):
671 687 try:
672 688 os.symlink(info, pathname)
673 689 except OSError, why:
674 690 if why.errno == errno.EEXIST:
675 691 raise
676 692 else:
677 693 _makelock_file(info, pathname)
678 694
679 695 def readlock(pathname):
680 696 try:
681 697 return os.readlink(pathname)
682 698 except OSError, why:
683 699 if why.errno == errno.EINVAL:
684 700 return _readlock_file(pathname)
685 701 else:
686 702 raise
687 703
688 704 def shellquote(s):
689 705 return "'%s'" % s.replace("'", "'\\''")
690 706
691 707 def testpid(pid):
692 708 '''return False if pid dead, True if running or not sure'''
693 709 try:
694 710 os.kill(pid, 0)
695 711 return True
696 712 except OSError, inst:
697 713 return inst.errno != errno.ESRCH
698 714
699 715 def explain_exit(code):
700 716 """return a 2-tuple (desc, code) describing a process's status"""
701 717 if os.WIFEXITED(code):
702 718 val = os.WEXITSTATUS(code)
703 719 return _("exited with status %d") % val, val
704 720 elif os.WIFSIGNALED(code):
705 721 val = os.WTERMSIG(code)
706 722 return _("killed by signal %d") % val, val
707 723 elif os.WIFSTOPPED(code):
708 724 val = os.WSTOPSIG(code)
709 725 return _("stopped by signal %d") % val, val
710 726 raise ValueError(_("invalid exit code"))
711 727
712 728 def opener(base, audit=True):
713 729 """
714 730 return a function that opens files relative to base
715 731
716 732 this function is used to hide the details of COW semantics and
717 733 remote file access from higher level code.
718 734 """
719 735 p = base
720 736 audit_p = audit
721 737
722 738 def mktempcopy(name):
723 739 d, fn = os.path.split(name)
724 740 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
725 741 os.close(fd)
726 742 ofp = posixfile(temp, "wb")
727 743 try:
728 744 try:
729 745 ifp = posixfile(name, "rb")
730 746 except IOError, inst:
731 747 if not getattr(inst, 'filename', None):
732 748 inst.filename = name
733 749 raise
734 750 for chunk in filechunkiter(ifp):
735 751 ofp.write(chunk)
736 752 ifp.close()
737 753 ofp.close()
738 754 except:
739 755 try: os.unlink(temp)
740 756 except: pass
741 757 raise
742 758 st = os.lstat(name)
743 759 os.chmod(temp, st.st_mode)
744 760 return temp
745 761
746 762 class atomictempfile(posixfile):
747 763 """the file will only be copied when rename is called"""
748 764 def __init__(self, name, mode):
749 765 self.__name = name
750 766 self.temp = mktempcopy(name)
751 767 posixfile.__init__(self, self.temp, mode)
752 768 def rename(self):
753 769 if not self.closed:
754 770 posixfile.close(self)
755 771 rename(self.temp, localpath(self.__name))
756 772 def __del__(self):
757 773 if not self.closed:
758 774 try:
759 775 os.unlink(self.temp)
760 776 except: pass
761 777 posixfile.close(self)
762 778
763 779 class atomicfile(atomictempfile):
764 780 """the file will only be copied on close"""
765 781 def __init__(self, name, mode):
766 782 atomictempfile.__init__(self, name, mode)
767 783 def close(self):
768 784 self.rename()
769 785 def __del__(self):
770 786 self.rename()
771 787
772 788 def o(path, mode="r", text=False, atomic=False, atomictemp=False):
773 789 if audit_p:
774 790 audit_path(path)
775 791 f = os.path.join(p, path)
776 792
777 793 if not text:
778 794 mode += "b" # for that other OS
779 795
780 796 if mode[0] != "r":
781 797 try:
782 798 nlink = nlinks(f)
783 799 except OSError:
784 800 d = os.path.dirname(f)
785 801 if not os.path.isdir(d):
786 802 os.makedirs(d)
787 803 else:
788 804 if atomic:
789 805 return atomicfile(f, mode)
790 806 elif atomictemp:
791 807 return atomictempfile(f, mode)
792 808 if nlink > 1:
793 809 rename(mktempcopy(f), f)
794 810 return posixfile(f, mode)
795 811
796 812 return o
797 813
798 814 class chunkbuffer(object):
799 815 """Allow arbitrary sized chunks of data to be efficiently read from an
800 816 iterator over chunks of arbitrary size."""
801 817
802 818 def __init__(self, in_iter, targetsize = 2**16):
803 819 """in_iter is the iterator that's iterating over the input chunks.
804 820 targetsize is how big a buffer to try to maintain."""
805 821 self.in_iter = iter(in_iter)
806 822 self.buf = ''
807 823 self.targetsize = int(targetsize)
808 824 if self.targetsize <= 0:
809 825 raise ValueError(_("targetsize must be greater than 0, was %d") %
810 826 targetsize)
811 827 self.iterempty = False
812 828
813 829 def fillbuf(self):
814 830 """Ignore target size; read every chunk from iterator until empty."""
815 831 if not self.iterempty:
816 832 collector = cStringIO.StringIO()
817 833 collector.write(self.buf)
818 834 for ch in self.in_iter:
819 835 collector.write(ch)
820 836 self.buf = collector.getvalue()
821 837 self.iterempty = True
822 838
823 839 def read(self, l):
824 840 """Read L bytes of data from the iterator of chunks of data.
825 841 Returns less than L bytes if the iterator runs dry."""
826 842 if l > len(self.buf) and not self.iterempty:
827 843 # Clamp to a multiple of self.targetsize
828 844 targetsize = self.targetsize * ((l // self.targetsize) + 1)
829 845 collector = cStringIO.StringIO()
830 846 collector.write(self.buf)
831 847 collected = len(self.buf)
832 848 for chunk in self.in_iter:
833 849 collector.write(chunk)
834 850 collected += len(chunk)
835 851 if collected >= targetsize:
836 852 break
837 853 if collected < targetsize:
838 854 self.iterempty = True
839 855 self.buf = collector.getvalue()
840 856 s, self.buf = self.buf[:l], buffer(self.buf, l)
841 857 return s
842 858
843 859 def filechunkiter(f, size=65536, limit=None):
844 860 """Create a generator that produces the data in the file size
845 861 (default 65536) bytes at a time, up to optional limit (default is
846 862 to read all data). Chunks may be less than size bytes if the
847 863 chunk is the last chunk in the file, or the file is a socket or
848 864 some other type of file that sometimes reads less data than is
849 865 requested."""
850 866 assert size >= 0
851 867 assert limit is None or limit >= 0
852 868 while True:
853 869 if limit is None: nbytes = size
854 870 else: nbytes = min(limit, size)
855 871 s = nbytes and f.read(nbytes)
856 872 if not s: break
857 873 if limit: limit -= len(s)
858 874 yield s
859 875
860 876 def makedate():
861 877 lt = time.localtime()
862 878 if lt[8] == 1 and time.daylight:
863 879 tz = time.altzone
864 880 else:
865 881 tz = time.timezone
866 882 return time.mktime(lt), tz
867 883
868 884 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
869 885 """represent a (unixtime, offset) tuple as a localized time.
870 886 unixtime is seconds since the epoch, and offset is the time zone's
871 887 number of seconds away from UTC. if timezone is false, do not
872 888 append time zone to string."""
873 889 t, tz = date or makedate()
874 890 s = time.strftime(format, time.gmtime(float(t) - tz))
875 891 if timezone:
876 892 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
877 893 return s
878 894
879 895 def strdate(string, format='%a %b %d %H:%M:%S %Y'):
880 896 """parse a localized time string and return a (unixtime, offset) tuple.
881 897 if the string cannot be parsed, ValueError is raised."""
882 898 def hastimezone(string):
883 899 return (string[-4:].isdigit() and
884 900 (string[-5] == '+' or string[-5] == '-') and
885 901 string[-6].isspace())
886 902
887 903 if hastimezone(string):
888 904 date, tz = string[:-6], string[-5:]
889 905 tz = int(tz)
890 906 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
891 907 else:
892 908 date, offset = string, 0
893 909 when = int(time.mktime(time.strptime(date, format))) + offset
894 910 return when, offset
895 911
896 912 def parsedate(string, formats=None):
897 913 """parse a localized time string and return a (unixtime, offset) tuple.
898 914 The date may be a "unixtime offset" string or in one of the specified
899 915 formats."""
900 916 if not formats:
901 917 formats = defaultdateformats
902 918 try:
903 919 when, offset = map(int, string.split(' '))
904 920 except ValueError:
905 921 for format in formats:
906 922 try:
907 923 when, offset = strdate(string, format)
908 924 except ValueError:
909 925 pass
910 926 else:
911 927 break
912 928 else:
913 929 raise ValueError(_('invalid date: %r') % string)
914 930 # validate explicit (probably user-specified) date and
915 931 # time zone offset. values must fit in signed 32 bits for
916 932 # current 32-bit linux runtimes. timezones go from UTC-12
917 933 # to UTC+14
918 934 if abs(when) > 0x7fffffff:
919 935 raise ValueError(_('date exceeds 32 bits: %d') % when)
920 936 if offset < -50400 or offset > 43200:
921 937 raise ValueError(_('impossible time zone offset: %d') % offset)
922 938 return when, offset
923 939
924 940 def shortuser(user):
925 941 """Return a short representation of a user name or email address."""
926 942 f = user.find('@')
927 943 if f >= 0:
928 944 user = user[:f]
929 945 f = user.find('<')
930 946 if f >= 0:
931 947 user = user[f+1:]
932 948 return user
933 949
934 950 def walkrepos(path):
935 951 '''yield every hg repository under path, recursively.'''
936 952 def errhandler(err):
937 953 if err.filename == path:
938 954 raise err
939 955
940 956 for root, dirs, files in os.walk(path, onerror=errhandler):
941 957 for d in dirs:
942 958 if d == '.hg':
943 959 yield root
944 960 dirs[:] = []
945 961 break
946 962
947 963 _rcpath = None
948 964
949 965 def rcpath():
950 966 '''return hgrc search path. if env var HGRCPATH is set, use it.
951 967 for each item in path, if directory, use files ending in .rc,
952 968 else use item.
953 969 make HGRCPATH empty to only look in .hg/hgrc of current repo.
954 970 if no HGRCPATH, use default os-specific path.'''
955 971 global _rcpath
956 972 if _rcpath is None:
957 973 if 'HGRCPATH' in os.environ:
958 974 _rcpath = []
959 975 for p in os.environ['HGRCPATH'].split(os.pathsep):
960 976 if not p: continue
961 977 if os.path.isdir(p):
962 978 for f in os.listdir(p):
963 979 if f.endswith('.rc'):
964 980 _rcpath.append(os.path.join(p, f))
965 981 else:
966 982 _rcpath.append(p)
967 983 else:
968 984 _rcpath = os_rcpath()
969 985 return _rcpath
970 986
971 987 def bytecount(nbytes):
972 988 '''return byte count formatted as readable string, with units'''
973 989
974 990 units = (
975 991 (100, 1<<30, _('%.0f GB')),
976 992 (10, 1<<30, _('%.1f GB')),
977 993 (1, 1<<30, _('%.2f GB')),
978 994 (100, 1<<20, _('%.0f MB')),
979 995 (10, 1<<20, _('%.1f MB')),
980 996 (1, 1<<20, _('%.2f MB')),
981 997 (100, 1<<10, _('%.0f KB')),
982 998 (10, 1<<10, _('%.1f KB')),
983 999 (1, 1<<10, _('%.2f KB')),
984 1000 (1, 1, _('%.0f bytes')),
985 1001 )
986 1002
987 1003 for multiplier, divisor, format in units:
988 1004 if nbytes >= divisor * multiplier:
989 1005 return format % (nbytes / float(divisor))
990 1006 return units[-1][2] % nbytes
991 1007
992 1008 def drop_scheme(scheme, path):
993 1009 sc = scheme + ':'
994 1010 if path.startswith(sc):
995 1011 path = path[len(sc):]
996 1012 if path.startswith('//'):
997 1013 path = path[2:]
998 1014 return path
General Comments 0
You need to be logged in to leave comments. Login now