/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: 2020-03-22 01:35:14 UTC
  • mfrom: (7490.7.6 work)
  • mto: This revision was merged to the branch mainline in revision 7499.
  • Revision ID: jelmer@jelmer.uk-20200322013514-7vw1ntwho04rcuj3
merge lp:brz/3.1.

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
 
 
19
17
import errno
20
18
import os
21
19
import re
52
50
from breezy.i18n import gettext
53
51
""")
54
52
 
55
 
from .sixish import (
56
 
    PY3,
57
 
    text_type,
58
 
    )
59
 
 
60
53
from hashlib import (
61
54
    md5,
62
55
    sha1 as sha,
70
63
    )
71
64
 
72
65
 
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
 
 
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
100
84
 
101
85
 
102
86
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))
 
87
    return sys.argv[1:]
111
88
 
112
89
 
113
90
def make_readonly(filename):
155
132
        return set(paths)
156
133
 
157
134
    def sort_key(path):
158
 
        return path.split('/')
 
135
        if isinstance(path, bytes):
 
136
            return path.split(b'/')
 
137
        else:
 
138
            return path.split('/')
159
139
    sorted_paths = sorted(list(paths), key=sort_key)
160
140
 
161
141
    search_paths = [sorted_paths[0]]
188
168
 
189
169
_directory_kind = 'directory'
190
170
 
 
171
 
191
172
def get_umask():
192
173
    """Return the current umask"""
193
174
    # Assume that people aren't messing with the umask while running
224
205
            return True
225
206
        except OSError as e:
226
207
            if e.errno == errno.ENOENT:
227
 
                return False;
 
208
                return False
228
209
            else:
229
 
                raise errors.BzrError(gettext("lstat/stat of ({0!r}): {1!r}").format(f, e))
 
210
                raise errors.BzrError(
 
211
                    gettext("lstat/stat of ({0!r}): {1!r}").format(f, e))
230
212
 
231
213
 
232
214
def fancy_rename(old, new, rename_func, unlink_func):
256
238
    file_existed = False
257
239
    try:
258
240
        rename_func(new, tmp_name)
259
 
    except (errors.NoSuchFile,) as e:
 
241
    except (errors.NoSuchFile,):
260
242
        pass
261
243
    except IOError as e:
262
244
        # RBC 20060103 abstraction leakage: the paramiko SFTP clients rename
266
248
            raise
267
249
    except Exception as e:
268
250
        if (getattr(e, 'errno', None) is None
269
 
            or e.errno not in (errno.ENOENT, errno.ENOTDIR)):
 
251
                or e.errno not in (errno.ENOENT, errno.ENOTDIR)):
270
252
            raise
271
253
    else:
272
254
        file_existed = True
282
264
        # case-insensitive filesystem), so we may have accidentally renamed
283
265
        # source by when we tried to rename target
284
266
        if (file_existed and e.errno in (None, errno.ENOENT)
285
 
            and old.lower() == new.lower()):
 
267
                and old.lower() == new.lower()):
286
268
            # source and target are the same file on a case-insensitive
287
269
            # filesystem, so we don't generate an exception
288
270
            pass
323
305
    # as a special case here by simply removing the first slash, as we consider
324
306
    # that breaking POSIX compatibility for this obscure feature is acceptable.
325
307
    # This is not a paranoid precaution, as we notably get paths like this when
326
 
    # the repo is hosted at the root of the filesystem, i.e. in "/".    
 
308
    # the repo is hosted at the root of the filesystem, i.e. in "/".
327
309
    if path.startswith('//'):
328
310
        path = path[1:]
329
311
    return path
335
317
    Note that posix systems use arbitrary byte strings for filesystem objects,
336
318
    so a path that raises BadFilenameEncoding here may still be accessible.
337
319
    """
338
 
    val = os.environ.get(key, None)
339
 
    if PY3 or val is None:
340
 
        return val
341
 
    try:
342
 
        return val.decode(_fs_enc)
343
 
    except UnicodeDecodeError:
344
 
        # GZ 2011-12-12:Ideally want to include `key` in the exception message
345
 
        raise errors.BadFilenameEncoding(val, _fs_enc)
 
320
    return os.environ.get(key, None)
346
321
 
347
322
 
348
323
def _posix_get_home_dir():
358
333
 
359
334
def _posix_getuser_unicode():
360
335
    """Get username from environment or password database as unicode"""
361
 
    name = getpass.getuser()
362
 
    if PY3:
363
 
        return name
364
 
    user_encoding = get_user_encoding()
365
 
    try:
366
 
        return name.decode(user_encoding)
367
 
    except UnicodeDecodeError:
368
 
        raise errors.BzrError("Encoding of username %r is unsupported by %s "
369
 
            "application locale." % (name, user_encoding))
 
336
    return getpass.getuser()
370
337
 
371
338
 
372
339
def _win32_fixdrive(path):
384
351
 
385
352
def _win32_abspath(path):
386
353
    # Real ntpath.abspath doesn't have a problem with a unicode cwd
387
 
    return _win32_fixdrive(ntpath.abspath(unicode(path)).replace('\\', '/'))
 
354
    return _win32_fixdrive(ntpath.abspath(path).replace('\\', '/'))
388
355
 
389
356
 
390
357
def _win32_realpath(path):
391
358
    # Real ntpath.realpath doesn't have a problem with a unicode cwd
392
 
    return _win32_fixdrive(ntpath.realpath(unicode(path)).replace('\\', '/'))
 
359
    return _win32_fixdrive(ntpath.realpath(path).replace('\\', '/'))
393
360
 
394
361
 
395
362
def _win32_pathjoin(*args):
397
364
 
398
365
 
399
366
def _win32_normpath(path):
400
 
    return _win32_fixdrive(ntpath.normpath(unicode(path)).replace('\\', '/'))
 
367
    return _win32_fixdrive(ntpath.normpath(path).replace('\\', '/'))
401
368
 
402
369
 
403
370
def _win32_getcwd():
442
409
            rename_func(old, new)
443
410
        except OSError as e:
444
411
            detailed_error = OSError(e.errno, e.strerror +
445
 
                                " [occurred when renaming '%s' to '%s']" %
446
 
                                (old, new))
 
412
                                     " [occurred when renaming '%s' to '%s']" %
 
413
                                     (old, new))
447
414
            detailed_error.old_filename = old
448
415
            detailed_error.new_filename = new
449
416
            raise detailed_error
451
418
    return _rename_wrapper
452
419
 
453
420
 
454
 
if sys.version_info > (3,):
455
 
    _getcwd = os.getcwd
456
 
else:
457
 
    _getcwd = os.getcwdu
 
421
_getcwd = os.getcwd
458
422
 
459
423
 
460
424
# Default rename wraps os.rename()
480
444
lstat = os.lstat
481
445
fstat = os.fstat
482
446
 
 
447
 
483
448
def wrap_stat(st):
484
449
    return st
485
450
 
512
477
        """
513
478
        exception = excinfo[1]
514
479
        if function in (os.remove, os.rmdir) \
515
 
            and isinstance(exception, OSError) \
516
 
            and exception.errno == errno.EACCES:
 
480
                and isinstance(exception, OSError) \
 
481
                and exception.errno == errno.EACCES:
517
482
            make_writable(path)
518
483
            function(path)
519
484
        else:
523
488
        """Replacer for shutil.rmtree: could remove readonly dirs/files"""
524
489
        return shutil.rmtree(path, ignore_errors, onerror)
525
490
 
526
 
    f = win32utils.get_unicode_argv     # special function or None
527
 
    if f is not None:
528
 
        get_unicode_argv = f
 
491
    get_unicode_argv = getattr(win32utils, 'get_unicode_argv', get_unicode_argv)
529
492
    path_from_environ = win32utils.get_environ_unicode
530
493
    _get_home_dir = win32utils.get_home_location
531
494
    getuser_unicode = win32utils.get_user_name
557
520
            output_encoding = get_user_encoding()
558
521
            if trace:
559
522
                mutter('encoding stdout as osutils.get_user_encoding() %r',
560
 
                   output_encoding)
 
523
                       output_encoding)
561
524
        else:
562
525
            output_encoding = input_encoding
563
526
            if trace:
564
527
                mutter('encoding stdout as sys.stdin encoding %r',
565
 
                    output_encoding)
 
528
                       output_encoding)
566
529
    else:
567
530
        if trace:
568
531
            mutter('encoding stdout as sys.stdout encoding %r', output_encoding)
571
534
        output_encoding = get_user_encoding()
572
535
        if trace:
573
536
            mutter('cp0 is invalid encoding.'
574
 
               ' encoding stdout as osutils.get_user_encoding() %r',
575
 
               output_encoding)
 
537
                   ' encoding stdout as osutils.get_user_encoding() %r',
 
538
                   output_encoding)
576
539
    # check encoding
577
540
    try:
578
541
        codecs.lookup(output_encoding)
581
544
                         ' unknown terminal encoding %s.\n'
582
545
                         '  Using encoding %s instead.\n'
583
546
                         % (output_encoding, get_user_encoding())
584
 
                        )
 
547
                         )
585
548
        output_encoding = get_user_encoding()
586
549
 
587
550
    return output_encoding
614
577
    except OSError:
615
578
        return False
616
579
 
 
580
 
617
581
def islink(f):
618
582
    """True if f is a symlink."""
619
583
    try:
621
585
    except OSError:
622
586
        return False
623
587
 
 
588
 
624
589
def is_inside(dir, fname):
625
590
    """True if fname is inside dir.
626
591
 
636
601
    if dir == fname:
637
602
        return True
638
603
 
639
 
    if dir == '':
 
604
    if dir in ('', b''):
640
605
        return True
641
606
 
642
 
    if dir[-1] != '/':
643
 
        dir += '/'
 
607
    if isinstance(dir, bytes):
 
608
        if not dir.endswith(b'/'):
 
609
            dir += b'/'
 
610
    else:
 
611
        if not dir.endswith('/'):
 
612
            dir += '/'
644
613
 
645
614
    return fname.startswith(dir)
646
615
 
719
688
    # writes fail on some platforms (e.g. Windows with SMB  mounted
720
689
    # drives).
721
690
    if not segment_size:
722
 
        segment_size = 5242880 # 5MB
 
691
        segment_size = 5242880  # 5MB
723
692
    offsets = range(0, len(bytes), segment_size)
724
693
    view = memoryview(bytes)
725
694
    write = file_handle.write
726
695
    for offset in offsets:
727
 
        write(view[offset:offset+segment_size])
 
696
        write(view[offset:offset + segment_size])
728
697
 
729
698
 
730
699
def file_iterator(input_file, readsize=32768):
737
706
 
738
707
# GZ 2017-09-16: Makes sense in general for hexdigest() result to be text, but
739
708
# used as bytes through most interfaces so encode with this wrapper.
740
 
if PY3:
741
 
    def _hexdigest(hashobj):
742
 
        return hashobj.hexdigest().encode()
743
 
else:
744
 
    def _hexdigest(hashobj):
745
 
        return hashobj.hexdigest()
 
709
def _hexdigest(hashobj):
 
710
    return hashobj.hexdigest().encode()
746
711
 
747
712
 
748
713
def sha_file(f):
751
716
    The file cursor should be already at the start.
752
717
    """
753
718
    s = sha()
754
 
    BUFSIZE = 128<<10
 
719
    BUFSIZE = 128 << 10
755
720
    while True:
756
721
        b = f.read(BUFSIZE)
757
722
        if not b:
768
733
    """
769
734
    size = 0
770
735
    s = sha()
771
 
    BUFSIZE = 128<<10
 
736
    BUFSIZE = 128 << 10
772
737
    while True:
773
738
        b = f.read(BUFSIZE)
