/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: 2018-02-18 21:42:57 UTC
  • mto: This revision was merged to the branch mainline in revision 6859.
  • Revision ID: jelmer@jelmer.uk-20180218214257-jpevutp1wa30tz3v
Update TODO to reference Breezy, not Bazaar.

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
128
155
        return set(paths)
129
156
 
130
157
    def sort_key(path):
131
 
        if isinstance(path, bytes):
132
 
            return path.split(b'/')
133
 
        else:
134
 
            return path.split('/')
 
158
        return path.split('/')
135
159
    sorted_paths = sorted(list(paths), key=sort_key)
136
160
 
137
161
    search_paths = [sorted_paths[0]]
164
188
 
165
189
_directory_kind = 'directory'
166
190
 
167
 
 
168
191
def get_umask():
169
192
    """Return the current umask"""
170
193
    # Assume that people aren't messing with the umask while running
201
224
            return True
202
225
        except OSError as e:
203
226
            if e.errno == errno.ENOENT:
204
 
                return False
 
227
                return False;
205
228
            else:
206
 
                raise errors.BzrError(
207
 
                    gettext("lstat/stat of ({0!r}): {1!r}").format(f, e))
 
229
                raise errors.BzrError(gettext("lstat/stat of ({0!r}): {1!r}").format(f, e))
208
230
 
209
231
 
210
232
def fancy_rename(old, new, rename_func, unlink_func):
234
256
    file_existed = False
235
257
    try:
236
258
        rename_func(new, tmp_name)
237
 
    except (errors.NoSuchFile,):
 
259
    except (errors.NoSuchFile,) as e:
238
260
        pass
239
261
    except IOError as e:
240
262
        # RBC 20060103 abstraction leakage: the paramiko SFTP clients rename
244
266
            raise
245
267
    except Exception as e:
246
268
        if (getattr(e, 'errno', None) is None
247
 
                or e.errno not in (errno.ENOENT, errno.ENOTDIR)):
 
269
            or e.errno not in (errno.ENOENT, errno.ENOTDIR)):
248
270
            raise
249
271
    else:
250
272
        file_existed = True
260
282
        # case-insensitive filesystem), so we may have accidentally renamed
261
283
        # source by when we tried to rename target
262
284
        if (file_existed and e.errno in (None, errno.ENOENT)
263
 
                and old.lower() == new.lower()):
 
285
            and old.lower() == new.lower()):
264
286
            # source and target are the same file on a case-insensitive
265
287
            # filesystem, so we don't generate an exception
266
288
            pass
301
323
    # as a special case here by simply removing the first slash, as we consider
302
324
    # that breaking POSIX compatibility for this obscure feature is acceptable.
303
325
    # This is not a paranoid precaution, as we notably get paths like this when
304
 
    # the repo is hosted at the root of the filesystem, i.e. in "/".
 
326
    # the repo is hosted at the root of the filesystem, i.e. in "/".    
305
327
    if path.startswith('//'):
306
328
        path = path[1:]
307
329
    return path
308
330
 
309
331
 
 
332
def _posix_path_from_environ(key):
 
333
    """Get unicode path from `key` in environment or None if not present
 
334
 
 
335
    Note that posix systems use arbitrary byte strings for filesystem objects,
 
336
    so a path that raises BadFilenameEncoding here may still be accessible.
 
337
    """
 
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)
 
346
 
 
347
 
310
348
def _posix_get_home_dir():
311
349
    """Get the home directory of the current user as a unicode path"""
312
350
    path = posixpath.expanduser("~")
320
358
 
321
359
def _posix_getuser_unicode():
322
360
    """Get username from environment or password database as unicode"""
323
 
    return getpass.getuser()
 
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))
324
370
 
325
371
 
326
372
def _win32_fixdrive(path):
338
384
 
339
385
def _win32_abspath(path):
340
386
    # Real ntpath.abspath doesn't have a problem with a unicode cwd
341
 
    return _win32_fixdrive(ntpath.abspath(path).replace('\\', '/'))
 
387
    return _win32_fixdrive(ntpath.abspath(unicode(path)).replace('\\', '/'))
342
388
 
343
389
 
344
390
def _win32_realpath(path):
345
391
    # Real ntpath.realpath doesn't have a problem with a unicode cwd
346
 
    return _win32_fixdrive(ntpath.realpath(path).replace('\\', '/'))
 
392
    return _win32_fixdrive(ntpath.realpath(unicode(path)).replace('\\', '/'))
347
393
 
348
394
 
349
395
def _win32_pathjoin(*args):
351
397
 
352
398
 
353
399
def _win32_normpath(path):
354
 
    return _win32_fixdrive(ntpath.normpath(path).replace('\\', '/'))
 
400
    return _win32_fixdrive(ntpath.normpath(unicode(path)).replace('\\', '/'))
355
401
 
356
402
 
357
403
def _win32_getcwd():
396
442
            rename_func(old, new)
397
443
        except OSError as e:
398
444
            detailed_error = OSError(e.errno, e.strerror +
399
 
                                     " [occurred when renaming '%s' to '%s']" %
400
 
                                     (old, new))
 
445
                                " [occurred when renaming '%s' to '%s']" %
 
446
                                (old, new))
401
447
            detailed_error.old_filename = old
402
448
            detailed_error.new_filename = new
403
449
            raise detailed_error
405
451
    return _rename_wrapper
406
452
 
407
453
 
408
 
_getcwd = os.getcwd
 
454
if sys.version_info > (3,):
 
455
    _getcwd = os.getcwd
 
456
else:
 
457
    _getcwd = os.getcwdu
409
458
 
410
459
 
411
460
# Default rename wraps os.rename()
417
466
realpath = _posix_realpath
418
467
pathjoin = os.path.join
419
468
normpath = _posix_normpath
 
469
path_from_environ = _posix_path_from_environ
420
470
_get_home_dir = _posix_get_home_dir
421
471
getuser_unicode = _posix_getuser_unicode
422
472
getcwd = _getcwd
430
480
lstat = os.lstat
431
481
fstat = os.fstat
432
482
 
433
 
 
434
483
def wrap_stat(st):
435
484
    return st
436
485
 
463
512
        """
464
513
        exception = excinfo[1]
465
514
        if function in (os.remove, os.rmdir) \
466
 
                and isinstance(exception, OSError) \
467
 
                and exception.errno == errno.EACCES:
 
515
            and isinstance(exception, OSError) \
 
516
            and exception.errno == errno.EACCES:
468
517
            make_writable(path)
469
518
            function(path)
470
519
        else:
474
523
        """Replacer for shutil.rmtree: could remove readonly dirs/files"""
475
524
        return shutil.rmtree(path, ignore_errors, onerror)
476
525
 
 
526
    f = win32utils.get_unicode_argv     # special function or None
 
527
    if f is not None:
 
528
        get_unicode_argv = f
 
529
    path_from_environ = win32utils.get_environ_unicode
477
530
    _get_home_dir = win32utils.get_home_location
478
531
    getuser_unicode = win32utils.get_user_name
479
532
 
504
557
            output_encoding = get_user_encoding()
505
558
            if trace:
506
559
                mutter('encoding stdout as osutils.get_user_encoding() %r',
507
 
                       output_encoding)
 
560
                   output_encoding)
508
561
        else:
509
562
            output_encoding = input_encoding
510
563
            if trace:
511
564
                mutter('encoding stdout as sys.stdin encoding %r',
512
 
                       output_encoding)
 
565
                    output_encoding)
513
566
    else:
514
567
        if trace:
515
568
            mutter('encoding stdout as sys.stdout encoding %r', output_encoding)
518
571
        output_encoding = get_user_encoding()
519
572
        if trace:
520
573
            mutter('cp0 is invalid encoding.'
521
 
                   ' encoding stdout as osutils.get_user_encoding() %r',
522
 
                   output_encoding)
 
574
               ' encoding stdout as osutils.get_user_encoding() %r',
 
575
               output_encoding)
523
576
    # check encoding
524
577
    try:
525
578
        codecs.lookup(output_encoding)
528
581
                         ' unknown terminal encoding %s.\n'
529
582
                         '  Using encoding %s instead.\n'
530
583
                         % (output_encoding, get_user_encoding())
531
 
                         )
 
584
                        )
532
585
        output_encoding = get_user_encoding()
533
586
 
534
587
    return output_encoding
561
614
    except OSError:
562
615
        return False
563
616
 
564
 
 
565
617
def islink(f):
566
618
    """True if f is a symlink."""
567
619
    try:
569
621
    except OSError:
570
622
        return False
571
623
 
572
 
 
573
624
def is_inside(dir, fname):
574
625
    """True if fname is inside dir.
575
626
 
585
636
    if dir == fname:
586
637
        return True
587
638
 
588
 
    if dir in ('', b''):
 
639
    if dir == '':
589
640
        return True
590
641
 
591
 
    if isinstance(dir, bytes):
592
 
        if not dir.endswith(b'/'):
593
 
            dir += b'/'
594
 
    else:
595
 
        if not dir.endswith('/'):
596
 
            dir += '/'
 
642
    if dir[-1] != '/':
 
643
        dir += '/'
597
644
 
598
645
    return fname.startswith(dir)
599
646
 
672
719
    # writes fail on some platforms (e.g. Windows with SMB  mounted
673
720
    # drives).
674
721
    if not segment_size:
675
 
        segment_size = 5242880  # 5MB
 
722
        segment_size = 5242880 # 5MB
676
723
    offsets = range(0, len(bytes), segment_size)
677
724
    view = memoryview(bytes)
678
725
    write = file_handle.write
679
726
    for offset in offsets:
680
 
        write(view[offset:offset + segment_size])
 
727
        write(view[offset:offset+segment_size])
681
728
 
682
729
 
683
730
def file_iterator(input_file, readsize=32768):
690
737
 
691
738
# GZ 2017-09-16: Makes sense in general for hexdigest() result to be text, but
692
739
# used as bytes through most interfaces so encode with this wrapper.
693
 
def _hexdigest(hashobj):
694
 
    return hashobj.hexdigest().encode()
 
740
if PY3:
 
741
    def _hexdigest(hashobj):
 
742
        return hashobj.hexdigest().encode()
 
743
else:
 
744
    def _hexdigest(hashobj):
 
745
        return hashobj.hexdigest()
695
746
 
696
747
 
697
748
def sha_file(f):
700
751
    The file cursor should be already at the start.
701
752
    """
702
753
    s = sha()
703
 
    BUFSIZE = 128 << 10
 
754
    BUFSIZE = 128<<10
704
755
    while True:
705
756
        b = f.read(BUFSIZE)
706
757
        if not b:
717
768
    """
718
769
    size = 0
719
770
    s = sha()
720
 
    BUFSIZE = 128 << 10
 
771
    BUFSIZE = 128<<10
721
772
    while True:
722
773
        b = f.read(BUFSIZE)
723
774
        if not b:
733
784
    f = os.open(fname, os.O_RDONLY | O_BINARY | O_NOINHERIT)
734
785
    try:
735
786
        while True:
736
 
            b = os.read(f, 1 << 16)
 
787
            b = os.read(f, 1<<16)
737
788
            if not b:
738
789
                return _hexdigest(s)
739
790
            s.update(b)
768
819
        bi = b.read(BUFSIZE)
769
820
        if ai != bi:
770
821
            return False
771
 
        if not ai:
 
822
        if ai == '':
772
823
            return True
773
824
 
774
825
 
779
830
    offset = datetime.fromtimestamp(t) - datetime.utcfromtimestamp(t)
780
831
    return offset.days * 86400 + offset.seconds
781
832
 
782
 
 
783
833
weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
784
834
_default_format_by_weekday_num = [wd + " %Y-%m-%d %H:%M:%S" for wd in weekdays]
785
835
 
797
847
    :param show_offset: Whether to append the timezone.
798
848
    """
799
849
    (date_fmt, tt, offset_str) = \
800
 
        _format_date(t, offset, timezone, date_fmt, show_offset)
 
850
               _format_date(t, offset, timezone, date_fmt, show_offset)
801
851
    date_fmt = date_fmt.replace('%a', weekdays[tt[6]])
802
852
    date_str = time.strftime(date_fmt, tt)
803
853
    return date_str + offset_str
808
858
 
809
859
 
810
860
def format_date_with_offset_in_original_timezone(t, offset=0,
811
 
                                                 _cache=_offset_cache):
 
861
    _cache=_offset_cache):
812
862
    """Return a formatted date string in the original timezone.
813
863
 
814
864
    This routine may be faster then format_date.
841
891
    :param show_offset: Whether to append the timezone.
842
892
    """
843
893
    (date_fmt, tt, offset_str) = \
844
 
        _format_date(t, offset, timezone, date_fmt, show_offset)
 
894
               _format_date(t, offset, timezone, date_fmt, show_offset)
845
895
    date_str = time.strftime(date_fmt, tt)
846
 
    if not isinstance(date_str, str):
 
896
    if not isinstance(date_str, text_type):
847
897
        date_str = date_str.decode(get_user_encoding(), 'replace')
848
898
    return date_str + offset_str
849
899
 
890
940
        delta = -delta
891
941
 
892
942
    seconds = delta
893
 
    if seconds < 90:  # print seconds up to 90 seconds
 
943
    if seconds < 90: # print seconds up to 90 seconds
894
944
        if seconds == 1:
895
945
            return '%d second %s' % (seconds, direction,)
896
946
        else:
902
952
        plural_seconds = ''
903
953
    else:
904
954
        plural_seconds = 's'
905
 
    if minutes < 90:  # print minutes, seconds up to 90 minutes
 
955
    if minutes < 90: # print minutes, seconds up to 90 minutes
906
956
        if minutes == 1:
907
957
            return '%d minute, %d second%s %s' % (
908
 
                minutes, seconds, plural_seconds, direction)
 
958
                    minutes, seconds, plural_seconds, direction)
909
959
        else:
910
960
            return '%d minutes, %d second%s %s' % (
911
 
                minutes, seconds, plural_seconds, direction)
 
961
                    minutes, seconds, plural_seconds, direction)
912
962
 
913
963
    hours = int(minutes / 60)
914
964
    minutes -= 60 * hours
923
973
    return '%d hours, %d minute%s %s' % (hours, minutes,
924
974
                                         plural_minutes, direction)
925
975
 
926
 
 
927
976
def filesize(f):
928
977
    """Return size of given open file."""
929
978
    return os.fstat(f.fileno())[stat.ST_SIZE]
930
979
 
931
980
 
932
 
# Alias os.urandom to support platforms (which?) without /dev/urandom and
 
981
# Alias os.urandom to support platforms (which?) without /dev/urandom and 
933
982
# override if it doesn't work. Avoid checking on windows where there is
934
983
# significant initialisation cost that can be avoided for some bzr calls.
935
984
 
950
999
 
951
1000
 
952
1001
ALNUM = '0123456789abcdefghijklmnopqrstuvwxyz'
953
 
 
954
 
 
955
1002
def rand_chars(num):
956
1003
    """Return a random string of num alphanumeric characters
957
1004
 
960
1007
    """
961
1008
    s = ''
962
1009
    for raw_byte in rand_bytes(num):
963
 
        s += ALNUM[raw_byte % 36]
 
1010
        if not PY3:
 
1011
            s += ALNUM[ord(raw_byte) % 36]
 
1012
        else:
 
1013
            s += ALNUM[raw_byte % 36]
964
1014
    return s
965
1015
 
966
1016
 
967
 
# TODO: We could later have path objects that remember their list
968
 
# decomposition (might be too tricksy though.)
 
1017
## TODO: We could later have path objects that remember their list
 
1018
## decomposition (might be too tricksy though.)
969
1019
 
970
1020
def splitpath(p):
971
1021
    """Turn string into list of parts."""
972
 
    use_bytes = isinstance(p, bytes)
973
 
    if os.path.sep == '\\':
974
 
        # split on either delimiter because people might use either on
975
 
        # Windows
976
 
        if use_bytes:
977
 
            ps = re.split(b'[\\\\/]', p)
978
 
        else:
979
 
            ps = re.split(r'[\\/]', p)
980
 
    else:
981
 
        if use_bytes:
982
 
            ps = p.split(b'/')
983
 
        else:
984
 
            ps = p.split('/')
985
 
 
986
 
    if use_bytes:
987
 
        parent_dir = b'..'
988
 
        current_empty_dir = (b'.', b'')
989
 
    else:
990
 
        parent_dir = '..'
991
 
        current_empty_dir = ('.', '')
 
1022
    # split on either delimiter because people might use either on
 
1023
    # Windows
 
1024
    ps = re.split(r'[\\/]', p)
992
1025
 
993
1026
    rps = []
994
1027
    for f in ps:
995
 
        if f == parent_dir:
 
1028
        if f == '..':
996
1029
            raise errors.BzrError(gettext("sorry, %r not allowed in path") % f)
997
 
        elif f in current_empty_dir:
 
1030
        elif (f == '.') or (f == ''):
998
1031
            pass
999
1032
        else:
1000
1033
            rps.append(f)
1077
1110
    """Split s into lines, but without removing the newline characters."""
1078
1111
    # Trivially convert a fulltext into a 'chunked' representation, and let
1079
1112
    # chunks_to_lines do the heavy lifting.
1080
 
    if isinstance(s, bytes):
 
1113
    if isinstance(s, str):
1081
1114
        # chunks_to_lines only supports 8-bit strings
1082
1115
        return chunks_to_lines([s])
1083
1116
    else:
1120
1153
    Will delete even if readonly.
1121
1154
    """
1122
1155
    try:
1123
 
        _delete_file_or_dir(path)
 
1156
       _delete_file_or_dir(path)
1124
1157
    except (OSError, IOError) as e:
1125
1158
        if e.errno in (errno.EPERM, errno.EACCES):
1126
1159
            # make writable and try again
1139
1172
    # - root can damage a solaris file system by using unlink,
1140
1173
    # - unlink raises different exceptions on different OSes (linux: EISDIR, win32:
1141
1174
    #   EACCES, OSX: EPERM) when invoked on a directory.
1142
 
    if isdir(path):  # Takes care of symlinks
 
1175
    if isdir(path): # Takes care of symlinks
1143
1176
        os.rmdir(path)
1144
1177
    else:
1145
1178
        os.unlink(path)
1224
1257
    if len(base) < MIN_ABS_PATHLENGTH:
1225
1258
        # must have space for e.g. a drive letter
1226
1259
        raise ValueError(gettext('%r is too short to calculate a relative path')
1227
 
                         % (base,))
 
1260
            % (base,))
1228
1261
 
1229
1262
    rp = abspath(path)
1230
1263
 
1267
1300
 
1268
1301
    abs_base = abspath(base)
1269
1302
    current = abs_base
 
1303
    _listdir = os.listdir
1270
1304
 
1271
1305
    # use an explicit iterator so we can easily consume the rest on early exit.
1272
1306
    bit_iter = iter(rel.split('/'))
1273
1307
    for bit in bit_iter:
1274
1308
        lbit = bit.lower()
1275
1309
        try:
1276
 
            next_entries = scandir(current)
1277
 
        except OSError:  # enoent, eperm, etc
 
1310
            next_entries = _listdir(current)
 
1311
        except OSError: # enoent, eperm, etc
1278
1312
            # We can't find this in the filesystem, so just append the
1279
1313
            # remaining bits.
1280
1314
            current = pathjoin(current, bit, *list(bit_iter))
1281
1315
            break
1282
 
        for entry in next_entries:
1283
 
            if lbit == entry.name.lower():
1284
 
                current = entry.path
 
1316
        for look in next_entries:
 
1317
            if lbit == look.lower():
 
1318
                current = pathjoin(current, look)
1285
1319
                break
1286
1320
        else:
1287
1321
            # got to the end, nothing matched, so we just return the
1291
1325
            break
1292
1326
    return current[len(abs_base):].lstrip('/')
1293
1327
 
1294
 
 
1295
1328
# XXX - TODO - we need better detection/integration of case-insensitive
1296
1329
# file-systems; Linux often sees FAT32 devices (or NFS-mounted OSX
1297
1330
# filesystems), for example, so could probably benefit from the same basic
1302
1335
else:
1303
1336
    canonical_relpath = relpath
1304
1337
 
1305
 
 
1306
1338
def canonical_relpaths(base, paths):
1307
1339
    """Create an iterable to canonicalize a sequence of relative paths.
1308
1340
 
1320
1352
    Otherwise it is decoded from the the filesystem's encoding. If decoding
1321
1353
    fails, a errors.BadFilenameEncoding exception is raised.
1322
1354
    """
1323
 
    if isinstance(filename, str):
 
1355
    if isinstance(filename, text_type):
1324
1356
        return filename
1325
1357
    try:
1326
1358
        return filename.decode(_fs_enc)
1335
1367
    Otherwise it is decoded from utf-8. If decoding fails, the exception is
1336
1368
    wrapped in a BzrBadParameterNotUnicode exception.
1337
1369
    """
1338
 
    if isinstance(unicode_or_utf8_string, str):
 
1370
    if isinstance(unicode_or_utf8_string, text_type):
1339
1371
        return unicode_or_utf8_string
1340
1372
    try:
1341
1373
        return unicode_or_utf8_string.decode('utf8')
1362
1394
    return unicode_or_utf8_string.encode('utf-8')
1363
1395
 
1364
1396
 
 
1397
def safe_revision_id(unicode_or_utf8_string):
 
1398
    """Revision ids should now be utf8, but at one point they were unicode.
 
1399
 
 
1400
    :param unicode_or_utf8_string: A possibly Unicode revision_id. (can also be
 
1401
        utf8 or None).
 
1402
    :return: None or a utf8 revision id.
 
1403
    """
 
1404
    if (unicode_or_utf8_string is None
 
1405
        or unicode_or_utf8_string.__class__ == bytes):
 
1406
        return unicode_or_utf8_string
 
1407
    raise TypeError('Unicode revision ids are no longer supported. '
 
1408
                    'Revision id generators should be creating utf8 revision '
 
1409
                    'ids.')
 
1410
 
 
1411
 
 
1412
def safe_file_id(unicode_or_utf8_string):
 
1413
    """File ids should now be utf8, but at one point they were unicode.
 
1414
 
 
1415
    This is the same as safe_utf8, except it uses the cached encode functions
 
1416
    to save a little bit of performance.
 
1417
 
 
1418
    :param unicode_or_utf8_string: A possibly Unicode file_id. (can also be
 
1419
        utf8 or None).
 
1420
    :return: None or a utf8 file id.
 
1421
    """
 
1422
    if (unicode_or_utf8_string is None
 
1423
        or unicode_or_utf8_string.__class__ == bytes):
 
1424
        return unicode_or_utf8_string
 
1425
    raise TypeError('Unicode file ids are no longer supported. '
 
1426
                    'File id generators should be creating utf8 file ids.')
 
1427
 
 
1428
 
1365
1429
_platform_normalizes_filenames = False
1366
1430
if sys.platform == 'darwin':
1367
1431
    _platform_normalizes_filenames = True
1390
1454
    can be accessed by that path.
1391
1455
    """
1392
1456
 
1393
 
    if isinstance(path, bytes):
1394
 
        path = path.decode(sys.getfilesystemencoding())
1395
 
    return unicodedata.normalize('NFC', path), True
 
1457
    return unicodedata.normalize('NFC', text_type(path)), True
1396
1458
 
1397
1459
 
1398
1460
def _inaccessible_normalized_filename(path):
1399
1461
    __doc__ = _accessible_normalized_filename.__doc__
1400
1462
 
1401
 
    if isinstance(path, bytes):
1402
 
        path = path.decode(sys.getfilesystemencoding())
1403
 
    normalized = unicodedata.normalize('NFC', path)
 
1463
    normalized = unicodedata.normalize('NFC', text_type(path))
1404
1464
    return normalized, normalized == path
1405
1465
 
1406
1466
 
1429
1489
    except AttributeError:
1430
1490
        # siginterrupt doesn't exist on this platform, or for this version
1431
1491
        # of Python.
1432
 
        def siginterrupt(signum, flag): return None
 
1492
        siginterrupt = lambda signum, flag: None
1433
1493
    if restart_syscall:
1434
1494
        def sig_handler(*args):
1435
1495
            # Python resets the siginterrupt flag when a signal is
1460
1520
_terminal_size_state = 'no_data'
1461
1521
_first_terminal_size = None
1462
1522
 
1463
 
 
1464
1523
def terminal_width():
1465
1524
    """Return terminal width.
1466
1525
 
1547
1606
 
1548
1607
 
1549
1608
def _win32_terminal_size(width, height):
1550
 
    width, height = win32utils.get_console_size(
1551
 
        defaultx=width, defaulty=height)
 
1609
    width, height = win32utils.get_console_size(defaultx=width, defaulty=height)
1552
1610
    return width, height
1553
1611
 
1554
1612
 
1555
1613
def _ioctl_terminal_size(width, height):
1556
1614
    try:
1557
 
        import struct
1558
 
        import fcntl
1559
 
        import termios
 
1615
        import struct, fcntl, termios
1560
1616
        s = struct.pack('HHHH', 0, 0, 0, 0)
1561
1617
        x = fcntl.ioctl(1, termios.TIOCGWINSZ, s)
1562
1618
        height, width = struct.unpack('HHHH', x)[0:2]
1564
1620
        pass
1565
1621
    return width, height
1566
1622
 
1567
 
 
1568
1623
_terminal_size = None
1569
1624
"""Returns the terminal size as (width, height).
1570
1625
 
1580
1635
    _terminal_size = _ioctl_terminal_size
1581
1636
 
1582
1637
 
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
 
1638
def supports_executable():
 
1639
    return sys.platform != "win32"
1617
1640
 
1618
1641
 
1619
1642
def supports_posix_readonly():
1642
1665
        if orig_val is not None:
1643
1666
            del os.environ[env_variable]
1644
1667
    else:
 
1668
        if not PY3 and isinstance(value, text_type):
 
1669
            value = value.encode(get_user_encoding())
1645
1670
        os.environ[env_variable] = value
1646
1671
    return orig_val
1647
1672
 
1660
1685
        raise errors.IllegalPath(path)
1661
1686
 
1662
1687
 
1663
 
_WIN32_ERROR_DIRECTORY = 267  # Similar to errno.ENOTDIR
1664
 
 
1665
 
 
1666
 
try:
1667
 
    scandir = os.scandir
1668
 
except AttributeError:  # Python < 3
1669
 
    lazy_import(globals(), """\
1670
 
from scandir import scandir
1671
 
""")
1672
 
 
 
1688
_WIN32_ERROR_DIRECTORY = 267 # Similar to errno.ENOTDIR
1673
1689
 
1674
1690
def _is_error_enotdir(e):
1675
1691
    """Check if this exception represents ENOTDIR.
1687
1703
    :return: True if this represents an ENOTDIR error. False otherwise.
1688
1704
    """
1689
1705
    en = getattr(e, 'errno', None)
1690
 
    if (en == errno.ENOTDIR or
1691
 
        (sys.platform == 'win32' and
1692
 
            (en == _WIN32_ERROR_DIRECTORY or
1693
 
             (en == errno.EINVAL
1694
 
              and getattr(e, 'winerror', None) == _WIN32_ERROR_DIRECTORY)
1695
 
             ))):
 
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
        ))):
1696
1712
        return True
1697
1713
    return False
1698
1714
 
1725
1741
        rooted higher up.
1726
1742
    :return: an iterator over the dirs.
1727
1743
    """
1728
 
    # TODO there is a bit of a smell where the results of the directory-
 
1744
    #TODO there is a bit of a smell where the results of the directory-
1729
1745
    # summary in this, and the path from the root, may not agree
1730
1746
    # depending on top and prefix - i.e. ./foo and foo as a pair leads to
1731
1747
    # potentially confusing output. We should make this more robust - but
1732
1748
    # not at a speed cost. RBC 20060731
 
1749
    _lstat = os.lstat
1733
1750
    _directory = _directory_kind
 
1751
    _listdir = os.listdir
 
1752
    _kind_from_mode = file_kind_from_stat_mode
1734
1753
    pending = [(safe_unicode(prefix), "", _directory, None, safe_unicode(top))]
1735
1754
    while pending:
1736
1755
        # 0 - relpath, 1- basename, 2- kind, 3- stat, 4-toppath
1742
1761
        top_slash = top + u'/'
1743
1762
 
1744
1763
        dirblock = []
 
1764
        append = dirblock.append
1745
1765
        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))
 
1766
            names = sorted(map(decode_filename, _listdir(top)))
1751
1767
        except OSError as e:
1752
1768
            if not _is_error_enotdir(e):
1753
1769
                raise
1754
 
        except UnicodeDecodeError as e:
1755
 
            raise errors.BadFilenameEncoding(e.object, _fs_enc)
1756
 
        dirblock.sort()
 
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))
1757
1776
        yield (relroot, top), dirblock
1758
1777
 
1759
1778
        # push the user specified dirs from dirblock
1865
1884
        See DirReader.read_dir for details.
1866
1885
        """
1867
1886
        _utf8_encode = self._utf8_encode
1868
 
 
1869
 
        def _fs_decode(s): return s.decode(_fs_enc)
1870
 
 
1871
 
        def _fs_encode(s): return s.encode(_fs_enc)
 
1887
        _lstat = os.lstat
 
1888
        _listdir = os.listdir
 
1889
        _kind_from_mode = file_kind_from_stat_mode
1872
1890
 
1873
1891
        if prefix:
1874
1892
            relprefix = prefix + b'/'
1878
1896
 
1879
1897
        dirblock = []
1880
1898
        append = dirblock.append
1881
 
        for entry in scandir(safe_utf8(top)):
 
1899
        for name in sorted(_listdir(top)):
1882
1900
            try:
1883
 
                name = _fs_decode(entry.name)
 
1901
                name_utf8 = _utf8_encode(name)[0]
1884
1902
            except UnicodeDecodeError:
1885
1903
                raise errors.BadFilenameEncoding(
1886
 
                    relprefix + entry.name, _fs_enc)
 
1904
                    _utf8_encode(relprefix)[0] + name, _fs_enc)
1887
1905
            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)
 
1906
            statvalue = _lstat(abspath)
 
1907
            kind = _kind_from_mode(statvalue.st_mode)
1891
1908
            append((relprefix + name_utf8, name_utf8, kind, statvalue, abspath))
1892
 
        return sorted(dirblock)
 
1909
        return dirblock
1893
1910
 
1894
1911
 
1895
1912
def copy_tree(from_path, to_path, handlers={}):
1923
1940
    real_handlers = {'file': shutil.copy2,
1924
1941
                     'symlink': copy_link,
1925
1942
                     'directory': copy_dir,
1926
 
                     }
 
1943
                    }
1927
1944
    real_handlers.update(handlers)
1928
1945
 
1929
1946
    if not os.path.exists(to_path):
