comparison mercurial/util.py @ 7890:e710f0f592b2

util: split out posix, windows, and win32 modules
author Matt Mackall <mpm@selenic.com>
date Thu, 26 Mar 2009 13:54:44 -0500
parents 5c4026a289a4
children 1b1b3dd630a5
comparison
equal deleted inserted replaced
7889:5ac1a72e5b74 7890:e710f0f592b2
11 This contains helper routines that are independent of the SCM core and hide 11 This contains helper routines that are independent of the SCM core and hide
12 platform-specific details from the core. 12 platform-specific details from the core.
13 """ 13 """
14 14
15 from i18n import _ 15 from i18n import _
16 import cStringIO, errno, getpass, re, shutil, sys, tempfile, traceback, error 16 import cStringIO, errno, re, shutil, sys, tempfile, traceback, error
17 import os, stat, threading, time, calendar, ConfigParser, locale, glob, osutil 17 import os, stat, threading, time, calendar, ConfigParser, locale, glob, osutil
18 import imp, unicodedata 18 import imp, unicodedata
19 19
20 # Python compatibility 20 # Python compatibility
21 21
336 class Abort(Exception): 336 class Abort(Exception):
337 """Raised if a command needs to print an error and exit.""" 337 """Raised if a command needs to print an error and exit."""
338 338
339 def always(fn): return True 339 def always(fn): return True
340 def never(fn): return False 340 def never(fn): return False
341
342 def expand_glob(pats):
343 '''On Windows, expand the implicit globs in a list of patterns'''
344 if os.name != 'nt':
345 return list(pats)
346 ret = []
347 for p in pats:
348 kind, name = patkind(p, None)
349 if kind is None:
350 globbed = glob.glob(name)
351 if globbed:
352 ret.extend(globbed)
353 continue
354 # if we couldn't expand the glob, just keep it around
355 ret.append(p)
356 return ret
357 341
358 def patkind(name, default): 342 def patkind(name, default):
359 """Split a string into an optional pattern kind prefix and the 343 """Split a string into an optional pattern kind prefix and the
360 actual pattern.""" 344 actual pattern."""
361 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre': 345 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
856 self.audited.add(path) 840 self.audited.add(path)
857 # only add prefixes to the cache after checking everything: we don't 841 # only add prefixes to the cache after checking everything: we don't
858 # want to add "foo/bar/baz" before checking if there's a "foo/.hg" 842 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
859 self.auditeddir.update(prefixes) 843 self.auditeddir.update(prefixes)
860 844
861 def _makelock_file(info, pathname): 845 if os.name == 'nt':
846 from windows import *
847 else:
848 from posix import *
849
850 def makelock(info, pathname):
851 try:
852 return os.symlink(info, pathname)
853 except OSError, why:
854 if why.errno == errno.EEXIST:
855 raise
856 except AttributeError: # no symlink in os
857 pass
858
862 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL) 859 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
863 os.write(ld, info) 860 os.write(ld, info)
864 os.close(ld) 861 os.close(ld)
865 862
866 def _readlock_file(pathname): 863 def readlock(pathname):
864 try:
865 return os.readlink(pathname)
866 except OSError, why:
867 if why.errno not in (errno.EINVAL, errno.ENOSYS):
868 raise
869 except AttributeError: # no symlink in os
870 pass
867 return posixfile(pathname).read() 871 return posixfile(pathname).read()
868 872
869 def nlinks(pathname): 873 def nlinks(pathname):
870 """Return number of hardlinks for the given file.""" 874 """Return number of hardlinks for the given file."""
871 return os.lstat(pathname).st_nlink 875 return os.lstat(pathname).st_nlink
880 '''stat file object that may not have fileno method.''' 884 '''stat file object that may not have fileno method.'''
881 try: 885 try:
882 return os.fstat(fp.fileno()) 886 return os.fstat(fp.fileno())
883 except AttributeError: 887 except AttributeError:
884 return os.stat(fp.name) 888 return os.stat(fp.name)
885
886 posixfile = file
887
888 def openhardlinks():
889 '''return true if it is safe to hold open file handles to hardlinks'''
890 return True
891
892 def _statfiles(files):
893 'Stat each file in files and yield stat or None if file does not exist.'
894 lstat = os.lstat
895 for nf in files:
896 try:
897 st = lstat(nf)
898 except OSError, err:
899 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
900 raise
901 st = None
902 yield st
903
904 def _statfiles_clustered(files):
905 '''Stat each file in files and yield stat or None if file does not exist.
906 Cluster and cache stat per directory to minimize number of OS stat calls.'''
907 ncase = os.path.normcase
908 sep = os.sep
909 dircache = {} # dirname -> filename -> status | None if file does not exist
910 for nf in files:
911 nf = ncase(nf)
912 pos = nf.rfind(sep)
913 if pos == -1:
914 dir, base = '.', nf
915 else:
916 dir, base = nf[:pos+1], nf[pos+1:]
917 cache = dircache.get(dir, None)
918 if cache is None:
919 try:
920 dmap = dict([(ncase(n), s)
921 for n, k, s in osutil.listdir(dir, True)])
922 except OSError, err:
923 # handle directory not found in Python version prior to 2.5
924 # Python <= 2.4 returns native Windows code 3 in errno
925 # Python >= 2.5 returns ENOENT and adds winerror field
926 # EINVAL is raised if dir is not a directory.
927 if err.errno not in (3, errno.ENOENT, errno.EINVAL,
928 errno.ENOTDIR):
929 raise
930 dmap = {}
931 cache = dircache.setdefault(dir, dmap)
932 yield cache.get(base, None)
933
934 if sys.platform == 'win32':
935 statfiles = _statfiles_clustered
936 else:
937 statfiles = _statfiles
938
939 getuser_fallback = None
940
941 def getuser():
942 '''return name of current user'''
943 try:
944 return getpass.getuser()
945 except ImportError:
946 # import of pwd will fail on windows - try fallback
947 if getuser_fallback:
948 return getuser_fallback()
949 # raised if win32api not available
950 raise Abort(_('user name not available - set USERNAME '
951 'environment variable'))
952
953 def username(uid=None):
954 """Return the name of the user with the given uid.
955
956 If uid is None, return the name of the current user."""
957 try:
958 import pwd
959 if uid is None:
960 uid = os.getuid()
961 try:
962 return pwd.getpwuid(uid)[0]
963 except KeyError:
964 return str(uid)
965 except ImportError:
966 return None
967
968 def groupname(gid=None):
969 """Return the name of the group with the given gid.
970
971 If gid is None, return the name of the current group."""
972 try:
973 import grp
974 if gid is None:
975 gid = os.getgid()
976 try:
977 return grp.getgrgid(gid)[0]
978 except KeyError:
979 return str(gid)
980 except ImportError:
981 return None
982 889
983 # File system features 890 # File system features
984 891
985 def checkcase(path): 892 def checkcase(path):
986 """ 893 """
1086 os.unlink(name) 993 os.unlink(name)
1087 return True 994 return True
1088 except (OSError, AttributeError): 995 except (OSError, AttributeError):
1089 return False 996 return False
1090 997
1091 _umask = os.umask(0)
1092 os.umask(_umask)
1093
1094 def needbinarypatch(): 998 def needbinarypatch():
1095 """return True if patches should be applied in binary mode by default.""" 999 """return True if patches should be applied in binary mode by default."""
1096 return os.name == 'nt' 1000 return os.name == 'nt'
1097 1001
1098 def endswithsep(path): 1002 def endswithsep(path):
1111 '''Are we running in a GUI?''' 1015 '''Are we running in a GUI?'''
1112 return os.name == "nt" or os.name == "mac" or os.environ.get("DISPLAY") 1016 return os.name == "nt" or os.name == "mac" or os.environ.get("DISPLAY")
1113 1017
1114 def lookup_reg(key, name=None, scope=None): 1018 def lookup_reg(key, name=None, scope=None):
1115 return None 1019 return None
1116
1117 # Platform specific variants
1118 if os.name == 'nt':
1119 import msvcrt
1120 nulldev = 'NUL:'
1121
1122 class winstdout:
1123 '''stdout on windows misbehaves if sent through a pipe'''
1124
1125 def __init__(self, fp):
1126 self.fp = fp
1127
1128 def __getattr__(self, key):
1129 return getattr(self.fp, key)
1130
1131 def close(self):
1132 try:
1133 self.fp.close()
1134 except: pass
1135
1136 def write(self, s):
1137 try:
1138 # This is workaround for "Not enough space" error on
1139 # writing large size of data to console.
1140 limit = 16000
1141 l = len(s)
1142 start = 0
1143 while start < l:
1144 end = start + limit
1145 self.fp.write(s[start:end])
1146 start = end
1147 except IOError, inst:
1148 if inst.errno != 0: raise
1149 self.close()
1150 raise IOError(errno.EPIPE, 'Broken pipe')
1151
1152 def flush(self):
1153 try:
1154 return self.fp.flush()
1155 except IOError, inst:
1156 if inst.errno != errno.EINVAL: raise
1157 self.close()
1158 raise IOError(errno.EPIPE, 'Broken pipe')
1159
1160 sys.stdout = winstdout(sys.stdout)
1161
1162 def _is_win_9x():
1163 '''return true if run on windows 95, 98 or me.'''
1164 try:
1165 return sys.getwindowsversion()[3] == 1
1166 except AttributeError:
1167 return 'command' in os.environ.get('comspec', '')
1168
1169 def openhardlinks():
1170 return not _is_win_9x and "win32api" in locals()
1171
1172 def system_rcpath():
1173 try:
1174 return system_rcpath_win32()
1175 except:
1176 return [r'c:\mercurial\mercurial.ini']
1177
1178 def user_rcpath():
1179 '''return os-specific hgrc search path to the user dir'''
1180 try:
1181 path = user_rcpath_win32()
1182 except:
1183 home = os.path.expanduser('~')
1184 path = [os.path.join(home, 'mercurial.ini'),
1185 os.path.join(home, '.hgrc')]
1186 userprofile = os.environ.get('USERPROFILE')
1187 if userprofile:
1188 path.append(os.path.join(userprofile, 'mercurial.ini'))
1189 path.append(os.path.join(userprofile, '.hgrc'))
1190 return path
1191
1192 def parse_patch_output(output_line):
1193 """parses the output produced by patch and returns the file name"""
1194 pf = output_line[14:]
1195 if pf[0] == '`':
1196 pf = pf[1:-1] # Remove the quotes
1197 return pf
1198
1199 def sshargs(sshcmd, host, user, port):
1200 '''Build argument list for ssh or Plink'''
1201 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
1202 args = user and ("%s@%s" % (user, host)) or host
1203 return port and ("%s %s %s" % (args, pflag, port)) or args
1204
1205 def testpid(pid):
1206 '''return False if pid dead, True if running or not known'''
1207 return True
1208
1209 def set_flags(f, l, x):
1210 pass
1211
1212 def set_binary(fd):
1213 # When run without console, pipes may expose invalid
1214 # fileno(), usually set to -1.
1215 if hasattr(fd, 'fileno') and fd.fileno() >= 0:
1216 msvcrt.setmode(fd.fileno(), os.O_BINARY)
1217
1218 def pconvert(path):
1219 return '/'.join(splitpath(path))
1220
1221 def localpath(path):
1222 return path.replace('/', '\\')
1223
1224 def normpath(path):
1225 return pconvert(os.path.normpath(path))
1226
1227 makelock = _makelock_file
1228 readlock = _readlock_file
1229
1230 def samestat(s1, s2):
1231 return False
1232
1233 # A sequence of backslashes is special iff it precedes a double quote:
1234 # - if there's an even number of backslashes, the double quote is not
1235 # quoted (i.e. it ends the quoted region)
1236 # - if there's an odd number of backslashes, the double quote is quoted
1237 # - in both cases, every pair of backslashes is unquoted into a single
1238 # backslash
1239 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
1240 # So, to quote a string, we must surround it in double quotes, double
1241 # the number of backslashes that preceed double quotes and add another
1242 # backslash before every double quote (being careful with the double
1243 # quote we've appended to the end)
1244 _quotere = None
1245 def shellquote(s):
1246 global _quotere
1247 if _quotere is None:
1248 _quotere = re.compile(r'(\\*)("|\\$)')
1249 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
1250
1251 def quotecommand(cmd):
1252 """Build a command string suitable for os.popen* calls."""
1253 # The extra quotes are needed because popen* runs the command
1254 # through the current COMSPEC. cmd.exe suppress enclosing quotes.
1255 return '"' + cmd + '"'
1256
1257 def popen(command, mode='r'):
1258 # Work around "popen spawned process may not write to stdout
1259 # under windows"
1260 # http://bugs.python.org/issue1366
1261 command += " 2> %s" % nulldev
1262 return os.popen(quotecommand(command), mode)
1263
1264 def explain_exit(code):
1265 return _("exited with status %d") % code, code
1266
1267 # if you change this stub into a real check, please try to implement the
1268 # username and groupname functions above, too.
1269 def isowner(fp, st=None):
1270 return True
1271
1272 def find_exe(command):
1273 '''Find executable for command searching like cmd.exe does.
1274 If command is a basename then PATH is searched for command.
1275 PATH isn't searched if command is an absolute or relative path.
1276 An extension from PATHEXT is found and added if not present.
1277 If command isn't found None is returned.'''
1278 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
1279 pathexts = [ext for ext in pathext.lower().split(os.pathsep)]
1280 if os.path.splitext(command)[1].lower() in pathexts:
1281 pathexts = ['']
1282
1283 def findexisting(pathcommand):
1284 'Will append extension (if needed) and return existing file'
1285 for ext in pathexts:
1286 executable = pathcommand + ext
1287 if os.path.exists(executable):
1288 return executable
1289 return None
1290
1291 if os.sep in command:
1292 return findexisting(command)
1293
1294 for path in os.environ.get('PATH', '').split(os.pathsep):
1295 executable = findexisting(os.path.join(path, command))
1296 if executable is not None:
1297 return executable
1298 return None
1299
1300 def set_signal_handler():
1301 try:
1302 set_signal_handler_win32()
1303 except NameError:
1304 pass
1305
1306 try:
1307 # override functions with win32 versions if possible
1308 from util_win32 import *
1309 if not _is_win_9x():
1310 posixfile = posixfile_nt
1311 except ImportError:
1312 pass
1313
1314 else:
1315 nulldev = '/dev/null'
1316
1317 def rcfiles(path):
1318 rcs = [os.path.join(path, 'hgrc')]
1319 rcdir = os.path.join(path, 'hgrc.d')
1320 try:
1321 rcs.extend([os.path.join(rcdir, f)
1322 for f, kind in osutil.listdir(rcdir)
1323 if f.endswith(".rc")])
1324 except OSError:
1325 pass
1326 return rcs
1327
1328 def system_rcpath():
1329 path = []
1330 # old mod_python does not set sys.argv
1331 if len(getattr(sys, 'argv', [])) > 0:
1332 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
1333 '/../etc/mercurial'))
1334 path.extend(rcfiles('/etc/mercurial'))
1335 return path
1336
1337 def user_rcpath():
1338 return [os.path.expanduser('~/.hgrc')]
1339
1340 def parse_patch_output(output_line):
1341 """parses the output produced by patch and returns the file name"""
1342 pf = output_line[14:]
1343 if os.sys.platform == 'OpenVMS':
1344 if pf[0] == '`':
1345 pf = pf[1:-1] # Remove the quotes
1346 else:
1347 if pf.startswith("'") and pf.endswith("'") and " " in pf:
1348 pf = pf[1:-1] # Remove the quotes
1349 return pf
1350
1351 def sshargs(sshcmd, host, user, port):
1352 '''Build argument list for ssh'''
1353 args = user and ("%s@%s" % (user, host)) or host
1354 return port and ("%s -p %s" % (args, port)) or args
1355
1356 def is_exec(f):
1357 """check whether a file is executable"""
1358 return (os.lstat(f).st_mode & 0100 != 0)
1359
1360 def set_flags(f, l, x):
1361 s = os.lstat(f).st_mode
1362 if l:
1363 if not stat.S_ISLNK(s):
1364 # switch file to link
1365 data = file(f).read()
1366 os.unlink(f)
1367 try:
1368 os.symlink(data, f)
1369 except:
1370 # failed to make a link, rewrite file
1371 file(f, "w").write(data)
1372 # no chmod needed at this point
1373 return
1374 if stat.S_ISLNK(s):
1375 # switch link to file
1376 data = os.readlink(f)
1377 os.unlink(f)
1378 file(f, "w").write(data)
1379 s = 0666 & ~_umask # avoid restatting for chmod
1380
1381 sx = s & 0100
1382 if x and not sx:
1383 # Turn on +x for every +r bit when making a file executable
1384 # and obey umask.
1385 os.chmod(f, s | (s & 0444) >> 2 & ~_umask)
1386 elif not x and sx:
1387 # Turn off all +x bits
1388 os.chmod(f, s & 0666)
1389
1390 def set_binary(fd):
1391 pass
1392
1393 def pconvert(path):
1394 return path
1395
1396 def localpath(path):
1397 return path
1398
1399 normpath = os.path.normpath
1400 samestat = os.path.samestat
1401
1402 def makelock(info, pathname):
1403 try:
1404 os.symlink(info, pathname)
1405 except OSError, why:
1406 if why.errno == errno.EEXIST:
1407 raise
1408 else:
1409 _makelock_file(info, pathname)
1410
1411 def readlock(pathname):
1412 try:
1413 return os.readlink(pathname)
1414 except OSError, why:
1415 if why.errno in (errno.EINVAL, errno.ENOSYS):
1416 return _readlock_file(pathname)
1417 else:
1418 raise
1419
1420 def shellquote(s):
1421 if os.sys.platform == 'OpenVMS':
1422 return '"%s"' % s
1423 else:
1424 return "'%s'" % s.replace("'", "'\\''")
1425
1426 def quotecommand(cmd):
1427 return cmd
1428
1429 def popen(command, mode='r'):
1430 return os.popen(command, mode)
1431
1432 def testpid(pid):
1433 '''return False if pid dead, True if running or not sure'''
1434 if os.sys.platform == 'OpenVMS':
1435 return True
1436 try:
1437 os.kill(pid, 0)
1438 return True
1439 except OSError, inst:
1440 return inst.errno != errno.ESRCH
1441
1442 def explain_exit(code):
1443 """return a 2-tuple (desc, code) describing a process's status"""
1444 if os.WIFEXITED(code):
1445 val = os.WEXITSTATUS(code)
1446 return _("exited with status %d") % val, val
1447 elif os.WIFSIGNALED(code):
1448 val = os.WTERMSIG(code)
1449 return _("killed by signal %d") % val, val
1450 elif os.WIFSTOPPED(code):
1451 val = os.WSTOPSIG(code)
1452 return _("stopped by signal %d") % val, val
1453 raise ValueError(_("invalid exit code"))
1454
1455 def isowner(fp, st=None):
1456 """Return True if the file object f belongs to the current user.
1457
1458 The return value of a util.fstat(f) may be passed as the st argument.
1459 """
1460 if st is None:
1461 st = fstat(fp)
1462 return st.st_uid == os.getuid()
1463
1464 def find_exe(command):
1465 '''Find executable for command searching like which does.
1466 If command is a basename then PATH is searched for command.
1467 PATH isn't searched if command is an absolute or relative path.
1468 If command isn't found None is returned.'''
1469 if sys.platform == 'OpenVMS':
1470 return command
1471
1472 def findexisting(executable):
1473 'Will return executable if existing file'
1474 if os.path.exists(executable):
1475 return executable
1476 return None
1477
1478 if os.sep in command:
1479 return findexisting(command)
1480
1481 for path in os.environ.get('PATH', '').split(os.pathsep):
1482 executable = findexisting(os.path.join(path, command))
1483 if executable is not None:
1484 return executable
1485 return None
1486
1487 def set_signal_handler():
1488 pass
1489 1020
1490 def mktempcopy(name, emptyok=False, createmode=None): 1021 def mktempcopy(name, emptyok=False, createmode=None):
1491 """Create a temporary file with the same contents from name 1022 """Create a temporary file with the same contents from name
1492 1023
1493 The permission bits are copied from the original file. 1024 The permission bits are copied from the original file.
1508 except OSError, inst: 1039 except OSError, inst:
1509 if inst.errno != errno.ENOENT: 1040 if inst.errno != errno.ENOENT:
1510 raise 1041 raise
1511 st_mode = createmode 1042 st_mode = createmode
1512 if st_mode is None: 1043 if st_mode is None:
1513 st_mode = ~_umask 1044 st_mode = ~umask
1514 st_mode &= 0666 1045 st_mode &= 0666
1515 os.chmod(temp, st_mode) 1046 os.chmod(temp, st_mode)
1516 if emptyok: 1047 if emptyok:
1517 return temp 1048 return temp
1518 try: 1049 try: