/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/osutils.py

  • Committer: Jelmer Vernooij
  • Date: 2019-03-05 07:32:38 UTC
  • mto: (7290.1.21 work)
  • mto: This revision was merged to the branch mainline in revision 7311.
  • Revision ID: jelmer@jelmer.uk-20190305073238-zlqn981opwnqsmzi
Add appveyor configuration.

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
 
17
from __future__ import absolute_import
 
18
 
17
19
import errno
18
20
import os
19
21
import re
50
52
from breezy.i18n import gettext
51
53
""")
52
54
 
 
55
from .sixish import (
 
56
    PY3,
 
57
    text_type,
 
58
    )
 
59
 
53
60
from hashlib import (
54
61
    md5,
55
62
    sha1 as sha,
63
70
    )
64
71
 
65
72
 
 
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
 
81
 
66
82
# On win32, O_BINARY is used to indicate the file should
67
83
# be opened in binary mode, rather than text mode.
68
84
# On other platforms, O_BINARY doesn't exist, because
83
99
        self.timezone = timezone
84
100
 
85
101
 
 
102
def get_unicode_argv():
 
103
    if PY3:
 
104
        return sys.argv[1:]
 
105
    try:
 
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))
 
111
 
 
112
 
86
113
def make_readonly(filename):
87
114
    """Make a filename read-only."""
88
115
    mod = os.lstat(filename).st_mode
307
334
    return path
308
335
 
309
336
 
 
337
def _posix_path_from_environ(key):
 
338
    """Get unicode path from `key` in environment or None if not present
 
339
 
 
340
    Note that posix systems use arbitrary byte strings for filesystem objects,
 
341
    so a path that raises BadFilenameEncoding here may still be accessible.
 
342
    """
 
343
    val = os.environ.get(key, None)
 
344
    if PY3 or val is None:
 
345
        return val
 
346
    try:
 
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)
 
351
 
 
352
 
310
353
def _posix_get_home_dir():
311
354
    """Get the home directory of the current user as a unicode path"""
312
355
    path = posixpath.expanduser("~")
320
363
 
321
364
def _posix_getuser_unicode():
322
365
    """Get username from environment or password database as unicode"""
323
 
    return getpass.getuser()
 
366
    name = getpass.getuser()
 
367
    if PY3:
 
368
        return name
 
369
    user_encoding = get_user_encoding()
 
370
    try:
 
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))
324
375
 
325
376
 
326
377
def _win32_fixdrive(path):
405
456
    return _rename_wrapper
406
457
 
407
458
 
408
 
_getcwd = os.getcwd
 
459
if sys.version_info > (3,):
 
460
    _getcwd = os.getcwd
 
461
else:
 
462
    _getcwd = os.getcwdu
409
463
 
410
464
 
411
465
# Default rename wraps os.rename()
417
471
realpath = _posix_realpath
418
472
pathjoin = os.path.join
419
473
normpath = _posix_normpath
 
474
path_from_environ = _posix_path_from_environ
420
475
_get_home_dir = _posix_get_home_dir
421
476
getuser_unicode = _posix_getuser_unicode
422
477
getcwd = _getcwd
474
529
        """Replacer for shutil.rmtree: could remove readonly dirs/files"""
475
530
        return shutil.rmtree(path, ignore_errors, onerror)
476
531
 
 
532
    f = win32utils.get_unicode_argv     # special function or None
 
533
    if f is not None:
 
534
        get_unicode_argv = f
 
535
    path_from_environ = win32utils.get_environ_unicode
477
536
    _get_home_dir = win32utils.get_home_location
478
537
    getuser_unicode = win32utils.get_user_name
479
538
 
690
749
 
691
750
# GZ 2017-09-16: Makes sense in general for hexdigest() result to be text, but
692
751
# used as bytes through most interfaces so encode with this wrapper.
693
 
def _hexdigest(hashobj):
694
 
    return hashobj.hexdigest().encode()
 
752
if PY3:
 
753
    def _hexdigest(hashobj):
 
754
        return hashobj.hexdigest().encode()
 
755
else:
 
756
    def _hexdigest(hashobj):
 
757
        return hashobj.hexdigest()
695
758
 
696
759
 
697
760
def sha_file(f):
843
906
    (date_fmt, tt, offset_str) = \
844
907
        _format_date(t, offset, timezone, date_fmt, show_offset)
845
908
    date_str = time.strftime(date_fmt, tt)
846
 
    if not isinstance(date_str, str):
 
909
    if not isinstance(date_str, text_type):
847
910
        date_str = date_str.decode(get_user_encoding(), 'replace')
848
911
    return date_str + offset_str
849
912
 
960
1023
    """
961
1024
    s = ''
962
1025
    for raw_byte in rand_bytes(num):