1944
1961
    if chown is None:
1945
1962
        return
1946
1963
 
1947
 
    if src is None:
 
1964
    if src == None:
1948
1965
        src = os.path.dirname(dst)
1949
1966
        if src == '':
1950
1967
            src = '.'
1952
1969
    try:
1953
1970
        s = os.stat(src)
1954
1971
        chown(dst, s.st_uid, s.st_gid)
1955
 
    except OSError:
 
1972
    except OSError as e:
1956
1973
        trace.warning(
1957
1974
            'Unable to copy ownership from "%s" to "%s". '
1958
1975
            'You may want to set it manually.', src, dst)
1971
1988
    """Compare path_a and path_b to generate the same order walkdirs uses."""
1972
1989
    key_a = path_prefix_key(path_a)
1973
1990
    key_b = path_prefix_key(path_b)
1974
 
    return (key_a > key_b) - (key_a < key_b)
 
1991
    return cmp(key_a, key_b)
1975
1992
 
1976
1993
 
1977
1994
_cached_user_encoding = None
2008
2025
                             ' unknown encoding %s.'
2009
2026
                             ' Continuing with ascii encoding.\n'
2010
2027
                             % user_encoding
2011
 
                             )
 
2028
                            )
2012
2029
        user_encoding = 'ascii'
2013
2030
    else:
2014
2031
        # Get 'ascii' when setlocale has not been called or LANG=C or unset.
2038
2055
        return win32utils.get_host_name()
2039
2056
    else:
2040
2057
        import socket
2041
 
        return socket.gethostname()
 
2058
        if PY3:
 
2059
            return socket.gethostname()
 
2060
        return socket.gethostname().decode(get_user_encoding())
2042
2061
 
2043
2062
 
2044
2063
# We must not read/write any more than 64k at a time from/to a socket so we
2056
2075
 
2057
2076
 
2058
2077
def read_bytes_from_socket(sock, report_activity=None,
2059
 
                           max_read_size=MAX_SOCKET_CHUNK):
 
2078
        max_read_size=MAX_SOCKET_CHUNK):
2060
2079
    """Read up to max_read_size of bytes from sock and notify of progress.
2061
2080
 
2062
2081
    Translates "Connection reset by peer" into file-like EOF (return an
2065
2084
    """
2066
2085
    while True:
2067
2086
        try:
2068
 
            data = sock.recv(max_read_size)
 
2087
            bytes = sock.recv(max_read_size)
2069
2088
        except socket.error as e:
2070
2089
            eno = e.args[0]
2071
2090
            if eno in _end_of_stream_errors:
2072
2091
                # The connection was closed by the other side.  Callers expect
2073
2092
                # an empty string to signal end-of-stream.
2074
 
                return b""
 
2093
                return ""
2075
2094
            elif eno == errno.EINTR:
2076
2095
                # Retry the interrupted recv.
2077
2096
                continue
2078
2097
            raise
2079
2098
        else:
2080
2099
            if report_activity is not None:
2081
 
                report_activity(len(data), 'read')
2082
 
            return data
 
2100
                report_activity(len(bytes), 'read')
 
2101
            return bytes
2083
2102
 
2084
2103
 
2085
2104
def recv_all(socket, count):
2092
2111
 
2093
2112
    This isn't optimized and is intended mostly for use in testing.
2094
2113
    """
2095
 
    b = b''
 
2114
    b = ''
2096
2115
    while len(b) < count:
2097
2116
        new = read_bytes_from_socket(socket, None, count - len(b))
2098
 
        if new == b'':
2099
 
            break  # eof
 
2117
        if new == '':
 
2118
            break # eof
2100
2119
        b += new
2101
2120
    return b
2102
2121
 
2119
2138
    view = memoryview(bytes)
2120
2139
    while sent_total < byte_count:
2121
2140
        try:
2122
 
            sent = sock.send(view[sent_total:sent_total + MAX_SOCKET_CHUNK])
 
2141
            sent = sock.send(view[sent_total:sent_total+MAX_SOCKET_CHUNK])
2123
2142
        except (socket.error, IOError) as e:
2124
2143
            if e.args[0] in _end_of_stream_errors:
2125
2144
                raise errors.ConnectionReset(
2151
2170
            sock.connect(sa)
2152
2171
            return sock
2153
2172
 
2154
 
        except socket.error as e:
2155
 
            err = e
 
2173
        except socket.error as err:
2156
2174
            # 'err' is now the most recent error
2157
2175
            if sock is not None:
2158
2176
                sock.close()
2203
2221
    base = dirname(breezy.__file__)
2204
2222
    if getattr(sys, 'frozen', None):    # bzr.exe
2205
2223
        base = abspath(pathjoin(base, '..', '..'))
2206
 
    with open(pathjoin(base, resource_relpath), "rt") as f:
 
2224
    f = file(pathjoin(base, resource_relpath), "rU")
 
2225
    try:
2207
2226
        return f.read()
2208
 
 
 
2227
    finally:
 
2228
        f.close()
2209
2229
 
2210
2230
def file_kind_from_stat_mode_thunk(mode):
2211
2231
    global file_kind_from_stat_mode
2213
2233
        try:
2214
2234
            from ._readdir_pyx import UTF8DirReader
2215
2235
            file_kind_from_stat_mode = UTF8DirReader().kind_from_mode
2216
 
        except ImportError:
 
2236
        except ImportError as e:
2217
2237
            # This is one time where we won't warn that an extension failed to
2218
2238
            # load. The extension is never available on Windows anyway.
2219
2239
            from ._readdir_py import (
2220
2240
                _kind_from_mode as file_kind_from_stat_mode
2221
2241
                )
2222
2242
    return file_kind_from_stat_mode(mode)
2223
 
 
2224
 
 
2225
2243
file_kind_from_stat_mode = file_kind_from_stat_mode_thunk
2226
2244
 
2227
 
 
2228
2245
def file_stat(f, _lstat=os.lstat):
2229
2246
    try:
2230
2247
        # XXX cache?
2234
2251
            raise errors.NoSuchFile(f)
2235
2252
        raise
2236
2253
 
2237
 
 
2238
2254
def file_kind(f, _lstat=os.lstat):
2239
2255
    stat_value = file_stat(f, _lstat)
2240
2256
    return file_kind_from_stat_mode(stat_value.st_mode)
2241
2257
 
2242
 
 
2243
2258
def until_no_eintr(f, *a, **kw):
2244
2259
    """Run f(*a, **kw), retrying if an EINTR error occurs.