774
739
        if not b:
784
749
    f = os.open(fname, os.O_RDONLY | O_BINARY | O_NOINHERIT)
785
750
    try:
786
751
        while True:
787
 
            b = os.read(f, 1<<16)
 
752
            b = os.read(f, 1 << 16)
788
753
            if not b:
789
754
                return _hexdigest(s)
790
755
            s.update(b)
819
784
        bi = b.read(BUFSIZE)
820
785
        if ai != bi:
821
786
            return False
822
 
        if ai == '':
 
787
        if not ai:
823
788
            return True
824
789
 
825
790
 
830
795
    offset = datetime.fromtimestamp(t) - datetime.utcfromtimestamp(t)
831
796
    return offset.days * 86400 + offset.seconds
832
797
 
 
798
 
833
799
weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
834
800
_default_format_by_weekday_num = [wd + " %Y-%m-%d %H:%M:%S" for wd in weekdays]
835
801
 
847
813
    :param show_offset: Whether to append the timezone.
848
814
    """
849
815
    (date_fmt, tt, offset_str) = \
850
 
               _format_date(t, offset, timezone, date_fmt, show_offset)
 
816
        _format_date(t, offset, timezone, date_fmt, show_offset)
851
817
    date_fmt = date_fmt.replace('%a', weekdays[tt[6]])
852
818
    date_str = time.strftime(date_fmt, tt)
853
819
    return date_str + offset_str
858
824
 
859
825
 
860
826
def format_date_with_offset_in_original_timezone(t, offset=0,
861
 
    _cache=_offset_cache):
 
827
                                                 _cache=_offset_cache):
862
828
    """Return a formatted date string in the original timezone.
863
829
 
864
830
    This routine may be faster then format_date.
891
857
    :param show_offset: Whether to append the timezone.
892
858
    """
893
859
    (date_fmt, tt, offset_str) = \
894
 
               _format_date(t, offset, timezone, date_fmt, show_offset)
 
860
        _format_date(t, offset, timezone, date_fmt, show_offset)
895
861
    date_str = time.strftime(date_fmt, tt)
896
 
    if not isinstance(date_str, text_type):
 
862
    if not isinstance(date_str, str):
897
863
        date_str = date_str.decode(get_user_encoding(), 'replace')
898
864
    return date_str + offset_str
899
865
 
940
906
        delta = -delta
941
907
 
942
908
    seconds = delta
943
 
    if seconds < 90: # print seconds up to 90 seconds
 
909
    if seconds < 90:  # print seconds up to 90 seconds
944
910
        if seconds == 1:
945
911
            return '%d second %s' % (seconds, direction,)
946
912
        else:
952
918
        plural_seconds = ''
953
919
    else:
954
920
        plural_seconds = 's'
955
 
    if minutes < 90: # print minutes, seconds up to 90 minutes
 
921
    if minutes < 90:  # print minutes, seconds up to 90 minutes
956
922
        if minutes == 1:
957
923
            return '%d minute, %d second%s %s' % (
958
 
                    minutes, seconds, plural_seconds, direction)
 
924
                minutes, seconds, plural_seconds, direction)
959
925
        else:
960
926
            return '%d minutes, %d second%s %s' % (
961
 
                    minutes, seconds, plural_seconds, direction)
 
927
                minutes, seconds, plural_seconds, direction)
962
928
 
963
929
    hours = int(minutes / 60)
964
930
    minutes -= 60 * hours
973
939
    return '%d hours, %d minute%s %s' % (hours, minutes,
974
940
                                         plural_minutes, direction)
975
941
 
 
942
 
976
943
def filesize(f):
977
944
    """Return size of given open file."""
978
945
    return os.fstat(f.fileno())[stat.ST_SIZE]
979
946
 
980
947
 
981
 
# Alias os.urandom to support platforms (which?) without /dev/urandom and 
 
948
# Alias os.urandom to support platforms (which?) without /dev/urandom and
982
949
# override if it doesn't work. Avoid checking on windows where there is
983
950
# significant initialisation cost that can be avoided for some bzr calls.
984
951
 
999
966
 
1000
967
 
1001
968
ALNUM = '0123456789abcdefghijklmnopqrstuvwxyz'
 
969
 
 
970
 
1002
971
def rand_chars(num):
1003
972
    """Return a random string of num alphanumeric characters
1004
973
 
1007
976
    """
1008
977
    s = ''
1009
978
    for raw_byte in rand_bytes(num):
1010
 
        if not PY3:
1011
 
            s += ALNUM[ord(raw_byte) % 36]
1012
 
        else:
1013
 
            s += ALNUM[raw_byte % 36]
 
979
        s += ALNUM[raw_byte % 36]
1014
980
    return s
1015
981
 
1016
982
 
1017
 
## TODO: We could later have path objects that remember their list
1018
 
## decomposition (might be too tricksy though.)
 
983
# TODO: We could later have path objects that remember their list
 
984
# decomposition (might be too tricksy though.)
1019
985
 
1020
986
def splitpath(p):
1021
987
    """Turn string into list of parts."""
1022
 
    # split on either delimiter because people might use either on
1023
 
    # Windows
1024
 
    ps = re.split(r'[\\/]', p)
 
988
    use_bytes = isinstance(p, bytes)
 
989
    if os.path.sep == '\\':
 
990
        # split on either delimiter because people might use either on
 
991
        # Windows
 
992
        if use_bytes:
 
993
            ps = re.split(b'[\\\\/]', p)
 
994
        else:
 
995
            ps = re.split(r'[\\/]', p)
 
996
    else:
 
997
        if use_bytes:
 
998
            ps = p.split(b'/')
 
999
        else:
 
1000
            ps = p.split('/')
 
1001
 
 
1002
    if use_bytes:
 
1003
        parent_dir = b'..'
 
1004
        current_empty_dir = (b'.', b'')
 
1005
    else:
 
1006
        parent_dir = '..'
 
1007
        current_empty_dir = ('.', '')
1025
1008
 
1026
1009
    rps = []
1027
1010
    for f in ps:
1028
 
        if f == '..':
 
1011
        if f == parent_dir:
1029
1012
            raise errors.BzrError(gettext("sorry, %r not allowed in path") % f)
1030
 
        elif (f == '.') or (f == ''):
 
1013
        elif f in current_empty_dir:
1031
1014
            pass
1032
1015
        else:
1033
1016
            rps.append(f)
1110
1093
    """Split s into lines, but without removing the newline characters."""
1111
1094
    # Trivially convert a fulltext into a 'chunked' representation, and let
1112
1095
    # chunks_to_lines do the heavy lifting.
1113
 
    if isinstance(s, str):
 
1096
    if isinstance(s, bytes):
1114
1097
        # chunks_to_lines only supports 8-bit strings
1115
1098
        return chunks_to_lines([s])
1116
1099
    else:
1153
1136
    Will delete even if readonly.
1154
1137
    """