963
 
        s += ALNUM[raw_byte % 36]
 
1026
        if not PY3:
 
1027
            s += ALNUM[ord(raw_byte) % 36]
 
1028
        else:
 
1029
            s += ALNUM[raw_byte % 36]
964
1030
    return s
965
1031
 
966
1032
 
969
1035
 
970
1036
def splitpath(p):
971
1037
    """Turn string into list of parts."""
972
 
    use_bytes = isinstance(p, bytes)
973
1038
    if os.path.sep == '\\':
974
1039
        # split on either delimiter because people might use either on
975
1040
        # Windows
976
 
        if use_bytes:
 
1041
        if isinstance(p, bytes):
977
1042
            ps = re.split(b'[\\\\/]', p)
978
1043
        else:
979
1044
            ps = re.split(r'[\\/]', p)
980
1045
    else:
981
 
        if use_bytes:
 
1046
        if isinstance(p, bytes):
982
1047
            ps = p.split(b'/')
983
1048
        else:
984
1049
            ps = p.split('/')
985
1050
 
986
 
    if use_bytes:
987
 
        parent_dir = b'..'
988
 
        current_empty_dir = (b'.', b'')
989
 
    else:
990
 
        parent_dir = '..'
991
 
        current_empty_dir = ('.', '')
992
 
 
993
1051
    rps = []
994
1052
    for f in ps:
995
 
        if f == parent_dir:
 
1053
        if f in ('..', b'..'):
996
1054
            raise errors.BzrError(gettext("sorry, %r not allowed in path") % f)
997
 
        elif f in current_empty_dir:
 
1055
        elif f in ('.', '', b'.', b''):
998
1056
            pass
999
1057
        else:
1000
1058
            rps.append(f)
1267
1325
 
1268
1326
    abs_base = abspath(base)
1269
1327
    current = abs_base
 
1328
    _listdir = os.listdir
1270
1329
 
1271
1330
    # use an explicit iterator so we can easily consume the rest on early exit.
1272
1331
    bit_iter = iter(rel.split('/'))
1273
1332
    for bit in bit_iter:
1274
1333
        lbit = bit.lower()
1275
1334
        try:
1276
 
            next_entries = scandir(current)
 
1335
            next_entries = _listdir(current)
1277
1336
        except OSError:  # enoent, eperm, etc
1278
1337
            # We can't find this in the filesystem, so just append the
1279
1338
            # remaining bits.
1280
1339
            current = pathjoin(current, bit, *list(bit_iter))
1281
1340
            break
1282
 
        for entry in next_entries:
1283
 
            if lbit == entry.name.lower():
1284
 
                current = entry.path
 
1341
        for look in next_entries:
 
1342
            if lbit == look.lower():
 
1343
                current = pathjoin(current, look)
1285
1344
                break
1286
1345
        else:
1287
1346
            # got to the end, nothing matched, so we just return the
1320
1379
    Otherwise it is decoded from the the filesystem's encoding. If decoding
1321
1380
    fails, a errors.BadFilenameEncoding exception is raised.
1322
1381
    """
1323
 
    if isinstance(filename, str):
 
1382
    if isinstance(filename, text_type):
1324
1383
        return filename
1325
1384
    try:
1326
1385
        return filename.decode(_fs_enc)
1335
1394
    Otherwise it is decoded from utf-8. If decoding fails, the exception is
1336
1395
    wrapped in a BzrBadParameterNotUnicode exception.
1337
1396
    """
1338
 
    if isinstance(unicode_or_utf8_string, str):
 
1397
    if isinstance(unicode_or_utf8_string, text_type):
1339
1398
        return unicode_or_utf8_string
1340
1399
    try:
1341
1400
        return unicode_or_utf8_string.decode('utf8')
1362
1421
    return unicode_or_utf8_string.encode('utf-8')
1363
1422
 
1364
1423
 
 
1424
def safe_revision_id(unicode_or_utf8_string):
 
1425
    """Revision ids should now be utf8, but at one point they were unicode.
 
1426
 
 
1427
    :param unicode_or_utf8_string: A possibly Unicode revision_id. (can also be
 
1428
        utf8 or None).
 
1429
    :return: None or a utf8 revision id.
 
1430
    """
 
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 '
 
1436
                    'ids.')
 
1437
 
 
1438
 
 
1439
def safe_file_id(unicode_or_utf8_string):
 
1440
    """File ids should now be utf8, but at one point they were unicode.
 
1441
 
 
1442
    This is the same as safe_utf8, except it uses the cached encode functions
 
1443
    to save a little bit of performance.
 
1444
 
 
1445
    :param unicode_or_utf8_string: A possibly Unicode file_id. (can also be
 
1446
        utf8 or None).
 
1447
    :return: None or a utf8 file id.
 
1448
    """
 
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.')
 