2245
2260
 
2296
2311
                                stdout=subprocess.PIPE).communicate()[0]
2297
2312
elif sys.platform == 'sunos5':
2298
2313
    def _local_concurrency():
2299
 
        return subprocess.Popen(['psrinfo', '-p', ],
 
2314
        return subprocess.Popen(['psrinfo', '-p',],
2300
2315
                                stdout=subprocess.PIPE).communicate()[0]
2301
2316
elif sys.platform == "win32":
2302
2317
    def _local_concurrency():
2310
2325
 
2311
2326
_cached_local_concurrency = None
2312
2327
 
2313
 
 
2314
2328
def local_concurrency(use_cache=True):
2315
2329
    """Return how many processes can be run concurrently.
2316
2330
 
2338
2352
    except (TypeError, ValueError):
2339
2353
        concurrency = 1
2340
2354
    if use_cache:
2341
 
        _cached_local_concurrency = concurrency
 
2355
        _cached_concurrency = concurrency
2342
2356
    return concurrency
2343
2357
 
2344
2358
 
2356
2370
            data, _ = self.encode(object, self.errors)
2357
2371
            self.stream.write(data)
2358
2372
 
2359
 
 
2360
2373
if sys.platform == 'win32':
2361
2374
    def open_file(filename, mode='r', bufsize=-1):
2362
2375
        """This function is used to override the ``open`` builtin.
2390
2403
            else:
2391
2404
                flags |= os.O_WRONLY
2392
2405
            flags |= os.O_CREAT | os.O_APPEND
2393
 
        else:  # reading
 
2406
        else: #reading
2394
2407
            if updating:
2395
2408
                flags |= os.O_RDWR
2396
2409
            else:
2436
2449
 
2437
2450
def find_executable_on_path(name):
2438
2451
    """Finds an executable on the PATH.
2439
 
 
 
2452
    
2440
2453
    On Windows, this will try to append each extension in the PATHEXT
2441
2454
    environment variable to the name, if it cannot be found with the name
2442
2455
    as given.
2443
 
 
 
2456
    
2444
2457
    :param name: The base name of the executable.
2445
2458
    :return: The path to the executable found or None.
2446
2459
    """
2484
2497
            # exists, though not ours
2485
2498
            return False
2486
2499
        else:
2487
 
            trace.mutter("os.kill(%d, 0) failed: %s" % (pid, e))
 
2500
            mutter("os.kill(%d, 0) failed: %s" % (pid, e))
2488
2501
            # Don't really know.
2489
2502
            return False
2490
2503
    else:
2491
2504
        # Exists and our process: not dead.
2492
2505
        return False
2493
2506
 
2494
 
 
2495
2507
if sys.platform == "win32":
2496
2508
    is_local_pid_dead = win32utils.is_local_pid_dead
2497
2509
else:
2504
2516
 
2505
2517
def fdatasync(fileno):
2506
2518
    """Flush file contents to disk if possible.
2507
 
 
 
2519
    
2508
2520
    :param fileno: Integer OS file handle.
2509
2521
    :raises TransportNotPossible: If flushing to disk is not possible.
2510
2522
    """
2524
2536
 
2525
2537
def ensure_empty_directory_exists(path, exception_class):
2526
2538
    """Make sure a local directory exists and is empty.
2527
 
 
 
2539
    
2528
2540
    If it does not exist, it is created.  If it exists and is not empty, an
2529
2541
    instance of exception_class is raised.
2530
2542
    """
2537
2549
            raise exception_class(path)
2538
2550
 
2539
2551
 
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
 
2552
def is_environment_error(evalue):
 
2553
    """True if exception instance is due to a process environment issue
 
2554
 
 
2555
    This includes OSError and IOError, but also other errors that come from
 
2556
    the operating system or core libraries but are not subclasses of those.
 
2557
    """
 
2558
    if isinstance(evalue, (EnvironmentError, select.error)):
 
2559
        return True
 
2560
    if sys.platform == "win32" and win32utils._is_pywintypes_error(evalue):
 
2561
        return True
 
2562
    return False