1155
1138
    try:
1156
 
       _delete_file_or_dir(path)
 
1139
        _delete_file_or_dir(path)
1157
1140
    except (OSError, IOError) as e:
1158
1141
        if e.errno in (errno.EPERM, errno.EACCES):
1159
1142
            # make writable and try again
1172
1155
    # - root can damage a solaris file system by using unlink,
1173
1156
    # - unlink raises different exceptions on different OSes (linux: EISDIR, win32:
1174
1157
    #   EACCES, OSX: EPERM) when invoked on a directory.
1175
 
    if isdir(path): # Takes care of symlinks
 
1158
    if isdir(path):  # Takes care of symlinks
1176
1159
        os.rmdir(path)
1177
1160
    else:
1178
1161
        os.unlink(path)
1257
1240
    if len(base) < MIN_ABS_PATHLENGTH:
1258
1241
        # must have space for e.g. a drive letter
1259
1242
        raise ValueError(gettext('%r is too short to calculate a relative path')
1260
 
            % (base,))
 
1243
                         % (base,))
1261
1244
 
1262
1245
    rp = abspath(path)
1263
1246
 
1300
1283
 
1301
1284
    abs_base = abspath(base)
1302
1285
    current = abs_base
1303
 
    _listdir = os.listdir
1304
1286
 
1305
1287
    # use an explicit iterator so we can easily consume the rest on early exit.
1306
1288
    bit_iter = iter(rel.split('/'))
1307
1289
    for bit in bit_iter:
1308
1290
        lbit = bit.lower()
1309
1291
        try:
1310
 
            next_entries = _listdir(current)
1311
 
        except OSError: # enoent, eperm, etc
 
1292
            next_entries = scandir(current)
 
1293
        except OSError:  # enoent, eperm, etc
1312
1294
            # We can't find this in the filesystem, so just append the
1313
1295
            # remaining bits.
1314
1296
            current = pathjoin(current, bit, *list(bit_iter))
1315
1297
            break
1316
 
        for look in next_entries:
1317
 
            if lbit == look.lower():
1318
 
                current = pathjoin(current, look)
 
1298
        for entry in next_entries:
 
1299
            if lbit == entry.name.lower():
 
1300
                current = entry.path
1319
1301
                break
1320
1302
        else:
1321
1303
            # got to the end, nothing matched, so we just return the
1325
1307
            break
1326
1308
    return current[len(abs_base):].lstrip('/')
1327
1309
 
 
1310
 
1328
1311
# XXX - TODO - we need better detection/integration of case-insensitive
1329
1312
# file-systems; Linux often sees FAT32 devices (or NFS-mounted OSX
1330
1313
# filesystems), for example, so could probably benefit from the same basic
1335
1318
else:
1336
1319
    canonical_relpath = relpath
1337
1320
 
 
1321
 
1338
1322
def canonical_relpaths(base, paths):
1339
1323
    """Create an iterable to canonicalize a sequence of relative paths.
1340
1324
 
1352
1336
    Otherwise it is decoded from the the filesystem's encoding. If decoding
1353
1337
    fails, a errors.BadFilenameEncoding exception is raised.
1354
1338
    """
1355
 
    if isinstance(filename, text_type):
 
1339
    if isinstance(filename, str):
1356
1340
        return filename
1357
1341
    try:
1358
1342
        return filename.decode(_fs_enc)
1367
1351
    Otherwise it is decoded from utf-8. If decoding fails, the exception is
1368
1352
    wrapped in a BzrBadParameterNotUnicode exception.
1369
1353
    """
1370
 
    if isinstance(unicode_or_utf8_string, text_type):
 
1354
    if isinstance(unicode_or_utf8_string, str):
1371
1355
        return unicode_or_utf8_string
1372
1356
    try:
1373
1357
        return unicode_or_utf8_string.decode('utf8')
1402
1386
    :return: None or a utf8 revision id.
1403
1387
    """
1404
1388
    if (unicode_or_utf8_string is None
1405
 
        or unicode_or_utf8_string.__class__ == bytes):
 
1389
            or unicode_or_utf8_string.__class__ == bytes):
1406
1390
        return unicode_or_utf8_string
