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