1454
 
 
1455
 
1365
1456
_platform_normalizes_filenames = False
1366
1457
if sys.platform == 'darwin':
1367
1458
    _platform_normalizes_filenames = True
1580
1671
    _terminal_size = _ioctl_terminal_size
1581
1672
 
1582
1673
 
1583
 
def supports_executable(path):
1584
 
    """Return if filesystem at path supports executable bit.
1585
 
 
1586
 
    :param path: Path for which to check the file system
1587
 
    :return: boolean indicating whether executable bit can be stored/relied upon
1588
 
    """
1589
 
    if sys.platform == 'win32':
1590
 
        return False
1591
 
    try:
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)
1595
 
    else:
1596
 
        if fs_type in ('vfat', 'ntfs'):
1597
 
            # filesystems known to not support executable bit
1598
 
            return False
1599
 
    return True
1600
 
 
1601
 
 
1602
 
def supports_symlinks(path):
1603
 
    """Return if the filesystem at path supports the creation of symbolic links.
1604
 
 
1605
 
    """
1606
 
    if not has_symlinks():
1607
 
        return False
1608
 
    try:
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)
1612
 
    else:
1613
 
        if fs_type in ('vfat', 'ntfs'):
1614
 
            # filesystems known to not support symlinks
1615
 
            return False
1616
 
    return True
 
1674
def supports_executable():
 
1675
    return sys.platform != "win32"
1617
1676
 
1618
1677
 
1619
1678
def supports_posix_readonly():
1642
1701
        if orig_val is not None:
1643
1702
            del os.environ[env_variable]
1644
1703
    else:
 
1704
        if not PY3 and isinstance(value, text_type):
 
1705
            value = value.encode(get_user_encoding())
1645
1706
        os.environ[env_variable] = value
1646
1707
    return orig_val
1647
1708
 
1663
1724
_WIN32_ERROR_DIRECTORY = 267  # Similar to errno.ENOTDIR
1664
1725
 
1665
1726
 
1666
 
try:
1667
 
    scandir = os.scandir
1668
 
except AttributeError:  # Python < 3
1669
 
    lazy_import(globals(), """\
1670
 
from scandir import scandir
1671
 
""")
1672
 
 
1673
 
 
1674
1727
def _is_error_enotdir(e):
1675
1728
    """Check if this exception represents ENOTDIR.
1676
1729
 
1730
1783
    # depending on top and prefix - i.e. ./foo and foo as a pair leads to
1731
1784
    # potentially confusing output. We should make this more robust - but
1732
1785
    # not at a speed cost. RBC 20060731
 
1786
    _lstat = os.lstat
1733
1787
    _directory = _directory_kind
 
1788
    _listdir = os.listdir
 
1789
    _kind_from_mode = file_kind_from_stat_mode
1734
1790
    pending = [(safe_unicode(prefix), "", _directory, None, safe_unicode(top))]
1735
1791
    while pending:
1736
1792
        # 0 - relpath, 1- basename, 2- kind, 3- stat, 4-toppath
1742
1798
        top_slash = top + u'/'
1743
1799
 
1744
1800
        dirblock = []
 
1801
        append = dirblock.append
1745
1802
        try:
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))
 
1803
            names = sorted(map(decode_filename, _listdir(top)))
1751
1804
        except OSError as e:
1752
1805
            if not _is_error_enotdir(e):
1753
1806
                raise
1754
 
        except UnicodeDecodeError as e:
1755
 
            raise errors.BadFilenameEncoding(e.object, _fs_enc)
1756
 
        dirblock.sort()
 
1807
        else:
 
1808
            for name in names:
 
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))
1757
1813
        yield (relroot, top), dirblock
1758
1814
 
1759
1815
        # push the user specified dirs from dirblock
1869
1925
        def _fs_decode(s): return s.decode(_fs_enc)
1870
1926
 
1871
1927
        def _fs_encode(s): return s.encode(_fs_enc)
 
1928
        _lstat = os.lstat
 
1929
        _listdir = os.listdir
 
1930
        _kind_from_mode = file_kind_from_stat_mode
1872
1931
 
1873
1932
        if prefix:
1874
1933
            relprefix = prefix + b'/'
1878
1937
 
1879
1938
        dirblock = []
1880
1939
        append = dirblock.append
1881
 
        for entry in scandir(safe_utf8(top)):
 
1940
        for name_native in _listdir(top.encode('utf-8')):
1882
1941
            try:
1883
 
                name = _fs_decode(entry.name)
 
1942
                name = _fs_decode(name_native)
1884
1943
            except UnicodeDecodeError:
1885
1944
                raise errors.BadFilenameEncoding(
1886
 
                    relprefix + entry.name, _fs_enc)
 
1945
                    relprefix + name_native, _fs_enc)
 
1946
            name_utf8 = _utf8_encode(name)[0]
1887
1947
            abspath = top_slash + name
1888
 
            name_utf8 = _utf8_encode(name)[0]
1889
 
            statvalue = entry.stat(follow_symlinks=False)
1890
 
            kind = file_kind_from_stat_mode(statvalue.st_mode)
 
1948
            statvalue = _lstat(abspath)
 
1949
            kind = _kind_from_mode(statvalue.st_mode)
1891
1950
            append((relprefix + name_utf8, name_utf8, kind, statvalue, abspath))
1892
1951
        return sorted(dirblock)
1893
1952
 
2038
2097
        return win32utils.get_host_name()
2039
2098
    else:
2040
2099
        import socket
2041
 
        return socket.gethostname()
 
2100
        if PY3:
 
2101
            return socket.gethostname()
 
2102
        return socket.gethostname().decode(get_user_encoding())
2042
2103
 
2043
2104
 
2044
2105
# We must not read/write any more than 64k at a time from/to a socket so we
2537
2598
            raise exception_class(path)
2538
2599
 
2539
2600
 
2540
 
def read_mtab(path):
2541
 
    """Read an fstab-style file and extract mountpoint+filesystem information.