1407
1391
    raise TypeError('Unicode revision ids are no longer supported. '
1408
1392
                    'Revision id generators should be creating utf8 revision '
1420
1404
    :return: None or a utf8 file id.
1421
1405
    """
1422
1406
    if (unicode_or_utf8_string is None
1423
 
        or unicode_or_utf8_string.__class__ == bytes):
 
1407
            or unicode_or_utf8_string.__class__ == bytes):
1424
1408
        return unicode_or_utf8_string
1425
1409
    raise TypeError('Unicode file ids are no longer supported. '
1426
1410
                    'File id generators should be creating utf8 file ids.')
1454
1438
    can be accessed by that path.
1455
1439
    """
1456
1440
 
1457
 
    return unicodedata.normalize('NFC', text_type(path)), True
 
1441
    if isinstance(path, bytes):
 
1442
        path = path.decode(sys.getfilesystemencoding())
 
1443
    return unicodedata.normalize('NFC', path), True
1458
1444
 
1459
1445
 
1460
1446
def _inaccessible_normalized_filename(path):
1461
1447
    __doc__ = _accessible_normalized_filename.__doc__
1462
1448
 
1463
 
    normalized = unicodedata.normalize('NFC', text_type(path))
 
1449
    if isinstance(path, bytes):
 
1450
        path = path.decode(sys.getfilesystemencoding())
 
1451
    normalized = unicodedata.normalize('NFC', path)
1464
1452
    return normalized, normalized == path
1465
1453
 
1466
1454
 
1489
1477
    except AttributeError:
1490
1478
        # siginterrupt doesn't exist on this platform, or for this version
1491
1479
        # of Python.
1492
 
        siginterrupt = lambda signum, flag: None
 
1480
        def siginterrupt(signum, flag): return None
1493
1481
    if restart_syscall:
1494
1482
        def sig_handler(*args):
1495
1483
            # Python resets the siginterrupt flag when a signal is
1520
1508
_terminal_size_state = 'no_data'
1521
1509
_first_terminal_size = None
1522
1510
 
 
1511
 
1523
1512
def terminal_width():
1524
1513
    """Return terminal width.
1525
1514
 
1606
1595
 
1607
1596
 
1608
1597
def _win32_terminal_size(width, height):
1609
 
    width, height = win32utils.get_console_size(defaultx=width, defaulty=height)
 
1598
    width, height = win32utils.get_console_size(
 
1599
        defaultx=width, defaulty=height)
1610
1600
    return width, height
1611
1601
 
1612
1602
 
1613
1603
def _ioctl_terminal_size(width, height):
1614
1604
    try:
1615
 
        import struct, fcntl, termios
 
1605
        import struct
 
1606
        import fcntl
 
1607
        import termios
1616
1608
        s = struct.pack('HHHH', 0, 0, 0, 0)
1617
1609
        x = fcntl.ioctl(1, termios.TIOCGWINSZ, s)
1618
1610
        height, width = struct.unpack('HHHH', x)[0:2]
1620
1612
        pass
1621
1613
    return width, height
1622
1614
 
 
1615
 
1623
1616
_terminal_size = None
1624
1617
"""Returns the terminal size as (width, height).
1625
1618
 
1635
1628
    _terminal_size = _ioctl_terminal_size
1636
1629
 
1637
1630
 
1638
 
def supports_executable():
1639
 
    return sys.platform != "win32"
 
1631
def supports_executable(path):
 
1632
    """Return if filesystem at path supports executable bit.
 
1633
 
 
1634
    :param path: Path for which to check the file system
 
1635
    :return: boolean indicating whether executable bit can be stored/relied upon
 
1636
    """
 
1637
    if sys.platform == 'win32':
 
1638
        return False
 
1639
    try:
 
1640
        fs_type = get_fs_type(path)
 
1641
    except errors.DependencyNotPresent as e:
 
1642
        trace.mutter('Unable to get fs type for %r: %s', path, e)
 
1643
    else:
 
1644
        if fs_type in ('vfat', 'ntfs'):
 
1645
            # filesystems known to not support executable bit
 
1646
            return False
 
1647
    return True
 
1648
 
 
1649
 
 
1650
def supports_symlinks(path):
 
1651
    """Return if the filesystem at path supports the creation of symbolic links.
 
1652
 
 
1653
    """
 
1654
    if not has_symlinks():
 
1655
        return False
 
1656
    try:
 
1657
        fs_type = get_fs_type(path)
 
1658
    except errors.DependencyNotPresent as e:
 
1659
        trace.mutter('Unable to get fs type for %r: %s', path, e)
 
1660
    else:
 
1661
        if fs_type in ('vfat', 'ntfs'):
 
1662
            # filesystems known to not support symlinks
 
1663
            return False
 
1664
    return True
1640
1665
 
1641
1666
 
1642
1667
def supports_posix_readonly():
1665
1690
        if orig_val is not None:
1666
1691
            del os.environ[env_variable]
1667
1692
    else:
1668
 
        if not PY3 and isinstance(value, text_type):
1669
 
            value = value.encode(get_user_encoding())
1670
1693
        os.environ[env_variable] = value
1671
1694
    return orig_val
1672
1695
 
1685
1708
        raise errors.IllegalPath(path)
1686
1709
 
1687
1710
 
1688
 
_WIN32_ERROR_DIRECTORY = 267 # Similar to errno.ENOTDIR
 
1711
_WIN32_ERROR_DIRECTORY = 267  # Similar to errno.ENOTDIR
 
1712
 
 
1713
 
 
1714
try:
 
1715
    scandir = os.scandir
 
1716
except AttributeError:  # Python < 3
 
1717
    lazy_import(globals(), """\
 
1718
from scandir import scandir
 
1719
""")
 
1720
 
1689
1721
 
1690
1722
def _is_error_enotdir(e):
1691
1723
    """Check if this exception represents ENOTDIR.
1703
1735
    :return: True if this represents an ENOTDIR error. False otherwise.
1704
1736
    """
1705
1737
    en = getattr(e, 'errno', None)
1706
 
    if (en == errno.ENOTDIR
1707
 
        or (sys.platform == 'win32'
1708
 
            and (en == _WIN32_ERROR_DIRECTORY
1709
 
                 or (en == errno.EINVAL
1710
 
                     and getattr(e, 'winerror', None) == _WIN32_ERROR_DIRECTORY)
1711
 
        ))):
 
1738
    if (en == errno.ENOTDIR or
 
1739
        (sys.platform == 'win32' and
 
1740
            (en == _WIN32_ERROR_DIRECTORY or
 
1741
             (en == errno.EINVAL
 
1742
              and getattr(e, 'winerror', None) == _WIN32_ERROR_DIRECTORY)
 
1743
             ))):
1712
1744
        return True
1713
1745
    return False
1714
1746
 
1741
1773
        rooted higher up.
1742
1774
    :return: an iterator over the dirs.
1743
1775
    """
1744
 
    #TODO there is a bit of a smell where the results of the directory-
 
1776
    # TODO there is a bit of a smell where the results of the directory-
1745
1777
    # summary in this, and the path from the root, may not agree
1746
1778
    # depending on top and prefix - i.e. ./foo and foo as a pair leads to
1747
1779
    # potentially confusing output. We should make this more robust - but
1748
1780
    # not at a speed cost. RBC 20060731
1749
 
    _lstat = os.lstat
1750
1781
    _directory = _directory_kind
1751
 
    _listdir = os.listdir
1752
 
    _kind_from_mode = file_kind_from_stat_mode
1753
1782
    pending = [(safe_unicode(prefix), "", _directory, None, safe_unicode(top))]
1754
1783
    while pending:
1755
1784
        # 0 - relpath, 1- basename, 2- kind, 3- stat, 4-toppath
1761
1790
        top_slash = top + u'/'
1762
1791
 
1763
1792
        dirblock = []
1764
 
        append = dirblock.append
1765
1793
        try:
1766
 
            names = sorted(map(decode_filename, _listdir(top)))
 
1794
            for entry in scandir(top):
 
1795
                name = decode_filename(entry.name)
 
1796
                statvalue = entry.stat(follow_symlinks=False)
 
1797
                kind = file_kind_from_stat_mode(statvalue.st_mode)
 
1798
                dirblock.append((relprefix + name, name, kind, statvalue, entry.path))
1767
1799
        except OSError as e:
1768
1800
            if not _is_error_enotdir(e):
1769
1801
                raise
1770
 
        else:
1771
 
            for name in names:
1772
 
                abspath = top_slash + name
1773
 
                statvalue = _lstat(abspath)
1774
 
                kind = _kind_from_mode(statvalue.st_mode)
1775
 
                append((relprefix + name, name, kind, statvalue, abspath))
 
1802
        except UnicodeDecodeError as e:
 
1803
            raise errors.BadFilenameEncoding(e.object, _fs_enc)
 
1804
        dirblock.sort()
1776
1805
        yield (relroot, top), dirblock
1777
1806
 
1778
1807
        # push the user specified dirs from dirblock
1884
1913
        See DirReader.read_dir for details.
1885
1914
        """
1886
1915
        _utf8_encode = self._utf8_encode
1887
 
        _lstat = os.lstat
1888
 
        _listdir = os.listdir
1889
 
        _kind_from_mode = file_kind_from_stat_mode
 
1916
 
 
1917
        def _fs_decode(s): return s.decode(_fs_enc)
 
1918
 
 
1919
        def _fs_encode(s): return s.encode(_fs_enc)
1890
1920
 
1891
1921
        if prefix:
1892
1922
            relprefix = prefix + b'/'
1896
1926
 
1897
1927
        dirblock = []
1898
1928
        append = dirblock.append
1899
 
        for name in sorted(_listdir(top)):
 
1929
        for entry in scandir(safe_utf8(top)):
1900
1930
            try:
1901
 
                name_utf8 = _utf8_encode(name)[0]
 
1931
                name = _fs_decode(entry.name)
1902
1932
            except UnicodeDecodeError:
1903
1933
                raise errors.BadFilenameEncoding(
1904
 
                    _utf8_encode(relprefix)[0] + name, _fs_enc)
 
1934
                    relprefix + entry.name, _fs_enc)
1905
1935
            abspath = top_slash + name
1906
 
            statvalue = _lstat(abspath)
1907
 
            kind = _kind_from_mode(statvalue.st_mode)
 
1936
            name_utf8 = _utf8_encode(name)[0]
 
1937
            statvalue = entry.stat(follow_symlinks=False)
 
1938
            kind = file_kind_from_stat_mode(statvalue.st_mode)
1908
1939
            append((relprefix + name_utf8, name_utf8, kind, statvalue, abspath))
1909
 
        return dirblock
 
1940
        return sorted(dirblock)
1910
1941
 
1911
1942
 
1912
1943
def copy_tree(from_path, to_path, handlers={}):
1940
1971
    real_handlers = {'file': shutil.copy2,
1941
1972
                     'symlink': copy_link,
1942
1973
                     'directory': copy_dir,
1943
 
                    }
 
1974
                     }
1944
1975
    real_handlers.update(handlers)
1945
1976
 
1946
1977
    if not os.path.exists(to_path):
1961
1992
    if chown is None:
1962
1993
        return
1963
1994
 
1964
 
    if src == None:
 
1995
    if src is None:
1965
1996
        src = os.path.dirname(dst)
1966
1997
        if src == '':
1967
1998
            src = '.'
1969
2000
    try:
1970
2001
        s = os.stat(src)
1971
2002
        chown(dst, s.st_uid, s.st_gid)
1972
 
    except OSError as e:
 
2003
    except OSError:
1973
2004
        trace.warning(
1974
2005
            'Unable to copy ownership from "%s" to "%s". '
1975
2006
            'You may want to set it manually.', src, dst)
1988
2019
    """Compare path_a and path_b to generate the same order walkdirs uses."""
1989
2020
    key_a = path_prefix_key(path_a)
1990
2021
    key_b = path_prefix_key(path_b)
1991
 
    return cmp(key_a, key_b)
 
2022
    return (key_a > key_b) - (key_a < key_b)
1992
2023
 
1993
2024
 
1994
2025
_cached_user_encoding = None
2025
2056
                             ' unknown encoding %s.'
2026
2057
                             ' Continuing with ascii encoding.\n'
2027
2058
                             % user_encoding
2028
 
                            )
 
2059
                             )
2029
2060
        user_encoding = 'ascii'
2030
2061
    else:
2031
2062
        # Get 'ascii' when setlocale has not been called or LANG=C or unset.
