comparison upmana/mercurial/util.py @ 121:496dbf12a6cb alpha

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