2542
 
 
2543
 
    :param path: Path to read from
2544
 
    :yield: Tuples with mountpoints (as bytestrings) and filesystem names
2545
 
    """
2546
 
    with open(path, 'rb') as f:
2547
 
        for line in f:
2548
 
            if line.startswith(b'#'):
2549
 
                continue
2550
 
            cols = line.split()
2551
 
            if len(cols) < 3:
2552
 
                continue
2553
 
            yield cols[1], cols[2].decode('ascii', 'replace')
2554
 
 
2555
 
 
2556
 
MTAB_PATH = '/etc/mtab'
2557
 
 
2558
 
class FilesystemFinder(object):
2559
 
    """Find the filesystem for a particular path."""
2560
 
 
2561
 
    def __init__(self, mountpoints):
2562
 
        def key(x):
2563
 
            return len(x[0])
2564
 
        self._mountpoints = sorted(mountpoints, key=key, reverse=True)
2565
 
 
2566
 
    @classmethod
2567
 
    def from_mtab(cls):
2568
 
        """Create a FilesystemFinder from an mtab-style file.
2569
 
 
2570
 
        Note that this will silenty ignore mtab if it doesn't exist or can not
2571
 
        be opened.
2572
 
        """
2573
 
        # TODO(jelmer): Use inotify to be notified when /etc/mtab changes and
2574
 
        # we need to re-read it.
2575
 
        try:
2576
 
            return cls(read_mtab(MTAB_PATH))
2577
 
        except EnvironmentError as e:
2578
 
            trace.mutter('Unable to read mtab: %s', e)
2579
 
            return cls([])
2580
 
 
2581
 
    def find(self, path):
2582
 
        """Find the filesystem used by a particular path.
2583
 
 
2584
 
        :param path: Path to find (bytestring or text type)
2585
 
        :return: Filesystem name (as text type) or None, if the filesystem is
2586
 
            unknown.
2587
 
        """
2588
 
        for mountpoint, filesystem in self._mountpoints:
2589
 
            if is_inside(mountpoint, path):
2590
 
                return filesystem
2591
 
        return None
2592
 
 
2593
 
 
2594
 
_FILESYSTEM_FINDER = None
2595
 
 
2596
 
 
2597
 
def get_fs_type(path):
2598
 
    """Return the filesystem type for the partition a path is in.
2599
 
 
2600
 
    :param path: Path to search filesystem type for
2601
 
    :return: A FS type, as string. E.g. "ext2"
2602
 
    """
2603
 
    global _FILESYSTEM_FINDER
2604
 
    if _FILESYSTEM_FINDER is None:
2605
 
        _FILESYSTEM_FINDER = FilesystemFinder.from_mtab()
2606
 
 
2607
 
    if not isinstance(path, bytes):
2608
 
        path = path.encode(_fs_enc)
2609
 
 
2610
 
    return _FILESYSTEM_FINDER.find(path)
2611
 
 
2612
 
 
2613
 
perf_counter = time.perf_counter
 
2601
def is_environment_error(evalue):
 
2602
    """True if exception instance is due to a process environment issue
 
2603
 
 
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.
 
2606
    """
 
2607
    if isinstance(evalue, (EnvironmentError, select.error)):
 
2608
        return True
 
2609
    if sys.platform == "win32" and win32utils._is_pywintypes_error(evalue):
 
2610
        return True
 
2611
    return False