2055
2086
        return win32utils.get_host_name()
2056
2087
    else:
2057
2088
        import socket
2058
 
        if PY3:
2059
 
            return socket.gethostname()
2060
 
        return socket.gethostname().decode(get_user_encoding())
 
2089
        return socket.gethostname()
2061
2090
 
2062
2091
 
2063
2092
# We must not read/write any more than 64k at a time from/to a socket so we
2075
2104
 
2076
2105
 
2077
2106
def read_bytes_from_socket(sock, report_activity=None,
2078
 
        max_read_size=MAX_SOCKET_CHUNK):
 
2107
                           max_read_size=MAX_SOCKET_CHUNK):
2079
2108
    """Read up to max_read_size of bytes from sock and notify of progress.
2080
2109
 
2081
2110
    Translates "Connection reset by peer" into file-like EOF (return an
2084
2113
    """
2085
2114
    while True:
2086
2115
        try:
2087
 
            bytes = sock.recv(max_read_size)
 
2116
            data = sock.recv(max_read_size)
2088
2117
        except socket.error as e:
2089
2118
            eno = e.args[0]
2090
2119
            if eno in _end_of_stream_errors:
2091
2120
                # The connection was closed by the other side.  Callers expect
2092
2121
                # an empty string to signal end-of-stream.
2093
 
                return ""
 
2122
                return b""
2094
2123
            elif eno == errno.EINTR:
2095
2124
                # Retry the interrupted recv.
2096
2125
                continue
2097
2126
            raise
2098
2127
        else:
2099
2128
            if report_activity is not None:
2100
 
                report_activity(len(bytes), 'read')
2101
 
            return bytes
 
2129
                report_activity(len(data), 'read')
 
2130
            return data
2102
2131
 
2103
2132
 
2104
2133
def recv_all(socket, count):
2111
2140
 
2112
2141
    This isn't optimized and is intended mostly for use in testing.
2113
2142
    """
2114
 
    b = ''
 
2143
    b = b''
2115
2144
    while len(b) < count:
2116
2145
        new = read_bytes_from_socket(socket, None, count - len(b))
2117
 
        if new == '':
2118
 
            break # eof
 
2146
        if new == b'':
 
2147
            break  # eof
2119
2148
        b += new
2120
2149
    return b
2121
2150
 
2138
2167
    view = memoryview(bytes)
2139
2168
    while sent_total < byte_count:
2140
2169
        try:
2141
 
            sent = sock.send(view[sent_total:sent_total+MAX_SOCKET_CHUNK])
 
2170
            sent = sock.send(view[sent_total:sent_total + MAX_SOCKET_CHUNK])
2142
2171
        except (socket.error, IOError) as e:
2143
2172
            if e.args[0] in _end_of_stream_errors:
2144
2173
                raise errors.ConnectionReset(
2170
2199
            sock.connect(sa)
2171
2200
            return sock
2172
2201
 
2173
 
        except socket.error as err:
 
2202
        except socket.error as e:
 
2203
            err = e
2174
2204
            # 'err' is now the most recent error
2175
2205
            if sock is not None:
2176
2206
                sock.close()
2221
2251
    base = dirname(breezy.__file__)
2222
2252
    if getattr(sys, 'frozen', None):    # bzr.exe
2223
2253
        base = abspath(pathjoin(base, '..', '..'))
2224
 
    f = file(pathjoin(base, resource_relpath), "rU")
2225
 
    try:
 
2254
    with open(pathjoin(base, resource_relpath), "rt") as f:
2226
2255
        return f.read()
2227
 
    finally:
2228
 
        f.close()
 
2256
 
2229
2257
 
2230
2258
def file_kind_from_stat_mode_thunk(mode):
2231
2259
    global file_kind_from_stat_mode
2233
2261
        try:
2234
2262
            from ._readdir_pyx import UTF8DirReader
2235
2263
            file_kind_from_stat_mode = UTF8DirReader().kind_from_mode
2236
 
        except ImportError as e:
 
2264
        except ImportError:
2237
2265
            # This is one time where we won't warn that an extension failed to
2238
2266
            # load. The extension is never available on Windows anyway.
2239
2267
            from ._readdir_py import (
2240
2268
                _kind_from_mode as file_kind_from_stat_mode
2241
2269
                )
2242
2270
    return file_kind_from_stat_mode(mode)
 
2271
 
 
2272
 
2243
2273
file_kind_from_stat_mode = file_kind_from_stat_mode_thunk
2244
2274
 
 
2275
 
2245
2276
def file_stat(f, _lstat=os.lstat):
2246
2277
    try:
2247
2278
        # XXX cache?
2251
2282
            raise errors.NoSuchFile(f)
2252
2283
        raise
2253
2284
 
 
2285
 
2254
2286
def file_kind(f, _lstat=os.lstat):
2255
2287
    stat_value = file_stat(f, _lstat)
2256
2288
    return file_kind_from_stat_mode(stat_value.st_mode)
2257
2289
 
 
2290
 
2258
2291
def until_no_eintr(f, *a, **kw):
2259
2292
    """Run f(*a, **kw), retrying if an EINTR error occurs.
2260
2293
 
2311
2344
                                stdout=subprocess.PIPE).communicate()[0]
2312
2345
elif sys.platform == 'sunos5':
2313
2346
    def _local_concurrency():
