diff --git a/mercurial/util.py b/mercurial/util.py --- a/mercurial/util.py +++ b/mercurial/util.py @@ -179,7 +179,7 @@ def canonpath(root, cwd, myname): if root == os.sep: rootsep = os.sep else: - rootsep = root + os.sep + rootsep = root + os.sep name = myname if not name.startswith(os.sep): name = os.path.join(root, cwd, name) @@ -315,15 +315,42 @@ def _matcher(canonroot, cwd, names, inc, (files and filematch(fn)))), (inc or exc or (pats and pats != [('glob', '**')])) and True) -def system(cmd, errprefix=None): - """execute a shell command that must succeed""" - rc = os.system(cmd) - if rc: - errmsg = "%s %s" % (os.path.basename(cmd.split(None, 1)[0]), - explain_exit(rc)[0]) - if errprefix: - errmsg = "%s: %s" % (errprefix, errmsg) - raise Abort(errmsg) +def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None): + '''enhanced shell command execution. + run with environment maybe modified, maybe in different dir. + + if command fails and onerr is None, return status. if ui object, + print error message and return status, else raise onerr object as + exception.''' + oldenv = {} + for k in environ: + oldenv[k] = os.environ.get(k) + if cwd is not None: + oldcwd = os.getcwd() + try: + for k, v in environ.iteritems(): + os.environ[k] = str(v) + if cwd is not None and oldcwd != cwd: + os.chdir(cwd) + rc = os.system(cmd) + if rc and onerr: + errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]), + explain_exit(rc)[0]) + if errprefix: + errmsg = '%s: %s' % (errprefix, errmsg) + try: + onerr.warn(errmsg + '\n') + except AttributeError: + raise onerr(errmsg) + return rc + finally: + for k, v in oldenv.iteritems(): + if v is None: + del os.environ[k] + else: + os.environ[k] = v + if cwd is not None and oldcwd != cwd: + os.chdir(oldcwd) def rename(src, dst): """forcibly rename a file""" @@ -363,7 +390,14 @@ def copyfiles(src, dst, hardlink=None): else: shutil.copy(src, dst) -def opener(base): +def audit_path(path): + """Abort if path contains dangerous components""" + parts = os.path.normcase(path).split(os.sep) + if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '') + or os.pardir in parts): + raise Abort(_("path contains illegal component: %s\n") % path) + +def opener(base, audit=True): """ return a function that opens files relative to base @@ -371,6 +405,7 @@ def opener(base): remote file access from higher level code. """ p = base + audit_p = audit def mktempcopy(name): d, fn = os.path.split(name) @@ -401,6 +436,8 @@ def opener(base): self.close() def o(path, mode="r", text=False, atomic=False): + if audit_p: + audit_path(path) f = os.path.join(p, path) if not text: @@ -489,7 +526,7 @@ if os.name == 'nt': return pf try: # ActivePython can create hard links using win32file module - import win32file + import win32api, win32con, win32file def os_link(src, dst): # NB will only succeed on NTFS win32file.CreateHardLink(dst, src) @@ -506,8 +543,18 @@ if os.name == 'nt': except: return os.stat(pathname).st_nlink + def testpid(pid): + '''return False if pid is dead, True if running or not known''' + try: + win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION, + False, pid) + except: + return True + except ImportError: - pass + def testpid(pid): + '''return False if pid dead, True if running or not known''' + return True def is_exec(f, last): return last @@ -604,6 +651,14 @@ else: else: raise + def testpid(pid): + '''return False if pid dead, True if running or not sure''' + try: + os.kill(pid, 0) + return True + except OSError, inst: + return inst.errno != errno.ESRCH + def explain_exit(code): """return a 2-tuple (desc, code) describing a process's status""" if os.WIFEXITED(code): @@ -700,3 +755,16 @@ def shortuser(user): if f >= 0: user = user[f+1:] return user + +def walkrepos(path): + '''yield every hg repository under path, recursively.''' + def errhandler(err): + if err.filename == path: + raise err + + for root, dirs, files in os.walk(path, onerror=errhandler): + for d in dirs: + if d == '.hg': + yield root + dirs[:] = [] + break