##// END OF EJS Templates
util: use propertycache in opener instead of __getattr__
Simon Heimberg -
r9112:54eb3782 default
parent child Browse files
Show More
@@ -1,1284 +1,1282
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, incorporated herein by reference.
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
18 18 import cStringIO, errno, re, shutil, sys, tempfile, traceback
19 19 import os, stat, time, calendar, random, textwrap
20 20 import imp
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 try:
32 32 from hashlib import sha1 as _sha1
33 33 except ImportError:
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 subprocess
40 40 closefds = os.name == 'posix'
41 41 def popen2(cmd):
42 42 # Setting bufsize to -1 lets the system decide the buffer size.
43 43 # The default for bufsize is 0, meaning unbuffered. This leads to
44 44 # poor performance on Mac OS X: http://bugs.python.org/issue4194
45 45 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
46 46 close_fds=closefds,
47 47 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
48 48 return p.stdin, p.stdout
49 49 def popen3(cmd):
50 50 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
51 51 close_fds=closefds,
52 52 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
53 53 stderr=subprocess.PIPE)
54 54 return p.stdin, p.stdout, p.stderr
55 55
56 56 def version():
57 57 """Return version information if available."""
58 58 try:
59 59 import __version__
60 60 return __version__.version
61 61 except ImportError:
62 62 return 'unknown'
63 63
64 64 # used by parsedate
65 65 defaultdateformats = (
66 66 '%Y-%m-%d %H:%M:%S',
67 67 '%Y-%m-%d %I:%M:%S%p',
68 68 '%Y-%m-%d %H:%M',
69 69 '%Y-%m-%d %I:%M%p',
70 70 '%Y-%m-%d',
71 71 '%m-%d',
72 72 '%m/%d',
73 73 '%m/%d/%y',
74 74 '%m/%d/%Y',
75 75 '%a %b %d %H:%M:%S %Y',
76 76 '%a %b %d %I:%M:%S%p %Y',
77 77 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
78 78 '%b %d %H:%M:%S %Y',
79 79 '%b %d %I:%M:%S%p %Y',
80 80 '%b %d %H:%M:%S',
81 81 '%b %d %I:%M:%S%p',
82 82 '%b %d %H:%M',
83 83 '%b %d %I:%M%p',
84 84 '%b %d %Y',
85 85 '%b %d',
86 86 '%H:%M:%S',
87 87 '%I:%M:%SP',
88 88 '%H:%M',
89 89 '%I:%M%p',
90 90 )
91 91
92 92 extendeddateformats = defaultdateformats + (
93 93 "%Y",
94 94 "%Y-%m",
95 95 "%b",
96 96 "%b %Y",
97 97 )
98 98
99 99 def cachefunc(func):
100 100 '''cache the result of function calls'''
101 101 # XXX doesn't handle keywords args
102 102 cache = {}
103 103 if func.func_code.co_argcount == 1:
104 104 # we gain a small amount of time because
105 105 # we don't need to pack/unpack the list
106 106 def f(arg):
107 107 if arg not in cache:
108 108 cache[arg] = func(arg)
109 109 return cache[arg]
110 110 else:
111 111 def f(*args):
112 112 if args not in cache:
113 113 cache[args] = func(*args)
114 114 return cache[args]
115 115
116 116 return f
117 117
118 118 def lrucachefunc(func):
119 119 '''cache most recent results of function calls'''
120 120 cache = {}
121 121 order = []
122 122 if func.func_code.co_argcount == 1:
123 123 def f(arg):
124 124 if arg not in cache:
125 125 if len(cache) > 20:
126 126 del cache[order.pop(0)]
127 127 cache[arg] = func(arg)
128 128 else:
129 129 order.remove(arg)
130 130 order.append(arg)
131 131 return cache[arg]
132 132 else:
133 133 def f(*args):
134 134 if args not in cache:
135 135 if len(cache) > 20:
136 136 del cache[order.pop(0)]
137 137 cache[args] = func(*args)
138 138 else:
139 139 order.remove(args)
140 140 order.append(args)
141 141 return cache[args]
142 142
143 143 return f
144 144
145 145 class propertycache(object):
146 146 def __init__(self, func):
147 147 self.func = func
148 148 self.name = func.__name__
149 149 def __get__(self, obj, type=None):
150 150 result = self.func(obj)
151 151 setattr(obj, self.name, result)
152 152 return result
153 153
154 154 def pipefilter(s, cmd):
155 155 '''filter string S through command CMD, returning its output'''
156 156 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
157 157 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
158 158 pout, perr = p.communicate(s)
159 159 return pout
160 160
161 161 def tempfilter(s, cmd):
162 162 '''filter string S through a pair of temporary files with CMD.
163 163 CMD is used as a template to create the real command to be run,
164 164 with the strings INFILE and OUTFILE replaced by the real names of
165 165 the temporary files generated.'''
166 166 inname, outname = None, None
167 167 try:
168 168 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
169 169 fp = os.fdopen(infd, 'wb')
170 170 fp.write(s)
171 171 fp.close()
172 172 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
173 173 os.close(outfd)
174 174 cmd = cmd.replace('INFILE', inname)
175 175 cmd = cmd.replace('OUTFILE', outname)
176 176 code = os.system(cmd)
177 177 if sys.platform == 'OpenVMS' and code & 1:
178 178 code = 0
179 179 if code: raise Abort(_("command '%s' failed: %s") %
180 180 (cmd, explain_exit(code)))
181 181 return open(outname, 'rb').read()
182 182 finally:
183 183 try:
184 184 if inname: os.unlink(inname)
185 185 except: pass
186 186 try:
187 187 if outname: os.unlink(outname)
188 188 except: pass
189 189
190 190 filtertable = {
191 191 'tempfile:': tempfilter,
192 192 'pipe:': pipefilter,
193 193 }
194 194
195 195 def filter(s, cmd):
196 196 "filter a string through a command that transforms its input to its output"
197 197 for name, fn in filtertable.iteritems():
198 198 if cmd.startswith(name):
199 199 return fn(s, cmd[len(name):].lstrip())
200 200 return pipefilter(s, cmd)
201 201
202 202 def binary(s):
203 203 """return true if a string is binary data"""
204 204 return bool(s and '\0' in s)
205 205
206 206 def increasingchunks(source, min=1024, max=65536):
207 207 '''return no less than min bytes per chunk while data remains,
208 208 doubling min after each chunk until it reaches max'''
209 209 def log2(x):
210 210 if not x:
211 211 return 0
212 212 i = 0
213 213 while x:
214 214 x >>= 1
215 215 i += 1
216 216 return i - 1
217 217
218 218 buf = []
219 219 blen = 0
220 220 for chunk in source:
221 221 buf.append(chunk)
222 222 blen += len(chunk)
223 223 if blen >= min:
224 224 if min < max:
225 225 min = min << 1
226 226 nmin = 1 << log2(blen)
227 227 if nmin > min:
228 228 min = nmin
229 229 if min > max:
230 230 min = max
231 231 yield ''.join(buf)
232 232 blen = 0
233 233 buf = []
234 234 if buf:
235 235 yield ''.join(buf)
236 236
237 237 Abort = error.Abort
238 238
239 239 def always(fn): return True
240 240 def never(fn): return False
241 241
242 242 def pathto(root, n1, n2):
243 243 '''return the relative path from one place to another.
244 244 root should use os.sep to separate directories
245 245 n1 should use os.sep to separate directories
246 246 n2 should use "/" to separate directories
247 247 returns an os.sep-separated path.
248 248
249 249 If n1 is a relative path, it's assumed it's
250 250 relative to root.
251 251 n2 should always be relative to root.
252 252 '''
253 253 if not n1: return localpath(n2)
254 254 if os.path.isabs(n1):
255 255 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
256 256 return os.path.join(root, localpath(n2))
257 257 n2 = '/'.join((pconvert(root), n2))
258 258 a, b = splitpath(n1), n2.split('/')
259 259 a.reverse()
260 260 b.reverse()
261 261 while a and b and a[-1] == b[-1]:
262 262 a.pop()
263 263 b.pop()
264 264 b.reverse()
265 265 return os.sep.join((['..'] * len(a)) + b) or '.'
266 266
267 267 def canonpath(root, cwd, myname):
268 268 """return the canonical path of myname, given cwd and root"""
269 269 if root == os.sep:
270 270 rootsep = os.sep
271 271 elif endswithsep(root):
272 272 rootsep = root
273 273 else:
274 274 rootsep = root + os.sep
275 275 name = myname
276 276 if not os.path.isabs(name):
277 277 name = os.path.join(root, cwd, name)
278 278 name = os.path.normpath(name)
279 279 audit_path = path_auditor(root)
280 280 if name != rootsep and name.startswith(rootsep):
281 281 name = name[len(rootsep):]
282 282 audit_path(name)
283 283 return pconvert(name)
284 284 elif name == root:
285 285 return ''
286 286 else:
287 287 # Determine whether `name' is in the hierarchy at or beneath `root',
288 288 # by iterating name=dirname(name) until that causes no change (can't
289 289 # check name == '/', because that doesn't work on windows). For each
290 290 # `name', compare dev/inode numbers. If they match, the list `rel'
291 291 # holds the reversed list of components making up the relative file
292 292 # name we want.
293 293 root_st = os.stat(root)
294 294 rel = []
295 295 while True:
296 296 try:
297 297 name_st = os.stat(name)
298 298 except OSError:
299 299 break
300 300 if samestat(name_st, root_st):
301 301 if not rel:
302 302 # name was actually the same as root (maybe a symlink)
303 303 return ''
304 304 rel.reverse()
305 305 name = os.path.join(*rel)
306 306 audit_path(name)
307 307 return pconvert(name)
308 308 dirname, basename = os.path.split(name)
309 309 rel.append(basename)
310 310 if dirname == name:
311 311 break
312 312 name = dirname
313 313
314 314 raise Abort('%s not under root' % myname)
315 315
316 316 _hgexecutable = None
317 317
318 318 def main_is_frozen():
319 319 """return True if we are a frozen executable.
320 320
321 321 The code supports py2exe (most common, Windows only) and tools/freeze
322 322 (portable, not much used).
323 323 """
324 324 return (hasattr(sys, "frozen") or # new py2exe
325 325 hasattr(sys, "importers") or # old py2exe
326 326 imp.is_frozen("__main__")) # tools/freeze
327 327
328 328 def hgexecutable():
329 329 """return location of the 'hg' executable.
330 330
331 331 Defaults to $HG or 'hg' in the search path.
332 332 """
333 333 if _hgexecutable is None:
334 334 hg = os.environ.get('HG')
335 335 if hg:
336 336 set_hgexecutable(hg)
337 337 elif main_is_frozen():
338 338 set_hgexecutable(sys.executable)
339 339 else:
340 340 set_hgexecutable(find_exe('hg') or 'hg')
341 341 return _hgexecutable
342 342
343 343 def set_hgexecutable(path):
344 344 """set location of the 'hg' executable"""
345 345 global _hgexecutable
346 346 _hgexecutable = path
347 347
348 348 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
349 349 '''enhanced shell command execution.
350 350 run with environment maybe modified, maybe in different dir.
351 351
352 352 if command fails and onerr is None, return status. if ui object,
353 353 print error message and return status, else raise onerr object as
354 354 exception.'''
355 355 def py2shell(val):
356 356 'convert python object into string that is useful to shell'
357 357 if val is None or val is False:
358 358 return '0'
359 359 if val is True:
360 360 return '1'
361 361 return str(val)
362 362 oldenv = {}
363 363 for k in environ:
364 364 oldenv[k] = os.environ.get(k)
365 365 if cwd is not None:
366 366 oldcwd = os.getcwd()
367 367 origcmd = cmd
368 368 if os.name == 'nt':
369 369 cmd = '"%s"' % cmd
370 370 try:
371 371 for k, v in environ.iteritems():
372 372 os.environ[k] = py2shell(v)
373 373 os.environ['HG'] = hgexecutable()
374 374 if cwd is not None and oldcwd != cwd:
375 375 os.chdir(cwd)
376 376 rc = os.system(cmd)
377 377 if sys.platform == 'OpenVMS' and rc & 1:
378 378 rc = 0
379 379 if rc and onerr:
380 380 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
381 381 explain_exit(rc)[0])
382 382 if errprefix:
383 383 errmsg = '%s: %s' % (errprefix, errmsg)
384 384 try:
385 385 onerr.warn(errmsg + '\n')
386 386 except AttributeError:
387 387 raise onerr(errmsg)
388 388 return rc
389 389 finally:
390 390 for k, v in oldenv.iteritems():
391 391 if v is None:
392 392 del os.environ[k]
393 393 else:
394 394 os.environ[k] = v
395 395 if cwd is not None and oldcwd != cwd:
396 396 os.chdir(oldcwd)
397 397
398 398 def checksignature(func):
399 399 '''wrap a function with code to check for calling errors'''
400 400 def check(*args, **kwargs):
401 401 try:
402 402 return func(*args, **kwargs)
403 403 except TypeError:
404 404 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
405 405 raise error.SignatureError
406 406 raise
407 407
408 408 return check
409 409
410 410 # os.path.lexists is not available on python2.3
411 411 def lexists(filename):
412 412 "test whether a file with this name exists. does not follow symlinks"
413 413 try:
414 414 os.lstat(filename)
415 415 except:
416 416 return False
417 417 return True
418 418
419 419 def rename(src, dst):
420 420 """forcibly rename a file"""
421 421 try:
422 422 os.rename(src, dst)
423 423 except OSError, err: # FIXME: check err (EEXIST ?)
424 424
425 425 # On windows, rename to existing file is not allowed, so we
426 426 # must delete destination first. But if a file is open, unlink
427 427 # schedules it for delete but does not delete it. Rename
428 428 # happens immediately even for open files, so we rename
429 429 # destination to a temporary name, then delete that. Then
430 430 # rename is safe to do.
431 431 # The temporary name is chosen at random to avoid the situation
432 432 # where a file is left lying around from a previous aborted run.
433 433 # The usual race condition this introduces can't be avoided as
434 434 # we need the name to rename into, and not the file itself. Due
435 435 # to the nature of the operation however, any races will at worst
436 436 # lead to the rename failing and the current operation aborting.
437 437
438 438 def tempname(prefix):
439 439 for tries in xrange(10):
440 440 temp = '%s-%08x' % (prefix, random.randint(0, 0xffffffff))
441 441 if not os.path.exists(temp):
442 442 return temp
443 443 raise IOError, (errno.EEXIST, "No usable temporary filename found")
444 444
445 445 temp = tempname(dst)
446 446 os.rename(dst, temp)
447 447 os.unlink(temp)
448 448 os.rename(src, dst)
449 449
450 450 def unlink(f):
451 451 """unlink and remove the directory if it is empty"""
452 452 os.unlink(f)
453 453 # try removing directories that might now be empty
454 454 try:
455 455 os.removedirs(os.path.dirname(f))
456 456 except OSError:
457 457 pass
458 458
459 459 def copyfile(src, dest):
460 460 "copy a file, preserving mode and atime/mtime"
461 461 if os.path.islink(src):
462 462 try:
463 463 os.unlink(dest)
464 464 except:
465 465 pass
466 466 os.symlink(os.readlink(src), dest)
467 467 else:
468 468 try:
469 469 shutil.copyfile(src, dest)
470 470 shutil.copystat(src, dest)
471 471 except shutil.Error, inst:
472 472 raise Abort(str(inst))
473 473
474 474 def copyfiles(src, dst, hardlink=None):
475 475 """Copy a directory tree using hardlinks if possible"""
476 476
477 477 if hardlink is None:
478 478 hardlink = (os.stat(src).st_dev ==
479 479 os.stat(os.path.dirname(dst)).st_dev)
480 480
481 481 if os.path.isdir(src):
482 482 os.mkdir(dst)
483 483 for name, kind in osutil.listdir(src):
484 484 srcname = os.path.join(src, name)
485 485 dstname = os.path.join(dst, name)
486 486 copyfiles(srcname, dstname, hardlink)
487 487 else:
488 488 if hardlink:
489 489 try:
490 490 os_link(src, dst)
491 491 except (IOError, OSError):
492 492 hardlink = False
493 493 shutil.copy(src, dst)
494 494 else:
495 495 shutil.copy(src, dst)
496 496
497 497 class path_auditor(object):
498 498 '''ensure that a filesystem path contains no banned components.
499 499 the following properties of a path are checked:
500 500
501 501 - under top-level .hg
502 502 - starts at the root of a windows drive
503 503 - contains ".."
504 504 - traverses a symlink (e.g. a/symlink_here/b)
505 505 - inside a nested repository'''
506 506
507 507 def __init__(self, root):
508 508 self.audited = set()
509 509 self.auditeddir = set()
510 510 self.root = root
511 511
512 512 def __call__(self, path):
513 513 if path in self.audited:
514 514 return
515 515 normpath = os.path.normcase(path)
516 516 parts = splitpath(normpath)
517 517 if (os.path.splitdrive(path)[0]
518 518 or parts[0].lower() in ('.hg', '.hg.', '')
519 519 or os.pardir in parts):
520 520 raise Abort(_("path contains illegal component: %s") % path)
521 521 if '.hg' in path.lower():
522 522 lparts = [p.lower() for p in parts]
523 523 for p in '.hg', '.hg.':
524 524 if p in lparts[1:]:
525 525 pos = lparts.index(p)
526 526 base = os.path.join(*parts[:pos])
527 527 raise Abort(_('path %r is inside repo %r') % (path, base))
528 528 def check(prefix):
529 529 curpath = os.path.join(self.root, prefix)
530 530 try:
531 531 st = os.lstat(curpath)
532 532 except OSError, err:
533 533 # EINVAL can be raised as invalid path syntax under win32.
534 534 # They must be ignored for patterns can be checked too.
535 535 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
536 536 raise
537 537 else:
538 538 if stat.S_ISLNK(st.st_mode):
539 539 raise Abort(_('path %r traverses symbolic link %r') %
540 540 (path, prefix))
541 541 elif (stat.S_ISDIR(st.st_mode) and
542 542 os.path.isdir(os.path.join(curpath, '.hg'))):
543 543 raise Abort(_('path %r is inside repo %r') %
544 544 (path, prefix))
545 545 parts.pop()
546 546 prefixes = []
547 547 while parts:
548 548 prefix = os.sep.join(parts)
549 549 if prefix in self.auditeddir:
550 550 break
551 551 check(prefix)
552 552 prefixes.append(prefix)
553 553 parts.pop()
554 554
555 555 self.audited.add(path)
556 556 # only add prefixes to the cache after checking everything: we don't
557 557 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
558 558 self.auditeddir.update(prefixes)
559 559
560 560 def nlinks(pathname):
561 561 """Return number of hardlinks for the given file."""
562 562 return os.lstat(pathname).st_nlink
563 563
564 564 if hasattr(os, 'link'):
565 565 os_link = os.link
566 566 else:
567 567 def os_link(src, dst):
568 568 raise OSError(0, _("Hardlinks not supported"))
569 569
570 570 def lookup_reg(key, name=None, scope=None):
571 571 return None
572 572
573 573 if os.name == 'nt':
574 574 from windows import *
575 575 else:
576 576 from posix import *
577 577
578 578 def makelock(info, pathname):
579 579 try:
580 580 return os.symlink(info, pathname)
581 581 except OSError, why:
582 582 if why.errno == errno.EEXIST:
583 583 raise
584 584 except AttributeError: # no symlink in os
585 585 pass
586 586
587 587 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
588 588 os.write(ld, info)
589 589 os.close(ld)
590 590
591 591 def readlock(pathname):
592 592 try:
593 593 return os.readlink(pathname)
594 594 except OSError, why:
595 595 if why.errno not in (errno.EINVAL, errno.ENOSYS):
596 596 raise
597 597 except AttributeError: # no symlink in os
598 598 pass
599 599 return posixfile(pathname).read()
600 600
601 601 def fstat(fp):
602 602 '''stat file object that may not have fileno method.'''
603 603 try:
604 604 return os.fstat(fp.fileno())
605 605 except AttributeError:
606 606 return os.stat(fp.name)
607 607
608 608 # File system features
609 609
610 610 def checkcase(path):
611 611 """
612 612 Check whether the given path is on a case-sensitive filesystem
613 613
614 614 Requires a path (like /foo/.hg) ending with a foldable final
615 615 directory component.
616 616 """
617 617 s1 = os.stat(path)
618 618 d, b = os.path.split(path)
619 619 p2 = os.path.join(d, b.upper())
620 620 if path == p2:
621 621 p2 = os.path.join(d, b.lower())
622 622 try:
623 623 s2 = os.stat(p2)
624 624 if s2 == s1:
625 625 return False
626 626 return True
627 627 except:
628 628 return True
629 629
630 630 _fspathcache = {}
631 631 def fspath(name, root):
632 632 '''Get name in the case stored in the filesystem
633 633
634 634 The name is either relative to root, or it is an absolute path starting
635 635 with root. Note that this function is unnecessary, and should not be
636 636 called, for case-sensitive filesystems (simply because it's expensive).
637 637 '''
638 638 # If name is absolute, make it relative
639 639 if name.lower().startswith(root.lower()):
640 640 l = len(root)
641 641 if name[l] == os.sep or name[l] == os.altsep:
642 642 l = l + 1
643 643 name = name[l:]
644 644
645 645 if not os.path.exists(os.path.join(root, name)):
646 646 return None
647 647
648 648 seps = os.sep
649 649 if os.altsep:
650 650 seps = seps + os.altsep
651 651 # Protect backslashes. This gets silly very quickly.
652 652 seps.replace('\\','\\\\')
653 653 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
654 654 dir = os.path.normcase(os.path.normpath(root))
655 655 result = []
656 656 for part, sep in pattern.findall(name):
657 657 if sep:
658 658 result.append(sep)
659 659 continue
660 660
661 661 if dir not in _fspathcache:
662 662 _fspathcache[dir] = os.listdir(dir)
663 663 contents = _fspathcache[dir]
664 664
665 665 lpart = part.lower()
666 666 for n in contents:
667 667 if n.lower() == lpart:
668 668 result.append(n)
669 669 break
670 670 else:
671 671 # Cannot happen, as the file exists!
672 672 result.append(part)
673 673 dir = os.path.join(dir, lpart)
674 674
675 675 return ''.join(result)
676 676
677 677 def checkexec(path):
678 678 """
679 679 Check whether the given path is on a filesystem with UNIX-like exec flags
680 680
681 681 Requires a directory (like /foo/.hg)
682 682 """
683 683
684 684 # VFAT on some Linux versions can flip mode but it doesn't persist
685 685 # a FS remount. Frequently we can detect it if files are created
686 686 # with exec bit on.
687 687
688 688 try:
689 689 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
690 690 fh, fn = tempfile.mkstemp("", "", path)
691 691 try:
692 692 os.close(fh)
693 693 m = os.stat(fn).st_mode & 0777
694 694 new_file_has_exec = m & EXECFLAGS
695 695 os.chmod(fn, m ^ EXECFLAGS)
696 696 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
697 697 finally:
698 698 os.unlink(fn)
699 699 except (IOError, OSError):
700 700 # we don't care, the user probably won't be able to commit anyway
701 701 return False
702 702 return not (new_file_has_exec or exec_flags_cannot_flip)
703 703
704 704 def checklink(path):
705 705 """check whether the given path is on a symlink-capable filesystem"""
706 706 # mktemp is not racy because symlink creation will fail if the
707 707 # file already exists
708 708 name = tempfile.mktemp(dir=path)
709 709 try:
710 710 os.symlink(".", name)
711 711 os.unlink(name)
712 712 return True
713 713 except (OSError, AttributeError):
714 714 return False
715 715
716 716 def needbinarypatch():
717 717 """return True if patches should be applied in binary mode by default."""
718 718 return os.name == 'nt'
719 719
720 720 def endswithsep(path):
721 721 '''Check path ends with os.sep or os.altsep.'''
722 722 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
723 723
724 724 def splitpath(path):
725 725 '''Split path by os.sep.
726 726 Note that this function does not use os.altsep because this is
727 727 an alternative of simple "xxx.split(os.sep)".
728 728 It is recommended to use os.path.normpath() before using this
729 729 function if need.'''
730 730 return path.split(os.sep)
731 731
732 732 def gui():
733 733 '''Are we running in a GUI?'''
734 734 return os.name == "nt" or os.name == "mac" or os.environ.get("DISPLAY")
735 735
736 736 def mktempcopy(name, emptyok=False, createmode=None):
737 737 """Create a temporary file with the same contents from name
738 738
739 739 The permission bits are copied from the original file.
740 740
741 741 If the temporary file is going to be truncated immediately, you
742 742 can use emptyok=True as an optimization.
743 743
744 744 Returns the name of the temporary file.
745 745 """
746 746 d, fn = os.path.split(name)
747 747 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
748 748 os.close(fd)
749 749 # Temporary files are created with mode 0600, which is usually not
750 750 # what we want. If the original file already exists, just copy
751 751 # its mode. Otherwise, manually obey umask.
752 752 try:
753 753 st_mode = os.lstat(name).st_mode & 0777
754 754 except OSError, inst:
755 755 if inst.errno != errno.ENOENT:
756 756 raise
757 757 st_mode = createmode
758 758 if st_mode is None:
759 759 st_mode = ~umask
760 760 st_mode &= 0666
761 761 os.chmod(temp, st_mode)
762 762 if emptyok:
763 763 return temp
764 764 try:
765 765 try:
766 766 ifp = posixfile(name, "rb")
767 767 except IOError, inst:
768 768 if inst.errno == errno.ENOENT:
769 769 return temp
770 770 if not getattr(inst, 'filename', None):
771 771 inst.filename = name
772 772 raise
773 773 ofp = posixfile(temp, "wb")
774 774 for chunk in filechunkiter(ifp):
775 775 ofp.write(chunk)
776 776 ifp.close()
777 777 ofp.close()
778 778 except:
779 779 try: os.unlink(temp)
780 780 except: pass
781 781 raise
782 782 return temp
783 783
784 784 class atomictempfile(object):
785 785 """file-like object that atomically updates a file
786 786
787 787 All writes will be redirected to a temporary copy of the original
788 788 file. When rename is called, the copy is renamed to the original
789 789 name, making the changes visible.
790 790 """
791 791 def __init__(self, name, mode, createmode):
792 792 self.__name = name
793 793 self._fp = None
794 794 self.temp = mktempcopy(name, emptyok=('w' in mode),
795 795 createmode=createmode)
796 796 self._fp = posixfile(self.temp, mode)
797 797
798 798 def __getattr__(self, name):
799 799 return getattr(self._fp, name)
800 800
801 801 def rename(self):
802 802 if not self._fp.closed:
803 803 self._fp.close()
804 804 rename(self.temp, localpath(self.__name))
805 805
806 806 def __del__(self):
807 807 if not self._fp:
808 808 return
809 809 if not self._fp.closed:
810 810 try:
811 811 os.unlink(self.temp)
812 812 except: pass
813 813 self._fp.close()
814 814
815 815 def makedirs(name, mode=None):
816 816 """recursive directory creation with parent mode inheritance"""
817 817 try:
818 818 os.mkdir(name)
819 819 if mode is not None:
820 820 os.chmod(name, mode)
821 821 return
822 822 except OSError, err:
823 823 if err.errno == errno.EEXIST:
824 824 return
825 825 if err.errno != errno.ENOENT:
826 826 raise
827 827 parent = os.path.abspath(os.path.dirname(name))
828 828 makedirs(parent, mode)
829 829 makedirs(name, mode)
830 830
831 831 class opener(object):
832 832 """Open files relative to a base directory
833 833
834 834 This class is used to hide the details of COW semantics and
835 835 remote file access from higher level code.
836 836 """
837 837 def __init__(self, base, audit=True):
838 838 self.base = base
839 839 if audit:
840 840 self.audit_path = path_auditor(base)
841 841 else:
842 842 self.audit_path = always
843 843 self.createmode = None
844 844
845 def __getattr__(self, name):
846 if name == '_can_symlink':
847 self._can_symlink = checklink(self.base)
848 return self._can_symlink
849 raise AttributeError(name)
845 @propertycache
846 def _can_symlink(self):
847 return checklink(self.base)
850 848
851 849 def _fixfilemode(self, name):
852 850 if self.createmode is None:
853 851 return
854 852 os.chmod(name, self.createmode & 0666)
855 853
856 854 def __call__(self, path, mode="r", text=False, atomictemp=False):
857 855 self.audit_path(path)
858 856 f = os.path.join(self.base, path)
859 857
860 858 if not text and "b" not in mode:
861 859 mode += "b" # for that other OS
862 860
863 861 nlink = -1
864 862 if mode not in ("r", "rb"):
865 863 try:
866 864 nlink = nlinks(f)
867 865 except OSError:
868 866 nlink = 0
869 867 d = os.path.dirname(f)
870 868 if not os.path.isdir(d):
871 869 makedirs(d, self.createmode)
872 870 if atomictemp:
873 871 return atomictempfile(f, mode, self.createmode)
874 872 if nlink > 1:
875 873 rename(mktempcopy(f), f)
876 874 fp = posixfile(f, mode)
877 875 if nlink == 0:
878 876 self._fixfilemode(f)
879 877 return fp
880 878
881 879 def symlink(self, src, dst):
882 880 self.audit_path(dst)
883 881 linkname = os.path.join(self.base, dst)
884 882 try:
885 883 os.unlink(linkname)
886 884 except OSError:
887 885 pass
888 886
889 887 dirname = os.path.dirname(linkname)
890 888 if not os.path.exists(dirname):
891 889 makedirs(dirname, self.createmode)
892 890
893 891 if self._can_symlink:
894 892 try:
895 893 os.symlink(src, linkname)
896 894 except OSError, err:
897 895 raise OSError(err.errno, _('could not symlink to %r: %s') %
898 896 (src, err.strerror), linkname)
899 897 else:
900 898 f = self(dst, "w")
901 899 f.write(src)
902 900 f.close()
903 901 self._fixfilemode(dst)
904 902
905 903 class chunkbuffer(object):
906 904 """Allow arbitrary sized chunks of data to be efficiently read from an
907 905 iterator over chunks of arbitrary size."""
908 906
909 907 def __init__(self, in_iter):
910 908 """in_iter is the iterator that's iterating over the input chunks.
911 909 targetsize is how big a buffer to try to maintain."""
912 910 self.iter = iter(in_iter)
913 911 self.buf = ''
914 912 self.targetsize = 2**16
915 913
916 914 def read(self, l):
917 915 """Read L bytes of data from the iterator of chunks of data.
918 916 Returns less than L bytes if the iterator runs dry."""
919 917 if l > len(self.buf) and self.iter:
920 918 # Clamp to a multiple of self.targetsize
921 919 targetsize = max(l, self.targetsize)
922 920 collector = cStringIO.StringIO()
923 921 collector.write(self.buf)
924 922 collected = len(self.buf)
925 923 for chunk in self.iter:
926 924 collector.write(chunk)
927 925 collected += len(chunk)
928 926 if collected >= targetsize:
929 927 break
930 928 if collected < targetsize:
931 929 self.iter = False
932 930 self.buf = collector.getvalue()
933 931 if len(self.buf) == l:
934 932 s, self.buf = str(self.buf), ''
935 933 else:
936 934 s, self.buf = self.buf[:l], buffer(self.buf, l)
937 935 return s
938 936
939 937 def filechunkiter(f, size=65536, limit=None):
940 938 """Create a generator that produces the data in the file size
941 939 (default 65536) bytes at a time, up to optional limit (default is
942 940 to read all data). Chunks may be less than size bytes if the
943 941 chunk is the last chunk in the file, or the file is a socket or
944 942 some other type of file that sometimes reads less data than is
945 943 requested."""
946 944 assert size >= 0
947 945 assert limit is None or limit >= 0
948 946 while True:
949 947 if limit is None: nbytes = size
950 948 else: nbytes = min(limit, size)
951 949 s = nbytes and f.read(nbytes)
952 950 if not s: break
953 951 if limit: limit -= len(s)
954 952 yield s
955 953
956 954 def makedate():
957 955 lt = time.localtime()
958 956 if lt[8] == 1 and time.daylight:
959 957 tz = time.altzone
960 958 else:
961 959 tz = time.timezone
962 960 return time.mktime(lt), tz
963 961
964 962 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
965 963 """represent a (unixtime, offset) tuple as a localized time.
966 964 unixtime is seconds since the epoch, and offset is the time zone's
967 965 number of seconds away from UTC. if timezone is false, do not
968 966 append time zone to string."""
969 967 t, tz = date or makedate()
970 968 if "%1" in format or "%2" in format:
971 969 sign = (tz > 0) and "-" or "+"
972 970 minutes = abs(tz) // 60
973 971 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
974 972 format = format.replace("%2", "%02d" % (minutes % 60))
975 973 s = time.strftime(format, time.gmtime(float(t) - tz))
976 974 return s
977 975
978 976 def shortdate(date=None):
979 977 """turn (timestamp, tzoff) tuple into iso 8631 date."""
980 978 return datestr(date, format='%Y-%m-%d')
981 979
982 980 def strdate(string, format, defaults=[]):
983 981 """parse a localized time string and return a (unixtime, offset) tuple.
984 982 if the string cannot be parsed, ValueError is raised."""
985 983 def timezone(string):
986 984 tz = string.split()[-1]
987 985 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
988 986 sign = (tz[0] == "+") and 1 or -1
989 987 hours = int(tz[1:3])
990 988 minutes = int(tz[3:5])
991 989 return -sign * (hours * 60 + minutes) * 60
992 990 if tz == "GMT" or tz == "UTC":
993 991 return 0
994 992 return None
995 993
996 994 # NOTE: unixtime = localunixtime + offset
997 995 offset, date = timezone(string), string
998 996 if offset != None:
999 997 date = " ".join(string.split()[:-1])
1000 998
1001 999 # add missing elements from defaults
1002 1000 for part in defaults:
1003 1001 found = [True for p in part if ("%"+p) in format]
1004 1002 if not found:
1005 1003 date += "@" + defaults[part]
1006 1004 format += "@%" + part[0]
1007 1005
1008 1006 timetuple = time.strptime(date, format)
1009 1007 localunixtime = int(calendar.timegm(timetuple))
1010 1008 if offset is None:
1011 1009 # local timezone
1012 1010 unixtime = int(time.mktime(timetuple))
1013 1011 offset = unixtime - localunixtime
1014 1012 else:
1015 1013 unixtime = localunixtime + offset
1016 1014 return unixtime, offset
1017 1015
1018 1016 def parsedate(date, formats=None, defaults=None):
1019 1017 """parse a localized date/time string and return a (unixtime, offset) tuple.
1020 1018
1021 1019 The date may be a "unixtime offset" string or in one of the specified
1022 1020 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1023 1021 """
1024 1022 if not date:
1025 1023 return 0, 0
1026 1024 if isinstance(date, tuple) and len(date) == 2:
1027 1025 return date
1028 1026 if not formats:
1029 1027 formats = defaultdateformats
1030 1028 date = date.strip()
1031 1029 try:
1032 1030 when, offset = map(int, date.split(' '))
1033 1031 except ValueError:
1034 1032 # fill out defaults
1035 1033 if not defaults:
1036 1034 defaults = {}
1037 1035 now = makedate()
1038 1036 for part in "d mb yY HI M S".split():
1039 1037 if part not in defaults:
1040 1038 if part[0] in "HMS":
1041 1039 defaults[part] = "00"
1042 1040 else:
1043 1041 defaults[part] = datestr(now, "%" + part[0])
1044 1042
1045 1043 for format in formats:
1046 1044 try:
1047 1045 when, offset = strdate(date, format, defaults)
1048 1046 except (ValueError, OverflowError):
1049 1047 pass
1050 1048 else:
1051 1049 break
1052 1050 else:
1053 1051 raise Abort(_('invalid date: %r ') % date)
1054 1052 # validate explicit (probably user-specified) date and
1055 1053 # time zone offset. values must fit in signed 32 bits for
1056 1054 # current 32-bit linux runtimes. timezones go from UTC-12
1057 1055 # to UTC+14
1058 1056 if abs(when) > 0x7fffffff:
1059 1057 raise Abort(_('date exceeds 32 bits: %d') % when)
1060 1058 if offset < -50400 or offset > 43200:
1061 1059 raise Abort(_('impossible time zone offset: %d') % offset)
1062 1060 return when, offset
1063 1061
1064 1062 def matchdate(date):
1065 1063 """Return a function that matches a given date match specifier
1066 1064
1067 1065 Formats include:
1068 1066
1069 1067 '{date}' match a given date to the accuracy provided
1070 1068
1071 1069 '<{date}' on or before a given date
1072 1070
1073 1071 '>{date}' on or after a given date
1074 1072
1075 1073 """
1076 1074
1077 1075 def lower(date):
1078 1076 d = dict(mb="1", d="1")
1079 1077 return parsedate(date, extendeddateformats, d)[0]
1080 1078
1081 1079 def upper(date):
1082 1080 d = dict(mb="12", HI="23", M="59", S="59")
1083 1081 for days in "31 30 29".split():
1084 1082 try:
1085 1083 d["d"] = days
1086 1084 return parsedate(date, extendeddateformats, d)[0]
1087 1085 except:
1088 1086 pass
1089 1087 d["d"] = "28"
1090 1088 return parsedate(date, extendeddateformats, d)[0]
1091 1089
1092 1090 date = date.strip()
1093 1091 if date[0] == "<":
1094 1092 when = upper(date[1:])
1095 1093 return lambda x: x <= when
1096 1094 elif date[0] == ">":
1097 1095 when = lower(date[1:])
1098 1096 return lambda x: x >= when
1099 1097 elif date[0] == "-":
1100 1098 try:
1101 1099 days = int(date[1:])
1102 1100 except ValueError:
1103 1101 raise Abort(_("invalid day spec: %s") % date[1:])
1104 1102 when = makedate()[0] - days * 3600 * 24
1105 1103 return lambda x: x >= when
1106 1104 elif " to " in date:
1107 1105 a, b = date.split(" to ")
1108 1106 start, stop = lower(a), upper(b)
1109 1107 return lambda x: x >= start and x <= stop
1110 1108 else:
1111 1109 start, stop = lower(date), upper(date)
1112 1110 return lambda x: x >= start and x <= stop
1113 1111
1114 1112 def shortuser(user):
1115 1113 """Return a short representation of a user name or email address."""
1116 1114 f = user.find('@')
1117 1115 if f >= 0:
1118 1116 user = user[:f]
1119 1117 f = user.find('<')
1120 1118 if f >= 0:
1121 1119 user = user[f+1:]
1122 1120 f = user.find(' ')
1123 1121 if f >= 0:
1124 1122 user = user[:f]
1125 1123 f = user.find('.')
1126 1124 if f >= 0:
1127 1125 user = user[:f]
1128 1126 return user
1129 1127
1130 1128 def email(author):
1131 1129 '''get email of author.'''
1132 1130 r = author.find('>')
1133 1131 if r == -1: r = None
1134 1132 return author[author.find('<')+1:r]
1135 1133
1136 1134 def ellipsis(text, maxlength=400):
1137 1135 """Trim string to at most maxlength (default: 400) characters."""
1138 1136 if len(text) <= maxlength:
1139 1137 return text
1140 1138 else:
1141 1139 return "%s..." % (text[:maxlength-3])
1142 1140
1143 1141 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
1144 1142 '''yield every hg repository under path, recursively.'''
1145 1143 def errhandler(err):
1146 1144 if err.filename == path:
1147 1145 raise err
1148 1146 if followsym and hasattr(os.path, 'samestat'):
1149 1147 def _add_dir_if_not_there(dirlst, dirname):
1150 1148 match = False
1151 1149 samestat = os.path.samestat
1152 1150 dirstat = os.stat(dirname)
1153 1151 for lstdirstat in dirlst:
1154 1152 if samestat(dirstat, lstdirstat):
1155 1153 match = True
1156 1154 break
1157 1155 if not match:
1158 1156 dirlst.append(dirstat)
1159 1157 return not match
1160 1158 else:
1161 1159 followsym = False
1162 1160
1163 1161 if (seen_dirs is None) and followsym:
1164 1162 seen_dirs = []
1165 1163 _add_dir_if_not_there(seen_dirs, path)
1166 1164 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
1167 1165 if '.hg' in dirs:
1168 1166 yield root # found a repository
1169 1167 qroot = os.path.join(root, '.hg', 'patches')
1170 1168 if os.path.isdir(os.path.join(qroot, '.hg')):
1171 1169 yield qroot # we have a patch queue repo here
1172 1170 if recurse:
1173 1171 # avoid recursing inside the .hg directory
1174 1172 dirs.remove('.hg')
1175 1173 else:
1176 1174 dirs[:] = [] # don't descend further
1177 1175 elif followsym:
1178 1176 newdirs = []
1179 1177 for d in dirs:
1180 1178 fname = os.path.join(root, d)
1181 1179 if _add_dir_if_not_there(seen_dirs, fname):
1182 1180 if os.path.islink(fname):
1183 1181 for hgname in walkrepos(fname, True, seen_dirs):
1184 1182 yield hgname
1185 1183 else:
1186 1184 newdirs.append(d)
1187 1185 dirs[:] = newdirs
1188 1186
1189 1187 _rcpath = None
1190 1188
1191 1189 def os_rcpath():
1192 1190 '''return default os-specific hgrc search path'''
1193 1191 path = system_rcpath()
1194 1192 path.extend(user_rcpath())
1195 1193 path = [os.path.normpath(f) for f in path]
1196 1194 return path
1197 1195
1198 1196 def rcpath():
1199 1197 '''return hgrc search path. if env var HGRCPATH is set, use it.
1200 1198 for each item in path, if directory, use files ending in .rc,
1201 1199 else use item.
1202 1200 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1203 1201 if no HGRCPATH, use default os-specific path.'''
1204 1202 global _rcpath
1205 1203 if _rcpath is None:
1206 1204 if 'HGRCPATH' in os.environ:
1207 1205 _rcpath = []
1208 1206 for p in os.environ['HGRCPATH'].split(os.pathsep):
1209 1207 if not p: continue
1210 1208 if os.path.isdir(p):
1211 1209 for f, kind in osutil.listdir(p):
1212 1210 if f.endswith('.rc'):
1213 1211 _rcpath.append(os.path.join(p, f))
1214 1212 else:
1215 1213 _rcpath.append(p)
1216 1214 else:
1217 1215 _rcpath = os_rcpath()
1218 1216 return _rcpath
1219 1217
1220 1218 def bytecount(nbytes):
1221 1219 '''return byte count formatted as readable string, with units'''
1222 1220
1223 1221 units = (
1224 1222 (100, 1<<30, _('%.0f GB')),
1225 1223 (10, 1<<30, _('%.1f GB')),
1226 1224 (1, 1<<30, _('%.2f GB')),
1227 1225 (100, 1<<20, _('%.0f MB')),
1228 1226 (10, 1<<20, _('%.1f MB')),
1229 1227 (1, 1<<20, _('%.2f MB')),
1230 1228 (100, 1<<10, _('%.0f KB')),
1231 1229 (10, 1<<10, _('%.1f KB')),
1232 1230 (1, 1<<10, _('%.2f KB')),
1233 1231 (1, 1, _('%.0f bytes')),
1234 1232 )
1235 1233
1236 1234 for multiplier, divisor, format in units:
1237 1235 if nbytes >= divisor * multiplier:
1238 1236 return format % (nbytes / float(divisor))
1239 1237 return units[-1][2] % nbytes
1240 1238
1241 1239 def drop_scheme(scheme, path):
1242 1240 sc = scheme + ':'
1243 1241 if path.startswith(sc):
1244 1242 path = path[len(sc):]
1245 1243 if path.startswith('//'):
1246 1244 path = path[2:]
1247 1245 return path
1248 1246
1249 1247 def uirepr(s):
1250 1248 # Avoid double backslash in Windows path repr()
1251 1249 return repr(s).replace('\\\\', '\\')
1252 1250
1253 1251 def termwidth():
1254 1252 if 'COLUMNS' in os.environ:
1255 1253 try:
1256 1254 return int(os.environ['COLUMNS'])
1257 1255 except ValueError:
1258 1256 pass
1259 1257 try:
1260 1258 import termios, array, fcntl
1261 1259 for dev in (sys.stdout, sys.stdin):
1262 1260 try:
1263 1261 try:
1264 1262 fd = dev.fileno()
1265 1263 except AttributeError:
1266 1264 continue
1267 1265 if not os.isatty(fd):
1268 1266 continue
1269 1267 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
1270 1268 return array.array('h', arri)[1]
1271 1269 except ValueError:
1272 1270 pass
1273 1271 except ImportError:
1274 1272 pass
1275 1273 return 80
1276 1274
1277 1275 def wrap(line, hangindent, width=78):
1278 1276 padding = '\n' + ' ' * hangindent
1279 1277 return padding.join(textwrap.wrap(line, width=width - hangindent))
1280 1278
1281 1279 def iterlines(iterator):
1282 1280 for chunk in iterator:
1283 1281 for line in chunk.splitlines():
1284 1282 yield line
General Comments 0
You need to be logged in to leave comments. Login now