2314
 
        return subprocess.Popen(['psrinfo', '-p',],
 
2347
        return subprocess.Popen(['psrinfo', '-p', ],
2315
2348
                                stdout=subprocess.PIPE).communicate()[0]
2316
2349
elif sys.platform == "win32":
2317
2350
    def _local_concurrency():
2325
2358
 
2326
2359
_cached_local_concurrency = None
2327
2360
 
 
2361
 
2328
2362
def local_concurrency(use_cache=True):
2329
2363
    """Return how many processes can be run concurrently.
2330
2364
 
2352
2386
    except (TypeError, ValueError):
2353
2387
        concurrency = 1
2354
2388
    if use_cache:
2355
 
        _cached_concurrency = concurrency
 
2389
        _cached_local_concurrency = concurrency
2356
2390
    return concurrency
2357
2391
 
2358
2392
 
2370
2404
            data, _ = self.encode(object, self.errors)
2371
2405
            self.stream.write(data)
2372
2406
 
 
2407
 
2373
2408
if sys.platform == 'win32':
2374
2409
    def open_file(filename, mode='r', bufsize=-1):
2375
2410
        """This function is used to override the ``open`` builtin.
2403
2438
            else:
2404
2439
                flags |= os.O_WRONLY
2405
2440
            flags |= os.O_CREAT | os.O_APPEND
2406
 
        else: #reading
 
2441
        else:  # reading
2407
2442
            if updating:
2408
2443
                flags |= os.O_RDWR
2409
2444
            else:
2449
2484
 
2450
2485
def find_executable_on_path(name):
2451
2486
    """Finds an executable on the PATH.
2452
 
    
 
2487
 
2453
2488
    On Windows, this will try to append each extension in the PATHEXT
2454
2489
    environment variable to the name, if it cannot be found with the name
2455
2490
    as given.
2456
 
    
 
2491
 
2457
2492
    :param name: The base name of the executable.
2458
2493
    :return: The path to the executable found or None.
2459
2494
    """
2497
2532
            # exists, though not ours
2498
2533
            return False
2499
2534
        else:
2500
 
            mutter("os.kill(%d, 0) failed: %s" % (pid, e))
 
2535
            trace.mutter("os.kill(%d, 0) failed: %s" % (pid, e))
2501
2536
            # Don't really know.
2502
2537
            return False
2503
2538
    else:
2504
2539
        # Exists and our process: not dead.
2505
2540
        return False
2506
2541
 
 
2542
 
2507
2543
if sys.platform == "win32":
2508
2544
    is_local_pid_dead = win32utils.is_local_pid_dead
2509
2545
else:
2516
2552
 
2517
2553
def fdatasync(fileno):
2518
2554
    """Flush file contents to disk if possible.
2519
 
    
 
2555
 
2520
2556
    :param fileno: Integer OS file handle.
2521
2557
    :raises TransportNotPossible: If flushing to disk is not possible.
2522
2558
    """
2536
2572
 
2537
2573
def ensure_empty_directory_exists(path, exception_class):
2538
2574
    """Make sure a local directory exists and is empty.
2539
 
    
 
2575
 
2540
2576
    If it does not exist, it is created.  If it exists and is not empty, an
2541
2577
    instance of exception_class is raised.
2542
2578
    """
2560
2596
    if sys.platform == "win32" and win32utils._is_pywintypes_error(evalue):
2561
2597
        return True
2562
2598
    return False
 
2599
 
 
2600
 
 
2601
def read_mtab(path):
 
2602
    """Read an fstab-style file and extract mountpoint+filesystem information.
 
2603
 
 
2604
    :param path: Path to read from
 
2605
    :yield: Tuples with mountpoints (as bytestrings) and filesystem names
 
2606
    """
 
2607
    with open(path, 'rb') as f:
 
2608
        for line in f:
 
2609
            if line.startswith(b'#'):
 
2610
                continue
 
2611
            cols = line.split()
 
2612
            if len(cols) < 3:
 
2613
                continue
 
2614
            yield cols[1], cols[2].decode('ascii', 'replace')
 
2615
 
 
2616
 
 
2617
MTAB_PATH = '/etc/mtab'
 
2618
 
 
2619
class FilesystemFinder(object):
 
2620
    """Find the filesystem for a particular path."""
 
2621
 
 
2622
    def __init__(self, mountpoints):
 
2623
        def key(x):
 
2624
            return len(x[0])
 
2625
        self._mountpoints = sorted(mountpoints, key=key, reverse=True)
 
2626
 
 
2627
    @classmethod
 
2628
    def from_mtab(cls):
 
2629
        """Create a FilesystemFinder from an mtab-style file.
 
2630
 
 
2631
        Note that this will silenty ignore mtab if it doesn't exist or can not
 
2632
        be opened.
 
2633
        """
 
2634
        # TODO(jelmer): Use inotify to be notified when /etc/mtab changes and
 
2635
        # we need to re-read it.
 
2636
        try:
 
2637
            return cls(read_mtab(MTAB_PATH))
 
2638
        except EnvironmentError as e:
 
2639
            trace.mutter('Unable to read mtab: %s', e)
 
2640
            return cls([])
 
2641
 
 
2642
    def find(self, path):
 
2643
        """Find the filesystem used by a particular path.
 
2644
 
 
2645
        :param path: Path to find (bytestring or text type)
 
2646
        :return: Filesystem name (as text type) or None, if the filesystem is
 
2647
            unknown.
 
2648
        """
 
2649
        for mountpoint, filesystem in self._mountpoints:
 
2650
            if is_inside(mountpoint, path):
 
2651
                return filesystem
 
2652
        return None
 
2653
 
 
2654
 
 
2655
_FILESYSTEM_FINDER = None
 
2656
 
 
2657
 
 
2658
def get_fs_type(path):
 
2659
    """Return the filesystem type for the partition a path is in.
 
2660
 
 
2661
    :param path: Path to search filesystem type for
 
2662
    :return: A FS type, as string. E.g. "ext2"
 
2663
    """
 
2664
    global _FILESYSTEM_FINDER
 
2665
    if _FILESYSTEM_FINDER is None:
 
2666
        _FILESYSTEM_FINDER = FilesystemFinder.from_mtab()
 
2667
 
 
2668
    if not isinstance(path, bytes):
 
2669
        path = path.encode(_fs_enc)
 
2670
 
 
2671
    return _FILESYSTEM_FINDER.find(path)
 
2672
 
 
2673
 
 
2674
perf_counter = time.perf_counter