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