73
# Cross platform wall-clock time functionality with decent resolution.
74
# On Linux ``time.clock`` returns only CPU time. On Windows, ``time.time()``
75
# only has a resolution of ~15ms. Note that ``time.clock()`` is not
76
# synchronized with ``time.time()``, this is only meant to be used to find
77
# delta times by subtracting from another call to this function.
78
timer_func = time.time
79
if sys.platform == 'win32':
80
timer_func = time.clock
82
66
# On win32, O_BINARY is used to indicate the file should
83
67
# be opened in binary mode, rather than text mode.
84
68
# On other platforms, O_BINARY doesn't exist, because
99
83
self.timezone = timezone
102
def get_unicode_argv():
106
user_encoding = get_user_encoding()
107
return [a.decode(user_encoding) for a in sys.argv[1:]]
108
except UnicodeDecodeError:
109
raise errors.BzrError(gettext("Parameter {0!r} encoding is unsupported by {1} "
110
"application locale.").format(a, user_encoding))
113
86
def make_readonly(filename):
114
87
"""Make a filename read-only."""
115
88
mod = os.lstat(filename).st_mode
337
def _posix_path_from_environ(key):
338
"""Get unicode path from `key` in environment or None if not present
340
Note that posix systems use arbitrary byte strings for filesystem objects,
341
so a path that raises BadFilenameEncoding here may still be accessible.
343
val = os.environ.get(key, None)
344
if PY3 or val is None:
347
return val.decode(_fs_enc)
348
except UnicodeDecodeError:
349
# GZ 2011-12-12:Ideally want to include `key` in the exception message
350
raise errors.BadFilenameEncoding(val, _fs_enc)
353
310
def _posix_get_home_dir():
354
311
"""Get the home directory of the current user as a unicode path"""
355
312
path = posixpath.expanduser("~")
364
321
def _posix_getuser_unicode():
365
322
"""Get username from environment or password database as unicode"""
366
name = getpass.getuser()
369
user_encoding = get_user_encoding()
371
return name.decode(user_encoding)
372
except UnicodeDecodeError:
373
raise errors.BzrError("Encoding of username %r is unsupported by %s "
374
"application locale." % (name, user_encoding))
323
return getpass.getuser()
377
326
def _win32_fixdrive(path):
529
474
"""Replacer for shutil.rmtree: could remove readonly dirs/files"""
530
475
return shutil.rmtree(path, ignore_errors, onerror)
532
f = win32utils.get_unicode_argv # special function or None
535
path_from_environ = win32utils.get_environ_unicode
536
477
_get_home_dir = win32utils.get_home_location
537
478
getuser_unicode = win32utils.get_user_name
906
843
(date_fmt, tt, offset_str) = \
907
844
_format_date(t, offset, timezone, date_fmt, show_offset)
908
845
date_str = time.strftime(date_fmt, tt)
909
if not isinstance(date_str, text_type):
846
if not isinstance(date_str, str):
910
847
date_str = date_str.decode(get_user_encoding(), 'replace')
911
848
return date_str + offset_str
1036
970
def splitpath(p):
1037
971
"""Turn string into list of parts."""
972
use_bytes = isinstance(p, bytes)
1038
973
if os.path.sep == '\\':
1039
974
# split on either delimiter because people might use either on
1041
if isinstance(p, bytes):
1042
977
ps = re.split(b'[\\\\/]', p)
1044
979
ps = re.split(r'[\\/]', p)
1046
if isinstance(p, bytes):
1047
982
ps = p.split(b'/')
1049
984
ps = p.split('/')
988
current_empty_dir = (b'.', b'')
991
current_empty_dir = ('.', '')
1053
if f in ('..', b'..'):
1054
996
raise errors.BzrError(gettext("sorry, %r not allowed in path") % f)
1055
elif f in ('.', '', b'.', b''):
997
elif f in current_empty_dir:
1326
1268
abs_base = abspath(base)
1327
1269
current = abs_base
1328
_listdir = os.listdir
1330
1271
# use an explicit iterator so we can easily consume the rest on early exit.
1331
1272
bit_iter = iter(rel.split('/'))
1332
1273
for bit in bit_iter:
1333
1274
lbit = bit.lower()
1335
next_entries = _listdir(current)
1276
next_entries = scandir(current)
1336
1277
except OSError: # enoent, eperm, etc
1337
1278
# We can't find this in the filesystem, so just append the
1338
1279
# remaining bits.
1339
1280
current = pathjoin(current, bit, *list(bit_iter))
1341
for look in next_entries:
1342
if lbit == look.lower():
1343
current = pathjoin(current, look)
1282
for entry in next_entries:
1283
if lbit == entry.name.lower():
1284
current = entry.path
1346
1287
# got to the end, nothing matched, so we just return the
1379
1320
Otherwise it is decoded from the the filesystem's encoding. If decoding
1380
1321
fails, a errors.BadFilenameEncoding exception is raised.
1382
if isinstance(filename, text_type):
1323
if isinstance(filename, str):
1383
1324
return filename
1385
1326
return filename.decode(_fs_enc)
1394
1335
Otherwise it is decoded from utf-8. If decoding fails, the exception is
1395
1336
wrapped in a BzrBadParameterNotUnicode exception.
1397
if isinstance(unicode_or_utf8_string, text_type):
1338
if isinstance(unicode_or_utf8_string, str):
1398
1339
return unicode_or_utf8_string
1400
1341
return unicode_or_utf8_string.decode('utf8')
1421
1362
return unicode_or_utf8_string.encode('utf-8')
1424
def safe_revision_id(unicode_or_utf8_string):
1425
"""Revision ids should now be utf8, but at one point they were unicode.
1427
:param unicode_or_utf8_string: A possibly Unicode revision_id. (can also be
1429
:return: None or a utf8 revision id.
1431
if (unicode_or_utf8_string is None
1432
or unicode_or_utf8_string.__class__ == bytes):
1433
return unicode_or_utf8_string
1434
raise TypeError('Unicode revision ids are no longer supported. '
1435
'Revision id generators should be creating utf8 revision '
1439
def safe_file_id(unicode_or_utf8_string):
1440
"""File ids should now be utf8, but at one point they were unicode.
1442
This is the same as safe_utf8, except it uses the cached encode functions
1443
to save a little bit of performance.
1445
:param unicode_or_utf8_string: A possibly Unicode file_id. (can also be
1447
:return: None or a utf8 file id.
1449
if (unicode_or_utf8_string is None
1450
or unicode_or_utf8_string.__class__ == bytes):
1451
return unicode_or_utf8_string
1452
raise TypeError('Unicode file ids are no longer supported. '
1453
'File id generators should be creating utf8 file ids.')
1456
1365
_platform_normalizes_filenames = False
1457
1366
if sys.platform == 'darwin':
1458
1367
_platform_normalizes_filenames = True
1671
1580
_terminal_size = _ioctl_terminal_size
1674
def supports_executable():
1675
return sys.platform != "win32"
1583
def supports_executable(path):
1584
"""Return if filesystem at path supports executable bit.
1586
:param path: Path for which to check the file system
1587
:return: boolean indicating whether executable bit can be stored/relied upon
1589
if sys.platform == 'win32':
1592
fs_type = get_fs_type(path)
1593
except errors.DependencyNotPresent as e:
1594
trace.mutter('Unable to get fs type for %r: %s', path, e)
1596
if fs_type in ('vfat', 'ntfs'):
1597
# filesystems known to not support executable bit
1602
def supports_symlinks(path):
1603
"""Return if the filesystem at path supports the creation of symbolic links.
1606
if not has_symlinks():
1609
fs_type = get_fs_type(path)
1610
except errors.DependencyNotPresent as e:
1611
trace.mutter('Unable to get fs type for %r: %s', path, e)
1613
if fs_type in ('vfat', 'ntfs'):
1614
# filesystems known to not support symlinks
1678
1619
def supports_posix_readonly():
1724
1663
_WIN32_ERROR_DIRECTORY = 267 # Similar to errno.ENOTDIR
1667
scandir = os.scandir
1668
except AttributeError: # Python < 3
1669
lazy_import(globals(), """\
1670
from scandir import scandir
1727
1674
def _is_error_enotdir(e):
1728
1675
"""Check if this exception represents ENOTDIR.
1783
1730
# depending on top and prefix - i.e. ./foo and foo as a pair leads to
1784
1731
# potentially confusing output. We should make this more robust - but
1785
1732
# not at a speed cost. RBC 20060731
1787
1733
_directory = _directory_kind
1788
_listdir = os.listdir
1789
_kind_from_mode = file_kind_from_stat_mode
1790
1734
pending = [(safe_unicode(prefix), "", _directory, None, safe_unicode(top))]
1792
1736
# 0 - relpath, 1- basename, 2- kind, 3- stat, 4-toppath
1798
1742
top_slash = top + u'/'
1801
append = dirblock.append
1803
names = sorted(map(decode_filename, _listdir(top)))
1746
for entry in scandir(top):
1747
name = decode_filename(entry.name)
1748
statvalue = entry.stat(follow_symlinks=False)
1749
kind = file_kind_from_stat_mode(statvalue.st_mode)
1750
dirblock.append((relprefix + name, name, kind, statvalue, entry.path))
1804
1751
except OSError as e:
1805
1752
if not _is_error_enotdir(e):
1809
abspath = top_slash + name
1810
statvalue = _lstat(abspath)
1811
kind = _kind_from_mode(statvalue.st_mode)
1812
append((relprefix + name, name, kind, statvalue, abspath))
1754
except UnicodeDecodeError as e:
1755
raise errors.BadFilenameEncoding(e.object, _fs_enc)
1813
1757
yield (relroot, top), dirblock
1815
1759
# push the user specified dirs from dirblock
1939
1880
append = dirblock.append
1940
for name_native in _listdir(top.encode('utf-8')):
1881
for entry in scandir(safe_utf8(top)):
1942
name = _fs_decode(name_native)
1883
name = _fs_decode(entry.name)
1943
1884
except UnicodeDecodeError:
1944
1885
raise errors.BadFilenameEncoding(
1945
relprefix + name_native, _fs_enc)
1886
relprefix + entry.name, _fs_enc)
1887
abspath = top_slash + name
1946
1888
name_utf8 = _utf8_encode(name)[0]
1947
abspath = top_slash + name
1948
statvalue = _lstat(abspath)
1949
kind = _kind_from_mode(statvalue.st_mode)
1889
statvalue = entry.stat(follow_symlinks=False)
1890
kind = file_kind_from_stat_mode(statvalue.st_mode)
1950
1891
append((relprefix + name_utf8, name_utf8, kind, statvalue, abspath))
1951
1892
return sorted(dirblock)
2598
2537
raise exception_class(path)
2601
def is_environment_error(evalue):
2602
"""True if exception instance is due to a process environment issue
2604
This includes OSError and IOError, but also other errors that come from
2605
the operating system or core libraries but are not subclasses of those.
2607
if isinstance(evalue, (EnvironmentError, select.error)):
2609
if sys.platform == "win32" and win32utils._is_pywintypes_error(evalue):
2540
def read_mtab(path):
2541
"""Read an fstab-style file and extract mountpoint+filesystem information.
2543
:param path: Path to read from
2544
:yield: Tuples with mountpoints (as bytestrings) and filesystem names
2546
with open(path, 'rb') as f:
2548
if line.startswith(b'#'):
2553
yield cols[1], cols[2].decode('ascii', 'replace')
2556
MTAB_PATH = '/etc/mtab'
2558
class FilesystemFinder(object):
2559
"""Find the filesystem for a particular path."""
2561
def __init__(self, mountpoints):
2564
self._mountpoints = sorted(mountpoints, key=key, reverse=True)
2568
"""Create a FilesystemFinder from an mtab-style file.
2570
Note that this will silenty ignore mtab if it doesn't exist or can not
2573
# TODO(jelmer): Use inotify to be notified when /etc/mtab changes and
2574
# we need to re-read it.
2576
return cls(read_mtab(MTAB_PATH))
2577
except EnvironmentError as e:
2578
trace.mutter('Unable to read mtab: %s', e)
2581
def find(self, path):
2582
"""Find the filesystem used by a particular path.
2584
:param path: Path to find (bytestring or text type)
2585
:return: Filesystem name (as text type) or None, if the filesystem is
2588
for mountpoint, filesystem in self._mountpoints:
2589
if is_inside(mountpoint, path):
2594
_FILESYSTEM_FINDER = None
2597
def get_fs_type(path):
2598
"""Return the filesystem type for the partition a path is in.
2600
:param path: Path to search filesystem type for
2601
:return: A FS type, as string. E.g. "ext2"
2603
global _FILESYSTEM_FINDER
2604
if _FILESYSTEM_FINDER is None:
2605
_FILESYSTEM_FINDER = FilesystemFinder.from_mtab()
2607
if not isinstance(path, bytes):
2608
path = path.encode(_fs_enc)
2610
return _FILESYSTEM_FINDER.find(path)
2613
perf_counter = time.perf_counter